Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 11 additions & 35 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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":
Expand All @@ -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)
Expand Down Expand Up @@ -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]:
Expand Down
4 changes: 1 addition & 3 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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":
Expand Down
4 changes: 2 additions & 2 deletions mypy/test/testsolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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])
Expand Down
38 changes: 27 additions & 11 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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]))
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion mypyc/test-data/irbuild-lists.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion mypyc/test-data/irbuild-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
4 changes: 2 additions & 2 deletions test-data/unit/check-enum.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -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"
10 changes: 5 additions & 5 deletions test-data/unit/check-native-int.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Loading