From b6dc76a4d84914ce6bd13b5b9fc725fe1de517d1 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 17 Oct 2016 15:33:54 -0400 Subject: [PATCH 1/6] Implement the descriptor protocol --- mypy/checker.py | 63 +++++- mypy/checkexpr.py | 73 ++++++- mypy/checkmember.py | 6 +- test-data/unit/check-classes.test | 324 +++++++++++++++++++++++++++++- 4 files changed, 450 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e1b844465c07..68ab5e76d02f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -31,7 +31,7 @@ Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, TypeType, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, - true_only, false_only, function_type + true_only, false_only, function_type, TypeType ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -45,7 +45,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.semanal import fill_typevars, set_callable_name, refers_to_fullname from mypy.erasetype import erase_typevars -from mypy.expandtype import expand_type +from mypy.expandtype import expand_type, expand_type_by_instance from mypy.visitor import NodeVisitor from mypy.join import join_types from mypy.treetransform import TransformVisitor @@ -1149,6 +1149,11 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type not new_syntax): # Allow None's to be assigned to class variables with non-Optional types. rvalue_type = lvalue_type + elif (isinstance(lvalue, MemberExpr) and + lvalue.kind is None): # Ignore member access to modules + instance_type = self.accept(lvalue.expr) + rvalue_type, infer_lvalue_type = self.check_member_assignment( + instance_type, lvalue_type, rvalue, lvalue) else: rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) @@ -1478,6 +1483,60 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression, '{} has type'.format(lvalue_name)) return rvalue_type + def check_member_assignment(self, instance_type: Type, attribute_type: Type, + rvalue: Expression, context: Context) -> Tuple[Type, bool]: + """Type member assigment. + + This is defers to check_simple_assignment, unless the member expression + is a descriptor, in which case this checks descriptor semantics as well. + + Return the inferred rvalue_type and whether to infer anything about the attribute type + """ + # Descriptors don't participate in class-attribute access + if ((isinstance(instance_type, FunctionLike) and instance_type.is_type_obj()) or + isinstance(instance_type, TypeType)): + rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context) + return rvalue_type, True + + if not isinstance(attribute_type, Instance): + rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context) + return rvalue_type, True + + if not attribute_type.type.has_readable_member('__set__'): + # If there is no __set__, we type-check that the assigned value matches + # the return type of __get__. This doesn't match the python semantics, + # (which allow you to override the descriptor with any value), but preserves + # the type of accessing the attribute (even after the override). + if attribute_type.type.has_readable_member('__get__'): + attribute_type = self.expr_checker.analyze_descriptor_access( + instance_type, attribute_type, context) + rvalue_type = self.check_simple_assignment(attribute_type, rvalue, context) + return rvalue_type, True + + dunder_set = attribute_type.type.get_method('__set__') + if dunder_set is None: + self.msg.fail("{}.__set__ is not callable".format(attribute_type), context) + return AnyType(), False + + function = function_type(dunder_set, self.named_type('builtins.function')) + bound_method = bind_self(function, attribute_type) + typ = map_instance_to_supertype(attribute_type, dunder_set.info) + dunder_set_type = expand_type_by_instance(bound_method, typ) + + _, inferred_dunder_set_type = self.expr_checker.check_call( + dunder_set_type, [TempNode(instance_type), rvalue], + [nodes.ARG_POS, nodes.ARG_POS], context) + + if not isinstance(inferred_dunder_set_type, CallableType): + self.fail("__set__ is not callable", context) + return AnyType(), True + + if len(inferred_dunder_set_type.arg_types) < 2: + # A message already will have been recorded in check_call + return AnyType(), False + + return inferred_dunder_set_type.arg_types[1], False + def check_indexed_assignment(self, lvalue: IndexExpr, rvalue: Expression, context: Context) -> None: """Type check indexed assignment base[index] = rvalue. diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 94db4701b6ff..76e5e26fa61f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -7,7 +7,7 @@ Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef, TupleType, TypedDictType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, - true_only, false_only, is_named_instance, function_type, + true_only, false_only, is_named_instance, function_type, FunctionLike, get_typ_args, set_typ_args, ) from mypy.nodes import ( @@ -30,13 +30,14 @@ from mypy import messages from mypy.infer import infer_type_arguments, infer_function_type_arguments from mypy import join +from mypy.maptype import map_instance_to_supertype from mypy.subtypes import is_subtype, is_equivalent from mypy import applytype from mypy import erasetype -from mypy.checkmember import analyze_member_access, type_object_type +from mypy.checkmember import analyze_member_access, type_object_type, bind_self from mypy.constraints import get_actual_type from mypy.checkstrformat import StringFormatterChecker -from mypy.expandtype import expand_type +from mypy.expandtype import expand_type, expand_type_by_instance from mypy.util import split_module_names from mypy.semanal import fill_typevars @@ -983,10 +984,68 @@ def analyze_ordinary_member_access(self, e: MemberExpr, else: # This is a reference to a non-module attribute. original_type = self.accept(e.expr) - return analyze_member_access(e.name, original_type, e, - is_lvalue, False, False, - self.named_type, self.not_ready_callback, self.msg, - original_type=original_type, chk=self.chk) + member_type = analyze_member_access( + e.name, original_type, e, is_lvalue, False, False, + self.named_type, self.not_ready_callback, self.msg, + original_type=original_type, chk=self.chk) + if is_lvalue: + return member_type + else: + return self.analyze_descriptor_access(original_type, member_type, e) + + def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type, + context: Context) -> Type: + """Type check descriptor access. + + Arguments: + instance_type: The type of the instance on which the descriptor + attribute is being accessed (the type of ``a`` in ``a.f`` when + ``f`` is a descriptor). + descriptor_type: The type of the descriptor attribute being accessed + (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). + contetx: The node defining the context of this inference. + Return: + The return type of the appropriate ``__get__`` overload for the descriptor. + """ + if not isinstance(descriptor_type, Instance): + return descriptor_type + + if not descriptor_type.type.has_readable_member('__get__'): + return descriptor_type + + dunder_get = descriptor_type.type.get_method('__get__') + + if dunder_get is None: + self.msg.fail("{}.__get__ is not callable".format(descriptor_type), context) + return AnyType() + + function = function_type(dunder_get, self.named_type('builtins.function')) + bound_method = bind_self(function, descriptor_type) + typ = map_instance_to_supertype(descriptor_type, dunder_get.info) + dunder_get_type = expand_type_by_instance(bound_method, typ) + + if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): + instance_type = instance_type.items()[0].ret_type + owner_type = NoneTyp() + elif isinstance(instance_type, TypeType): + instance_type = instance_type.item + owner_type = NoneTyp() + else: + owner_type = instance_type + + _, inferred_dunder_get_type = self.check_call( + dunder_get_type, [TempNode(owner_type), TempNode(TypeType(instance_type))], + [nodes.ARG_POS, nodes.ARG_POS], context) + + if isinstance(inferred_dunder_get_type, AnyType): + # check_call failed, and will have reported an error + return inferred_dunder_get_type + + if not isinstance(inferred_dunder_get_type, CallableType): + self.msg.fail("{}.__get__ is not callable".format(descriptor_type), context) + return AnyType() + + return inferred_dunder_get_type.ret_type def analyze_external_member_access(self, member: str, base_type: Type, context: Context) -> Type: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d1e9ab1cc777..b77a5af677cc 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -143,7 +143,7 @@ def analyze_member_access(name: str, # 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, node, is_lvalue, - builtin_type, not_ready_callback, msg, + builtin_type, not_ready_callback, msg, chk, original_type=original_type) if result: return result @@ -176,7 +176,7 @@ def analyze_member_access(name: str, if item and not is_operator: # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, - builtin_type, not_ready_callback, msg, + builtin_type, not_ready_callback, msg, chk, original_type=original_type) if result: return result @@ -210,6 +210,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, if isinstance(vv, Decorator): # The associated Var node of a decorator contains the type. v = vv.var + if isinstance(v, Var): return analyze_var(name, v, itype, info, node, is_lvalue, msg, original_type, not_ready_callback) @@ -349,6 +350,7 @@ def analyze_class_attribute_access(itype: Instance, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, + chk: 'mypy.checker.TypeChecker', original_type: Type) -> Type: """original_type is the type of E in the expression E.var""" node = itype.type.get(name) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1434bb3c7caa..964c0da019e0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -805,10 +805,8 @@ import typing class A: @property def f(self) -> str: pass -a = A() # type. A -s = '' # type: str -s = a.f -a = a.f # E: Incompatible types in assignment (expression has type "str", variable has type "A") +a = A() +reveal_type(a.f) # E: Revealed type is 'builtins.str' [builtins fixtures/property.pyi] [case testAssigningToReadOnlyProperty] @@ -834,7 +832,7 @@ import typing class A: @property def f(self): pass -a = A() # type. A +a = A() a.f.xx a.f = '' # E: Property "f" defined in "A" is read-only [builtins fixtures/property.pyi] @@ -868,6 +866,322 @@ a.f = a.f # E: Property "f" defined in "A" is read-only a.f.x # E: "int" has no attribute "x" [builtins fixtures/property.pyi] +-- Descriptors +-- ----------- + + +[case testAccessingNonDataDescriptor] +from typing import Any +class D: + def __get__(self, inst: Any, own: Any) -> str: return 's' +class A: + f = D() +a = A() +reveal_type(a.f) # E: Revealed type is 'builtins.str' + +[case testSettingNonDataDescriptor] +from typing import Any +class D: + def __get__(self, inst: Any, own: Any) -> str: return 's' +class A: + f = D() +a = A() +a.f = 'foo' +a.f = D() # E: Incompatible types in assignment (expression has type "D", variable has type "str") + +[case testSettingDataDescriptor] +from typing import Any +class D: + def __get__(self, inst: Any, own: Any) -> str: return 's' + def __set__(self, inst: Any, value: str) -> None: pass +class A: + f = D() +a = A() +a.f = '' +a.f = 1 # E: Argument 2 to "__set__" of "D" has incompatible type "int"; expected "str" + +[case testReadingDescriptorWithoutDunderGet] +from typing import Union, Any +class D: + def __set__(self, inst: Any, value: str) -> None: pass +class A: + f = D() + def __init__(self): self.f = 's' +a = A() +reveal_type(a.f) # E: Revealed type is '__main__.D' + +[case testAccessingGenericNonDataDescriptor] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +a = A() +reveal_type(a.f) # E: Revealed type is 'builtins.int*' +reveal_type(a.g) # E: Revealed type is 'builtins.str*' + +[case testSettingGenericDataDescriptor] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v + def __set__(self, inst: T, v: V) -> None: pass +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +a = A() +a.f = 1 +a.f = '' # E: Argument 2 to "__set__" of "D" has incompatible type "str"; expected "int" +a.g = '' +a.g = 1 # E: Argument 2 to "__set__" of "D" has incompatible type "int"; expected "str" + +[case testAccessingGenericDescriptorFromClass] +# flags: --strict-optional +from d import D +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +reveal_type(A.f) # E: Revealed type is 'd.D[__main__.A*, builtins.int*]' +reveal_type(A.g) # E: Revealed type is 'd.D[__main__.A*, builtins.str*]' +[file d.pyi] +from typing import TypeVar, Type, Generic, overload +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: pass + @overload + def __get__(self, inst: None, own: Type[T]) -> 'D[T, V]': pass + @overload + def __get__(self, inst: T, own: Type[T]) -> V: pass +[builtins fixtures/bool.pyi] + +[case testAccessingGenericDescriptorFromInferredClass] +# flags: --strict-optional +from typing import Type +from d import D +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +def f(some_class: Type[A]): + reveal_type(some_class.f) + reveal_type(some_class.g) +[file d.pyi] +from typing import TypeVar, Type, Generic, overload +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: pass + @overload + def __get__(self, inst: None, own: Type[T]) -> 'D[T, V]': pass + @overload + def __get__(self, inst: T, own: Type[T]) -> V: pass +[builtins fixtures/bool.pyi] +[out] +main: note: In function "f": +main:8: error: Revealed type is 'd.D[__main__.A*, builtins.int*]' +main:9: error: Revealed type is 'd.D[__main__.A*, builtins.str*]' + + +[case testAccessingGenericDescriptorFromClassBadOverload] +# flags: --strict-optional +from d import D +class A: + f = D(10) # type: D[A, int] +reveal_type(A.f) +[file d.pyi] +from typing import TypeVar, Type, Generic, overload +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: pass + @overload + def __get__(self, inst: None, own: None) -> 'D[T, V]': pass + @overload + def __get__(self, inst: T, own: Type[T]) -> V: pass +[builtins fixtures/bool.pyi] +[out] +main:5: error: Revealed type is 'Any' +main:5: error: No overload variant of "__get__" of "D" matches argument types [builtins.None, Type[__main__.A]] + +[case testAccessingNonDataDescriptorSubclass] +from typing import Any +class C: + def __get__(self, inst: Any, own: Any) -> str: return 's' +class D(C): pass +class A: + f = D() +a = A() +reveal_type(a.f) # E: Revealed type is 'builtins.str' + +[case testSettingDataDescriptorSubclass] +from typing import Any +class C: + def __get__(self, inst: Any, own: Any) -> str: return 's' + def __set__(self, inst: Any, v: str) -> None: pass +class D(C): pass +class A: + f = D() +a = A() +a.f = '' +a.f = 1 # E: Argument 2 to "__set__" of "C" has incompatible type "int"; expected "str" + +[case testReadingDescriptorSubclassWithoutDunderGet] +from typing import Union, Any +class C: + def __set__(self, inst: Any, v: str) -> None: pass +class D(C): pass +class A: + f = D() + def __init__(self): self.f = 's' +a = A() +reveal_type(a.f) # E: Revealed type is '__main__.D' + +[case testAccessingGenericNonDataDescriptorSubclass] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class C(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v +class D(C[T, V], Generic[T, V]): pass +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +a = A() +reveal_type(a.f) # E: Revealed type is 'builtins.int*' +reveal_type(a.g) # E: Revealed type is 'builtins.str*' + +[case testSettingGenericDataDescriptorSubclass] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class C(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v + def __set__(self, inst: T, v: V) -> None: pass +class D(C[T, V], Generic[T, V]): pass +class A: + f = D(10) # type: D[A, int] + g = D('10') # type: D[A, str] +a = A() +a.f = 1 +a.f = '' # E: Argument 2 to "__set__" of "C" has incompatible type "str"; expected "int" +a.g = '' +a.g = 1 # E: Argument 2 to "__set__" of "C" has incompatible type "int"; expected "str" + +[case testSetDescriptorOnClass] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v + def __set__(self, inst: T, v: V) -> None: pass +class A: + f = D(10) # type: D[A, int] +A.f = D(20) +A.f = D('some string') # E: Argument 1 to "D" has incompatible type "str"; expected "int" + +[case testSetDescriptorOnInferredClass] +from typing import TypeVar, Type, Generic +T = TypeVar('T') +V = TypeVar('V') +class D(Generic[T, V]): + def __init__(self, v: V) -> None: self.v = v + def __get__(self, inst: T, own: Type[T]) -> V: return self.v + def __set__(self, inst: T, v: V) -> None: pass +class A: + f = D(10) # type: D[A, int] +def f(some_class: Type[A]): + A.f = D(20) + A.f = D('some string') +[out] +main: note: In function "f": +main:12: error: Argument 1 to "D" has incompatible type "str"; expected "int" + +[case testDescriptorUncallableDunderSet] +class D: + __set__ = 's' +class A: + f = D() +A().f = 'x' # E: __main__.D.__set__ is not callable + +[case testDescriptorDunderSetTooFewArgs] +class D: + def __set__(self, inst): pass +class A: + f = D() +A().f = 'x' # E: Too many arguments for "__set__" + +[case testDescriptorDunderSetTooManyArgs] +class D: + def __set__(self, inst, v, other): pass +class A: + f = D() +A().f = 'x' # E: Too few arguments for "__set__" + +[case testDescriptorDunderSetWrongArgTypes] +class D: + def __set__(self, inst: str, v:str) -> None: pass +class A: + f = D() +A().f = 'x' # E: Argument 1 to "__set__" of "D" has incompatible type "A"; expected "str" + +[case testDescriptorUncallableDunderGet] +class D: + __get__ = 's' +class A: + f = D() +A().f # E: __main__.D.__get__ is not callable + +[case testDescriptorDunderGetTooFewArgs] +class D: + def __get__(self, inst): pass +class A: + f = D() +A().f # E: Too many arguments for "__get__" + +[case testDescriptorDunderGetTooManyArgs] +class D: + def __get__(self, inst, own, other): pass +class A: + f = D() +A().f = 'x' # E: Too few arguments for "__get__" + +[case testDescriptorDunderGetWrongArgTypeForInstance] +from typing import Any +class D: + def __get__(self, inst: str, own: Any) -> Any: pass +class A: + f = D() +A().f # E: Argument 1 to "__get__" of "D" has incompatible type "A"; expected "str" + +[case testDescriptorDunderGetWrongArgTypeForOwner] +from typing import Any +class D: + def __get__(self, inst: Any, own: str) -> Any: pass +class A: + f = D() +A().f # E: Argument 2 to "__get__" of "D" has incompatible type Type[A]; expected "str" + +[case testDescriptorGetSetDifferentTypes] +from typing import Any +class D: + def __get__(self, inst: Any, own: Any) -> str: return 's' + def __set__(self, inst: Any, v: int) -> None: pass +class A: + f = D() +a = A() +a.f = 1 +reveal_type(a.f) # E: Revealed type is 'builtins.str' + + -- _promote decorators -- ------------------- From 602a9ce7ac3d5314a7160d112c90da4503ede303 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 2 Dec 2016 15:54:42 -0500 Subject: [PATCH 2/6] Fix test failures --- mypy/checker.py | 2 +- mypy/checkexpr.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 68ab5e76d02f..0d1068449e05 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -31,7 +31,7 @@ Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, Instance, NoneTyp, ErrorType, strip_type, TypeType, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, - true_only, false_only, function_type, TypeType + true_only, false_only, function_type ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 76e5e26fa61f..1f0558da69f2 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1023,6 +1023,7 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type, bound_method = bind_self(function, descriptor_type) typ = map_instance_to_supertype(descriptor_type, dunder_get.info) dunder_get_type = expand_type_by_instance(bound_method, typ) + owner_type = None # type: Type if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): instance_type = instance_type.items()[0].ret_type From bfabe20acf725ad91688944a6d993f1d09d26708 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 5 Dec 2016 07:28:14 -0500 Subject: [PATCH 3/6] Fix typo --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1f0558da69f2..4a04b4e1ebd7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1003,7 +1003,7 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type, ``f`` is a descriptor). descriptor_type: The type of the descriptor attribute being accessed (the type of ``f`` in ``a.f`` when ``f`` is a descriptor). - contetx: The node defining the context of this inference. + context: The node defining the context of this inference. Return: The return type of the appropriate ``__get__`` overload for the descriptor. """ From 8ebb185b40f8ad2d73ab9a72ca431982df6b776b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 6 Dec 2016 13:47:07 -0500 Subject: [PATCH 4/6] Remove some instances of unnecessary generics in Descriptor tests --- mypy/checkexpr.py | 10 +++--- test-data/unit/check-classes.test | 51 ++++++++++++++----------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4a04b4e1ebd7..6c6f58ff185d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1026,16 +1026,16 @@ def analyze_descriptor_access(self, instance_type: Type, descriptor_type: Type, owner_type = None # type: Type if isinstance(instance_type, FunctionLike) and instance_type.is_type_obj(): - instance_type = instance_type.items()[0].ret_type - owner_type = NoneTyp() + owner_type = instance_type.items()[0].ret_type + instance_type = NoneTyp() elif isinstance(instance_type, TypeType): - instance_type = instance_type.item - owner_type = NoneTyp() + owner_type = instance_type.item + instance_type = NoneTyp() else: owner_type = instance_type _, inferred_dunder_get_type = self.check_call( - dunder_get_type, [TempNode(owner_type), TempNode(TypeType(instance_type))], + dunder_get_type, [TempNode(instance_type), TempNode(TypeType(owner_type))], [nodes.ARG_POS, nodes.ARG_POS], context) if isinstance(inferred_dunder_get_type, AnyType): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 964c0da019e0..26c572d18934 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -911,30 +911,28 @@ a = A() reveal_type(a.f) # E: Revealed type is '__main__.D' [case testAccessingGenericNonDataDescriptor] -from typing import TypeVar, Type, Generic -T = TypeVar('T') +from typing import TypeVar, Type, Generic, Any V = TypeVar('V') -class D(Generic[T, V]): +class D(Generic[V]): def __init__(self, v: V) -> None: self.v = v - def __get__(self, inst: T, own: Type[T]) -> V: return self.v + def __get__(self, inst: Any, own: Type) -> V: return self.v class A: - f = D(10) # type: D[A, int] - g = D('10') # type: D[A, str] + f = D(10) + g = D('10') a = A() reveal_type(a.f) # E: Revealed type is 'builtins.int*' reveal_type(a.g) # E: Revealed type is 'builtins.str*' [case testSettingGenericDataDescriptor] -from typing import TypeVar, Type, Generic -T = TypeVar('T') +from typing import TypeVar, Type, Generic, Any V = TypeVar('V') -class D(Generic[T, V]): +class D(Generic[V]): def __init__(self, v: V) -> None: self.v = v - def __get__(self, inst: T, own: Type[T]) -> V: return self.v - def __set__(self, inst: T, v: V) -> None: pass + def __get__(self, inst: Any, own: Type) -> V: return self.v + def __set__(self, inst: Any, v: V) -> None: pass class A: - f = D(10) # type: D[A, int] - g = D('10') # type: D[A, str] + f = D(10) + g = D('10') a = A() a.f = 1 a.f = '' # E: Argument 2 to "__set__" of "D" has incompatible type "str"; expected "int" @@ -987,7 +985,6 @@ main: note: In function "f": main:8: error: Revealed type is 'd.D[__main__.A*, builtins.int*]' main:9: error: Revealed type is 'd.D[__main__.A*, builtins.str*]' - [case testAccessingGenericDescriptorFromClassBadOverload] # flags: --strict-optional from d import D @@ -1043,16 +1040,15 @@ a = A() reveal_type(a.f) # E: Revealed type is '__main__.D' [case testAccessingGenericNonDataDescriptorSubclass] -from typing import TypeVar, Type, Generic -T = TypeVar('T') +from typing import TypeVar, Type, Generic, Any V = TypeVar('V') -class C(Generic[T, V]): +class C(Generic[V]): def __init__(self, v: V) -> None: self.v = v - def __get__(self, inst: T, own: Type[T]) -> V: return self.v -class D(C[T, V], Generic[T, V]): pass + def __get__(self, inst: Any, own: Type) -> V: return self.v +class D(C[V], Generic[V]): pass class A: - f = D(10) # type: D[A, int] - g = D('10') # type: D[A, str] + f = D(10) + g = D('10') a = A() reveal_type(a.f) # E: Revealed type is 'builtins.int*' reveal_type(a.g) # E: Revealed type is 'builtins.str*' @@ -1089,21 +1085,20 @@ A.f = D(20) A.f = D('some string') # E: Argument 1 to "D" has incompatible type "str"; expected "int" [case testSetDescriptorOnInferredClass] -from typing import TypeVar, Type, Generic -T = TypeVar('T') +from typing import TypeVar, Type, Generic, Any V = TypeVar('V') -class D(Generic[T, V]): +class D(Generic[V]): def __init__(self, v: V) -> None: self.v = v - def __get__(self, inst: T, own: Type[T]) -> V: return self.v - def __set__(self, inst: T, v: V) -> None: pass + def __get__(self, inst: Any, own: Type) -> V: return self.v + def __set__(self, inst: Any, v: V) -> None: pass class A: - f = D(10) # type: D[A, int] + f = D(10) def f(some_class: Type[A]): A.f = D(20) A.f = D('some string') [out] main: note: In function "f": -main:12: error: Argument 1 to "D" has incompatible type "str"; expected "int" +main:11: error: Argument 1 to "D" has incompatible type "str"; expected "int" [case testDescriptorUncallableDunderSet] class D: From 4779fdb61ac38b1831f45038767b29ddce146164 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 13 Dec 2016 11:00:01 -0500 Subject: [PATCH 5/6] Address review feedback --- mypy/checkmember.py | 5 ++-- test-data/unit/check-classes.test | 44 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b77a5af677cc..4295d4e6ed2b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -143,7 +143,7 @@ def analyze_member_access(name: str, # 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, node, is_lvalue, - builtin_type, not_ready_callback, msg, chk, + builtin_type, not_ready_callback, msg, original_type=original_type) if result: return result @@ -176,7 +176,7 @@ def analyze_member_access(name: str, if item and not is_operator: # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, - builtin_type, not_ready_callback, msg, chk, + builtin_type, not_ready_callback, msg, original_type=original_type) if result: return result @@ -350,7 +350,6 @@ def analyze_class_attribute_access(itype: Instance, builtin_type: Callable[[str], Instance], not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, - chk: 'mypy.checker.TypeChecker', original_type: Type) -> Type: """original_type is the type of E in the expression E.var""" node = itype.type.get(name) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 26c572d18934..1e841fac44a5 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -910,6 +910,48 @@ class A: a = A() reveal_type(a.f) # E: Revealed type is '__main__.D' +[case testAccessingDescriptorFromClass] +# flags: --strict-optional +from d import D, Base +class A(Base): + f = D() +reveal_type(A.f) # E: Revealed type is 'd.D' +reveal_type(A().f) # E: Revealed type is 'builtins.str' +[file d.pyi] +from typing import TypeVar, Type, Generic, overload +class Base: pass +class D: + def __init__(self) -> None: pass + @overload + def __get__(self, inst: None, own: Type[Base]) -> D: pass + @overload + def __get__(self, inst: Base, own: Type[Base]) -> str: pass +[builtins fixtures/bool.pyi] + +[case testAccessingDescriptorFromClassWrongBase] +# flags: --strict-optional +from d import D, Base +class A: + f = D() +reveal_type(A.f) +reveal_type(A().f) +[file d.pyi] +from typing import TypeVar, Type, Generic, overload +class Base: pass +class D: + def __init__(self) -> None: pass + @overload + def __get__(self, inst: None, own: Type[Base]) -> D: pass + @overload + def __get__(self, inst: Base, own: Type[Base]) -> str: pass +[builtins fixtures/bool.pyi] +[out] +main:5: error: Revealed type is 'Any' +main:5: error: No overload variant of "__get__" of "D" matches argument types [builtins.None, Type[__main__.A]] +main:6: error: Revealed type is 'Any' +main:6: error: No overload variant of "__get__" of "D" matches argument types [__main__.A, Type[__main__.A]] + + [case testAccessingGenericNonDataDescriptor] from typing import TypeVar, Type, Generic, Any V = TypeVar('V') @@ -947,6 +989,8 @@ class A: g = D('10') # type: D[A, str] reveal_type(A.f) # E: Revealed type is 'd.D[__main__.A*, builtins.int*]' reveal_type(A.g) # E: Revealed type is 'd.D[__main__.A*, builtins.str*]' +reveal_type(A().f) # E: Revealed type is 'builtins.int*' +reveal_type(A().g) # E: Revealed type is 'builtins.str*' [file d.pyi] from typing import TypeVar, Type, Generic, overload T = TypeVar('T') From 70ab7491271f32eb026a7d12fbf673231e2ec6e7 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 13 Dec 2016 11:27:43 -0500 Subject: [PATCH 6/6] Fix expected error output in check-classes.test for Descriptor tests --- test-data/unit/check-classes.test | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1e841fac44a5..bdfa2de9b340 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1025,7 +1025,6 @@ class D(Generic[T, V]): def __get__(self, inst: T, own: Type[T]) -> V: pass [builtins fixtures/bool.pyi] [out] -main: note: In function "f": main:8: error: Revealed type is 'd.D[__main__.A*, builtins.int*]' main:9: error: Revealed type is 'd.D[__main__.A*, builtins.str*]' @@ -1141,7 +1140,6 @@ def f(some_class: Type[A]): A.f = D(20) A.f = D('some string') [out] -main: note: In function "f": main:11: error: Argument 1 to "D" has incompatible type "str"; expected "int" [case testDescriptorUncallableDunderSet]