Skip to content

Commit

Permalink
[PEP 695] Add more error checks and tests for error conditions (pytho…
Browse files Browse the repository at this point in the history
…n#17339)

Detect invalid number of constrained types. At least two are required,
according do PEP 695.

Add tests for other simple errors.

Work on python#15238.
  • Loading branch information
JukkaL authored Jun 14, 2024
1 parent dac88f3 commit 740d39e
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 6 deletions.
2 changes: 1 addition & 1 deletion mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def __hash__(self) -> int:
del error_codes[FILE.code]

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

OVERLOAD_OVERLAP: Final[ErrorCode] = ErrorCode(
"overload-overlap",
Expand Down
12 changes: 10 additions & 2 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,8 +1185,16 @@ def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_TUPLE_KIND, None, []))
else:
if isinstance(p.bound, ast3.Tuple):
conv = TypeConverter(self.errors, line=p.lineno)
values = [conv.visit(t) for t in p.bound.elts]
if len(p.bound.elts) < 2:
self.fail(
message_registry.TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES,
p.lineno,
p.col_offset,
blocker=False,
)
else:
conv = TypeConverter(self.errors, line=p.lineno)
values = [conv.visit(t) for t in p.bound.elts]
elif p.bound is not None:
bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound)
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values))
Expand Down
3 changes: 3 additions & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorMessage(
"Narrowed type {} is not a subtype of input type {}", codes.NARROWED_TYPE_NOT_SUBTYPE
)
TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES: Final = ErrorMessage(
"Type variable must have at least two constrained types", codes.MISC
)
9 changes: 7 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from mypy.errorcodes import PROPERTY_DECORATOR, ErrorCode
from mypy.errors import Errors, report_internal_error
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.message_registry import ErrorMessage
from mypy.messages import (
SUGGESTED_TEST_FIXTURES,
TYPES_FOR_UNIMPORTED_HINTS,
Expand Down Expand Up @@ -4618,7 +4619,7 @@ def process_typevar_parameters(
self.fail("TypeVar cannot be both covariant and contravariant", context)
return None
elif num_values == 1:
self.fail("TypeVar cannot have only a single constraint", context)
self.fail(message_registry.TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES, context)
return None
elif covariant:
variance = COVARIANT
Expand Down Expand Up @@ -7034,7 +7035,7 @@ def in_checked_function(self) -> bool:

def fail(
self,
msg: str,
msg: str | ErrorMessage,
ctx: Context,
serious: bool = False,
*,
Expand All @@ -7045,6 +7046,10 @@ def fail(
return
# In case it's a bug and we don't really have context
assert ctx is not None, msg
if isinstance(msg, ErrorMessage):
if code is None:
code = msg.code
msg = msg.value
self.errors.report(ctx.line, ctx.column, msg, blocker=blocker, code=code)

def note(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None:
Expand Down
31 changes: 31 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1494,3 +1494,34 @@ reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"
# flags: --enable-incomplete-feature=NewGenericSyntax
def f[T](x: foobar, y: T) -> T: ... # E: Name "foobar" is not defined
reveal_type(f) # N: Revealed type is "def [T] (x: Any, y: T`-1) -> T`-1"

[case testPEP695WrongNumberOfConstrainedTypes]
# flags: --enable-incomplete-feature=NewGenericSyntax
type A[T: ()] = list[T] # E: Type variable must have at least two constrained types
a: A[int]
reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"

type B[T: (int,)] = list[T] # E: Type variable must have at least two constrained types
b: B[str]
reveal_type(b) # N: Revealed type is "builtins.list[builtins.str]"

[case testPEP695UsingTypeVariableInOwnBoundOrConstraint]
# flags: --enable-incomplete-feature=NewGenericSyntax
type A[T: list[T]] = str # E: Name "T" is not defined
type B[S: (list[S], str)] = str # E: Name "S" is not defined
type C[T, S: list[T]] = str # E: Name "T" is not defined

def f[T: T](x: T) -> T: ... # E: Name "T" is not defined
class D[T: T]: # E: Name "T" is not defined
pass

[case testPEP695InvalidType]
# flags: --enable-incomplete-feature=NewGenericSyntax
def f[T: 1](x: T) -> T: ... # E: Invalid type: try using Literal[1] instead?
class C[T: (int, (1 + 2))]: pass # E: Invalid type comment or annotation
type A = list[1] # E: Invalid type: try using Literal[1] instead?
type B = (1 + 2) # E: Invalid type alias: expression is not a valid type
a: A
reveal_type(a) # N: Revealed type is "builtins.list[Any]"
b: B
reveal_type(b) # N: Revealed type is "Any"
2 changes: 1 addition & 1 deletion test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@ 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: Type expected
g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint
g = TypeVar('g', int) # E: Type variable must have at least two constrained types
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to "TypeVar()": "x"
i = TypeVar('i', bound=1) # E: TypeVar "bound" must be a type
j = TypeVar('j', covariant=None) # E: TypeVar "covariant" may only be a literal bool
Expand Down

0 comments on commit 740d39e

Please sign in to comment.