diff --git a/mypy/constraints.py b/mypy/constraints.py index efb277ede1d0..e54983c9744f 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -557,11 +557,13 @@ def any_constraints(options: list[list[Constraint] | None], *, eager: bool) -> l # Multiple sets of constraints that are all the same. Just pick any one of them. return valid_options[0] - if all(is_similar_constraints(valid_options[0], c) for c in valid_options[1:]): + # This special-casing is not safe for ParamSpecs and TypeVarTuples, since unions are + # not valid constraints for them. This is however only needed in niche edge cases. + if all(is_similar_constraints(valid_options[0], c) for c in valid_options[1:]) and all( + isinstance(c.origin_type_var, TypeVarType) for c in valid_options[0] + ): # All options have same structure. In this case we can merge-in trivial # options (i.e. those that only have Any) and try again. - # TODO: More generally, if a given (variable, direction) pair appears in - # every option, combine the bounds with meet/join always, not just for Any. trivial_options = select_trivial(valid_options) if trivial_options and len(trivial_options) < len(valid_options): merged_options = [] @@ -1422,7 +1424,13 @@ def infer_against_any(self, types: Iterable[Type], any_type: AnyType) -> list[Co for t in flatten_nested_tuples(types): if isinstance(t, UnpackType): if isinstance(t.type, TypeVarTupleType): - res.append(Constraint(t.type, self.direction, any_type)) + res.append( + Constraint( + t.type, + self.direction, + t.type.tuple_fallback.copy_modified(args=[any_type]), + ) + ) else: unpacked = get_proper_type(t.type) assert isinstance(unpacked, Instance) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 795af772f5fb..2441770b5598 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -2800,3 +2800,20 @@ def takes_paramspec(f: Callable[P, object]) -> bool: def run(callback: Callable[[Concatenate[int, ...]], None]) -> None: # E: Concatenate is only valid as the first argument to Callable takes_paramspec(callback) [builtins fixtures/paramspec.pyi] + +[case testParamSpecApplyResultsFromUnionWithAnyNoCrash] +from typing import Any, Callable, Generic, Union +from typing_extensions import ParamSpec + +P = ParamSpec("P") +class C(Generic[P]): ... + +def foo(c: Callable[P, None]) -> C[P]: ... + +x: Union[Any, C[[Any, Any]]] + +def test(x: int) -> None: ... + +x = foo(test) # E: Argument 1 to "foo" has incompatible type "Callable[[int], None]"; expected "Callable[[Any, Any], None]" +reveal_type(x) # N: Revealed type is "__main__.C[[Any, Any]]" +[builtins fixtures/paramspec.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 6d8c69a147a3..5449613aa3c5 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -3255,3 +3255,21 @@ class K(Base): class Base: ... reveal_type(K.q) # N: Revealed type is "builtins.list[tuple[builtins.bool, builtins.int, builtins.str]]" [builtins fixtures/tuple.pyi] + +[case testTypeVarTupleApplyResultsFromUnionWithAnyNoCrash] +from typing import Any, Generic, Union +from typing_extensions import TypeVarTuple, Unpack + +Ts = TypeVarTuple("Ts") +class C(Generic[Unpack[Ts]]): ... + +def foo(t: tuple[Unpack[Ts]]) -> C[Unpack[Ts]]: ... +def bar(c: C[Unpack[Ts]]) -> tuple[Unpack[Ts]]: ... + +x: Union[Any, C[Unpack[tuple[int, ...]]]] +x = foo((1, 2)) +reveal_type(x) # N: Revealed type is "__main__.C[builtins.int, builtins.int]" + +y: Union[Any, C[Unpack[tuple[int, ...]]]] +reveal_type(bar(y)) # N: Revealed type is "builtins.tuple[Any, ...]" +[builtins fixtures/tuple.pyi]