From 7f0ad943e4b189224f2fedf43aa7b38f53ec561e Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 16 Nov 2021 18:27:11 +0300 Subject: [PATCH] Improves `TypeVar` error messages (#11448) --- mypy/message_registry.py | 3 +++ mypy/semanal.py | 34 ++++++++++++++---------------- test-data/unit/semanal-errors.test | 8 ++++--- test-data/unit/semanal-types.test | 29 +++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 9ddb1604ca27..a1f6f651af99 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -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"' diff --git a/mypy/semanal.py b/mypy/semanal.py index 1a99a4837cee..3e83f0f95899 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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: @@ -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. @@ -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: diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 9dc75a4930e4..e5aea483a210 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -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] @@ -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] diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test index 67ce14c001a3..772de61b5900 100644 --- a/test-data/unit/semanal-types.test +++ b/test-data/unit/semanal-types.test @@ -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)