diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 798f98915660..211f183a4733 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1188,14 +1188,14 @@ def check_overload_call(self, # gives a narrower type. if unioned_return: returns, inferred_types = zip(*unioned_return) - # Note that we use `union_overload_matches` instead of just returning + # Note that we use `combine_function_signatures` instead of just returning # a union of inferred callables because for example a call # Union[int -> int, str -> str](Union[int, str]) is invalid and # we don't want to introduce internal inconsistencies. unioned_result = (UnionType.make_simplified_union(list(returns), context.line, context.column), - self.union_overload_matches(inferred_types)) + self.combine_function_signatures(inferred_types)) # Step 3: We try checking each branch one-by-one. inferred_result = self.infer_overload_return_type(plausible_targets, args, arg_types, @@ -1492,8 +1492,8 @@ def type_overrides_set(self, exprs: Sequence[Expression], for expr in exprs: del self.type_overrides[expr] - def union_overload_matches(self, types: Sequence[Type]) -> Union[AnyType, CallableType]: - """Accepts a list of overload signatures and attempts to combine them together into a + def combine_function_signatures(self, types: Sequence[Type]) -> Union[AnyType, CallableType]: + """Accepts a list of function signatures and attempts to combine them together into a new CallableType consisting of the union of all of the given arguments and return types. If there is at least one non-callable type, return Any (this can happen if there is @@ -1507,7 +1507,7 @@ def union_overload_matches(self, types: Sequence[Type]) -> Union[AnyType, Callab return callables[0] # Note: we are assuming here that if a user uses some TypeVar 'T' in - # two different overloads, they meant for that TypeVar to mean the + # two different functions, they meant for that TypeVar to mean the # same thing. # # This function will make sure that all instances of that TypeVar 'T' @@ -1525,7 +1525,7 @@ def union_overload_matches(self, types: Sequence[Type]) -> Union[AnyType, Callab too_complex = False for target in callables: - # We fall back to Callable[..., Union[]] if the overloads do not have + # We fall back to Callable[..., Union[]] if the functions do not have # the exact same signature. The only exception is if one arg is optional and # the other is positional: in that case, we continue unioning (and expect a # positional arg). @@ -1820,19 +1820,12 @@ def check_op_reversible(self, left_expr: Expression, right_type: Type, right_expr: Expression, - context: Context) -> Tuple[Type, Type]: - # Note: this kludge exists mostly to maintain compatibility with - # existing error messages. Apparently, if the left-hand-side is a - # union and we have a type mismatch, we print out a special, - # abbreviated error message. (See messages.unsupported_operand_types). - unions_present = isinstance(left_type, UnionType) - + context: Context, + msg: MessageBuilder) -> Tuple[Type, Type]: def make_local_errors() -> MessageBuilder: """Creates a new MessageBuilder object.""" - local_errors = self.msg.clean_copy() + local_errors = msg.clean_copy() local_errors.disable_count = 0 - if unions_present: - local_errors.disable_type_names += 1 return local_errors def lookup_operator(op_name: str, base_type: Type) -> Optional[Type]: @@ -2006,7 +1999,7 @@ def lookup_definer(typ: Instance, attr_name: str) -> Optional[str]: # TODO: Remove this extra case return result - self.msg.add_errors(errors[0]) + msg.add_errors(errors[0]) if len(results) == 1: return results[0] else: @@ -2014,8 +2007,8 @@ def lookup_definer(typ: Instance, attr_name: str) -> Optional[str]: result = error_any, error_any return result - def check_op(self, method: str, base_type: Type, arg: Expression, - context: Context, + def check_op(self, method: str, base_type: Type, + arg: Expression, context: Context, allow_reverse: bool = False) -> Tuple[Type, Type]: """Type check a binary operation which maps to a method call. @@ -2023,13 +2016,84 @@ def check_op(self, method: str, base_type: Type, arg: Expression, """ if allow_reverse: - return self.check_op_reversible( - op_name=method, - left_type=base_type, - left_expr=TempNode(base_type), - right_type=self.accept(arg), - right_expr=arg, - context=context) + left_variants = [base_type] + if isinstance(base_type, UnionType): + left_variants = [item for item in base_type.relevant_items()] + right_type = self.accept(arg) + + # Step 1: We first try leaving the right arguments alone and destructure + # just the left ones. (Mypy can sometimes perform some more precise inference + # if we leave the right operands a union -- see testOperatorWithEmptyListAndSum. + msg = self.msg.clean_copy() + msg.disable_count = 0 + all_results = [] + all_inferred = [] + + for left_possible_type in left_variants: + result, inferred = self.check_op_reversible( + op_name=method, + left_type=left_possible_type, + left_expr=TempNode(left_possible_type), + right_type=right_type, + right_expr=arg, + context=context, + msg=msg) + all_results.append(result) + all_inferred.append(inferred) + + if not msg.is_errors(): + results_final = UnionType.make_simplified_union(all_results) + inferred_final = UnionType.make_simplified_union(all_inferred) + return results_final, inferred_final + + # Step 2: If that fails, we try again but also destructure the right argument. + # This is also necessary to make certain edge cases work -- see + # testOperatorDoubleUnionInterwovenUnionAdd, for example. + + # Note: We want to pass in the original 'arg' for 'left_expr' and 'right_expr' + # whenever possible so that plugins and similar things can introspect on the original + # node if possible. + # + # We don't do the same for the base expression because it could lead to weird + # type inference errors -- e.g. see 'testOperatorDoubleUnionSum'. + # TODO: Can we use `type_overrides_set()` here? + right_variants = [(right_type, arg)] + if isinstance(right_type, UnionType): + right_variants = [(item, TempNode(item)) for item in right_type.relevant_items()] + + msg = self.msg.clean_copy() + msg.disable_count = 0 + all_results = [] + all_inferred = [] + + for left_possible_type in left_variants: + for right_possible_type, right_expr in right_variants: + result, inferred = self.check_op_reversible( + op_name=method, + left_type=left_possible_type, + left_expr=TempNode(left_possible_type), + right_type=right_possible_type, + right_expr=right_expr, + context=context, + msg=msg) + all_results.append(result) + all_inferred.append(inferred) + + if msg.is_errors(): + self.msg.add_errors(msg) + if len(left_variants) >= 2 and len(right_variants) >= 2: + self.msg.warn_both_operands_are_from_unions(context) + elif len(left_variants) >= 2: + self.msg.warn_operand_was_from_union("Left", base_type, context) + elif len(right_variants) >= 2: + self.msg.warn_operand_was_from_union("Right", right_type, context) + + # See the comment in 'check_overload_call' for more details on why + # we call 'combine_function_signature' instead of just unioning the inferred + # callable types. + results_final = UnionType.make_simplified_union(all_results) + inferred_final = self.combine_function_signatures(all_inferred) + return results_final, inferred_final else: return self.check_op_local_by_name( method=method, diff --git a/mypy/messages.py b/mypy/messages.py index df225dcde0a5..f121f6e4b3d9 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1003,6 +1003,12 @@ def overloaded_signatures_ret_specific(self, index: int, context: Context) -> No self.fail('Overloaded function implementation cannot produce return type ' 'of signature {}'.format(index), context) + def warn_both_operands_are_from_unions(self, context: Context) -> None: + self.note('Both left and right operands are unions', context) + + def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: + self.note('{} operand is of type {}'.format(side, self.format(original)), context) + def operator_method_signatures_overlap( self, reverse_class: TypeInfo, reverse_method: str, forward_class: Type, forward_method: str, context: Context) -> None: diff --git a/test-data/unit/check-callable.test b/test-data/unit/check-callable.test index 39e7bd77d60f..b7026207fd19 100644 --- a/test-data/unit/check-callable.test +++ b/test-data/unit/check-callable.test @@ -46,7 +46,8 @@ from typing import Callable, Union x = 5 # type: Union[int, Callable[[], str], Callable[[], int]] if callable(x): - y = x() + 2 # E: Unsupported operand types for + (likely involving Union) + y = x() + 2 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[str, int]" else: z = x + 6 @@ -60,7 +61,8 @@ x = 5 # type: Union[int, str, Callable[[], str]] if callable(x): y = x() + 'test' else: - z = x + 6 # E: Unsupported operand types for + (likely involving Union) + z = x + 6 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/callable.pyi] @@ -153,7 +155,8 @@ x = 5 # type: Union[int, Callable[[], str]] if callable(x) and x() == 'test': x() else: - x + 5 # E: Unsupported left operand type for + (some union) + x + 5 # E: Unsupported left operand type for + ("Callable[[], str]") \ + # N: Left operand is of type "Union[int, Callable[[], str]]" [builtins fixtures/callable.pyi] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e042cc4faad1..f53a5d22f8fd 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2016,6 +2016,183 @@ class FractionChild(Fraction): pass class A(metaclass=Real): pass +[case testOperatorDoubleUnionIntFloat] +from typing import Union + +a: Union[int, float] +b: int +c: float + +reveal_type(a + a) # E: Revealed type is 'builtins.float' +reveal_type(a + b) # E: Revealed type is 'builtins.float' +reveal_type(b + a) # E: Revealed type is 'builtins.float' +reveal_type(a + c) # E: Revealed type is 'builtins.float' +reveal_type(c + a) # E: Revealed type is 'builtins.float' +[builtins fixtures/ops.pyi] + +[case testOperatorDoubleUnionStandardSubtyping] +from typing import Union + +class Parent: + def __add__(self, x: Parent) -> Parent: pass + def __radd__(self, x: Parent) -> Parent: pass + +class Child(Parent): + def __add__(self, x: Parent) -> Child: pass + def __radd__(self, x: Parent) -> Child: pass + +a: Union[Parent, Child] +b: Parent +c: Child + +reveal_type(a + a) # E: Revealed type is '__main__.Parent' +reveal_type(a + b) # E: Revealed type is '__main__.Parent' +reveal_type(b + a) # E: Revealed type is '__main__.Parent' +reveal_type(a + c) # E: Revealed type is '__main__.Child' +reveal_type(c + a) # E: Revealed type is '__main__.Child' + +[case testOperatorDoubleUnionNoRelationship1] +from typing import Union + +class Foo: + def __add__(self, x: Foo) -> Foo: pass + def __radd__(self, x: Foo) -> Foo: pass + +class Bar: + def __add__(self, x: Bar) -> Bar: pass + def __radd__(self, x: Bar) -> Bar: pass + +a: Union[Foo, Bar] +b: Foo +c: Bar + +a + a # E: Unsupported operand types for + ("Foo" and "Bar") \ + # E: Unsupported operand types for + ("Bar" and "Foo") \ + # N: Both left and right operands are unions + +a + b # E: Unsupported operand types for + ("Bar" and "Foo") \ + # N: Left operand is of type "Union[Foo, Bar]" + +b + a # E: Unsupported operand types for + ("Foo" and "Bar") \ + # N: Right operand is of type "Union[Foo, Bar]" + +a + c # E: Unsupported operand types for + ("Foo" and "Bar") \ + # N: Left operand is of type "Union[Foo, Bar]" + +c + a # E: Unsupported operand types for + ("Bar" and "Foo") \ + # N: Right operand is of type "Union[Foo, Bar]" + +[case testOperatorDoubleUnionNoRelationship2] +from typing import Union + +class Foo: + def __add__(self, x: Foo) -> Foo: pass + def __radd__(self, x: Foo) -> Foo: pass + +class Bar: + def __add__(self, x: Union[Foo, Bar]) -> Bar: pass + def __radd__(self, x: Union[Foo, Bar]) -> Bar: pass + +a: Union[Foo, Bar] +b: Foo +c: Bar + +reveal_type(a + a) # E: Revealed type is 'Union[__main__.Foo, __main__.Bar]' +reveal_type(a + b) # E: Revealed type is 'Union[__main__.Foo, __main__.Bar]' +reveal_type(b + a) # E: Revealed type is 'Union[__main__.Foo, __main__.Bar]' +reveal_type(a + c) # E: Revealed type is '__main__.Bar' +reveal_type(c + a) # E: Revealed type is '__main__.Bar' + +[case testOperatorDoubleUnionNaiveAdd] +from typing import Union + +class A: pass +class B: pass +class C: + def __radd__(self, x: A) -> int: pass +class D: + def __radd__(self, x: B) -> str: pass + +x: Union[A, B] +y: Union[C, D] + +x + y # E: Unsupported operand types for + ("A" and "D") \ + # E: Unsupported operand types for + ("B" and "C") \ + # N: Both left and right operands are unions + +[case testOperatorDoubleUnionInterwovenUnionAdd] +from typing import Union + +class Out1: pass +class Out2: pass +class Out3: pass +class Out4: pass + +class A: + def __add__(self, x: D) -> Out1: pass +class B: + def __add__(self, x: C) -> Out2: pass +class C: + def __radd__(self, x: A) -> Out3: pass +class D: + def __radd__(self, x: B) -> Out4: pass + +x: Union[A, B] +y: Union[C, D] + +reveal_type(x + y) # E: Revealed type is 'Union[__main__.Out3, __main__.Out1, __main__.Out2, __main__.Out4]' +reveal_type(A() + y) # E: Revealed type is 'Union[__main__.Out3, __main__.Out1]' +reveal_type(B() + y) # E: Revealed type is 'Union[__main__.Out2, __main__.Out4]' +reveal_type(x + C()) # E: Revealed type is 'Union[__main__.Out3, __main__.Out2]' +reveal_type(x + D()) # E: Revealed type is 'Union[__main__.Out1, __main__.Out4]' + +[case testOperatorDoubleUnionDivisionPython2] +# flags: --python-version 2.7 +from typing import Union +def f(a): + # type: (Union[int, float]) -> None + a /= 1.1 + b = a / 1.1 + reveal_type(b) # E: Revealed type is 'builtins.float' +[builtins_py2 fixtures/ops.pyi] + +[case testOperatorDoubleUnionDivisionPython3] +from typing import Union +def f(a): + # type: (Union[int, float]) -> None + a /= 1.1 + b = a / 1.1 + reveal_type(b) # E: Revealed type is 'builtins.float' +[builtins fixtures/ops.pyi] + +[case testOperatorWithInference] +from typing import TypeVar, Iterable, Union + +T = TypeVar('T') +def sum(x: Iterable[T]) -> Union[T, int]: ... + +def len(x: Iterable[T]) -> int: ... + +x = [1.1, 2.2, 3.3] +reveal_type(sum(x)) # E: Revealed type is 'builtins.float*' +reveal_type(sum(x) / len(x)) # E: Revealed type is 'builtins.float' +[builtins fixtures/floatdict.pyi] + +[case testOperatorWithEmptyListAndSum] +from typing import TypeVar, Iterable, Union, overload + +T = TypeVar('T') +S = TypeVar('S') +@overload +def sum(x: Iterable[T]) -> Union[T, int]: ... +@overload +def sum(x: Iterable[T], default: S) -> Union[T, S]: ... +def sum(*args): pass + +x = ["a", "b", "c"] +reveal_type(x + sum([x, x, x], [])) # E: Revealed type is 'builtins.list[builtins.str*]' +[builtins fixtures/floatdict.pyi] + [case testAbstractReverseOperatorMethod] import typing from abc import abstractmethod diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 3e9437c6a09f..e6fcda7ae6e2 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -755,7 +755,8 @@ class Node(Generic[T]): UNode = Union[int, Node[T]] x = 1 # type: UNode[int] -x + 1 # E: Unsupported left operand type for + (some union) +x + 1 # E: Unsupported left operand type for + ("Node[int]") \ + # N: Left operand is of type "Union[int, Node[int]]" if not isinstance(x, Node): x + 1 diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ccd55b8666c5..bd1bb3d563ba 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -3515,7 +3515,8 @@ from typing import Optional def foo() -> Optional[int]: return 0 [out1] [out2] -main:3: error: Unsupported operand types for + ("int" and "Optional[int]") +main:3: error: Unsupported operand types for + ("int" and "None") +main:3: note: Right operand is of type "Optional[int]" [case testAttrsIncrementalSubclassingCached] from a import A @@ -4082,7 +4083,8 @@ class Baz: return 1 [out] [out2] -tmp/a.py:3: error: Unsupported operand types for + ("int" and "Optional[int]") +tmp/a.py:3: error: Unsupported operand types for + ("int" and "None") +tmp/a.py:3: note: Right operand is of type "Optional[int]" [case testIncrementalMetaclassUpdate] import a diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index a7e08ba9a22b..3e155f25c0b8 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -183,7 +183,8 @@ def bar() -> None: while bool(): x + 'a' while bool(): - x + 'a' # E: Unsupported operand types for + (likely involving Union) + x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" x = foo() if bool(): continue @@ -409,12 +410,16 @@ def f(x: Union[List[int], List[str], int]) -> None: # type of a? reveal_type(x) # E: Revealed type is 'Union[builtins.list[builtins.int], builtins.list[builtins.str]]' - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("List[int]" and "int") \ + # E: Unsupported operand types for + ("List[str]" and "int") \ + # N: Left operand is of type "Union[List[int], List[str]]" else: x[0] # E: Value of type "int" is not indexable x + 1 x[0] # E: Value of type "Union[List[int], List[str], int]" is not indexable - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("List[int]" and "int") \ + # E: Unsupported operand types for + ("List[str]" and "int") \ + # N: Left operand is of type "Union[List[int], List[str], int]" [builtins fixtures/isinstancelist.pyi] [case testUnionListIsinstance2] @@ -445,7 +450,8 @@ x = foo() x = 1 x = x + 1 x = foo() -x = x + 1 # E: Unsupported operand types for + (likely involving Union) +x = x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" if isinstance(x, str): x = x + 1 # E: Unsupported operand types for + ("str" and "int") x = 1 @@ -509,8 +515,10 @@ while bool(): if isinstance(h.pet, Dog): if isinstance(h.pet.paws, str): x = h.pet.paws + 'a' - y = h.pet.paws + 1 # E: Unsupported operand types for + (likely involving Union) - z = h.pet.paws + 'a' # E: Unsupported operand types for + (likely involving Union) + y = h.pet.paws + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" + z = h.pet.paws + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" if isinstance(h.pet.paws, str): x = h.pet.paws + 'a' break @@ -652,7 +660,8 @@ def foo() -> None: break else: pass - y = x + 'asdad' # E: Unsupported operand types for + (likely involving Union) + y = x + 'asdad' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" foo() [builtins fixtures/isinstancelist.pyi] @@ -669,8 +678,13 @@ while bool(): x + 'a' else: x + [1] - x + 'a' # E: Unsupported operand types for + (likely involving Union) -x + [1] # E: Unsupported operand types for + (likely involving Union) + x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # E: Unsupported operand types for + ("List[int]" and "str") \ + # N: Left operand is of type "Union[int, str, List[int]]" + +x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ + # E: Unsupported operand types for + ("str" and "List[int]") \ + # N: Left operand is of type "Union[int, str, List[int]]" [builtins fixtures/isinstancelist.pyi] [case testIsInstanceThreeUnion2] @@ -685,7 +699,9 @@ while bool(): break x + [1] x + 'a' # E: Unsupported operand types for + ("List[int]" and "str") -x + [1] # E: Unsupported operand types for + (likely involving Union) +x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ + # E: Unsupported operand types for + ("str" and "List[int]") \ + # N: Left operand is of type "Union[int, str, List[int]]" [builtins fixtures/isinstancelist.pyi] [case testIsInstanceThreeUnion3] @@ -702,7 +718,9 @@ while bool(): break x + [1] # These lines aren't reached because x was an int x + 'a' -x + [1] # E: Unsupported operand types for + (likely involving Union) +x + [1] # E: Unsupported operand types for + ("int" and "List[int]") \ + # E: Unsupported operand types for + ("str" and "List[int]") \ + # N: Left operand is of type "Union[int, str, List[int]]" [builtins fixtures/isinstancelist.pyi] [case testRemovingTypeRepeatedly] @@ -712,24 +730,28 @@ def foo() -> Union[int, str]: pass for i in [1, 2]: x = foo() - x + 'a' # E: Unsupported operand types for + (likely involving Union) + x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" if isinstance(x, int): break x + 'a' x = foo() - x + 'a' # E: Unsupported operand types for + (likely involving Union) + x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" if isinstance(x, int): break x + 'a' x = foo() - x + 'a' # E: Unsupported operand types for + (likely involving Union) + x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" if isinstance(x, int): break x + 'a' -x + 'a' # E: Unsupported operand types for + (likely involving Union) +x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testModifyRepeatedly] @@ -738,8 +760,10 @@ from typing import Union def foo() -> Union[int, str]: pass x = foo() -x + 1 # E: Unsupported operand types for + (likely involving Union) -x + 'a' # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" +x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" x = 1 x + 1 @@ -750,8 +774,10 @@ x + 1 # E: Unsupported operand types for + ("str" and "int") x + 'a' x = foo() -x + 1 # E: Unsupported operand types for + (likely involving Union) -x + 'a' # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" +x + 'a' # E: Unsupported operand types for + ("int" and "str") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testModifyLoop] @@ -760,14 +786,16 @@ from typing import Union def foo() -> Union[int, str]: pass x = foo() -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 'a' x + 1 # E: Unsupported operand types for + ("str" and "int") x = 1 x + 1 while bool(): - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 'a' [builtins fixtures/isinstancelist.pyi] @@ -777,7 +805,8 @@ from typing import Union def foo() -> Union[int, str]: pass x = foo() -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 'a' x + 1 # E: Unsupported operand types for + ("str" and "int") x = 1 @@ -786,7 +815,8 @@ x + 1 for i in [1]: x = 'a' -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testModifyLoop3] @@ -803,7 +833,8 @@ while bool(): break else: x + 1 -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 1 for y in [1]: x + 1 @@ -811,7 +842,8 @@ for y in [1]: break else: x + 1 -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testModifyLoopWhile4] @@ -833,12 +865,14 @@ else: x + 'a' x = 1 while bool(): - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" if bool(): x = 'a' continue else: - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 'a' x + 'a' [builtins fixtures/isinstancelist.pyi] @@ -862,12 +896,14 @@ else: x + 'a' x = 1 for y in [1]: - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" if bool(): x = 'a' continue else: - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 'a' x + 'a' [builtins fixtures/isinstancelist.pyi] @@ -888,7 +924,8 @@ for y in [1]: break else: x + 1 -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" x = 1 while bool(): while bool(): @@ -898,7 +935,8 @@ while bool(): break else: x + 1 -x + 1 # E: Unsupported operand types for + (likely involving Union) +x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testModifyLoopLong] @@ -910,8 +948,9 @@ def foo() -> Union[int, str, A]: pass def bar() -> None: x = foo() - x + 1 # E: Unsupported left operand type for + (some union) \ - # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # E: Unsupported left operand type for + ("A") \ + # N: Left operand is of type "Union[int, str, A]" if isinstance(x, A): x.a else: @@ -1375,7 +1414,8 @@ from typing import Union def f(x: Union[int, str], typ: type) -> None: if isinstance(x, (typ, int)): - x + 1 # E: Unsupported operand types for + (likely involving Union) + x + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' else: reveal_type(x) # E: Revealed type is 'builtins.str' diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 55345d11b996..9b1c7a694713 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -127,7 +127,8 @@ f(None) [case testInferOptionalFromDefaultNone] def f(x: int = None) -> None: - x + 1 # E: Unsupported left operand type for + (some union) + x + 1 # E: Unsupported left operand type for + ("None") \ + # N: Left operand is of type "Optional[int]" f(None) [out] @@ -140,7 +141,8 @@ def f(x: int = None) -> None: # E: Incompatible default for argument "x" (defau [case testInferOptionalFromDefaultNoneComment] def f(x=None): # type: (int) -> None - x + 1 # E: Unsupported left operand type for + (some union) + x + 1 # E: Unsupported left operand type for + ("None") \ + # N: Left operand is of type "Optional[int]" f(None) [out] @@ -437,7 +439,8 @@ from typing import Optional x = None # type: Optional[int] x + 1 [out] -tmp/a.py:3: error: Unsupported left operand type for + (some union) +tmp/a.py:3: error: Unsupported left operand type for + ("None") +tmp/a.py:3: note: Left operand is of type "Optional[str]" [case testNoneContextInference] from typing import Dict, List diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 799dea119fc3..1cb274c2c4a8 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -98,7 +98,8 @@ i = y.foo() # E: Incompatible types in assignment (expression has type "Union[ from typing import Union, List x = None # type: Union[List[int], str] x[2] -x[2] + 1 # E: Unsupported operand types for + (likely involving Union) +x[2] + 1 # E: Unsupported operand types for + ("str" and "int") \ + # N: Left operand is of type "Union[int, str]" [builtins fixtures/isinstancelist.pyi] [case testUnionAsOverloadArg] @@ -289,8 +290,10 @@ reveal_type(u('', 1, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.s from typing import Union class A: pass def f(x: Union[int, str, A]): - x + object() # E: Unsupported left operand type for + (some union) \ - # E: Unsupported operand types for + (likely involving Union) + x + object() # E: Unsupported operand types for + ("int" and "object") \ + # E: Unsupported operand types for + ("str" and "object") \ + # E: Unsupported left operand type for + ("A") \ + # N: Left operand is of type "Union[int, str, A]" [case testNarrowingDownNamedTupleUnion] from typing import NamedTuple, Union diff --git a/test-data/unit/fixtures/floatdict.pyi b/test-data/unit/fixtures/floatdict.pyi index 54850d7f6e27..7db0c77b9058 100644 --- a/test-data/unit/fixtures/floatdict.pyi +++ b/test-data/unit/fixtures/floatdict.pyi @@ -55,9 +55,13 @@ class int: def __int__(self) -> int: ... def __mul__(self, x: int) -> int: ... def __rmul__(self, x: int) -> int: ... + def __truediv__(self, x: int) -> int: ... + def __rtruediv__(self, x: int) -> int: ... class float: def __float__(self) -> float: ... def __int__(self) -> int: ... def __mul__(self, x: float) -> float: ... def __rmul__(self, x: float) -> float: ... + def __truediv__(self, x: float) -> float: ... + def __rtruediv__(self, x: float) -> float: ... diff --git a/test-data/unit/fixtures/ops.pyi b/test-data/unit/fixtures/ops.pyi index 8f0eb8b2fcce..279e20c021ab 100644 --- a/test-data/unit/fixtures/ops.pyi +++ b/test-data/unit/fixtures/ops.pyi @@ -36,8 +36,13 @@ class unicode: pass class int: def __add__(self, x: 'int') -> 'int': pass + def __radd__(self, x: 'int') -> 'int': pass def __sub__(self, x: 'int') -> 'int': pass def __mul__(self, x: 'int') -> 'int': pass + def __div__(self, x: 'int') -> 'int': pass + def __rdiv__(self, x: 'int') -> 'int': pass + def __truediv__(self, x: 'int') -> 'int': pass + def __rtruediv__(self, x: 'int') -> 'int': pass def __mod__(self, x: 'int') -> 'int': pass def __floordiv__(self, x: 'int') -> 'int': pass def __pow__(self, x: 'int') -> Any: pass @@ -50,7 +55,13 @@ class int: def __gt__(self, x: 'int') -> bool: pass def __ge__(self, x: 'int') -> bool: pass -class float: pass +class float: + def __add__(self, x: 'float') -> 'float': pass + def __radd__(self, x: 'float') -> 'float': pass + def __div__(self, x: 'float') -> 'float': pass + def __rdiv__(self, x: 'float') -> 'float': pass + def __truediv__(self, x: 'float') -> 'float': pass + def __rtruediv__(self, x: 'float') -> 'float': pass class BaseException: pass