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
54 changes: 34 additions & 20 deletions mypy/semanal_typeargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None:

def visit_tuple_type(self, t: TupleType) -> None:
t.items = flatten_nested_tuples(t.items)
for i, it in enumerate(t.items):
if self.check_non_paramspec(it, "tuple", t):
t.items[i] = AnyType(TypeOfAny.from_error)

# We could also normalize Tuple[*tuple[X, ...]] -> tuple[X, ...] like in
# expand_type() but we can't do this here since it is not a translator visitor,
# and we need to return an Instance instead of TupleType.
Expand Down Expand Up @@ -137,6 +141,28 @@ def visit_instance(self, t: Instance) -> None:
assert unpacked.type.fullname == "builtins.tuple"
t.args = unpacked.args

def check_non_paramspec(self, arg: Type, tv_kind: str, context: Context) -> bool:
if isinstance(arg, ParamSpecType):
self.fail(
INVALID_PARAM_SPEC_LOCATION.format(format_type(arg, self.options)),
context,
code=codes.VALID_TYPE,
)
self.note(
INVALID_PARAM_SPEC_LOCATION_NOTE.format(arg.name), context, code=codes.VALID_TYPE
)
return True
if isinstance(arg, Parameters):
self.fail(
f"Cannot use {format_type(arg, self.options)} for {tv_kind},"
" only for ParamSpec",
context,
code=codes.VALID_TYPE,
)
return True

return False

def validate_args(
self, name: str, args: tuple[Type, ...], type_vars: list[TypeVarLikeType], ctx: Context
) -> tuple[bool, bool]:
Expand All @@ -154,28 +180,10 @@ def validate_args(
for arg, tvar in zip(args, type_vars):
context = ctx if arg.line < 0 else arg
if isinstance(tvar, TypeVarType):
if isinstance(arg, ParamSpecType):
is_invalid = True
self.fail(
INVALID_PARAM_SPEC_LOCATION.format(format_type(arg, self.options)),
context,
code=codes.VALID_TYPE,
)
self.note(
INVALID_PARAM_SPEC_LOCATION_NOTE.format(arg.name),
context,
code=codes.VALID_TYPE,
)
continue
if isinstance(arg, Parameters):
if self.check_non_paramspec(arg, "regular type variable", context):
is_invalid = True
self.fail(
f"Cannot use {format_type(arg, self.options)} for regular type variable,"
" only for ParamSpec",
context,
code=codes.VALID_TYPE,
)
continue

if self.in_type_alias_expr and isinstance(arg, TypeVarType):
# Type aliases are allowed to use unconstrained type variables
# error will be checked at substitution point.
Expand Down Expand Up @@ -226,6 +234,12 @@ def validate_args(
context,
code=codes.VALID_TYPE,
)
elif isinstance(tvar, TypeVarTupleType):
p_arg = get_proper_type(arg)
assert isinstance(p_arg, TupleType)
for it in p_arg.items:
if self.check_non_paramspec(it, "TypeVarTuple", context):
is_invalid = True
if is_invalid:
is_error = True
return is_error, is_invalid
Expand Down
7 changes: 7 additions & 0 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -1824,3 +1824,10 @@ class tuple_aa_subclass(Tuple[A, A]): ...

inst_tuple_aa_subclass: tuple_aa_subclass = tuple_aa_subclass((A(), A()))[:] # E: Incompatible types in assignment (expression has type "tuple[A, A]", variable has type "tuple_aa_subclass")
[builtins fixtures/tuple.pyi]

[case testTuplePassedParameters]
from typing_extensions import Concatenate

def c(t: tuple[Concatenate[int, ...]]) -> None: # E: Cannot use "[int, VarArg(Any), KwArg(Any)]" for tuple, only for ParamSpec
reveal_type(t) # N: Revealed type is "tuple[Any]"
[builtins fixtures/tuple.pyi]
19 changes: 17 additions & 2 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -2481,9 +2481,11 @@ reveal_type(a) # N: Revealed type is "__main__.A[[builtins.int, builtins.str],
b: B[int, str, [int, str]]
reveal_type(b) # N: Revealed type is "__main__.B[builtins.int, builtins.str, [builtins.int, builtins.str]]"

x: A[int, str, [int, str]] # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int"
x: A[int, str, [int, str]] # E: Cannot use "[int, str]" for TypeVarTuple, only for ParamSpec \
# E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int"
reveal_type(x) # N: Revealed type is "__main__.A[Any, Unpack[builtins.tuple[Any, ...]]]"
y: B[[int, str], int, str] # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str"
y: B[[int, str], int, str] # E: Cannot use "[int, str]" for TypeVarTuple, only for ParamSpec \
# E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str"
reveal_type(y) # N: Revealed type is "__main__.B[Unpack[builtins.tuple[Any, ...]], Any]"

R = TypeVar("R")
Expand Down Expand Up @@ -2739,3 +2741,16 @@ def foo() -> str:
# this is a false positive, but it no longer crashes
call(run, foo, some_kwarg="a") # E: Argument 1 to "call" has incompatible type "def [Ts`-1, T] run(func: def (*Unpack[Ts]) -> T, *args: Unpack[Ts], some_kwarg: str = ...) -> T"; expected "Callable[[Callable[[], str], str], str]"
[builtins fixtures/tuple.pyi]

[case testTypeVarTuplePassedParameters]
from typing import TypeVarTuple, Generic, Unpack
from typing_extensions import Concatenate

Ts = TypeVarTuple("Ts")

class X(Generic[Unpack[Ts]]):
...

def c(t: X[Concatenate[int, ...]]) -> None: # E: Cannot use "[int, VarArg(Any), KwArg(Any)]" for TypeVarTuple, only for ParamSpec
reveal_type(t) # N: Revealed type is "__main__.X[Unpack[builtins.tuple[Any, ...]]]"
[builtins fixtures/tuple.pyi]