Skip to content

Commit 740d39e

Browse files
authored
[PEP 695] Add more error checks and tests for error conditions (#17339)
Detect invalid number of constrained types. At least two are required, according do PEP 695. Add tests for other simple errors. Work on #15238.
1 parent dac88f3 commit 740d39e

File tree

6 files changed

+53
-6
lines changed

6 files changed

+53
-6
lines changed

mypy/errorcodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def __hash__(self) -> int:
271271
del error_codes[FILE.code]
272272

273273
# This is a catch-all for remaining uncategorized errors.
274-
MISC: Final = ErrorCode("misc", "Miscellaneous other checks", "General")
274+
MISC: Final[ErrorCode] = ErrorCode("misc", "Miscellaneous other checks", "General")
275275

276276
OVERLOAD_OVERLAP: Final[ErrorCode] = ErrorCode(
277277
"overload-overlap",

mypy/fastparse.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,8 +1185,16 @@ def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
11851185
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_TUPLE_KIND, None, []))
11861186
else:
11871187
if isinstance(p.bound, ast3.Tuple):
1188-
conv = TypeConverter(self.errors, line=p.lineno)
1189-
values = [conv.visit(t) for t in p.bound.elts]
1188+
if len(p.bound.elts) < 2:
1189+
self.fail(
1190+
message_registry.TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES,
1191+
p.lineno,
1192+
p.col_offset,
1193+
blocker=False,
1194+
)
1195+
else:
1196+
conv = TypeConverter(self.errors, line=p.lineno)
1197+
values = [conv.visit(t) for t in p.bound.elts]
11901198
elif p.bound is not None:
11911199
bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound)
11921200
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values))

mypy/message_registry.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
334334
NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorMessage(
335335
"Narrowed type {} is not a subtype of input type {}", codes.NARROWED_TYPE_NOT_SUBTYPE
336336
)
337+
TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES: Final = ErrorMessage(
338+
"Type variable must have at least two constrained types", codes.MISC
339+
)

mypy/semanal.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from mypy.errorcodes import PROPERTY_DECORATOR, ErrorCode
6060
from mypy.errors import Errors, report_internal_error
6161
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
62+
from mypy.message_registry import ErrorMessage
6263
from mypy.messages import (
6364
SUGGESTED_TEST_FIXTURES,
6465
TYPES_FOR_UNIMPORTED_HINTS,
@@ -4618,7 +4619,7 @@ def process_typevar_parameters(
46184619
self.fail("TypeVar cannot be both covariant and contravariant", context)
46194620
return None
46204621
elif num_values == 1:
4621-
self.fail("TypeVar cannot have only a single constraint", context)
4622+
self.fail(message_registry.TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES, context)
46224623
return None
46234624
elif covariant:
46244625
variance = COVARIANT
@@ -7034,7 +7035,7 @@ def in_checked_function(self) -> bool:
70347035

70357036
def fail(
70367037
self,
7037-
msg: str,
7038+
msg: str | ErrorMessage,
70387039
ctx: Context,
70397040
serious: bool = False,
70407041
*,
@@ -7045,6 +7046,10 @@ def fail(
70457046
return
70467047
# In case it's a bug and we don't really have context
70477048
assert ctx is not None, msg
7049+
if isinstance(msg, ErrorMessage):
7050+
if code is None:
7051+
code = msg.code
7052+
msg = msg.value
70487053
self.errors.report(ctx.line, ctx.column, msg, blocker=blocker, code=code)
70497054

70507055
def note(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None:

test-data/unit/check-python312.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,3 +1494,34 @@ reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"
14941494
# flags: --enable-incomplete-feature=NewGenericSyntax
14951495
def f[T](x: foobar, y: T) -> T: ... # E: Name "foobar" is not defined
14961496
reveal_type(f) # N: Revealed type is "def [T] (x: Any, y: T`-1) -> T`-1"
1497+
1498+
[case testPEP695WrongNumberOfConstrainedTypes]
1499+
# flags: --enable-incomplete-feature=NewGenericSyntax
1500+
type A[T: ()] = list[T] # E: Type variable must have at least two constrained types
1501+
a: A[int]
1502+
reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"
1503+
1504+
type B[T: (int,)] = list[T] # E: Type variable must have at least two constrained types
1505+
b: B[str]
1506+
reveal_type(b) # N: Revealed type is "builtins.list[builtins.str]"
1507+
1508+
[case testPEP695UsingTypeVariableInOwnBoundOrConstraint]
1509+
# flags: --enable-incomplete-feature=NewGenericSyntax
1510+
type A[T: list[T]] = str # E: Name "T" is not defined
1511+
type B[S: (list[S], str)] = str # E: Name "S" is not defined
1512+
type C[T, S: list[T]] = str # E: Name "T" is not defined
1513+
1514+
def f[T: T](x: T) -> T: ... # E: Name "T" is not defined
1515+
class D[T: T]: # E: Name "T" is not defined
1516+
pass
1517+
1518+
[case testPEP695InvalidType]
1519+
# flags: --enable-incomplete-feature=NewGenericSyntax
1520+
def f[T: 1](x: T) -> T: ... # E: Invalid type: try using Literal[1] instead?
1521+
class C[T: (int, (1 + 2))]: pass # E: Invalid type comment or annotation
1522+
type A = list[1] # E: Invalid type: try using Literal[1] instead?
1523+
type B = (1 + 2) # E: Invalid type alias: expression is not a valid type
1524+
a: A
1525+
reveal_type(a) # N: Revealed type is "builtins.list[Any]"
1526+
b: B
1527+
reveal_type(b) # N: Revealed type is "Any"

test-data/unit/semanal-errors.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ T = TypeVar(b'T') # E: TypeVar() expects a string literal as first argument
10461046
d = TypeVar('D') # E: String argument 1 "D" to TypeVar(...) does not match variable name "d"
10471047
e = TypeVar('e', int, str, x=1) # E: Unexpected argument to "TypeVar()": "x"
10481048
f = TypeVar('f', (int, str), int) # E: Type expected
1049-
g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint
1049+
g = TypeVar('g', int) # E: Type variable must have at least two constrained types
10501050
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to "TypeVar()": "x"
10511051
i = TypeVar('i', bound=1) # E: TypeVar "bound" must be a type
10521052
j = TypeVar('j', covariant=None) # E: TypeVar "covariant" may only be a literal bool

0 commit comments

Comments
 (0)