From 46e63dd8eb6070b61199b7bb625a07825bd63e49 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 11:02:38 -0700 Subject: [PATCH 01/12] Print the union in question when an attribute doesn't exist in one of its elements. --- mypy/messages.py | 4 ++-- tmp-test-dirs/.gitignore | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 tmp-test-dirs/.gitignore diff --git a/mypy/messages.py b/mypy/messages.py index b15562b6268c..c8f29d1dd55a 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -425,8 +425,8 @@ def has_no_attr(self, typ: Type, member: str, context: Context) -> Type: self.fail('{} has no attribute "{}"'.format(self.format(typ), member), context) else: - self.fail('Some element of union has no attribute "{}"'.format( - member), context) + self.fail('Some element of {} has no attribute "{}"'.format( + typ, member), context) return AnyType() def unsupported_operand_types(self, op: str, left_type: Any, diff --git a/tmp-test-dirs/.gitignore b/tmp-test-dirs/.gitignore deleted file mode 100644 index e6579d8f495f..000000000000 --- a/tmp-test-dirs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# This directory is used to store temporary directories for the testsuite. -# If anything manages to exist here, it means python crashed instead of -# calling tempfile.TemporaryDirectory's cleanup while unwinding. -# Therefore, don't actually provide any ignore patterns. From c3b19a7aaf9d29a5c8f9585af00e8ff84f0e01b3 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 12:21:35 -0700 Subject: [PATCH 02/12] Print the offending element in the Union for which the attribute does not exist. --- mypy/checkmember.py | 4 +-- mypy/messages.py | 38 ++++++++++++++-------------- test-data/unit/check-generics.test | 2 +- test-data/unit/check-isinstance.test | 4 +-- test-data/unit/check-optional.test | 4 +-- test-data/unit/check-unions.test | 7 ++--- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ed3239a2bb46..d4dca6b6441d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -202,7 +202,7 @@ def analyze_member_access(name: str, if chk and chk.should_suppress_optional_error([typ]): return AnyType() - return msg.has_no_attr(original_type, name, node) + return msg.has_no_attr(original_type, typ, name, node) def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, @@ -256,7 +256,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, else: if chk and chk.should_suppress_optional_error([itype]): return AnyType() - return msg.has_no_attr(original_type, name, node) + return msg.has_no_attr(original_type, itype, name, node) def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, diff --git a/mypy/messages.py b/mypy/messages.py index c8f29d1dd55a..a3e6afebd319 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -361,72 +361,72 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]: # get some information as arguments, and they build an error message based # on them. - def has_no_attr(self, typ: Type, member: str, context: Context) -> Type: + def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: """Report a missing or non-accessible member. The type argument is the base type. If member corresponds to an operator, use the corresponding operator name in the messages. Return type Any. """ - if (isinstance(typ, Instance) and - typ.type.has_readable_member(member)): + if (isinstance(original_type, Instance) and + original_type.type.has_readable_member(member)): self.fail('Member "{}" is not assignable'.format(member), context) elif member == '__contains__': self.fail('Unsupported right operand type for in ({})'.format( - self.format(typ)), context) + self.format(original_type)), context) elif member in op_methods.values(): # Access to a binary operator member (e.g. _add). This case does # not handle indexing operations. for op, method in op_methods.items(): if method == member: - self.unsupported_left_operand(op, typ, context) + self.unsupported_left_operand(op, original_type, context) break elif member == '__neg__': self.fail('Unsupported operand type for unary - ({})'.format( - self.format(typ)), context) + self.format(original_type)), context) elif member == '__pos__': self.fail('Unsupported operand type for unary + ({})'.format( - self.format(typ)), context) + self.format(original_type)), context) elif member == '__invert__': self.fail('Unsupported operand type for ~ ({})'.format( - self.format(typ)), context) + self.format(original_type)), context) elif member == '__getitem__': # Indexed get. # TODO: Fix this consistently in self.format - if isinstance(typ, CallableType) and typ.is_type_obj(): + if isinstance(original_type, CallableType) and original_type.is_type_obj(): self.fail('The type {} is not generic and not indexable'.format( - self.format(typ)), context) + self.format(original_type)), context) else: self.fail('Value of type {} is not indexable'.format( - self.format(typ)), context) + self.format(original_type)), context) elif member == '__setitem__': # Indexed set. self.fail('Unsupported target for indexed assignment', context) elif member == '__call__': - if isinstance(typ, Instance) and (typ.type.fullname() == 'builtins.function'): + if isinstance(original_type, Instance) and (original_type.type.fullname() == 'builtins.function'): # "'function' not callable" is a confusing error message. # Explain that the problem is that the type of the function is not known. self.fail('Cannot call function of unknown type', context) else: - self.fail('{} not callable'.format(self.format(typ)), context) + self.fail('{} not callable'.format(self.format(original_type)), context) else: # The non-special case: a missing ordinary attribute. if not self.disable_type_names: failed = False - if isinstance(typ, Instance) and typ.type.names: - alternatives = set(typ.type.names.keys()) + if isinstance(original_type, Instance) and original_type.type.names: + alternatives = set(original_type.type.names.keys()) matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives] matches.extend(best_matches(member, alternatives)[:3]) if matches: self.fail('{} has no attribute "{}"; maybe {}?'.format( - self.format(typ), member, pretty_or(matches)), context) + self.format(original_type), member, pretty_or(matches)), context) failed = True if not failed: - self.fail('{} has no attribute "{}"'.format(self.format(typ), + self.fail('{} has no attribute "{}"'.format(self.format(original_type), member), context) else: - self.fail('Some element of {} has no attribute "{}"'.format( - typ, member), context) + self.fail('Element {} of {} has no attribute "{}"'.format( + typ, original_type, member), context) return AnyType() def unsupported_operand_types(self, op: str, left_type: Any, diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index cdb19b43cf46..93c49626aa7d 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -779,7 +779,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # E: Some element of union has no attribute "foo" +z.foo() # E: Element __main__.Node[builtins.int] of Union[Any, __main__.Node[builtins.int]] has no attribute "foo" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c47d25c3715f..2a6614e5ce12 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -548,7 +548,7 @@ v = A() # type: Union[A, B, C] if isinstance(v, (B, C)): v.method2(123) - v.method3('xyz') # E: Some element of union has no attribute "method3" + v.method3('xyz') # E: Element __main__.B of Union[__main__.B, __main__.C] has no attribute "method3" [builtins fixtures/isinstance.pyi] [case testIsinstanceNeverWidens] @@ -945,7 +945,7 @@ def bar() -> None: if isinstance(x, int): x + 1 else: - x.a # E: Some element of union has no attribute "a" + x.a # E: Element builtins.str of Union[builtins.str, __main__.A] has no attribute "a" x = 'a' [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index e4fb2a16025c..6f239a75d14a 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -534,7 +534,7 @@ x = None # type: ONode[int] x = f(1) x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" -x.x = 1 # E: Some element of union has no attribute "x" +x.x = 1 # E: Element builtins.object of Union[__main__.Node[builtins.int], builtins.None] has no attribute "x" if x is not None: x.x = 1 # OK here @@ -572,7 +572,7 @@ A = None # type: Any class C(A): pass x = None # type: Optional[C] -x.foo() # E: Some element of union has no attribute "foo" +x.foo() # E: Element builtins.object of Union[__main__.C, builtins.None] has no attribute "foo" [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index f7774d5e5bcb..4e8d161f953a 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -60,8 +60,8 @@ z = None # type: str y = w.y z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str") w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -y = x.y # E: Some element of union has no attribute "y" -zz = x.y # E: Some element of union has no attribute "y" +y = x.y # E: Element __main__.C of Union[__main__.A, __main__.C] has no attribute "y" +zz = x.y # E: Element __main__.C of Union[__main__.A, __main__.C] has no attribute "y" z = zz # E: Incompatible types in assignment (expression has type "Union[int, Any]", variable has type "str") [builtins fixtures/isinstance.pyi] @@ -296,7 +296,8 @@ def foo(a: Union[A, B, C]): if isinstance(a, (B, C)): reveal_type(a) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]]' a.x - a.y # E: Some element of union has no attribute "y" + a.y # E: Element __main__.B of Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]] has no attribute "y" \ + # E: Element __main__.C of Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]] has no attribute "y" b = a # type: Union[B, C] [builtins fixtures/isinstance.pyi] From c15d92ef4f8f4accb4ccf70cf7d50db0ba75e86b Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 14:01:47 -0700 Subject: [PATCH 03/12] Added back ignore file. --- tmp-test-dirs/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tmp-test-dirs/.gitignore diff --git a/tmp-test-dirs/.gitignore b/tmp-test-dirs/.gitignore new file mode 100644 index 000000000000..e6579d8f495f --- /dev/null +++ b/tmp-test-dirs/.gitignore @@ -0,0 +1,4 @@ +# This directory is used to store temporary directories for the testsuite. +# If anything manages to exist here, it means python crashed instead of +# calling tempfile.TemporaryDirectory's cleanup while unwinding. +# Therefore, don't actually provide any ignore patterns. From 6c3345380f9cd4ce5e815f47b0bc3d526c6c62e5 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 14:12:38 -0700 Subject: [PATCH 04/12] Fix style issue. --- mypy/messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index a3e6afebd319..f3d7f65e27ce 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -403,7 +403,8 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont # Indexed set. self.fail('Unsupported target for indexed assignment', context) elif member == '__call__': - if isinstance(original_type, Instance) and (original_type.type.fullname() == 'builtins.function'): + if isinstance(original_type, Instance) and \ + (original_type.type.fullname() == 'builtins.function'): # "'function' not callable" is a confusing error message. # Explain that the problem is that the type of the function is not known. self.fail('Cannot call function of unknown type', context) From 0ac3f210c7234b6d8609667e3448f643ded6cbf5 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 14:50:00 -0700 Subject: [PATCH 05/12] Use nicer type formatting for error. --- mypy/messages.py | 2 +- test-data/unit/check-generics.test | 2 +- test-data/unit/check-isinstance.test | 4 ++-- test-data/unit/check-optional.test | 4 ++-- test-data/unit/check-unions.test | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index f3d7f65e27ce..4c3a8617ef3c 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -427,7 +427,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont member), context) else: self.fail('Element {} of {} has no attribute "{}"'.format( - typ, original_type, member), context) + self.format(typ), self.format(original_type), member), context) return AnyType() def unsupported_operand_types(self, op: str, left_type: Any, diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 93c49626aa7d..14b7442c4888 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -779,7 +779,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # E: Element __main__.Node[builtins.int] of Union[Any, __main__.Node[builtins.int]] has no attribute "foo" +z.foo() # E: Element Node[int] of "Union[Any, Node[int]]" has no attribute "foo" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 2a6614e5ce12..299eceb937e9 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -548,7 +548,7 @@ v = A() # type: Union[A, B, C] if isinstance(v, (B, C)): v.method2(123) - v.method3('xyz') # E: Element __main__.B of Union[__main__.B, __main__.C] has no attribute "method3" + v.method3('xyz') # E: Element "B" of "Union[B, C]" has no attribute "method3" [builtins fixtures/isinstance.pyi] [case testIsinstanceNeverWidens] @@ -945,7 +945,7 @@ def bar() -> None: if isinstance(x, int): x + 1 else: - x.a # E: Element builtins.str of Union[builtins.str, __main__.A] has no attribute "a" + x.a # E: Element "str" of "Union[str, A]" has no attribute "a" x = 'a' [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 6f239a75d14a..3c5d8e446ab8 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -534,7 +534,7 @@ x = None # type: ONode[int] x = f(1) x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" -x.x = 1 # E: Element builtins.object of Union[__main__.Node[builtins.int], builtins.None] has no attribute "x" +x.x = 1 # E: Element "object" of "Optional[Node[int]]" has no attribute "x" if x is not None: x.x = 1 # OK here @@ -572,7 +572,7 @@ A = None # type: Any class C(A): pass x = None # type: Optional[C] -x.foo() # E: Element builtins.object of Union[__main__.C, builtins.None] has no attribute "foo" +x.foo() # E: Element "object" of "Optional[C]" has no attribute "foo" [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 4e8d161f953a..7871bc43d954 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -60,8 +60,8 @@ z = None # type: str y = w.y z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str") w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -y = x.y # E: Element __main__.C of Union[__main__.A, __main__.C] has no attribute "y" -zz = x.y # E: Element __main__.C of Union[__main__.A, __main__.C] has no attribute "y" +y = x.y # E: Element "C" of "Union[A, C]" has no attribute "y" +zz = x.y # E: Element "C" of "Union[A, C]" has no attribute "y" z = zz # E: Incompatible types in assignment (expression has type "Union[int, Any]", variable has type "str") [builtins fixtures/isinstance.pyi] @@ -296,8 +296,8 @@ def foo(a: Union[A, B, C]): if isinstance(a, (B, C)): reveal_type(a) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]]' a.x - a.y # E: Element __main__.B of Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]] has no attribute "y" \ - # E: Element __main__.C of Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]] has no attribute "y" + a.y # E: Element "B" of "Union[B, C]" has no attribute "y" \ + # E: Element "C" of "Union[B, C]" has no attribute "y" b = a # type: Union[B, C] [builtins fixtures/isinstance.pyi] From f52b4184a15af286d2dcbcff0ef82b4f19e6489b Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 15:41:56 -0700 Subject: [PATCH 06/12] Fixed issue where "object" was reported instead of "None". --- mypy/messages.py | 8 +++++++- test-data/unit/check-optional.test | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 4c3a8617ef3c..c7335919fc83 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -426,8 +426,14 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont self.fail('{} has no attribute "{}"'.format(self.format(original_type), member), context) else: + # The checker passes "object" in lieu of "None" for attribute + # checks, so we manually convert it back. + typ_format = self.format(typ) + if typ_format == '"object"' and \ + any(type(item) == NoneTyp for item in original_type.items): + typ_format = '"None"' self.fail('Element {} of {} has no attribute "{}"'.format( - self.format(typ), self.format(original_type), member), context) + typ_format, self.format(original_type), member), context) return AnyType() def unsupported_operand_types(self, op: str, left_type: Any, diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 3c5d8e446ab8..c5a5fde501d2 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -534,7 +534,7 @@ x = None # type: ONode[int] x = f(1) x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" -x.x = 1 # E: Element "object" of "Optional[Node[int]]" has no attribute "x" +x.x = 1 # E: Element "None" of "Optional[Node[int]]" has no attribute "x" if x is not None: x.x = 1 # OK here @@ -572,7 +572,7 @@ A = None # type: Any class C(A): pass x = None # type: Optional[C] -x.foo() # E: Element "object" of "Optional[C]" has no attribute "foo" +x.foo() # E: Element "None" of "Optional[C]" has no attribute "foo" [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional From ae0a81aec2e31404c97e47e39343ea64cd0d49ad Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 15:48:25 -0700 Subject: [PATCH 07/12] Fixed mypy conformance issue. --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index c7335919fc83..2cd266ed49b5 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -425,7 +425,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont if not failed: self.fail('{} has no attribute "{}"'.format(self.format(original_type), member), context) - else: + elif isinstance(original_type, UnionType): # The checker passes "object" in lieu of "None" for attribute # checks, so we manually convert it back. typ_format = self.format(typ) From c47ba2ccbd91b506f8f67fc623c7304df61b499d Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 17:01:33 -0700 Subject: [PATCH 08/12] Changed "Element" to "Item", added more tests. --- mypy/messages.py | 2 +- test-data/unit/check-unions.test | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 2cd266ed49b5..743aa6aaaff1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -432,7 +432,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont if typ_format == '"object"' and \ any(type(item) == NoneTyp for item in original_type.items): typ_format = '"None"' - self.fail('Element {} of {} has no attribute "{}"'.format( + self.fail('Item {} of {} has no attribute "{}"'.format( typ_format, self.format(original_type), member), context) return AnyType() diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 7871bc43d954..044c6eb0e947 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -51,17 +51,24 @@ from typing import Union class A: y = 1 class B: y = 2 class C: pass +class D: pass +u = None # type: Union[A, C, D] +v = None # type: Union[C, D] w = None # type: Union[A, B] x = None # type: Union[A, C] y = None # type: int z = None # type: str y = w.y +v.y # E: Item "C" of "Union[C, D]" has no attribute "y" \ + # E: Item "D" of "Union[C, D]" has no attribute "y" +u.y # E: Item "C" of "Union[A, C, D]" has no attribute "y" \ + # E: Item "D" of "Union[A, C, D]" has no attribute "y" z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str") w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -y = x.y # E: Element "C" of "Union[A, C]" has no attribute "y" -zz = x.y # E: Element "C" of "Union[A, C]" has no attribute "y" +y = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" +zz = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" z = zz # E: Incompatible types in assignment (expression has type "Union[int, Any]", variable has type "str") [builtins fixtures/isinstance.pyi] From 114e6dfcb113fc1dc3202b7683f8e7fec5a171e2 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 17:11:40 -0700 Subject: [PATCH 09/12] Fix tests for the Element -> Item change. --- test-data/unit/check-generics.test | 2 +- test-data/unit/check-isinstance.test | 4 ++-- test-data/unit/check-optional.test | 4 ++-- test-data/unit/check-unions.test | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 14b7442c4888..f9e85cceb9f8 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -779,7 +779,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # E: Element Node[int] of "Union[Any, Node[int]]" has no attribute "foo" +z.foo() # E: Item Node[int] of "Union[Any, Node[int]]" has no attribute "foo" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 299eceb937e9..825a5557fc63 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -548,7 +548,7 @@ v = A() # type: Union[A, B, C] if isinstance(v, (B, C)): v.method2(123) - v.method3('xyz') # E: Element "B" of "Union[B, C]" has no attribute "method3" + v.method3('xyz') # E: Item "B" of "Union[B, C]" has no attribute "method3" [builtins fixtures/isinstance.pyi] [case testIsinstanceNeverWidens] @@ -945,7 +945,7 @@ def bar() -> None: if isinstance(x, int): x + 1 else: - x.a # E: Element "str" of "Union[str, A]" has no attribute "a" + x.a # E: Item "str" of "Union[str, A]" has no attribute "a" x = 'a' [builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index c5a5fde501d2..6ae6df914376 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -534,7 +534,7 @@ x = None # type: ONode[int] x = f(1) x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" -x.x = 1 # E: Element "None" of "Optional[Node[int]]" has no attribute "x" +x.x = 1 # E: Item "None" of "Optional[Node[int]]" has no attribute "x" if x is not None: x.x = 1 # OK here @@ -572,7 +572,7 @@ A = None # type: Any class C(A): pass x = None # type: Optional[C] -x.foo() # E: Element "None" of "Optional[C]" has no attribute "foo" +x.foo() # E: Item "None" of "Optional[C]" has no attribute "foo" [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 044c6eb0e947..37a170e7367e 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -303,8 +303,8 @@ def foo(a: Union[A, B, C]): if isinstance(a, (B, C)): reveal_type(a) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]]' a.x - a.y # E: Element "B" of "Union[B, C]" has no attribute "y" \ - # E: Element "C" of "Union[B, C]" has no attribute "y" + a.y # E: Item "B" of "Union[B, C]" has no attribute "y" \ + # E: Item "C" of "Union[B, C]" has no attribute "y" b = a # type: Union[B, C] [builtins fixtures/isinstance.pyi] From b5ce5392f5a21fe2b4f8a4074da8ce7d3114187f Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 18:06:42 -0700 Subject: [PATCH 10/12] Updated docstring for `has_no_attr`. --- mypy/messages.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 743aa6aaaff1..2aad91309912 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -364,9 +364,14 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]: def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: """Report a missing or non-accessible member. - The type argument is the base type. If member corresponds to - an operator, use the corresponding operator name in the - messages. Return type Any. + original_type is the top-level type on which the error occureed. + typ is the actual type that is missing the member. These can be + different, e.g., in a union, original_type will be the union and typ + will be the specific item in the union that does not have the member + attribute. + + If member corresponds to an operator, use the corresponding operator + name in the messages. Return type Any. """ if (isinstance(original_type, Instance) and original_type.type.has_readable_member(member)): From 3a5cb70b4de927acf5e24e4c088de1be435e0761 Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 18:30:42 -0700 Subject: [PATCH 11/12] Fixed flake8 issues. --- mypy/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 2aad91309912..1b9318e84352 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -364,12 +364,12 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]: def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: """Report a missing or non-accessible member. - original_type is the top-level type on which the error occureed. + original_type is the top-level type on which the error occurred. typ is the actual type that is missing the member. These can be different, e.g., in a union, original_type will be the union and typ will be the specific item in the union that does not have the member attribute. - + If member corresponds to an operator, use the corresponding operator name in the messages. Return type Any. """ From 0dc419b4d3ca029c9d006e8c76ab14c76e9d2c5e Mon Sep 17 00:00:00 2001 From: Amir Rachum Date: Mon, 22 May 2017 18:32:27 -0700 Subject: [PATCH 12/12] Fixed flake8 issues. --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 1b9318e84352..dd8165b69bba 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -369,7 +369,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont different, e.g., in a union, original_type will be the union and typ will be the specific item in the union that does not have the member attribute. - + If member corresponds to an operator, use the corresponding operator name in the messages. Return type Any. """