From 7ad9cd27bad27b19d7958f77ae887dd9040468e7 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 20 Sep 2021 21:29:35 +0300 Subject: [PATCH 1/7] Now `**CustomType` argument is properly checked --- mypy/argmap.py | 32 +++++++---- mypy/checkexpr.py | 21 ++++++- mypy/constraints.py | 14 +++-- mypy/infer.py | 19 ++++++- test-data/unit/check-expressions.test | 35 ++---------- test-data/unit/check-kwargs.test | 56 ++++++++++++++++++- test-data/unit/check-varargs.test | 26 +++++++++ test-data/unit/fixtures/__init_subclass__.pyi | 2 + test-data/unit/fixtures/alias.pyi | 2 + test-data/unit/fixtures/dict.pyi | 4 +- 10 files changed, 157 insertions(+), 54 deletions(-) diff --git a/mypy/argmap.py b/mypy/argmap.py index d9453aa0b640..03a6ad947e24 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -1,12 +1,15 @@ """Utilities for mapping between actual and formal arguments (and their types).""" -from typing import List, Optional, Sequence, Callable, Set +from typing import TYPE_CHECKING, List, Optional, Sequence, Callable, Set from mypy.types import ( Type, Instance, TupleType, AnyType, TypeOfAny, TypedDictType, get_proper_type ) from mypy import nodes +if TYPE_CHECKING: + from mypy.infer import ArgumentInferContext + def map_actuals_to_formals(actual_kinds: List[nodes.ArgKind], actual_names: Optional[Sequence[Optional[str]]], @@ -140,11 +143,13 @@ def f(x: int, *args: str) -> None: ... needs a separate instance since instances have per-call state. """ - def __init__(self) -> None: + def __init__(self, context: 'ArgumentInferContext') -> None: # Next tuple *args index to use. self.tuple_index = 0 # Keyword arguments in TypedDict **kwargs used. self.kwargs_used: Set[str] = set() + # Type context for `*` and `**` arg kinds. + self.context = context def expand_actual_type(self, actual_type: Type, @@ -162,16 +167,18 @@ def expand_actual_type(self, This is supposed to be called for each formal, in order. Call multiple times per formal if multiple actuals map to a formal. """ + from mypy.subtypes import is_subtype + actual_type = get_proper_type(actual_type) if actual_kind == nodes.ARG_STAR: - if isinstance(actual_type, Instance): - if actual_type.type.fullname == 'builtins.list': - # List *arg. - return actual_type.args[0] - elif actual_type.args: - # TODO: Try to map type arguments to Iterable + if isinstance(actual_type, Instance) and actual_type.args: + if is_subtype(actual_type, self.context.iterable_type): return actual_type.args[0] else: + # We cannot properly unpack anything other + # than `Iterable` type with `*`. + # Just return `Any`, other parts of code would raise + # a different error for improper use. return AnyType(TypeOfAny.from_error) elif isinstance(actual_type, TupleType): # Get the next tuple item of a tuple *arg. @@ -193,10 +200,11 @@ def expand_actual_type(self, formal_name = (set(actual_type.items.keys()) - self.kwargs_used).pop() self.kwargs_used.add(formal_name) return actual_type.items[formal_name] - elif (isinstance(actual_type, Instance) - and (actual_type.type.fullname == 'builtins.dict')): - # Dict **arg. - # TODO: Handle arbitrary Mapping + elif (isinstance(actual_type, Instance) and + len(actual_type.args) > 1 and + is_subtype(actual_type, self.context.mapping_type)): + # Only `Mapping` type can be unpacked with `**`. + # Other types will produce an error somewhere else. return actual_type.args[1] else: return AnyType(TypeOfAny.from_error) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 280e9a35d537..adda1fdc6001 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -45,7 +45,9 @@ from mypy.maptype import map_instance_to_supertype from mypy.messages import MessageBuilder from mypy import message_registry -from mypy.infer import infer_type_arguments, infer_function_type_arguments +from mypy.infer import ( + ArgumentInferContext, infer_type_arguments, infer_function_type_arguments, +) from mypy import join from mypy.meet import narrow_declared_type, is_overlapping_types from mypy.subtypes import is_subtype, is_proper_subtype, is_equivalent, non_method_protocol_members @@ -1237,6 +1239,9 @@ def infer_function_type_arguments(self, callee_type: CallableType, inferred_args = infer_function_type_arguments( callee_type, pass1_args, arg_kinds, formal_to_actual, + context=ArgumentInferContext( + self.chk.named_type('typing.Mapping'), + self.chk.named_type('typing.Iterable')), strict=self.chk.in_checked_function()) if 2 in arg_pass_nums: @@ -1298,7 +1303,12 @@ def infer_function_type_arguments_pass2( callee_type, args, arg_kinds, formal_to_actual) inferred_args = infer_function_type_arguments( - callee_type, arg_types, arg_kinds, formal_to_actual) + callee_type, arg_types, arg_kinds, formal_to_actual, + context=ArgumentInferContext( + self.chk.named_type('typing.Mapping'), + self.chk.named_type('typing.Iterable'), + ), + ) return callee_type, inferred_args @@ -1476,7 +1486,12 @@ def check_argument_types(self, messages = messages or self.msg check_arg = check_arg or self.check_arg # Keep track of consumed tuple *arg items. - mapper = ArgTypeExpander() + mapper = ArgTypeExpander( + ArgumentInferContext( + self.chk.named_type('typing.Mapping'), + self.chk.named_type('typing.Iterable'), + ), + ) for i, actuals in enumerate(formal_to_actual): for actual in actuals: actual_type = arg_types[actual] diff --git a/mypy/constraints.py b/mypy/constraints.py index 7fc28eb35c8f..3f2b67662b3d 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -1,6 +1,6 @@ """Type inference constraints.""" -from typing import Iterable, List, Optional, Sequence +from typing import TYPE_CHECKING, Iterable, List, Optional, Sequence from typing_extensions import Final from mypy.types import ( @@ -18,6 +18,9 @@ from mypy.argmap import ArgTypeExpander from mypy.typestate import TypeState +if TYPE_CHECKING: + from mypy.infer import ArgumentInferContext + SUBTYPE_OF: Final = 0 SUPERTYPE_OF: Final = 1 @@ -45,14 +48,17 @@ def __repr__(self) -> str: def infer_constraints_for_callable( - callee: CallableType, arg_types: Sequence[Optional[Type]], arg_kinds: List[ArgKind], - formal_to_actual: List[List[int]]) -> List[Constraint]: + callee: CallableType, + arg_types: Sequence[Optional[Type]], + arg_kinds: List[ArgKind], + formal_to_actual: List[List[int]], + context: 'ArgumentInferContext') -> List[Constraint]: """Infer type variable constraints for a callable and actual arguments. Return a list of constraints. """ constraints: List[Constraint] = [] - mapper = ArgTypeExpander() + mapper = ArgTypeExpander(context) for i, actuals in enumerate(formal_to_actual): for actual in actuals: diff --git a/mypy/infer.py b/mypy/infer.py index da8acf31e1e1..cfae9b4ba918 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -1,6 +1,6 @@ """Utilities for type argument inference.""" -from typing import List, Optional, Sequence +from typing import List, Optional, Sequence, NamedTuple from mypy.constraints import ( infer_constraints, infer_constraints_for_callable, SUBTYPE_OF, SUPERTYPE_OF @@ -10,10 +10,25 @@ from mypy.solve import solve_constraints +class ArgumentInferContext(NamedTuple): + """ + Type argument inference context. + + We need this because we pass around ``Mapping`` and ``Iterable`` types. + It is required for ``*`` and ``**`` argument parsing. + + https://github.com/python/mypy/issues/11144 + """ + + mapping_type: Type + iterable_type: Type + + def infer_function_type_arguments(callee_type: CallableType, arg_types: Sequence[Optional[Type]], arg_kinds: List[ArgKind], formal_to_actual: List[List[int]], + context: ArgumentInferContext, strict: bool = True) -> List[Optional[Type]]: """Infer the type arguments of a generic function. @@ -30,7 +45,7 @@ def infer_function_type_arguments(callee_type: CallableType, """ # Infer constraints. constraints = infer_constraints_for_callable( - callee_type, arg_types, arg_kinds, formal_to_actual) + callee_type, arg_types, arg_kinds, formal_to_actual, context) # Solve constraints. type_vars = callee_type.type_var_ids() diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 395bafce9ac3..d7f8bd7f7ad0 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -63,13 +63,7 @@ if str(): a = 1.1 class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class type: pass -class function: pass -class float: pass -class str: pass +[builtins fixtures/dict.pyi] [case testComplexLiteral] a = 0.0j @@ -80,13 +74,7 @@ if str(): a = 1.1j class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class type: pass -class function: pass -class complex: pass -class str: pass +[builtins fixtures/dict.pyi] [case testBytesLiteral] b, a = None, None # type: (bytes, A) @@ -99,14 +87,7 @@ if str(): if str(): a = b'foo' # E: Incompatible types in assignment (expression has type "bytes", variable has type "A") class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class type: pass -class tuple: pass -class function: pass -class bytes: pass -class str: pass +[builtins fixtures/dict.pyi] [case testUnicodeLiteralInPython3] s = None # type: str @@ -2126,15 +2107,7 @@ if str(): ....a # E: "ellipsis" has no attribute "a" class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class ellipsis: - def __init__(self): pass - __class__ = object() -class type: pass -class function: pass -class str: pass +[builtins fixtures/dict.pyi] [out] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 4828d1bb37b9..0371f1991789 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -490,7 +490,7 @@ g(**d) # E: Argument 1 to "g" has incompatible type "**Dict[str, object]"; expe m = {} # type: Mapping[str, object] f(**m) -g(**m) # TODO: Should be an error +g(**m) # E: Argument 1 to "g" has incompatible type "**Mapping[str, object]"; expected "int" [builtins fixtures/dict.pyi] [case testPassingEmptyDictWithStars] @@ -500,3 +500,57 @@ def g(x=1): pass f(**{}) g(**{}) [builtins fixtures/dict.pyi] + +[case testKeywordUnpackWithDifferentTypes] +# https://github.com/python/mypy/issues/11144 +from typing import Dict, Generic, TypeVar, Mapping + +T = TypeVar("T") +T2 = TypeVar("T2") + +class A(Dict[T, T2]): + ... + +class B(Mapping[T, T2]): + ... + +class C(Generic[T, T2]): + ... + +class D: + ... + +def foo(**i: float) -> float: + ... + +a: A[str, str] +b: B[str, str] +c: C[str, float] +d: D +e = {"a": "b"} + +foo(k=1.5) +foo(**a) +foo(**b) +foo(**c) +foo(**d) +foo(**e) + +# Correct: + +class Good(Mapping[str, float]): + ... + +good1: Good +good2: A[str, float] +good3: B[str, float] +foo(**good1) +foo(**good2) +foo(**good3) +[out] +main:29: error: Argument 1 to "foo" has incompatible type "**A[str, str]"; expected "float" +main:30: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expected "float" +main:31: error: Argument after ** must be a mapping, not "C[str, float]" +main:32: error: Argument after ** must be a mapping, not "D" +main:33: error: Argument 1 to "foo" has incompatible type "**Dict[str, str]"; expected "float" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index d648381f2fc3..0493951c677d 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -734,3 +734,29 @@ b = {'b': 1} f(a) # E: Argument 1 to "f" has incompatible type "List[int]"; expected "Listener" g(b) # E: Argument 1 to "g" has incompatible type "Dict[str, int]"; expected "DictReader" [builtins fixtures/dict.pyi] + +[case testInvariantTypeConfusingNames] +from typing import Iterable, Generic, TypeVar, List + +T = TypeVar('T') + +class I(Iterable[T]): + ... + +class Bad(Generic[T]): + ... + +def bar(*args: float) -> float: + ... + +good1: Iterable[float] +good2: List[float] +good3: I[float] +bad1: I[str] +bad2: Bad[float] +bar(*good1) +bar(*good2) +bar(*good3) +bar(*bad1) # E: Argument 1 to "bar" has incompatible type "*I[str]"; expected "float" +bar(*bad2) # E: List or tuple expected as variable arguments +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/fixtures/__init_subclass__.pyi b/test-data/unit/fixtures/__init_subclass__.pyi index 79fd04fd964e..c5a17f60688e 100644 --- a/test-data/unit/fixtures/__init_subclass__.pyi +++ b/test-data/unit/fixtures/__init_subclass__.pyi @@ -1,5 +1,7 @@ # builtins stub with object.__init_subclass__ +from typing import Mapping, Iterable # needed for ArgumentInferContext + class object: def __init_subclass__(cls) -> None: pass diff --git a/test-data/unit/fixtures/alias.pyi b/test-data/unit/fixtures/alias.pyi index 5909cb616794..08b145f4efd1 100644 --- a/test-data/unit/fixtures/alias.pyi +++ b/test-data/unit/fixtures/alias.pyi @@ -1,5 +1,7 @@ # Builtins test fixture with a type alias 'bytes' +from typing import Mapping, Iterable # needed for `ArgumentInferContext` + class object: def __init__(self) -> None: pass class type: diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index ab8127badd4c..64f20c6b895e 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -48,8 +48,10 @@ class list(Sequence[T]): # needed by some test cases class tuple(Generic[T]): pass class function: pass class float: pass +class complex: pass class bool(int): pass -class ellipsis: pass +class ellipsis: + __class__: object def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass class BaseException: pass From e4c4807a535fff73caf4438e1da59a7fea93ad1d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 20 Sep 2021 21:35:21 +0300 Subject: [PATCH 2/7] Fixes flake8 --- mypy/argmap.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/argmap.py b/mypy/argmap.py index 03a6ad947e24..a8e929eb2744 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -200,9 +200,11 @@ def expand_actual_type(self, formal_name = (set(actual_type.items.keys()) - self.kwargs_used).pop() self.kwargs_used.add(formal_name) return actual_type.items[formal_name] - elif (isinstance(actual_type, Instance) and - len(actual_type.args) > 1 and - is_subtype(actual_type, self.context.mapping_type)): + elif ( + isinstance(actual_type, Instance) and + len(actual_type.args) > 1 and + is_subtype(actual_type, self.context.mapping_type) + ): # Only `Mapping` type can be unpacked with `**`. # Other types will produce an error somewhere else. return actual_type.args[1] From 9fad3760be7fcf22f816c6c6f45775b0f724cab0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 21 Sep 2021 02:55:06 +0300 Subject: [PATCH 3/7] Slight refactoring --- mypy/checkexpr.py | 22 +++++++++------------- mypy/infer.py | 12 ++++++------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index adda1fdc6001..8d1c8b20e543 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1239,9 +1239,7 @@ def infer_function_type_arguments(self, callee_type: CallableType, inferred_args = infer_function_type_arguments( callee_type, pass1_args, arg_kinds, formal_to_actual, - context=ArgumentInferContext( - self.chk.named_type('typing.Mapping'), - self.chk.named_type('typing.Iterable')), + context=self.argument_infer_context(), strict=self.chk.in_checked_function()) if 2 in arg_pass_nums: @@ -1304,14 +1302,17 @@ def infer_function_type_arguments_pass2( inferred_args = infer_function_type_arguments( callee_type, arg_types, arg_kinds, formal_to_actual, - context=ArgumentInferContext( - self.chk.named_type('typing.Mapping'), - self.chk.named_type('typing.Iterable'), - ), + context=self.argument_infer_context(), ) return callee_type, inferred_args + def argument_infer_context(self) -> ArgumentInferContext: + return ArgumentInferContext( + self.chk.named_type('typing.Mapping'), + self.chk.named_type('typing.Iterable'), + ) + def get_arg_infer_passes(self, arg_types: List[Type], formal_to_actual: List[List[int]], num_actuals: int) -> List[int]: @@ -1486,12 +1487,7 @@ def check_argument_types(self, messages = messages or self.msg check_arg = check_arg or self.check_arg # Keep track of consumed tuple *arg items. - mapper = ArgTypeExpander( - ArgumentInferContext( - self.chk.named_type('typing.Mapping'), - self.chk.named_type('typing.Iterable'), - ), - ) + mapper = ArgTypeExpander(self.argument_infer_context()) for i, actuals in enumerate(formal_to_actual): for actual in actuals: actual_type = arg_types[actual] diff --git a/mypy/infer.py b/mypy/infer.py index cfae9b4ba918..ca521e211493 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -5,23 +5,23 @@ from mypy.constraints import ( infer_constraints, infer_constraints_for_callable, SUBTYPE_OF, SUPERTYPE_OF ) -from mypy.types import Type, TypeVarId, CallableType +from mypy.types import Type, TypeVarId, CallableType, Instance from mypy.nodes import ArgKind from mypy.solve import solve_constraints class ArgumentInferContext(NamedTuple): - """ - Type argument inference context. + """Type argument inference context. We need this because we pass around ``Mapping`` and ``Iterable`` types. - It is required for ``*`` and ``**`` argument parsing. + These types are only known by ``TypeChecker`` itself. + It is required for ``*`` and ``**`` argument inference. https://github.com/python/mypy/issues/11144 """ - mapping_type: Type - iterable_type: Type + mapping_type: Instance + iterable_type: Instance def infer_function_type_arguments(callee_type: CallableType, From 4581dfde431d594b21605d3f4e406b66d44d72a2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 21 Sep 2021 10:04:03 +0300 Subject: [PATCH 4/7] Adds TODO for later --- mypy/argmap.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mypy/argmap.py b/mypy/argmap.py index a8e929eb2744..f180ddc639c6 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -173,6 +173,9 @@ def expand_actual_type(self, if actual_kind == nodes.ARG_STAR: if isinstance(actual_type, Instance) and actual_type.args: if is_subtype(actual_type, self.context.iterable_type): + # TODO: this can be invalid, when generic type has: + # `class Some(Generic[X, Y], Iterable[Y]):` + # https://github.com/python/mypy/issues/11138 return actual_type.args[0] else: # We cannot properly unpack anything other @@ -207,6 +210,9 @@ def expand_actual_type(self, ): # Only `Mapping` type can be unpacked with `**`. # Other types will produce an error somewhere else. + # TODO: this can be invalid, when generic type has: + # `class Some(Generic[A, B, C], Mapping[B, C]):` + # https://github.com/python/mypy/issues/11138 return actual_type.args[1] else: return AnyType(TypeOfAny.from_error) From c82098ecb07f6447f5e734b1a3b4e489ad0a3476 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Sep 2021 18:34:46 +0300 Subject: [PATCH 5/7] Adds correct supertype mapping --- mypy/argmap.py | 17 +- mypy/typeshed/stdlib/typing.pyi | 2 +- test-data/unit/check-generic-subtyping.test | 163 ++++++++++++++++++++ test-data/unit/fixtures/dict.pyi | 2 + test-data/unit/lib-stub/typing.pyi | 3 +- 5 files changed, 177 insertions(+), 10 deletions(-) diff --git a/mypy/argmap.py b/mypy/argmap.py index f180ddc639c6..cb3811161783 100644 --- a/mypy/argmap.py +++ b/mypy/argmap.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, List, Optional, Sequence, Callable, Set +from mypy.maptype import map_instance_to_supertype from mypy.types import ( Type, Instance, TupleType, AnyType, TypeOfAny, TypedDictType, get_proper_type ) @@ -173,10 +174,10 @@ def expand_actual_type(self, if actual_kind == nodes.ARG_STAR: if isinstance(actual_type, Instance) and actual_type.args: if is_subtype(actual_type, self.context.iterable_type): - # TODO: this can be invalid, when generic type has: - # `class Some(Generic[X, Y], Iterable[Y]):` - # https://github.com/python/mypy/issues/11138 - return actual_type.args[0] + return map_instance_to_supertype( + actual_type, + self.context.iterable_type.type, + ).args[0] else: # We cannot properly unpack anything other # than `Iterable` type with `*`. @@ -210,10 +211,10 @@ def expand_actual_type(self, ): # Only `Mapping` type can be unpacked with `**`. # Other types will produce an error somewhere else. - # TODO: this can be invalid, when generic type has: - # `class Some(Generic[A, B, C], Mapping[B, C]):` - # https://github.com/python/mypy/issues/11138 - return actual_type.args[1] + return map_instance_to_supertype( + actual_type, + self.context.mapping_type.type, + ).args[1] else: return AnyType(TypeOfAny.from_error) else: diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index b87788667c37..6ade8fb211de 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -433,7 +433,7 @@ class AsyncContextManager(Protocol[_T_co]): self, exc_type: Type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None ) -> Awaitable[bool | None]: ... -class Mapping(_Collection[_KT], Generic[_KT, _VT_co]): +class Mapping(_Collection[_KT], Generic[_KT, _VT_co]): # TODO: We wish the key type could also be covariant, but that doesn't work, # see discussion in https://github.com/python/typing/pull/273. @abstractmethod diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index d1420a43f9e4..c16fadafab87 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -842,3 +842,166 @@ main:14: error: Item "U" of the upper bound "Union[U, V, W]" of type variable "T main:14: error: Item "W" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "b" main:15: error: Item "U" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "c" main:15: error: Item "V" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "c" + + +[case testSubtypingIterableUnpacking1] +# https://github.com/python/mypy/issues/11138 +from typing import Generic, Iterator, TypeVar +T = TypeVar("T") +U = TypeVar("U") + +class X1(Iterator[U], Generic[T, U]): + pass + +x1: X1[str, int] +reveal_type(list(x1)) # N: Revealed type is "builtins.list[builtins.int*]" +reveal_type([*x1]) # N: Revealed type is "builtins.list[builtins.int*]" + +class X2(Iterator[T], Generic[T, U]): + pass + +x2: X2[str, int] +reveal_type(list(x2)) # N: Revealed type is "builtins.list[builtins.str*]" +reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.str*]" + +class X3(Generic[T, U], Iterator[U]): + pass + +x3: X3[str, int] +reveal_type(list(x3)) # N: Revealed type is "builtins.list[builtins.int*]" +reveal_type([*x3]) # N: Revealed type is "builtins.list[builtins.int*]" + +class X4(Generic[T, U], Iterator[T]): + pass + +x4: X4[str, int] +reveal_type(list(x4)) # N: Revealed type is "builtins.list[builtins.str*]" +reveal_type([*x4]) # N: Revealed type is "builtins.list[builtins.str*]" + +class X5(Iterator[T]): + pass + +x5: X5[str] +reveal_type(list(x5)) # N: Revealed type is "builtins.list[builtins.str*]" +reveal_type([*x5]) # N: Revealed type is "builtins.list[builtins.str*]" + +class X6(Generic[T, U], Iterator[bool]): + pass + +x6: X6[str, int] +reveal_type(list(x6)) # N: Revealed type is "builtins.list[builtins.bool*]" +reveal_type([*x6]) # N: Revealed type is "builtins.list[builtins.bool*]" +[builtins fixtures/list.pyi] + +[case testSubtypingIterableUnpacking2] +from typing import Generic, Iterator, TypeVar, Mapping +T = TypeVar("T") +U = TypeVar("U") + +class X1(Generic[T, U], Iterator[U], Mapping[U, T]): + pass + +x1: X1[str, int] +reveal_type(list(x1)) # N: Revealed type is "builtins.list[builtins.int*]" +reveal_type([*x1]) # N: Revealed type is "builtins.list[builtins.int*]" + +class X2(Generic[T, U], Iterator[U], Mapping[T, U]): + pass + +x2: X2[str, int] +reveal_type(list(x2)) # N: Revealed type is "builtins.list[builtins.int*]" +reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int*]" +[builtins fixtures/list.pyi] + +[case testSubtypingMappingUnpacking1] +# https://github.com/python/mypy/issues/11138 +from typing import Generic, TypeVar, Mapping +T = TypeVar("T") +U = TypeVar("U") + +class X1(Generic[T, U], Mapping[U, T]): + pass + +x1: X1[str, int] +reveal_type(iter(x1)) # N: Revealed type is "typing.Iterator[builtins.int*]" +reveal_type({**x1}) # N: Revealed type is "builtins.dict[builtins.int*, builtins.str*]" + +class X2(Generic[T, U], Mapping[T, U]): + pass + +x2: X2[str, int] +reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.str*]" +reveal_type({**x2}) # N: Revealed type is "builtins.dict[builtins.str*, builtins.int*]" + +class X3(Generic[T, U], Mapping[bool, float]): + pass + +x3: X3[str, int] +reveal_type(iter(x3)) # N: Revealed type is "typing.Iterator[builtins.bool*]" +reveal_type({**x3}) # N: Revealed type is "builtins.dict[builtins.bool*, builtins.float*]" +[builtins fixtures/dict.pyi] + +[case testSubtypingMappingUnpacking2] +from typing import Generic, TypeVar, Mapping +T = TypeVar("T") +U = TypeVar("U") + +class X1(Generic[T, U], Mapping[U, T]): + pass + +def func_with_kwargs(**kwargs: int): + pass + +x1: X1[str, int] +reveal_type(iter(x1)) +reveal_type({**x1}) +func_with_kwargs(**x1) +[out] +main:12: note: Revealed type is "typing.Iterator[builtins.int*]" +main:13: note: Revealed type is "builtins.dict[builtins.int*, builtins.str*]" +main:14: error: Keywords must be strings +main:14: error: Argument 1 to "func_with_kwargs" has incompatible type "**X1[str, int]"; expected "int" +[builtins fixtures/dict.pyi] + +[case testSubtypingMappingUnpacking3] +from typing import Generic, TypeVar, Mapping, Iterable +T = TypeVar("T") +U = TypeVar("U") + +class X1(Generic[T, U], Mapping[U, T], Iterable[U]): + pass + +x1: X1[str, int] +reveal_type(iter(x1)) # N: Revealed type is "typing.Iterator[builtins.int*]" +reveal_type({**x1}) # N: Revealed type is "builtins.dict[builtins.int*, builtins.str*]" + +# Some people would expect this to raise an error, but this currently does not: +# `Mapping` has `Iterable[U]` base class, `X2` has direct `Iterable[T]` base class. +# It would be impossible to define correct `__iter__` method for incompatible `T` and `U`. +class X2(Generic[T, U], Mapping[U, T], Iterable[T]): + pass + +x2: X2[str, int] +reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.int*]" +reveal_type({**x2}) # N: Revealed type is "builtins.dict[builtins.int*, builtins.str*]" +[builtins fixtures/dict.pyi] + +[case testNotDirectIterableAndMappingSubtyping] +from typing import Generic, TypeVar, Dict, Iterable, Iterator, List +T = TypeVar("T") +U = TypeVar("U") + +class X1(Generic[T, U], Dict[U, T], Iterable[U]): + def __iter__(self) -> Iterator[U]: pass + +x1: X1[str, int] +reveal_type(iter(x1)) # N: Revealed type is "typing.Iterator[builtins.int*]" +reveal_type({**x1}) # N: Revealed type is "builtins.dict[builtins.int*, builtins.str*]" + +class X2(Generic[T, U], List[U]): + def __iter__(self) -> Iterator[U]: pass + +x2: X2[str, int] +reveal_type(iter(x2)) # N: Revealed type is "typing.Iterator[builtins.int*]" +reveal_type([*x2]) # N: Revealed type is "builtins.list[builtins.int*]" +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 64f20c6b895e..fd509de8a6c2 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -55,3 +55,5 @@ class ellipsis: __class__: object def isinstance(x: object, t: Union[type, Tuple[type, ...]]) -> bool: pass class BaseException: pass + +def iter(__iterable: Iterable[T]) -> Iterator[T]: pass diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 2f42633843e0..b9f1752aee1b 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -43,6 +43,7 @@ class Generator(Iterator[T], Generic[T, U, V]): class Sequence(Iterable[T_co]): def __getitem__(self, n: Any) -> T_co: pass -class Mapping(Generic[T, T_co]): pass +# Mapping type is oversimplified intentionally. +class Mapping(Iterable[T], Generic[T, T_co]): pass def final(meth: T) -> T: pass From 28068e11ab9d0f89c71e3a29ee547afe325c9065 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Sep 2021 19:11:04 +0300 Subject: [PATCH 6/7] Adds correct supertype mapping --- test-data/unit/fixtures/args.pyi | 4 ++-- test-data/unit/fixtures/floatdict.pyi | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-data/unit/fixtures/args.pyi b/test-data/unit/fixtures/args.pyi index 2ee8736a154c..ffe54375f68e 100644 --- a/test-data/unit/fixtures/args.pyi +++ b/test-data/unit/fixtures/args.pyi @@ -20,9 +20,9 @@ class type: class tuple(Iterable[Tco], Generic[Tco]): pass -class dict(Iterable[T], Mapping[T, S], Generic[T, S]): pass +class dict(Mapping[T, S], Generic[T, S]): pass -class list(Generic[T], Sequence[T]): pass +class list(Sequence[T], Generic[T]): pass class int: def __eq__(self, o: object) -> bool: pass diff --git a/test-data/unit/fixtures/floatdict.pyi b/test-data/unit/fixtures/floatdict.pyi index 7d2f55a6f6dd..7baa7ca9206f 100644 --- a/test-data/unit/fixtures/floatdict.pyi +++ b/test-data/unit/fixtures/floatdict.pyi @@ -36,7 +36,7 @@ class list(Iterable[T], Generic[T]): def append(self, x: T) -> None: pass def extend(self, x: Iterable[T]) -> None: pass -class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]): +class dict(Mapping[KT, VT], Generic[KT, VT]): @overload def __init__(self, **kwargs: VT) -> None: pass @overload From d340e05d444cd3c921652d475d08e18c97100ddd Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 29 Sep 2021 20:03:13 +0300 Subject: [PATCH 7/7] Adds correct supertype mapping --- test-data/unit/fixtures/floatdict_python2.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/fixtures/floatdict_python2.pyi b/test-data/unit/fixtures/floatdict_python2.pyi index aa22c5464d6b..f177355d5d4b 100644 --- a/test-data/unit/fixtures/floatdict_python2.pyi +++ b/test-data/unit/fixtures/floatdict_python2.pyi @@ -36,7 +36,7 @@ class list(Iterable[T], Generic[T]): def append(self, x: T) -> None: pass def extend(self, x: Iterable[T]) -> None: pass -class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]): +class dict(Mapping[KT, VT], Generic[KT, VT]): @overload def __init__(self, **kwargs: VT) -> None: pass @overload