From 47cb2405670d9eba710af04760c22f33994bab2b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 11:02:27 +0000 Subject: [PATCH 1/8] Start fixing --- mypy/checkmember.py | 15 +++-- test-data/unit/check-generics.test | 90 ++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d12bf3c5ca30..d021cf3b60f9 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1,6 +1,6 @@ """Type checking of attribute access""" -from typing import cast, Callable, Optional, Union +from typing import cast, Callable, Optional, Union, List from typing_extensions import TYPE_CHECKING from mypy.types import ( @@ -234,7 +234,8 @@ def analyze_type_callable_member_access(name: str, # This check makes sure that when we encounter an operator, we skip looking up # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. - result = analyze_class_attribute_access(ret_type, name, mx) + result = analyze_class_attribute_access(ret_type, name, mx, + original_vars=typ.items()[0].variables) if result: return result # Look up from the 'type' type. @@ -622,7 +623,9 @@ class A: def analyze_class_attribute_access(itype: Instance, name: str, mx: MemberContext, - override_info: Optional[TypeInfo] = None) -> Optional[Type]: + override_info: Optional[TypeInfo] = None, + original_vars: Optional[List[TypeVarDef]] = None + ) -> Optional[Type]: """original_type is the type of E in the expression E.var""" info = itype.type if override_info: @@ -703,7 +706,7 @@ def analyze_class_attribute_access(itype: Instance, is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) result = add_class_tvars(get_proper_type(t), itype, isuper, is_classmethod, - mx.builtin_type, mx.self_type) + mx.builtin_type, mx.self_type, original_vars=original_vars) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, mx.msg, mx.context, chk=mx.chk) @@ -749,7 +752,8 @@ def analyze_class_attribute_access(itype: Instance, def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance], is_classmethod: bool, builtin_type: Callable[[str], Instance], - original_type: Type) -> Type: + original_type: Type, + original_vars: Optional[List[TypeVarDef]] = None) -> Type: """Instantiate type variables during analyze_class_attribute_access, e.g T and Q in the following: @@ -791,6 +795,7 @@ class B(A[str]): pass for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars) # use 'is' to avoid id clashes with unrelated variables if any(tv.id is id for id in free_ids)] + tvars = original_vars if original_vars is not None else [] if is_classmethod: t = bind_self(t, original_type, is_classmethod=True) return t.copy_modified(variables=tvars + t.variables) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 0441f26cee36..42da08f73147 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1928,6 +1928,96 @@ reveal_type(E.make_one) # N: Revealed type is 'def [S] (x: Tuple[S`1, builtins. reveal_type(E[int].make_one) # N: Revealed type is 'def (x: Tuple[builtins.int*, builtins.str*]) -> __main__.C[Tuple[builtins.int*, builtins.str*]]' [builtins fixtures/classmethod.pyi] +[case testGenericClassClsNonGeneric] +from typing import TypeVar, Generic + +T = TypeVar('T') + +class C(Generic[T]): + @classmethod + def f(cls, x: T) -> T: + return x + + @classmethod + def other(cls) -> None: + reveal_type(C) # N: Revealed type is 'def [T] () -> __main__.C[T`1]' + reveal_type(C[T]) # N: Revealed type is 'def () -> __main__.C[T`1]' + reveal_type(C.f) # N: Revealed type is 'def [T] (x: T`1) -> T`1' + reveal_type(C[T].f) # N: Revealed type is 'def (x: T`1) -> T`1' + reveal_type(cls.f) # N: Revealed type is 'def (x: T`1) -> T`1' +[builtins fixtures/classmethod.pyi] + +[case testGenericClassUnrelatedVars] +from typing import TypeVar, Generic + +T = TypeVar('T') +T2 = TypeVar('T2') + +class C(Generic[T]): + @classmethod + def f(cls, x: T) -> T: + return x + + @classmethod + def g(cls, x: T2) -> T2: + cls.f(x) # E: Argument 1 to "f" of "C" has incompatible type "T2"; expected "T + return x +[builtins fixtures/classmethod.pyi] + +[case testGenericClassInGenericFunction] +from typing import TypeVar, Generic + +T = TypeVar('T') + +class C(Generic[T]): + def __init__(self, item: T) -> None: ... + @classmethod + def f(cls, x: T) -> T: + return x + +def foo(x: T, y: int) -> T: + C(y) # OK + C[T](y) # E: Argument 1 to "C" has incompatible type "int"; expected "T" + C[T].f(y) # E: Argument 1 to "f" of "C" has incompatible type "int"; expected "T" + return x +[builtins fixtures/classmethod.pyi] + +[case testGenericClassDirectCall] +from typing import TypeVar, Generic + +T = TypeVar('T') + +class C(Generic[T]): + def __init__(self, item: T) -> None: ... + @classmethod + def f(cls) -> C[T]: + return cls(1) + +[builtins fixtures/classmethod.pyi] + +[case testGenericClassAlternativeConstructorPrecise] +from typing import Generic, TypeVar, Type, Tuple, Any + +T = TypeVar('T') + +class Base(Generic[T]): + Q = TypeVar('Q', bound=Base[T]) + + def __init__(self, item: T) -> None: ... + + @classmethod + def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: + if bool(): + return cls(0), cls(0) # E: error + return cls(item), cls(item) + +class Sub(Base[T]): + ... + +reveal_type(Sub.make_pair('yes')) # N: Revealed type is 'Tuple[__main__.Sub[builtins.str*], __main__.Sub[builtins.str*]]' +Sub[int].make_pair('no') # E: Argument 1 to "make_pair" of "Base" has incompatible type "str"; expected "int" +[builtins fixtures/classmethod.pyi] + [case testGenericClassMethodUnboundOnClassNonMatchingIdNonGeneric] from typing import Generic, TypeVar, Any, Tuple, Type From 534976e32298da32031ea9e42cc2fddb67df2cbb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 11:30:06 +0000 Subject: [PATCH 2/8] Some fixes --- mypy/checkmember.py | 3 ++- test-data/unit/check-generics.test | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d021cf3b60f9..02d89b61542f 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -801,7 +801,8 @@ class B(A[str]): pass return t.copy_modified(variables=tvars + t.variables) elif isinstance(t, Overloaded): return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod, - builtin_type, original_type)) + builtin_type, original_type, + original_vars=original_vars)) for item in t.items()]) return t diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 42da08f73147..202588b92fcf 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1960,7 +1960,7 @@ class C(Generic[T]): @classmethod def g(cls, x: T2) -> T2: - cls.f(x) # E: Argument 1 to "f" of "C" has incompatible type "T2"; expected "T + cls.f(x) # E: Argument 1 to "f" of "C" has incompatible type "T2"; expected "T" return x [builtins fixtures/classmethod.pyi] @@ -1988,11 +1988,17 @@ from typing import TypeVar, Generic T = TypeVar('T') class C(Generic[T]): - def __init__(self, item: T) -> None: ... + def __init__(self, item: T) -> None: + self.item = item @classmethod - def f(cls) -> C[T]: - return cls(1) + def f(cls) -> None: + cls(1) + +class D(C[str]): + def __init__(self, item: str) -> None: + self.item = item + 'no way' +D().f() [builtins fixtures/classmethod.pyi] [case testGenericClassAlternativeConstructorPrecise] @@ -2034,7 +2040,7 @@ class B(A[T], Generic[T, S]): reveal_type(A[T].foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.A*[T`1]]' @classmethod def other(cls) -> None: - reveal_type(cls.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__.B*[T`1, S`2]]' + reveal_type(cls.foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.B*[T`1, S`2]]' reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__.B*[T`1, S`2]]' [builtins fixtures/classmethod.pyi] From 0a79576558395ff508404afc232853dd5b4d8d62 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 11:40:35 +0000 Subject: [PATCH 3/8] Tweak also direct cls call --- mypy/checkexpr.py | 7 ++++++- mypy/checkmember.py | 8 -------- test-data/unit/check-generics.test | 19 +++---------------- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 27f462b7d00f..27bf58573ce5 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -911,7 +911,12 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Proper res = type_object_type(item.type, self.named_type) if isinstance(res, CallableType): res = res.copy_modified(from_type_type=True) - return expand_type_by_instance(res, item) + expanded = expand_type_by_instance(res, item) + if isinstance(expanded, CallableType): + # Callee of the form Type[...] should never be generic, only + # proper class objects can be. + expanded = expanded.copy_modified(variables=[]) + return expanded if isinstance(item, UnionType): return UnionType([self.analyze_type_type_callee(tp, context) for tp in item.relevant_items()], item.line) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 02d89b61542f..2b4efaf157d2 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -769,7 +769,6 @@ class B(A[str]): pass component in case if a union (this is used to bind the self-types). """ # TODO: verify consistency between Q and T - info = itype.type # type: TypeInfo if is_classmethod: assert isuper is not None t = expand_type_by_instance(t, isuper) @@ -788,13 +787,6 @@ class B(A[str]): pass free_ids = {t.id for t in itype.args if isinstance(t, TypeVarType)} if isinstance(t, CallableType): - # NOTE: in practice either all or none of the variables are free, since - # visit_type_application() will detect any type argument count mismatch and apply - # a correct number of Anys. - tvars = [TypeVarDef(n, n, i + 1, [], builtin_type('builtins.object'), tv.variance) - for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars) - # use 'is' to avoid id clashes with unrelated variables - if any(tv.id is id for id in free_ids)] tvars = original_vars if original_vars is not None else [] if is_classmethod: t = bind_self(t, original_type, is_classmethod=True) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 202588b92fcf..8e87752b8acc 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1988,17 +1988,10 @@ from typing import TypeVar, Generic T = TypeVar('T') class C(Generic[T]): - def __init__(self, item: T) -> None: - self.item = item + def __init__(self, item: T) -> None: ... @classmethod def f(cls) -> None: - cls(1) - -class D(C[str]): - def __init__(self, item: str) -> None: - self.item = item + 'no way' - -D().f() + cls(1) # E: Argument 1 to "C" has incompatible type "int"; expected "T" [builtins fixtures/classmethod.pyi] [case testGenericClassAlternativeConstructorPrecise] @@ -2014,14 +2007,8 @@ class Base(Generic[T]): @classmethod def make_pair(cls: Type[Q], item: T) -> Tuple[Q, Q]: if bool(): - return cls(0), cls(0) # E: error + return cls(0), cls(0) # E: Argument 1 to "Base" has incompatible type "int"; expected "T" return cls(item), cls(item) - -class Sub(Base[T]): - ... - -reveal_type(Sub.make_pair('yes')) # N: Revealed type is 'Tuple[__main__.Sub[builtins.str*], __main__.Sub[builtins.str*]]' -Sub[int].make_pair('no') # E: Argument 1 to "make_pair" of "Base" has incompatible type "str"; expected "int" [builtins fixtures/classmethod.pyi] [case testGenericClassMethodUnboundOnClassNonMatchingIdNonGeneric] From 28664293e1a824ed20188cb2a33704d374e883b8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 12:21:45 +0000 Subject: [PATCH 4/8] Update tests --- mypy/plugins/ctypes.py | 30 ++++++++++----------- test-data/unit/check-ctypes.test | 38 +++++++++++++-------------- test-data/unit/check-dataclasses.test | 7 +++-- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index ce943cec8850..88fb2f04769e 100644 --- a/mypy/plugins/ctypes.py +++ b/mypy/plugins/ctypes.py @@ -6,6 +6,7 @@ import mypy.plugin from mypy import nodes from mypy.maptype import map_instance_to_supertype +from mypy.messages import format_type from mypy.subtypes import is_subtype from mypy.types import ( AnyType, CallableType, Instance, NoneType, Type, TypeOfAny, UnionType, @@ -125,18 +126,17 @@ def array_constructor_callback(ctx: 'mypy.plugin.FunctionContext') -> Type: for arg_num, (arg_kind, arg_type) in enumerate(zip(ctx.arg_kinds[0], ctx.arg_types[0]), 1): if arg_kind == nodes.ARG_POS and not is_subtype(arg_type, allowed): ctx.api.msg.fail( - 'Array constructor argument {} of type "{}"' - ' is not convertible to the array element type "{}"' - .format(arg_num, arg_type, et), - ctx.context) + 'Array constructor argument {} of type {}' + ' is not convertible to the array element type {}' + .format(arg_num, format_type(arg_type), format_type(et)), ctx.context) elif arg_kind == nodes.ARG_STAR: ty = ctx.api.named_generic_type("typing.Iterable", [allowed]) if not is_subtype(arg_type, ty): + it = ctx.api.named_generic_type("typing.Iterable", [et]) ctx.api.msg.fail( - 'Array constructor argument {} of type "{}"' - ' is not convertible to the array element type "Iterable[{}]"' - .format(arg_num, arg_type, et), - ctx.context) + 'Array constructor argument {} of type {}' + ' is not convertible to the array element type {}' + .format(arg_num, format_type(arg_type), format_type(it)), ctx.context) return ctx.default_return_type @@ -204,10 +204,9 @@ def array_value_callback(ctx: 'mypy.plugin.AttributeContext') -> Type: types.append(_get_text_type(ctx.api)) else: ctx.api.msg.fail( - 'ctypes.Array attribute "value" is only available' - ' with element type c_char or c_wchar, not "{}"' - .format(et), - ctx.context) + 'Array attribute "value" is only available' + ' with element type "c_char" or "c_wchar", not {}' + .format(format_type(et)), ctx.context) return make_simplified_union(types) return ctx.default_attr_type @@ -223,9 +222,8 @@ def array_raw_callback(ctx: 'mypy.plugin.AttributeContext') -> Type: types.append(_get_bytes_type(ctx.api)) else: ctx.api.msg.fail( - 'ctypes.Array attribute "raw" is only available' - ' with element type c_char, not "{}"' - .format(et), - ctx.context) + 'Array attribute "raw" is only available' + ' with element type "c_char", not {}' + .format(format_type(et)), ctx.context) return make_simplified_union(types) return ctx.default_attr_type diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test index a27011ca48a1..f6e55a451794 100644 --- a/test-data/unit/check-ctypes.test +++ b/test-data/unit/check-ctypes.test @@ -6,8 +6,8 @@ class MyCInt(ctypes.c_int): intarr4 = ctypes.c_int * 4 a = intarr4(1, ctypes.c_int(2), MyCInt(3), 4) -intarr4(1, 2, 3, "invalid") # E: Array constructor argument 4 of type "builtins.str" is not convertible to the array element type "ctypes.c_int" -reveal_type(a) # N: Revealed type is 'ctypes.Array[ctypes.c_int]' +intarr4(1, 2, 3, "invalid") # E: Array constructor argument 4 of type "str" is not convertible to the array element type "c_int" +reveal_type(a) # N: Revealed type is 'ctypes.Array[ctypes.c_int*]' reveal_type(a[0]) # N: Revealed type is 'builtins.int' reveal_type(a[1:3]) # N: Revealed type is 'builtins.list[builtins.int]' a[0] = 42 @@ -30,11 +30,11 @@ class MyCInt(ctypes.c_int): myintarr4 = MyCInt * 4 mya = myintarr4(1, 2, MyCInt(3), 4) -myintarr4(1, ctypes.c_int(2), MyCInt(3), "invalid") # E: Array constructor argument 2 of type "ctypes.c_int" is not convertible to the array element type "__main__.MyCInt" \ - # E: Array constructor argument 4 of type "builtins.str" is not convertible to the array element type "__main__.MyCInt" -reveal_type(mya) # N: Revealed type is 'ctypes.Array[__main__.MyCInt]' -reveal_type(mya[0]) # N: Revealed type is '__main__.MyCInt' -reveal_type(mya[1:3]) # N: Revealed type is 'builtins.list[__main__.MyCInt]' +myintarr4(1, ctypes.c_int(2), MyCInt(3), "invalid") # E: Array constructor argument 2 of type "c_int" is not convertible to the array element type "MyCInt" \ + # E: Array constructor argument 4 of type "str" is not convertible to the array element type "MyCInt" +reveal_type(mya) # N: Revealed type is 'ctypes.Array[__main__.MyCInt*]' +reveal_type(mya[0]) # N: Revealed type is '__main__.MyCInt*' +reveal_type(mya[1:3]) # N: Revealed type is 'builtins.list[__main__.MyCInt*]' mya[0] = 42 mya[1] = ctypes.c_int(42) # E: No overload variant of "__setitem__" of "Array" matches argument types "int", "c_int" \ # N: Possible overload variants: \ @@ -106,7 +106,7 @@ import ctypes wca = (ctypes.c_wchar * 4)('a', 'b', 'c', '\x00') reveal_type(wca.value) # N: Revealed type is 'builtins.str' -wca.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "ctypes.c_wchar" +wca.raw # E: Array attribute "raw" is only available with element type "c_char", not "c_wchar" [builtins fixtures/floatdict.pyi] [case testCtypesWcharArrayAttrsPy2] @@ -115,7 +115,7 @@ import ctypes wca = (ctypes.c_wchar * 4)(u'a', u'b', u'c', u'\x00') reveal_type(wca.value) # N: Revealed type is 'builtins.unicode' -wca.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "ctypes.c_wchar" +wca.raw # E: Array attribute "raw" is only available with element type "c_char", not "c_wchar" [builtins_py2 fixtures/floatdict_python2.pyi] [case testCtypesCharUnionArrayAttrs] @@ -124,7 +124,7 @@ from typing import Union cua: ctypes.Array[Union[ctypes.c_char, ctypes.c_wchar]] reveal_type(cua.value) # N: Revealed type is 'Union[builtins.bytes, builtins.str]' -cua.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "Union[ctypes.c_char, ctypes.c_wchar]" +cua.raw # E: Array attribute "raw" is only available with element type "c_char", not "Union[c_char, c_wchar]" [builtins fixtures/floatdict.pyi] [case testCtypesAnyUnionArrayAttrs] @@ -141,8 +141,8 @@ import ctypes from typing import Union cua: ctypes.Array[Union[ctypes.c_char, ctypes.c_int]] -cua.value # E: ctypes.Array attribute "value" is only available with element type c_char or c_wchar, not "Union[ctypes.c_char, ctypes.c_int]" -cua.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "Union[ctypes.c_char, ctypes.c_int]" +cua.value # E: Array attribute "value" is only available with element type "c_char" or "c_wchar", not "Union[c_char, c_int]" +cua.raw # E: Array attribute "raw" is only available with element type "c_char", not "Union[c_char, c_int]" [builtins fixtures/floatdict.pyi] [case testCtypesAnyArrayAttrs] @@ -157,8 +157,8 @@ reveal_type(aa.raw) # N: Revealed type is 'builtins.bytes' import ctypes oa = (ctypes.c_int * 4)(1, 2, 3, 4) -oa.value # E: ctypes.Array attribute "value" is only available with element type c_char or c_wchar, not "ctypes.c_int" -oa.raw # E: ctypes.Array attribute "raw" is only available with element type c_char, not "ctypes.c_int" +oa.value # E: Array attribute "value" is only available with element type "c_char" or "c_wchar", not "c_int" +oa.raw # E: Array attribute "raw" is only available with element type "c_char", not "c_int" [builtins fixtures/floatdict.pyi] [case testCtypesArrayConstructorStarargs] @@ -168,13 +168,13 @@ intarr4 = ctypes.c_int * 4 intarr6 = ctypes.c_int * 6 int_values = [1, 2, 3, 4] c_int_values = [ctypes.c_int(1), ctypes.c_int(2), ctypes.c_int(3), ctypes.c_int(4)] -reveal_type(intarr4(*int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int]' -reveal_type(intarr4(*c_int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int]' -reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int]' -reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int]' +reveal_type(intarr4(*int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int*]' +reveal_type(intarr4(*c_int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int*]' +reveal_type(intarr6(1, ctypes.c_int(2), *int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int*]' +reveal_type(intarr6(1, ctypes.c_int(2), *c_int_values)) # N: Revealed type is 'ctypes.Array[ctypes.c_int*]' float_values = [1.0, 2.0, 3.0, 4.0] -intarr4(*float_values) # E: Array constructor argument 1 of type "builtins.list[builtins.float*]" is not convertible to the array element type "Iterable[ctypes.c_int]" +intarr4(*float_values) # E: Array constructor argument 1 of type "List[float]" is not convertible to the array element type "Iterable[c_int]" [builtins fixtures/floatdict.pyi] [case testCtypesArrayConstructorKwargs] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 3a5e68c90ed5..c4e5e6d6d3f1 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -484,9 +484,12 @@ class A(Generic[T]): @classmethod def foo(cls) -> None: reveal_type(cls) # N: Revealed type is 'Type[__main__.A[T`1]]' - reveal_type(cls(1)) # N: Revealed type is '__main__.A[builtins.int*]' - reveal_type(cls('wooooo')) # N: Revealed type is '__main__.A[builtins.str*]' + reveal_type(cls(cls.x)) # N: Revealed type is '__main__.A[T`1]' + @classmethod + def other(cls, x: T) -> A[T]: ... + +reveal_type(A(0).other) # N: Revealed type is 'def (x: builtins.int*) -> __main__.A[builtins.int*]' [builtins fixtures/classmethod.pyi] [case testDataclassesForwardRefs] From b7efd222b5f289d14081bd60f747dc6ebceb1a86 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 3 Nov 2019 12:31:17 +0000 Subject: [PATCH 5/8] Fix lint --- mypy/checkmember.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2b4efaf157d2..e97cd75354b6 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -784,8 +784,6 @@ class B(A[str]): pass # This behaviour is useful for defining alternative constructors for generic classes. # To achieve such behaviour, we add the class type variables that are still free # (i.e. appear in the return type of the class object on which the method was accessed). - free_ids = {t.id for t in itype.args if isinstance(t, TypeVarType)} - if isinstance(t, CallableType): tvars = original_vars if original_vars is not None else [] if is_classmethod: From a59cecfb5d7210bdccee0e9c0b5f47d3f4b0fc8f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 11 Nov 2019 22:50:36 +0000 Subject: [PATCH 6/8] Fix self-check --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f9a00a51c074..ee03eba59dac 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -911,7 +911,7 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: res = type_object_type(item.type, self.named_type) if isinstance(res, CallableType): res = res.copy_modified(from_type_type=True) - expanded = expand_type_by_instance(res, item) + expanded = get_proper_type(expand_type_by_instance(res, item)) if isinstance(expanded, CallableType): # Callee of the form Type[...] should never be generic, only # proper class objects can be. From 5921ef93b4b599278a804dfe8d1ba52a3219079e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 12 Nov 2019 17:47:55 +0000 Subject: [PATCH 7/8] Address CR --- mypy/checkmember.py | 13 +++-- mypy/plugins/dataclasses.py | 5 ++ mypy/typeops.py | 1 - test-data/unit/check-dataclasses.test | 2 +- test-data/unit/check-generics.test | 78 ++++++++++++++++++++++++++- 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index acf6c0ea3e3b..ad767fe33f60 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -240,6 +240,7 @@ def analyze_type_callable_member_access(name: str, # This check makes sure that when we encounter an operator, we skip looking up # the corresponding method in the current instance to avoid this edge case. # See https://github.com/python/mypy/pull/1787 for more info. + # TODO: do not rely on same type variables being present in all constructor overloads. result = analyze_class_attribute_access(ret_type, name, mx, original_vars=typ.items()[0].variables) if result: @@ -653,7 +654,12 @@ def analyze_class_attribute_access(itype: Instance, override_info: Optional[TypeInfo] = None, original_vars: Optional[List[TypeVarDef]] = None ) -> Optional[Type]: - """original_type is the type of E in the expression E.var""" + """Analyze access to an attribute on a class object. + + itype is the return type of the class object callable, original_type is the type + of E in the expression E.var, original_vars are type variables of the class callable + (for generic classes). + """ info = itype.type if override_info: info = override_info @@ -721,7 +727,7 @@ def analyze_class_attribute_access(itype: Instance, # C[int].x # Also an error, since C[int] is same as C at runtime if isinstance(t, TypeVarType) or has_type_vars(t): # Exception: access on Type[...], including first argument of class methods is OK. - if not isinstance(get_proper_type(mx.original_type), TypeType): + if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit: if node.node.is_classvar: message = message_registry.GENERIC_CLASS_VAR_ACCESS else: @@ -800,7 +806,8 @@ class B(A[str]): pass B.foo() original_type is the value of the type B in the expression B.foo() or the corresponding - component in case if a union (this is used to bind the self-types). + component in case if a union (this is used to bind the self-types); original_vars are type + variables of the class callable on which the method was accessed. """ # TODO: verify consistency between Q and T if is_classmethod: diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index a6b598709e52..d4a43c617e29 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -269,6 +269,11 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: elif not isinstance(stmt.rvalue, TempNode): has_default = True + if not has_default: + # Make all non-default attributes implicit because they are de-facto set + # on self in the generated __init__(), not in the class body. + sym.implicit = True + known_attrs.add(lhs.name) attrs.append(DataclassAttribute( name=lhs.name, diff --git a/mypy/typeops.py b/mypy/typeops.py index b26aa8b3ea73..8be81d68c7d6 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -192,7 +192,6 @@ class B(A): pass """ from mypy.infer import infer_type_arguments - if isinstance(method, Overloaded): return cast(F, Overloaded([bind_self(c, original_type, is_classmethod) for c in method.items()])) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index c4e5e6d6d3f1..2822abec62b4 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -484,7 +484,7 @@ class A(Generic[T]): @classmethod def foo(cls) -> None: reveal_type(cls) # N: Revealed type is 'Type[__main__.A[T`1]]' - reveal_type(cls(cls.x)) # N: Revealed type is '__main__.A[T`1]' + cls.x # E: Access to generic instance variables via class is ambiguous @classmethod def other(cls, x: T) -> A[T]: ... diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 140add167fb2..42fcd30135a6 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1979,6 +1979,33 @@ def foo(x: T, y: int) -> T: C(y) # OK C[T](y) # E: Argument 1 to "C" has incompatible type "int"; expected "T" C[T].f(y) # E: Argument 1 to "f" of "C" has incompatible type "int"; expected "T" + C[T].f(x) # OK + return x +[builtins fixtures/classmethod.pyi] + +# TODO: enable this when #7935 is fixed. +[case testGenericClassInGenericFunctionOverloadedConstructor-skip] +from typing import TypeVar, Generic, overload + +T = TypeVar('T') + +class C(Generic[T]): + @overload + def __new__(cls) -> C[None]: ... + @overload + def __new__(cls, item: T) -> C[T]: ... + def __new__(cls, item=None): + ... + @classmethod + def f(cls, x: T) -> T: + return x + +def foo(x: T, y: int) -> T: + C.f(y) + C(y) # OK + C[T](y) # E: Argument 1 to "C" has incompatible type "int"; expected "T" + C[T].f(y) # E: Argument 1 to "f" of "C" has incompatible type "int"; expected "T" + C[T].f(x) # OK return x [builtins fixtures/classmethod.pyi] @@ -1995,7 +2022,7 @@ class C(Generic[T]): [builtins fixtures/classmethod.pyi] [case testGenericClassAlternativeConstructorPrecise] -from typing import Generic, TypeVar, Type, Tuple, Any +from typing import Generic, TypeVar, Type, Tuple T = TypeVar('T') @@ -2011,6 +2038,55 @@ class Base(Generic[T]): return cls(item), cls(item) [builtins fixtures/classmethod.pyi] +[case testGenericClassAlternativeConstructorPreciseOverloaded] +from typing import Generic, TypeVar, Type, Tuple, overload, Union + +T = TypeVar('T') + +class Base(Generic[T]): + Q = TypeVar('Q', bound=Base[T]) + + def __init__(self, item: T) -> None: ... + + @overload + @classmethod + def make_some(cls: Type[Q], item: T) -> Q: ... + + @overload + @classmethod + def make_some(cls: Type[Q], item: T, n: int) -> Tuple[Q, ...]: ... + + @classmethod + def make_some(cls: Type[Q], item: T, n: int = 0) -> Union[Q, Tuple[Q, ...]]: + if n: + return (cls(item),) + return cls(item) + +reveal_type(Base.make_some) # N: Revealed type is 'Overload(def [T] (item: T`1) -> __main__.Base*[T`1], def [T] (item: T`1, n: builtins.int) -> builtins.tuple[__main__.Base*[T`1]])' +reveal_type(Base.make_some(1)) # N: Revealed type is '__main__.Base[builtins.int*]' +reveal_type(Base.make_some(1, 1)) # N: Revealed type is 'builtins.tuple[__main__.Base[builtins.int*]]' + +class Sub(Base[str]): ... +Sub.make_some(1) # E: No overload variant of "make_some" of "Base" matches argument type "int" \ + # N: Possible overload variant: \ + # N: def make_some(cls, item: str) -> Sub \ + # N: <1 more non-matching overload not shown> +[builtins fixtures/classmethod.pyi] + +[case testNoGenericAccessOnImplicitAttributes] +from typing import TypeVar, Generic + +T = TypeVar('T') + +class C(Generic[T]): + def __init__(self, x: T) -> None: + self.x = x + + @classmethod + def meth(cls) -> None: + cls.x # E: Access to generic instance variables via class is ambiguous +[builtins fixtures/classmethod.pyi] + [case testGenericClassMethodUnboundOnClassNonMatchingIdNonGeneric] from typing import Generic, TypeVar, Any, Tuple, Type From 3bbf7dea7ed0b72303369ce9e520c3aab00d5087 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 12 Nov 2019 18:03:36 +0000 Subject: [PATCH 8/8] Put back an empty line --- mypy/typeops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/typeops.py b/mypy/typeops.py index 8be81d68c7d6..b26aa8b3ea73 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -192,6 +192,7 @@ class B(A): pass """ from mypy.infer import infer_type_arguments + if isinstance(method, Overloaded): return cast(F, Overloaded([bind_self(c, original_type, is_classmethod) for c in method.items()]))