Skip to content

Commit

Permalink
Require ListExpr for ParamSpec defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed May 29, 2023
1 parent 9c37a1a commit d6e39c4
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 62 deletions.
6 changes: 1 addition & 5 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
Type,
TypeList,
TypeOfAny,
TypeOfTypeList,
UnboundType,
UnionType,
)
Expand Down Expand Up @@ -162,12 +161,9 @@ def expr_to_unanalyzed_type(
else:
raise TypeTranslationError()
return CallableArgument(typ, name, arg_const, expr.line, expr.column)
elif isinstance(expr, (ListExpr, TupleExpr)):
elif isinstance(expr, ListExpr):
return TypeList(
[expr_to_unanalyzed_type(t, options, allow_new_syntax, expr) for t in expr.items],
TypeOfTypeList.callable_args
if isinstance(expr, ListExpr)
else TypeOfTypeList.param_spec_defaults,
line=expr.line,
column=expr.column,
)
Expand Down
40 changes: 23 additions & 17 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4132,9 +4132,7 @@ def process_typevar_parameters(
tv_arg = self.get_typevarlike_argument(
"TypeVar", param_name, param_value, context, allow_unbound_tvars=True
)
if tv_arg is None:
return None
default = tv_arg
default = tv_arg or AnyType(TypeOfAny.from_error)
elif param_name == "values":
# Probably using obsolete syntax with values=(...). Explain the current syntax.
self.fail('TypeVar "values" argument not supported', context)
Expand Down Expand Up @@ -4171,6 +4169,7 @@ def get_typevarlike_argument(
*,
allow_unbound_tvars: bool = False,
allow_param_spec_literals: bool = False,
report_invalid_typevar_arg: bool = True,
) -> ProperType | None:
try:
# We want to use our custom error message below, so we suppress
Expand All @@ -4191,7 +4190,7 @@ def get_typevarlike_argument(
# ...
analyzed = PlaceholderType(None, [], context.line)
typ = get_proper_type(analyzed)
if isinstance(typ, AnyType) and typ.is_from_error:
if report_invalid_typevar_arg and isinstance(typ, AnyType) and typ.is_from_error:
self.fail(
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
param_value,
Expand All @@ -4200,10 +4199,11 @@ def get_typevarlike_argument(
# using the AnyType as the upper bound.
return typ
except TypeTranslationError:
self.fail(
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
param_value,
)
if report_invalid_typevar_arg:
self.fail(
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
param_value,
)
return None

def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> str | None:
Expand Down Expand Up @@ -4254,20 +4254,23 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
s,
allow_unbound_tvars=True,
allow_param_spec_literals=True,
report_invalid_typevar_arg=False,
)
if tv_arg is None:
return False
default = tv_arg
default = tv_arg or AnyType(TypeOfAny.from_error)
if isinstance(tv_arg, Parameters):
for i, arg_type in enumerate(tv_arg.arg_types):
typ = get_proper_type(arg_type)
if isinstance(typ, AnyType) and typ.is_from_error:
self.fail(
f"Argument {i} of ParamSpec default must be a type", param_value
)
elif not isinstance(default, (AnyType, UnboundType)):
elif (
isinstance(default, AnyType)
and default.is_from_error
or not isinstance(default, (AnyType, UnboundType))
):
self.fail(
"The default argument to ParamSpec must be a tuple expression, ellipsis, or a ParamSpec",
"The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec",
param_value,
)
default = AnyType(TypeOfAny.from_error)
Expand Down Expand Up @@ -4324,11 +4327,14 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool:
):
if param_name == "default":
tv_arg = self.get_typevarlike_argument(
"TypeVarTuple", param_name, param_value, s, allow_unbound_tvars=True
"TypeVarTuple",
param_name,
param_value,
s,
allow_unbound_tvars=True,
report_invalid_typevar_arg=False,
)
if tv_arg is None:
return False
default = tv_arg
default = tv_arg or AnyType(TypeOfAny.from_error)
if not isinstance(default, UnpackType):
self.fail(
"The default argument to TypeVarTuple must be an Unpacked tuple",
Expand Down
7 changes: 2 additions & 5 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
TypedDictType,
TypeList,
TypeOfAny,
TypeOfTypeList,
TypeQuery,
TypeType,
TypeVarLikeType,
Expand Down Expand Up @@ -891,12 +890,10 @@ def visit_type_list(self, t: TypeList) -> Type:
else:
return AnyType(TypeOfAny.from_error)
else:
s = "[...]" if t.list_type == TypeOfTypeList.callable_args else "(...)"
self.fail(
f'Bracketed expression "{s}" is not valid as a type', t, code=codes.VALID_TYPE
'Bracketed expression "[...]" is not valid as a type', t, code=codes.VALID_TYPE
)
if t.list_type == TypeOfTypeList.callable_args:
self.note('Did you mean "List[...]"?', t)
self.note('Did you mean "List[...]"?', t)
return AnyType(TypeOfAny.from_error)

def visit_callable_argument(self, t: CallableArgument) -> Type:
Expand Down
28 changes: 3 additions & 25 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,17 +197,6 @@ class TypeOfAny:
suggestion_engine: Final = 9


class TypeOfTypeList:
"""This class describes the different types of TypeList."""

__slots__ = ()

# List expressions for callable args
callable_args: Final = 1
# Tuple expressions for ParamSpec defaults
param_spec_defaults: Final = 2


def deserialize_type(data: JsonDict | str) -> Type:
if isinstance(data, str):
return Instance.deserialize(data)
Expand Down Expand Up @@ -1022,20 +1011,13 @@ class TypeList(ProperType):
types before they are processed into Callable types.
"""

__slots__ = ("items", "list_type")
__slots__ = ("items",)

items: list[Type]

def __init__(
self,
items: list[Type],
list_type: int = TypeOfTypeList.callable_args,
line: int = -1,
column: int = -1,
) -> None:
def __init__(self, items: list[Type], line: int = -1, column: int = -1) -> None:
super().__init__(line, column)
self.items = items
self.list_type = list_type

def accept(self, visitor: TypeVisitor[T]) -> T:
assert isinstance(visitor, SyntheticTypeVisitor)
Expand All @@ -1049,11 +1031,7 @@ def __hash__(self) -> int:
return hash(tuple(self.items))

def __eq__(self, other: object) -> bool:
return (
isinstance(other, TypeList)
and self.items == other.items
and self.list_type == other.list_type
)
return isinstance(other, TypeList) and self.items == other.items


class UnpackType(ProperType):
Expand Down
19 changes: 10 additions & 9 deletions test-data/unit/check-typevar-defaults.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ from typing import Generic, TypeVar, ParamSpec, Callable, Tuple, List
from typing_extensions import TypeVarTuple, Unpack

T1 = TypeVar("T1", default=int)
P1 = ParamSpec("P1", default=(int, str))
P1 = ParamSpec("P1", default=[int, str])
Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]])

def f1(a: T1) -> List[T1]: ...
Expand Down Expand Up @@ -44,9 +44,9 @@ T5 = TypeVar("T5", default=S0)
T6 = TypeVar("T6", bound=float, default=S1)
# T7 = TypeVar("T7", bound=List[Any], default=List[S0]) # TODO

P1 = ParamSpec("P1", default=())
P1 = ParamSpec("P1", default=[])
P2 = ParamSpec("P2", default=...)
P3 = ParamSpec("P3", default=(int, str))
P3 = ParamSpec("P3", default=[int, str])
P4 = ParamSpec("P4", default=P0)

Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int]])
Expand All @@ -59,15 +59,16 @@ from typing import TypeVar, ParamSpec, Tuple
from typing_extensions import TypeVarTuple, Unpack

T1 = TypeVar("T1", default=2) # E: TypeVar "default" must be a type
T2 = TypeVar("T2", default=(int, str)) # E: Bracketed expression "(...)" is not valid as a type \
T2 = TypeVar("T2", default=[int, str]) # E: Bracketed expression "[...]" is not valid as a type \
# N: Did you mean "List[...]"? \
# E: TypeVar "default" must be a type

P1 = ParamSpec("P1", default=int) # E: The default argument to ParamSpec must be a tuple expression, ellipsis, or a ParamSpec
P2 = ParamSpec("P2", default=2) # E: ParamSpec "default" must be a type
P3 = ParamSpec("P3", default=(2, int)) # E: Argument 0 of ParamSpec default must be a type
P1 = ParamSpec("P1", default=int) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec
P2 = ParamSpec("P2", default=2) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec
P3 = ParamSpec("P3", default=(2, int)) # E: The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec
P4 = ParamSpec("P4", default=[2, int]) # E: Argument 0 of ParamSpec default must be a type

Ts1 = TypeVarTuple("Ts1", default=2) # E: TypeVarTuple "default" must be a type \
# E: The default argument to TypeVarTuple must be an Unpacked tuple
Ts1 = TypeVarTuple("Ts1", default=2) # E: The default argument to TypeVarTuple must be an Unpacked tuple
Ts2 = TypeVarTuple("Ts2", default=int) # E: The default argument to TypeVarTuple must be an Unpacked tuple
Ts3 = TypeVarTuple("Ts3", default=Tuple[int]) # E: The default argument to TypeVarTuple must be an Unpacked tuple
[builtins fixtures/tuple.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ c = TypeVar(1) # E: TypeVar() expects a string literal as first argument
T = TypeVar(b'T') # E: TypeVar() expects a string literal as first argument
d = TypeVar('D') # E: String argument 1 "D" to TypeVar(...) does not match variable name "d"
e = TypeVar('e', int, str, x=1) # E: Unexpected argument to "TypeVar()": "x"
f = TypeVar('f', (int, str), int) # E: Bracketed expression "(...)" is not valid as a type
f = TypeVar('f', (int, str), int) # E: Type expected
g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to "TypeVar()": "x"
i = TypeVar('i', bound=1) # E: TypeVar "bound" must be a type
Expand Down

0 comments on commit d6e39c4

Please sign in to comment.