diff --git a/mypy/checker.py b/mypy/checker.py index 557ceb8a71c0..c946d4067307 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5,8 +5,7 @@ from contextlib import contextmanager from typing import ( - Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Iterable, - Sequence + Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Sequence ) from typing_extensions import Final @@ -48,7 +47,7 @@ from mypy.typeops import ( map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union, erase_def_to_union_or_bound, erase_to_union_or_bound, - true_only, false_only, function_type, + true_only, false_only, function_type, TypeVarExtractor ) from mypy import message_registry from mypy.subtypes import ( @@ -4527,20 +4526,6 @@ def detach_callable(typ: CallableType) -> CallableType: return out -class TypeVarExtractor(TypeQuery[List[TypeVarType]]): - def __init__(self) -> None: - super().__init__(self._merge) - - def _merge(self, iter: Iterable[List[TypeVarType]]) -> List[TypeVarType]: - out = [] - for item in iter: - out.extend(item) - return out - - def visit_type_var(self, t: TypeVarType) -> List[TypeVarType]: - return [t] - - def overload_can_never_match(signature: CallableType, other: CallableType) -> bool: """Check if the 'other' method can never be matched due to 'signature'. diff --git a/mypy/typeops.py b/mypy/typeops.py index b26aa8b3ea73..9873a8e9afd9 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -5,14 +5,14 @@ since these may assume that MROs are ready. """ -from typing import cast, Optional, List, Sequence, Set +from typing import cast, Optional, List, Sequence, Set, Iterable import sys from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded, TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types, - copy_type, TypeAliasType + copy_type, TypeAliasType, TypeQuery ) from mypy.nodes import ( FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, ARG_POS, @@ -215,23 +215,29 @@ class B(A): pass original_type = erase_to_bound(self_param_type) original_type = get_proper_type(original_type) - ids = [x.id for x in func.variables] - typearg = get_proper_type(infer_type_arguments(ids, self_param_type, - original_type, is_supertype=True)[0]) - if (is_classmethod and isinstance(typearg, UninhabitedType) + all_ids = [x.id for x in func.variables] + typeargs = infer_type_arguments(all_ids, self_param_type, original_type, + is_supertype=True) + if (is_classmethod + # TODO: why do we need the extra guards here? + and any(isinstance(get_proper_type(t), UninhabitedType) for t in typeargs) and isinstance(original_type, (Instance, TypeVarType, TupleType))): # In case we call a classmethod through an instance x, fallback to type(x) - typearg = get_proper_type(infer_type_arguments(ids, self_param_type, - TypeType(original_type), - is_supertype=True)[0]) + typeargs = infer_type_arguments(all_ids, self_param_type, TypeType(original_type), + is_supertype=True) + + ids = [tid for tid in all_ids + if any(tid == t.id for t in get_type_vars(self_param_type))] + + # Technically, some constrains might be unsolvable, make them . + to_apply = [t if t is not None else UninhabitedType() for t in typeargs] def expand(target: Type) -> Type: - assert typearg is not None - return expand_type(target, {func.variables[0].id: typearg}) + return expand_type(target, {id: to_apply[all_ids.index(id)] for id in ids}) arg_types = [expand(x) for x in func.arg_types[1:]] ret_type = expand(func.ret_type) - variables = func.variables[1:] + variables = [v for v in func.variables if v.id not in ids] else: arg_types = func.arg_types[1:] ret_type = func.ret_type @@ -587,3 +593,21 @@ def coerce_to_literal(typ: Type) -> ProperType: if len(enum_values) == 1: return LiteralType(value=enum_values[0], fallback=typ) return typ + + +def get_type_vars(tp: Type) -> List[TypeVarType]: + return tp.accept(TypeVarExtractor()) + + +class TypeVarExtractor(TypeQuery[List[TypeVarType]]): + def __init__(self) -> None: + super().__init__(self._merge) + + def _merge(self, iter: Iterable[List[TypeVarType]]) -> List[TypeVarType]: + out = [] + for item in iter: + out.extend(item) + return out + + def visit_type_var(self, t: TypeVarType) -> List[TypeVarType]: + return [t] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c4d1bb3d2e53..7b4fc2239cce 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -824,6 +824,46 @@ ab: Union[A, B, C] reveal_type(ab.x) # N: Revealed type is 'builtins.int' [builtins fixtures/property.pyi] +[case testSelfTypeNoTypeVars] +from typing import Generic, List, Optional, TypeVar, Any + +Q = TypeVar("Q") +T = TypeVar("T", bound=Super[Any]) + +class Super(Generic[Q]): + @classmethod + def meth(cls, arg: List[T]) -> List[T]: + pass + +class Sub(Super[int]): ... + +def test(x: List[Sub]) -> None: + reveal_type(Sub.meth(x)) # N: Revealed type is 'builtins.list[__main__.Sub*]' +[builtins fixtures/isinstancelist.pyi] + +[case testSelfTypeNoTypeVarsRestrict] +from typing import Generic, TypeVar + +T = TypeVar('T') +S = TypeVar('S') + +class C(Generic[T]): + def limited(self: C[str], arg: S) -> S: ... + +reveal_type(C[str]().limited(0)) # N: Revealed type is 'builtins.int*' + +[case testSelfTypeMultipleTypeVars] +from typing import Generic, TypeVar, Tuple + +T = TypeVar('T') +S = TypeVar('S') +U = TypeVar('U') + +class C(Generic[T]): + def magic(self: C[Tuple[S, U]]) -> Tuple[T, S, U]: ... + +reveal_type(C[Tuple[int, str]]().magic()) # N: Revealed type is 'Tuple[Tuple[builtins.int, builtins.str], builtins.int, builtins.str]' + [case testSelfTypeOnUnion] from typing import TypeVar, Union