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
16 changes: 12 additions & 4 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
18 changes: 18 additions & 0 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading