Skip to content

Commit 7032f8c

Browse files
authored
[PEP 695] Detect errors related to mixing old and new style features (#17269)
`Generic[...]` or `Protocol[...]` shouldn't be used with new-style syntax. Generic functions and classes using the new syntax shouldn't mix new-style and old-style type parameters. Work on #15238.
1 parent f60f458 commit 7032f8c

File tree

3 files changed

+75
-9
lines changed

3 files changed

+75
-9
lines changed

mypy/messages.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,10 @@ def annotation_in_unchecked_function(self, context: Context) -> None:
24212421
code=codes.ANNOTATION_UNCHECKED,
24222422
)
24232423

2424+
def type_parameters_should_be_declared(self, undeclared: list[str], context: Context) -> None:
2425+
names = ", ".join('"' + n + '"' for n in undeclared)
2426+
self.fail(f"All type parameters should be declared ({names} not declared)", context)
2427+
24242428

24252429
def quote_type_string(type_string: str) -> str:
24262430
"""Quotes a type representation for use in messages."""

mypy/semanal.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,14 @@ def update_function_type_variables(self, fun_type: CallableType, defn: FuncItem)
11191119
fun_type.variables, has_self_type = a.bind_function_type_variables(fun_type, defn)
11201120
if has_self_type and self.type is not None:
11211121
self.setup_self_type()
1122+
if defn.type_args:
1123+
bound_fullnames = {v.fullname for v in fun_type.variables}
1124+
declared_fullnames = {self.qualified_name(p.name) for p in defn.type_args}
1125+
extra = sorted(bound_fullnames - declared_fullnames)
1126+
if extra:
1127+
self.msg.type_parameters_should_be_declared(
1128+
[n.split(".")[-1] for n in extra], defn
1129+
)
11221130
return has_self_type
11231131

11241132
def setup_self_type(self) -> None:
@@ -2076,11 +2084,19 @@ class Foo(Bar, Generic[T]): ...
20762084
continue
20772085
result = self.analyze_class_typevar_declaration(base)
20782086
if result is not None:
2079-
if declared_tvars:
2080-
self.fail("Only single Generic[...] or Protocol[...] can be in bases", context)
2081-
removed.append(i)
20822087
tvars = result[0]
20832088
is_protocol |= result[1]
2089+
if declared_tvars:
2090+
if defn.type_args:
2091+
if is_protocol:
2092+
self.fail('No arguments expected for "Protocol" base class', context)
2093+
else:
2094+
self.fail("Generic[...] base class is redundant", context)
2095+
else:
2096+
self.fail(
2097+
"Only single Generic[...] or Protocol[...] can be in bases", context
2098+
)
2099+
removed.append(i)
20842100
declared_tvars.extend(tvars)
20852101
if isinstance(base, UnboundType):
20862102
sym = self.lookup_qualified(base.name, base)
@@ -2092,15 +2108,21 @@ class Foo(Bar, Generic[T]): ...
20922108

20932109
all_tvars = self.get_all_bases_tvars(base_type_exprs, removed)
20942110
if declared_tvars:
2095-
if len(remove_dups(declared_tvars)) < len(declared_tvars):
2111+
if len(remove_dups(declared_tvars)) < len(declared_tvars) and not defn.type_args:
20962112
self.fail("Duplicate type variables in Generic[...] or Protocol[...]", context)
20972113
declared_tvars = remove_dups(declared_tvars)
20982114
if not set(all_tvars).issubset(set(declared_tvars)):
2099-
self.fail(
2100-
"If Generic[...] or Protocol[...] is present"
2101-
" it should list all type variables",
2102-
context,
2103-
)
2115+
if defn.type_args:
2116+
undeclared = sorted(set(all_tvars) - set(declared_tvars))
2117+
self.msg.type_parameters_should_be_declared(
2118+
[tv[0] for tv in undeclared], context
2119+
)
2120+
else:
2121+
self.fail(
2122+
"If Generic[...] or Protocol[...] is present"
2123+
" it should list all type variables",
2124+
context,
2125+
)
21042126
# In case of error, Generic tvars will go first
21052127
declared_tvars = remove_dups(declared_tvars + all_tvars)
21062128
else:

test-data/unit/check-python312.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,3 +1161,43 @@ def decorator(x: str) -> Any: ...
11611161
@decorator(T) # E: Argument 1 to "decorator" has incompatible type "int"; expected "str"
11621162
class C[T]:
11631163
pass
1164+
1165+
[case testPEP695InvalidGenericOrProtocolBaseClass]
1166+
# mypy: enable-incomplete-feature=NewGenericSyntax
1167+
from typing import Generic, Protocol, TypeVar
1168+
1169+
S = TypeVar("S")
1170+
1171+
class C[T](Generic[T]): # E: Generic[...] base class is redundant
1172+
pass
1173+
class C2[T](Generic[S]): # E: Generic[...] base class is redundant
1174+
pass
1175+
1176+
a: C[int]
1177+
b: C2[int, str]
1178+
1179+
class P[T](Protocol[T]): # E: No arguments expected for "Protocol" base class
1180+
pass
1181+
class P2[T](Protocol[S]): # E: No arguments expected for "Protocol" base class
1182+
pass
1183+
1184+
[case testPEP695MixNewAndOldStyleGenerics]
1185+
# mypy: enable-incomplete-feature=NewGenericSyntax
1186+
from typing import TypeVar
1187+
1188+
S = TypeVar("S")
1189+
U = TypeVar("U")
1190+
1191+
def f[T](x: T, y: S) -> T | S: ... # E: All type parameters should be declared ("S" not declared)
1192+
def g[T](x: S, y: U) -> T | S | U: ... # E: All type parameters should be declared ("S", "U" not declared)
1193+
1194+
def h[S: int](x: S) -> S:
1195+
a: int = x
1196+
return x
1197+
1198+
class C[T]:
1199+
def m[X, S](self, x: S, y: U) -> X | S | U: ... # E: All type parameters should be declared ("U" not declared)
1200+
def m2(self, x: T, y: S) -> T | S: ...
1201+
1202+
class D[T](C[S]): # E: All type parameters should be declared ("S" not declared)
1203+
pass

0 commit comments

Comments
 (0)