Skip to content

Commit

Permalink
Improves TypeVar error messages (#11448)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Nov 16, 2021
1 parent 7a5c6f0 commit 7f0ad94
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 21 deletions.
3 changes: 3 additions & 0 deletions mypy/message_registry.py
Expand Up @@ -152,6 +152,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
INVALID_TYPEVAR_AS_TYPEARG: Final = 'Type variable "{}" not valid as type argument value for "{}"'
INVALID_TYPEVAR_ARG_BOUND: Final = 'Type argument {} of "{}" must be a subtype of {}'
INVALID_TYPEVAR_ARG_VALUE: Final = 'Invalid type argument value for "{}"'
TYPEVAR_VARIANCE_DEF: Final = 'TypeVar "{}" may only be a literal bool'
TYPEVAR_BOUND_MUST_BE_TYPE: Final = 'TypeVar "bound" must be a type'
TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"'

# Super
TOO_MANY_ARGS_FOR_SUPER: Final = 'Too many arguments for "super"'
Expand Down
34 changes: 16 additions & 18 deletions mypy/semanal.py
Expand Up @@ -3172,27 +3172,23 @@ def process_typevar_parameters(self, args: List[Expression],
upper_bound: Type = self.object_type()
for param_value, param_name, param_kind in zip(args, names, kinds):
if not param_kind.is_named():
self.fail("Unexpected argument to TypeVar()", context)
self.fail(message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, context)
return None
if param_name == 'covariant':
if isinstance(param_value, NameExpr):
if param_value.name == 'True':
covariant = True
else:
self.fail("TypeVar 'covariant' may only be 'True'", context)
return None
if (isinstance(param_value, NameExpr)
and param_value.name in ('True', 'False')):
covariant = param_value.name == 'True'
else:
self.fail("TypeVar 'covariant' may only be 'True'", context)
self.fail(message_registry.TYPEVAR_VARIANCE_DEF.format(
'covariant'), context)
return None
elif param_name == 'contravariant':
if isinstance(param_value, NameExpr):
if param_value.name == 'True':
contravariant = True
else:
self.fail("TypeVar 'contravariant' may only be 'True'", context)
return None
if (isinstance(param_value, NameExpr)
and param_value.name in ('True', 'False')):
contravariant = param_value.name == 'True'
else:
self.fail("TypeVar 'contravariant' may only be 'True'", context)
self.fail(message_registry.TYPEVAR_VARIANCE_DEF.format(
'contravariant'), context)
return None
elif param_name == 'bound':
if has_values:
Expand All @@ -3214,11 +3210,11 @@ def process_typevar_parameters(self, args: List[Expression],
analyzed = PlaceholderType(None, [], context.line)
upper_bound = get_proper_type(analyzed)
if isinstance(upper_bound, AnyType) and upper_bound.is_from_error:
self.fail('TypeVar "bound" must be a type', param_value)
self.fail(message_registry.TYPEVAR_BOUND_MUST_BE_TYPE, param_value)
# Note: we do not return 'None' here -- we want to continue
# using the AnyType as the upper bound.
except TypeTranslationError:
self.fail('TypeVar "bound" must be a type', param_value)
self.fail(message_registry.TYPEVAR_BOUND_MUST_BE_TYPE, param_value)
return None
elif param_name == 'values':
# Probably using obsolete syntax with values=(...). Explain the current syntax.
Expand All @@ -3227,7 +3223,9 @@ def process_typevar_parameters(self, args: List[Expression],
context)
return None
else:
self.fail('Unexpected argument to TypeVar(): "{}"'.format(param_name), context)
self.fail('{}: "{}"'.format(
message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, param_name,
), context)
return None

if covariant and contravariant:
Expand Down
8 changes: 5 additions & 3 deletions test-data/unit/semanal-errors.test
Expand Up @@ -1030,11 +1030,13 @@ a = TypeVar() # E: Too few arguments for TypeVar()
b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument
c = TypeVar(1) # 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"
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
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to TypeVar(): "x"
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
k = TypeVar('k', contravariant=1) # E: TypeVar "contravariant" may only be a literal bool
[out]

[case testMoreInvalidTypevarArguments]
Expand All @@ -1046,7 +1048,7 @@ S = TypeVar('S', covariant=True, contravariant=True) \

[case testInvalidTypevarValues]
from typing import TypeVar
b = TypeVar('b', *[int]) # E: Unexpected argument to TypeVar()
b = TypeVar('b', *[int]) # E: Unexpected argument to "TypeVar()"
c = TypeVar('c', int, 2) # E: Invalid type: try using Literal[2] instead?
[out]

Expand Down
29 changes: 29 additions & 0 deletions test-data/unit/semanal-types.test
Expand Up @@ -1284,6 +1284,35 @@ MypyFile:1(
builtins.int
builtins.str))))

[case testTypevarWithFalseVariance]
from typing import TypeVar
T1 = TypeVar('T1', covariant=False)
T2 = TypeVar('T2', covariant=False, contravariant=False)
T3 = TypeVar('T3', contravariant=False)
T4 = TypeVar('T4', covariant=True, contravariant=False)
T5 = TypeVar('T5', covariant=False, contravariant=True)
[builtins fixtures/bool.pyi]
[out]
MypyFile:1(
ImportFrom:1(typing, [TypeVar])
AssignmentStmt:2(
NameExpr(T1* [__main__.T1])
TypeVarExpr:2())
AssignmentStmt:3(
NameExpr(T2* [__main__.T2])
TypeVarExpr:3())
AssignmentStmt:4(
NameExpr(T3* [__main__.T3])
TypeVarExpr:4())
AssignmentStmt:5(
NameExpr(T4* [__main__.T4])
TypeVarExpr:5(
Variance(COVARIANT)))
AssignmentStmt:6(
NameExpr(T5* [__main__.T5])
TypeVarExpr:6(
Variance(CONTRAVARIANT))))

[case testTypevarWithBound]
from typing import TypeVar
T = TypeVar('T', bound=int)
Expand Down

0 comments on commit 7f0ad94

Please sign in to comment.