diff --git a/mypy/checkmember.py b/mypy/checkmember.py index dd477cd87fbe..41cd1e97ba6d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -107,11 +107,17 @@ def analyze_member_access(name: str, elif isinstance(typ, UnionType): # The base object has dynamic type. msg.disable_type_names += 1 + old_num_messages = msg.num_messages() results = [analyze_member_access(name, subtype, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, original_type=original_type, chk=chk) for subtype in typ.items] msg.disable_type_names -= 1 + if msg.num_messages() != old_num_messages and any(isinstance(t, AnyType) + for t in results): + # If there was an error, return AnyType to avoid generating multiple messages for the + # same error. + return AnyType() return UnionType.make_simplified_union(results) elif isinstance(typ, TupleType): # Actually look up from the fallback instance type. diff --git a/mypy/meet.py b/mypy/meet.py index 7aa479c0eefc..26dc64d805c7 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -25,9 +25,19 @@ def meet_types(s: Type, t: Type) -> Type: return t.accept(TypeMeetVisitor(s)) -def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: +def meet_simple(s: Type, t: Type) -> Type: + """Return the type s "narrowed down" to t. + + Note that this is not symmetric with respect to s and t. + + TODO: Explain what this does in more detail and how this is + different from meet_types. + """ if s == t: return s + if isinstance(t, AnyType): + # Anything can be narrowed down to Any. + return t if isinstance(s, UnionType): return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t, use_promotions=True): @@ -36,10 +46,7 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: else: return NoneTyp() else: - if default_right: - return t - else: - return s + return t def is_overlapping_types(t: Type, s: Type, use_promotions: bool = False) -> bool: diff --git a/mypy/messages.py b/mypy/messages.py index 53c46ac14332..8d58f6128dd8 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -141,6 +141,9 @@ def enable_errors(self) -> None: def is_errors(self) -> bool: return self.errors.is_errors() + def num_messages(self) -> int: + return self.errors.num_messages() + def report(self, msg: str, context: Context, severity: str, file: str = None, origin: Context = None) -> None: """Report an error or note (unless disabled).""" diff --git a/mypy/types.py b/mypy/types.py index 32d7c8340a6c..8afd76aa3893 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1030,17 +1030,28 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) - all_items.append(typ) items = all_items - if any(isinstance(typ, AnyType) for typ in items): - return AnyType() - from mypy.subtypes import is_subtype + from mypy.sametypes import is_same_type + + def is_any_like(typ: Type) -> bool: + return (isinstance(typ, AnyType) or + (isinstance(typ, Instance) and typ.type.fallback_to_any)) + removed = set() # type: Set[int] for i, ti in enumerate(items): if i in removed: continue # Keep track of the truishness info for deleted subtypes which can be relevant cbt = cbf = False for j, tj in enumerate(items): - if i != j and is_subtype(tj, ti): + # Attempt to only combine true subtypes by avoiding types containing Any. + # TODO: Properly exclude generics and functions. + is_either_anylike = is_any_like(ti) or is_any_like(tj) + if (i != j + and is_subtype(tj, ti) + and (not is_either_anylike or + is_same_type(ti, tj) or + (isinstance(tj, NoneTyp) + and not experiments.STRICT_OPTIONAL))): removed.add(j) cbt = cbt or tj.can_be_true cbf = cbf or tj.can_be_false diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index 997a9961eed7..5b001fe2fb32 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -79,8 +79,8 @@ n = 0 d in a # E: Unsupported right operand type for in ("A") d and a d or a -c = d and b # Unintuitive type inference? -c = d or b # Unintuitive type inference? +c = d and b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C") +c = d or b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C") c = d + a c = d - a @@ -123,8 +123,8 @@ n = 0 a and d a or d c = a in d -c = b and d # Unintuitive type inference? -c = b or d # Unintuitive type inference? +c = b and d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C") +c = b or d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C") b = a + d b = a / d diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 225d66f421de..5de45ea06c8b 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -777,7 +777,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # Any simplifies Union to Any now. This test should be updated after #2197 +z.foo() # E: Some element of union has no attribute "foo" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index eca81bf8d37f..7ae1aa2f14d6 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -525,3 +525,26 @@ f = None # type: Optional[Callable[[int], None]] f = lambda x: None f(0) [builtins fixtures/function.pyi] + +[case testOptionalAndAnyBaseClass] +from typing import Any, Optional +class C(Any): + pass +x = None # type: Optional[C] +x.foo() # E: Some element of union has no attribute "foo" + +[case testUnionSimplificationWithStrictOptional] +from typing import Any, TypeVar, Union +class C(Any): pass +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass +a = None # type: Any + +# Test both orders +reveal_type(u(C(), None)) # E: Revealed type is 'Union[builtins.None, __main__.C*]' +reveal_type(u(None, C())) # E: Revealed type is 'Union[__main__.C*, builtins.None]' +reveal_type(u(a, None)) # E: Revealed type is 'Union[builtins.None, Any]' +reveal_type(u(None, a)) # E: Revealed type is 'Union[Any, builtins.None]' +reveal_type(u(1, None)) # E: Revealed type is 'Union[builtins.None, builtins.int*]' +reveal_type(u(None, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.None]' diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index b945fd1e8dad..0f1263e00234 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -630,11 +630,11 @@ try: except BaseException as e1: reveal_type(e1) # E: Revealed type is 'builtins.BaseException' except (E1, BaseException) as e2: - reveal_type(e2) # E: Revealed type is 'Any' + reveal_type(e2) # E: Revealed type is 'Union[Any, builtins.BaseException]' except (E1, E2) as e3: - reveal_type(e3) # E: Revealed type is 'Any' + reveal_type(e3) # E: Revealed type is 'Union[Any, __main__.E2]' except (E1, E2, BaseException) as e4: - reveal_type(e4) # E: Revealed type is 'Any' + reveal_type(e4) # E: Revealed type is 'Union[Any, builtins.BaseException]' try: pass except E1 as e1: diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index e1a30434bffe..38c7008d3fe7 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -37,7 +37,7 @@ def f(x: Union[int, str]) -> None: [case testUnionAnyIsInstance] from typing import Any, Union -def func(v:Union[int, Any]) -> None: +def func(v: Union[int, Any]) -> None: if isinstance(v, int): reveal_type(v) # E: Revealed type is 'builtins.int' else: @@ -61,7 +61,8 @@ 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" -z = x.y # E: Some element of union has no attribute "y" +v = x.y # E: Some element of union has no attribute "y" +reveal_type(v) # E: Revealed type is 'Any' [builtins fixtures/isinstance.pyi] @@ -168,3 +169,66 @@ if foo(): def g(x: Union[int, str, bytes]) -> None: pass else: def g(x: Union[int, str]) -> None: pass # E: All conditional function variants must have identical signatures + +[case testUnionSimplificationSpecialCases] +from typing import Any, TypeVar, Union + +class C(Any): pass + +T = TypeVar('T') +S = TypeVar('S') +def u(x: T, y: S) -> Union[S, T]: pass + +a = None # type: Any + +# Base-class-Any and None, simplify +reveal_type(u(C(), None)) # E: Revealed type is '__main__.C*' +reveal_type(u(None, C())) # E: Revealed type is '__main__.C*' + +# Normal instance type and None, simplify +reveal_type(u(1, None)) # E: Revealed type is 'builtins.int*' +reveal_type(u(None, 1)) # E: Revealed type is 'builtins.int*' + +# Normal instance type and base-class-Any, no simplification +reveal_type(u(C(), 1)) # E: Revealed type is 'Union[builtins.int*, __main__.C*]' +reveal_type(u(1, C())) # E: Revealed type is 'Union[__main__.C*, builtins.int*]' + +# Normal instance type and Any, no simplification +reveal_type(u(1, a)) # E: Revealed type is 'Union[Any, builtins.int*]' +reveal_type(u(a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]' + +# Any and base-class-Any, no simplificaiton +reveal_type(u(C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]' +reveal_type(u(a, C())) # E: Revealed type is 'Union[__main__.C*, Any]' + +# Two normal instance types, simplify +reveal_type(u(1, object())) # E: Revealed type is 'builtins.object*' +reveal_type(u(object(), 1)) # E: Revealed type is 'builtins.object*' + +# Two normal instance types, no simplification +reveal_type(u(1, '')) # E: Revealed type is 'Union[builtins.str*, builtins.int*]' +reveal_type(u('', 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' + +[case testUnionSimplificationWithDuplicateItems] +from typing import Any, TypeVar, Union + +class C(Any): pass + +T = TypeVar('T') +S = TypeVar('S') +R = TypeVar('R') +def u(x: T, y: S, z: R) -> Union[R, S, T]: pass + +a = None # type: Any + +reveal_type(u(1, 1, 1)) # E: Revealed type is 'builtins.int*' +reveal_type(u(C(), C(), None)) # E: Revealed type is '__main__.C*' +reveal_type(u(a, a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]' +reveal_type(u(a, C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]' +reveal_type(u('', 1, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' + +[case testUnionAndBinaryOperation] +from typing import Union +class A: pass +def f(x: Union[int, str, A]): + x + object() # E: Unsupported left operand type for + (some union)