diff --git a/mypy/join.py b/mypy/join.py index 824dab5940131..3ef31c60f3860 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -245,7 +245,9 @@ def join_types(s: Type, t: Type, instance_joiner: InstanceJoiner | None = None) s, t = t, s if isinstance(s, AnyType): - return s + if isinstance(t, AnyType): + return s + s, t = t, s if isinstance(s, ErasedType): return t @@ -284,13 +286,15 @@ def visit_union_type(self, t: UnionType) -> ProperType: return mypy.typeops.make_simplified_union([self.s, t]) def visit_any(self, t: AnyType) -> ProperType: - return t + if isinstance(self.s, Instance) and self.s.type.fullname == "builtins.object": + return self.s + return UnionType([self.s, t]) def visit_none_type(self, t: NoneType) -> ProperType: if state.strict_optional: if isinstance(self.s, (NoneType, UninhabitedType)): return t - elif isinstance(self.s, (UnboundType, AnyType)): + elif isinstance(self.s, UnboundType): return AnyType(TypeOfAny.special_form) else: return mypy.typeops.make_simplified_union([self.s, t]) @@ -390,8 +394,6 @@ def visit_instance(self, t: Instance) -> ProperType: def visit_callable_type(self, t: CallableType) -> ProperType: if isinstance(self.s, CallableType): if is_similar_callables(t, self.s): - if is_equivalent(t, self.s): - return combine_similar_callables(t, self.s) result = join_similar_callables(t, self.s) if any( isinstance(tp, (NoneType, UninhabitedType)) @@ -462,7 +464,7 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: for s_item in s.items: if is_similar_callables(t_item, s_item): if is_equivalent(t_item, s_item): - result.append(combine_similar_callables(t_item, s_item)) + result.append(join_similar_callables(t_item, s_item)) elif is_subtype(t_item, s_item): result.append(s_item) if result: @@ -824,7 +826,8 @@ def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: arg_types.append(s.arg_types[i]) else: arg_types.append(safe_meet(t.arg_types[i], s.arg_types[i])) - # TODO in combine_similar_callables also applies here (names and kinds; user metaclasses) + # TODO Handle more kinds and different argument names. + # TODO what should happen if one fallback is 'type' and the other is a custom metaclass? # The fallback type can be either 'function', 'type', or some user-provided metaclass. # The result should always use 'function' as a fallback if either operands are using it. if t.fallback.type.fullname == "builtins.function": @@ -846,7 +849,7 @@ def join_similar_callables(t: CallableType, s: CallableType) -> CallableType: def safe_join(t: Type, s: Type) -> Type: - # This is a temporary solution to prevent crashes in combine_similar_callables() etc., + # This is a temporary solution to prevent crashes in join_similar_callables() etc., # until relevant TODOs on handling arg_kinds will be addressed there. if not isinstance(t, UnpackType) and not isinstance(s, UnpackType): return join_types(t, s) @@ -877,33 +880,6 @@ def safe_meet(t: Type, s: Type) -> Type: return UninhabitedType() -def combine_similar_callables(t: CallableType, s: CallableType) -> CallableType: - t, s = match_generic_callables(t, s) - arg_types: list[Type] = [] - for i in range(len(t.arg_types)): - arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) - # TODO kinds and argument names - # TODO what should happen if one fallback is 'type' and the other is a user-provided metaclass? - # The fallback type can be either 'function', 'type', or some user-provided metaclass. - # The result should always use 'function' as a fallback if either operands are using it. - if t.fallback.type.fullname == "builtins.function": - fallback = t.fallback - else: - fallback = s.fallback - instance_type = None - if t.instance_type is not None and s.instance_type is not None: - instance_type = join_types(t.instance_type, s.instance_type) - return t.copy_modified( - arg_types=arg_types, - arg_kinds=combine_arg_kinds(t, s), - arg_names=combine_arg_names(t, s), - ret_type=join_types(t.ret_type, s.ret_type), - instance_type=instance_type, - fallback=fallback, - name=None, - ) - - def combine_arg_names( t: CallableType | Parameters, s: CallableType | Parameters ) -> list[str | None]: diff --git a/mypy/meet.py b/mypy/meet.py index dd7894628dfe9..0ca4dfabc73cb 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -961,8 +961,6 @@ def visit_callable_type(self, t: CallableType) -> ProperType: if isinstance(self.s, CallableType) and join.is_similar_callables( t, self.s, for_meet=True ): - if is_equivalent(t, self.s): - return join.combine_similar_callables(t, self.s) result = meet_similar_callables(t, self.s) # We set the from_type_type flag to suppress error when a collection of # concrete class objects gets inferred as their common abstract superclass. @@ -1247,7 +1245,7 @@ def meet_similar_callables(t: CallableType, s: CallableType) -> CallableType: arg_types: list[Type] = [] for i in range(len(t.arg_types)): arg_types.append(safe_join(t.arg_types[i], s.arg_types[i])) - # TODO in combine_similar_callables also applies here (names and kinds) + # TODOs in join_similar_callables() also applies here. # The fallback type can be either 'function' or 'type'. The result should have 'function' as # fallback only if both operands have it as 'function'. if t.fallback.type.fullname != "builtins.function": diff --git a/mypy/test/testsolve.py b/mypy/test/testsolve.py index d60b2cb3fcc55..73979bf11d77e 100644 --- a/mypy/test/testsolve.py +++ b/mypy/test/testsolve.py @@ -6,7 +6,7 @@ from mypy.solve import Bounds, Graph, solve_constraints, transitive_closure from mypy.test.helpers import Suite, assert_equal from mypy.test.typefixture import TypeFixture -from mypy.types import Type, TypeVarId, TypeVarLikeType, TypeVarType +from mypy.types import Type, TypeVarId, TypeVarLikeType, TypeVarType, UnionType class SolveSuite(Suite): @@ -84,7 +84,7 @@ def test_simple_constraints_with_dynamic_type(self) -> None: self.assert_solve( [self.fx.t], [self.supc(self.fx.t, self.fx.anyt), self.supc(self.fx.t, self.fx.a)], - [self.fx.anyt], + [UnionType([self.fx.a, self.fx.anyt])], ) self.assert_solve([self.fx.t], [self.subc(self.fx.t, self.fx.anyt)], [self.fx.anyt]) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 03b6f1def084d..ed110094638cb 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -840,21 +840,28 @@ def test_unbound_type(self) -> None: self.assert_join(t, UnboundType("X"), self.fx.anyt) def test_any_type(self) -> None: - # Join against 'Any' type always results in 'Any'. with state.strict_optional_set(False): self.assert_join(NoneType(), self.fx.anyt, self.fx.anyt) for t in [ - self.fx.anyt, self.fx.a, - self.fx.o, - NoneType(), UnboundType("x"), self.fx.t, self.tuple(), self.callable(self.fx.a, self.fx.b), ]: - self.assert_join(t, self.fx.anyt, self.fx.anyt) + self.assert_join(t, self.fx.anyt, UnionType([t, self.fx.anyt])) + self.assert_join(self.fx.anyt, t, UnionType([t, self.fx.anyt])) + + self.assert_join(self.fx.anyt, self.fx.anyt, self.fx.anyt) + + self.assert_join(self.fx.o, self.fx.anyt, self.fx.o) + self.assert_join(NoneType(), self.fx.anyt, UnionType([self.fx.anyt, NoneType()])) + self.assert_join(UninhabitedType(), self.fx.anyt, self.fx.anyt) + + self.assert_join(self.fx.anyt, self.fx.o, self.fx.o) + self.assert_join(self.fx.anyt, NoneType(), UnionType([self.fx.anyt, NoneType()])) + self.assert_join(self.fx.anyt, UninhabitedType(), self.fx.anyt) def test_mixed_truth_restricted_type_simple(self) -> None: # make_simplified_union against differently restricted truthiness types drops restrictions. @@ -885,7 +892,7 @@ def test_simple_generics(self) -> None: with state.strict_optional_set(True): self.assert_join(self.fx.ga, self.fx.nonet, UnionType([self.fx.ga, NoneType()])) - self.assert_join(self.fx.ga, self.fx.anyt, self.fx.anyt) + self.assert_join(self.fx.ga, self.fx.anyt, UnionType([self.fx.ga, self.fx.anyt])) for t in [ self.fx.a, @@ -938,7 +945,9 @@ def test_callables_with_any(self) -> None: self.assert_join( self.callable(self.fx.a, self.fx.a, self.fx.anyt, self.fx.a), self.callable(self.fx.a, self.fx.anyt, self.fx.a, self.fx.anyt), - self.callable(self.fx.a, self.fx.anyt, self.fx.anyt, self.fx.anyt), + self.callable( + self.fx.a, self.fx.anyt, self.fx.anyt, UnionType([self.fx.a, self.fx.anyt]) + ), ) def test_overloaded(self) -> None: @@ -1016,12 +1025,14 @@ def test_simple_type_objects(self) -> None: def test_type_type(self) -> None: self.assert_join(self.fx.type_a, self.fx.type_b, self.fx.type_a) - self.assert_join(self.fx.type_b, self.fx.type_any, self.fx.type_any) + self.assert_join( + self.fx.type_b, self.fx.type_any, UnionType([self.fx.type_b, self.fx.type_any]) + ) self.assert_join(self.fx.type_b, self.fx.type_type, self.fx.type_type) self.assert_join(self.fx.type_b, self.fx.type_c, self.fx.type_a) self.assert_join(self.fx.type_c, self.fx.type_d, TypeType.make_normalized(self.fx.o)) self.assert_join(self.fx.type_type, self.fx.type_any, self.fx.type_type) - self.assert_join(self.fx.type_b, self.fx.anyt, self.fx.anyt) + self.assert_join(self.fx.type_b, self.fx.anyt, UnionType([self.fx.type_b, self.fx.anyt])) def test_literal_type(self) -> None: a = self.fx.a @@ -1035,7 +1046,7 @@ def test_literal_type(self) -> None: self.assert_join(lit1, d, self.fx.o) self.assert_join(lit1, lit2, a) self.assert_join(lit1, lit3, self.fx.o) - self.assert_join(lit1, self.fx.anyt, self.fx.anyt) + self.assert_join(lit1, self.fx.anyt, UnionType([lit1, self.fx.anyt])) self.assert_join(UnionType([lit1, lit2]), lit2, UnionType([lit1, lit2])) self.assert_join(UnionType([lit1, lit2]), a, a) self.assert_join(UnionType([lit1, lit3]), a, UnionType([a, lit3])) @@ -1324,7 +1335,12 @@ def test_callables_with_dynamic(self) -> None: self.assert_meet( self.callable(self.fx.a, self.fx.a, self.fx.anyt, self.fx.a), self.callable(self.fx.a, self.fx.anyt, self.fx.a, self.fx.anyt), - self.callable(self.fx.a, self.fx.anyt, self.fx.anyt, self.fx.anyt), + self.callable( + self.fx.a, + UnionType([self.fx.a, self.fx.anyt]), + UnionType([self.fx.a, self.fx.anyt]), + self.fx.a, + ), ) def test_meet_interface_types(self) -> None: diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index 3e23c7e6f8a07..44ce501756308 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -986,7 +986,8 @@ def test(): r19 :: list r20, r21 :: native_int r22 :: bit - r23, x :: object + r23 :: object + x :: union[str, tuple[str, str], object] r24 :: str r25 :: native_int a :: list diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index b397d812668ea..2cb6fac30b22e 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -1042,7 +1042,8 @@ def test(): r19 :: tuple r20, r21 :: native_int r22 :: bit - r23, x :: object + r23 :: object + x :: union[str, tuple[str, str], object] r24 :: str r25 :: native_int a :: tuple diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a89a5f3690281..48910c0e64ce2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -9844,7 +9844,9 @@ fs = [f, lambda x: 1, f] reveal_type(fs) # N: Revealed type is "builtins.list[def (x: Any) -> builtins.int]" [case testLambdaInferredInCollection2] -def f(x: int, y: int)-> int: ... +def f(x: int, y: int) -> int: ... fs = [lambda df, x: df.foo, f] -reveal_type(fs) # N: Revealed type is "builtins.list[def (Any, Any) -> Any]" +reveal_type(fs) # N: Revealed type is "builtins.list[def (Any, Any) -> builtins.int | Any]" +fs2 = [f, lambda df, x: df.foo] +reveal_type(fs2) # N: Revealed type is "builtins.list[def (Any, Any) -> builtins.int | Any]" diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 4e8bf0a047502..8012081ea31e4 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2303,9 +2303,9 @@ def f1(c: C) -> None: def f2(c: C, a: Any) -> None: x = {'x': c.value, 'y': a} - reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, Any]" + reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.int | Any]" y = {'y': a, 'x': c.value} - reveal_type(y) # N: Revealed type is "builtins.dict[builtins.str, Any]" + reveal_type(y) # N: Revealed type is "builtins.dict[builtins.str, builtins.int | Any]" [builtins fixtures/dict.pyi] [case testEnumIgnoreIsDeleted] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 1570249c0dab1..63282a979462e 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -3595,7 +3595,7 @@ def draw_none( takes_int_str_none(c3) [builtins fixtures/tuple.pyi] -[case testDeterminismFromJoinOrderingInSolver2] +[case testDeterminismFromJoinOrderingInSolver2_no_verbose_reveal] # Used to fail non-deterministically # https://github.com/python/mypy/issues/21445 from typing import Generic, Iterable, TypeVar, overload @@ -3632,7 +3632,7 @@ class dictlike(Generic[K, V]): def __init__(self, arg: Iterable[tuple[K, V]]) -> None: pass x = dictlike(ziplike("012", (f0, f1, g))) -reveal_type(x) # N: Revealed type is "__main__.dictlike[builtins.str, Overload(def (Any, Any) -> Any, def (Any, Any) -> Any, def (Any, Any) -> Any, def (Any, Any) -> Any)]" +reveal_type(x) # N: Revealed type is "__main__.dictlike[str, Overload(def (Any, Any) -> object, def (Any, Any) -> object, def (Any, Any) -> object, def (Any, Any) -> object)]" [builtins fixtures/dict.pyi] [case testPropertyWithGenericSetter] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 8f97d4f6cba10..d6874f945dac3 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -4395,3 +4395,13 @@ def f(x: Union[A[T], B[Optional[T]]]) -> T: ... reveal_type(f(C())) # N: Revealed type is "builtins.int | builtins.str" [builtins fixtures/list.pyi] + +[case testJoinWithAnyIsPrecise] +from typing import Any + +a: Any +y = [a, "no"] +x: list[int] = y # E: Incompatible types in assignment (expression has type "list[str | Any]", variable has type "list[int]") + +def test() -> list[int]: + return [a, "no"] # E: List item 1 has incompatible type "str"; expected "int" diff --git a/test-data/unit/check-native-int.test b/test-data/unit/check-native-int.test index c0b5d2b9b64c4..c9782fb62081b 100644 --- a/test-data/unit/check-native-int.test +++ b/test-data/unit/check-native-int.test @@ -61,11 +61,11 @@ reveal_type(join(n32, n64)) # N: Revealed type is "builtins.object" reveal_type(join(n64, n32)) # N: Revealed type is "builtins.object" a: Any -reveal_type(join(n, a)) # N: Revealed type is "Any" -reveal_type(join(n32, a)) # N: Revealed type is "Any" -reveal_type(join(a, n64)) # N: Revealed type is "Any" -reveal_type(join(n64, a)) # N: Revealed type is "Any" -reveal_type(join(a, n64)) # N: Revealed type is "Any" +reveal_type(join(n, a)) # N: Revealed type is "builtins.int | Any" +reveal_type(join(n32, a)) # N: Revealed type is "mypy_extensions.i32 | Any" +reveal_type(join(a, n64)) # N: Revealed type is "mypy_extensions.i64 | Any" +reveal_type(join(n64, a)) # N: Revealed type is "mypy_extensions.i64 | Any" +reveal_type(join(a, n64)) # N: Revealed type is "mypy_extensions.i64 | Any" [builtins fixtures/dict.pyi] [case testNativeIntMeets] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index c1d2c2b515b72..5b0958dc9da89 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -5476,10 +5476,10 @@ reveal_type(j(a, c)) # N: Revealed type is "TypedDict({'x'?=: builtins.int, 'y'? reveal_type(j(c, a)) # N: Revealed type is "TypedDict({'y'?=: builtins.int, 'x'?=: builtins.int}, closed=True)" reveal_type(j(a, d)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y'?=: builtins.int}, closed=True)" reveal_type(j(d, a)) # N: Revealed type is "TypedDict({'x': builtins.int, 'y'?=: builtins.int}, closed=True)" -reveal_type(j(a, e)) # N: Revealed type is "TypedDict({'x': Any, 'y'?=: Any})" -reveal_type(j(e, a)) # N: Revealed type is "TypedDict({'x': Any, 'y'?=: Any})" -reveal_type(j(b, e)) # N: Revealed type is "TypedDict({'y': Any})" -reveal_type(j(e, b)) # N: Revealed type is "TypedDict({'y': Any})" +reveal_type(j(a, e)) # N: Revealed type is "TypedDict({'x': builtins.int | Any, 'y'?=: Any})" +reveal_type(j(e, a)) # N: Revealed type is "TypedDict({'x': builtins.int | Any, 'y'?=: Any})" +reveal_type(j(b, e)) # N: Revealed type is "TypedDict({'y': builtins.int | Any})" +reveal_type(j(e, b)) # N: Revealed type is "TypedDict({'y': builtins.int | Any})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi]