Skip to content

Commit

Permalink
[PEP 695] Fix a few issues and add tests (#17318)
Browse files Browse the repository at this point in the history
Fix badly formed types that could be created when using aliases like
`type A = list`.

Improve some error messages when using PEP 695 syntax.

Add a few PEP 695 tests.

Work on #15238.
  • Loading branch information
JukkaL committed Jun 3, 2024
1 parent aa4410f commit 93dac05
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 8 deletions.
3 changes: 3 additions & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
"A function returning TypeVar should receive at least "
"one argument containing the same TypeVar"
)
TYPE_PARAMETERS_SHOULD_BE_DECLARED: Final = (
"All type parameters should be declared ({} not declared)"
)

# Super
TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"')
Expand Down
6 changes: 5 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2423,7 +2423,11 @@ def annotation_in_unchecked_function(self, context: Context) -> None:

def type_parameters_should_be_declared(self, undeclared: list[str], context: Context) -> None:
names = ", ".join('"' + n + '"' for n in undeclared)
self.fail(f"All type parameters should be declared ({names} not declared)", context)
self.fail(
message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(names),
context,
code=codes.VALID_TYPE,
)


def quote_type_string(type_string: str) -> str:
Expand Down
5 changes: 5 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3694,6 +3694,7 @@ def analyze_alias(
allow_placeholder: bool = False,
declared_type_vars: TypeVarLikeList | None = None,
all_declared_type_params_names: list[str] | None = None,
python_3_12_type_alias: bool = False,
) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]:
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
Expand Down Expand Up @@ -3747,6 +3748,7 @@ def analyze_alias(
global_scope=global_scope,
allowed_alias_tvars=tvar_defs,
alias_type_params_names=all_declared_type_params_names,
python_3_12_type_alias=python_3_12_type_alias,
)

# There can be only one variadic variable at most, the error is reported elsewhere.
Expand Down Expand Up @@ -5321,6 +5323,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
allow_placeholder=True,
declared_type_vars=type_params,
all_declared_type_params_names=all_type_params_names,
python_3_12_type_alias=True,
)
if not res:
res = AnyType(TypeOfAny.from_error)
Expand Down Expand Up @@ -5355,6 +5358,8 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
# so we need to replace it with non-explicit Anys.
res = make_any_non_explicit(res)
eager = self.is_func_scope()
if isinstance(res, ProperType) and isinstance(res, Instance) and not res.args:
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
alias_node = TypeAlias(
res,
self.qualified_name(s.name.name),
Expand Down
30 changes: 23 additions & 7 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def analyze_type_alias(
global_scope: bool = True,
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
alias_type_params_names: list[str] | None = None,
python_3_12_type_alias: bool = False,
) -> tuple[Type, set[str]]:
"""Analyze r.h.s. of a (potential) type alias definition.
Expand All @@ -160,6 +161,7 @@ def analyze_type_alias(
prohibit_self_type="type alias target",
allowed_alias_tvars=allowed_alias_tvars,
alias_type_params_names=alias_type_params_names,
python_3_12_type_alias=python_3_12_type_alias,
)
analyzer.in_dynamic_func = in_dynamic_func
analyzer.global_scope = global_scope
Expand Down Expand Up @@ -202,6 +204,7 @@ def __init__(
is_typeshed_stub: bool,
*,
defining_alias: bool = False,
python_3_12_type_alias: bool = False,
allow_tuple_literal: bool = False,
allow_unbound_tvars: bool = False,
allow_placeholder: bool = False,
Expand All @@ -220,6 +223,7 @@ def __init__(
self.tvar_scope = tvar_scope
# Are we analysing a type alias definition rvalue?
self.defining_alias = defining_alias
self.python_3_12_type_alias = python_3_12_type_alias
self.allow_tuple_literal = allow_tuple_literal
# Positive if we are analyzing arguments of another (outer) type
self.nesting_level = 0
Expand Down Expand Up @@ -364,7 +368,12 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
and (tvar_def is None or tvar_def not in self.allowed_alias_tvars)
):
if self.not_declared_in_type_params(t.name):
msg = f'Type variable "{t.name}" is not included in type_params'
if self.python_3_12_type_alias:
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
f'"{t.name}"'
)
else:
msg = f'Type variable "{t.name}" is not included in type_params'
else:
msg = f'Can\'t use bound type variable "{t.name}" to define generic alias'
self.fail(msg, t, code=codes.VALID_TYPE)
Expand Down Expand Up @@ -393,7 +402,12 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
if self.allow_unbound_tvars:
return t
if self.defining_alias and self.not_declared_in_type_params(t.name):
msg = f'TypeVarTuple "{t.name}" is not included in type_params'
if self.python_3_12_type_alias:
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
f'"{t.name}"'
)
else:
msg = f'TypeVarTuple "{t.name}" is not included in type_params'
else:
msg = f'TypeVarTuple "{t.name}" is unbound'
self.fail(msg, t, code=codes.VALID_TYPE)
Expand Down Expand Up @@ -1309,11 +1323,13 @@ def analyze_callable_args_for_paramspec(
and self.not_declared_in_type_params(tvar_def.name)
and tvar_def not in self.allowed_alias_tvars
):
self.fail(
f'ParamSpec "{tvar_def.name}" is not included in type_params',
callable_args,
code=codes.VALID_TYPE,
)
if self.python_3_12_type_alias:
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
f'"{tvar_def.name}"'
)
else:
msg = f'ParamSpec "{tvar_def.name}" is not included in type_params'
self.fail(msg, callable_args, code=codes.VALID_TYPE)
return callable_with_ellipsis(
AnyType(TypeOfAny.special_form), ret_type=ret_type, fallback=fallback
)
Expand Down
69 changes: 69 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1244,3 +1244,72 @@ class C[T]:

class D[T](C[S]): # E: All type parameters should be declared ("S" not declared)
pass

[case testPEP695MixNewAndOldStyleTypeVarTupleAndParamSpec]
# mypy: enable-incomplete-feature=NewGenericSyntax
from typing import TypeVarTuple, ParamSpec, Callable
Ts = TypeVarTuple("Ts")
P = ParamSpec("P")

def f[T](x: T, f: Callable[P, None] # E: All type parameters should be declared ("P" not declared)
) -> Callable[P, T]: ...
def g[T](x: T, f: tuple[*Ts] # E: All type parameters should be declared ("Ts" not declared)
) -> tuple[T, *Ts]: ...
[builtins fixtures/tuple.pyi]

[case testPEP695MixNewAndOldStyleGenericsInTypeAlias]
# mypy: enable-incomplete-feature=NewGenericSyntax
from typing import TypeVar, ParamSpec, TypeVarTuple, Callable

T = TypeVar("T")
Ts = TypeVarTuple("Ts")
P = ParamSpec("P")

type A = list[T] # E: All type parameters should be declared ("T" not declared)
a: A[int] # E: Bad number of arguments for type alias, expected 0, given 1
reveal_type(a) # N: Revealed type is "builtins.list[Any]"

type B = tuple[*Ts] # E: All type parameters should be declared ("Ts" not declared)
type C = Callable[P, None] # E: All type parameters should be declared ("P" not declared)
[builtins fixtures/tuple.pyi]

[case testPEP695NonGenericAliasToGenericClass]
# mypy: enable-incomplete-feature=NewGenericSyntax
class C[T]: pass
type A = C
x: C
y: A
reveal_type(x) # N: Revealed type is "__main__.C[Any]"
reveal_type(y) # N: Revealed type is "__main__.C[Any]"
z: A[int] # E: Bad number of arguments for type alias, expected 0, given 1

[case testPEP695SelfType]
# mypy: enable-incomplete-feature=NewGenericSyntax
from typing import Self

class C:
@classmethod
def m[T](cls, x: T) -> tuple[Self, T]:
return cls(), x

class D(C):
pass

reveal_type(C.m(1)) # N: Revealed type is "Tuple[__main__.C, builtins.int]"
reveal_type(D.m(1)) # N: Revealed type is "Tuple[__main__.D, builtins.int]"

class E[T]:
def m(self) -> Self:
return self

def mm[S](self, x: S) -> tuple[Self, S]:
return self, x

class F[T](E[T]):
pass

reveal_type(E[int]().m()) # N: Revealed type is "__main__.E[builtins.int]"
reveal_type(E[int]().mm(b'x')) # N: Revealed type is "Tuple[__main__.E[builtins.int], builtins.bytes]"
reveal_type(F[str]().m()) # N: Revealed type is "__main__.F[builtins.str]"
reveal_type(F[str]().mm(b'x')) # N: Revealed type is "Tuple[__main__.F[builtins.str], builtins.bytes]"
[builtins fixtures/tuple.pyi]

0 comments on commit 93dac05

Please sign in to comment.