From 5da91cb15e86dc41ce7ea6f8cccf513156ec6e4c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Mon, 17 Jan 2022 23:51:36 +0530 Subject: [PATCH 01/20] Migrate fastparse to use ErrorMessage class --- mypy/fastparse.py | 43 ++++++++++++++++++++-------------------- mypy/fastparse2.py | 22 ++++++++++---------- mypy/message_registry.py | 39 +++++++++++++++++++++++++++++++++++- mypy/nodes.py | 6 ++++-- 4 files changed, 75 insertions(+), 35 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index f3dd1f0a8fef..979db7c10eac 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -37,6 +37,7 @@ ) from mypy import defaults from mypy import message_registry, errorcodes as codes +from mypy.message_registry import ErrorMessage from mypy.errors import Errors from mypy.options import Options from mypy.reachability import mark_block_unreachable @@ -222,8 +223,8 @@ def parse_type_comment(type_comment: str, except SyntaxError: if errors is not None: stripped_type = type_comment.split("#", 2)[0].strip() - err_msg = '{} "{}"'.format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type) - errors.report(line, column, err_msg, blocker=True, code=codes.SYNTAX) + err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type) + errors.report(line, column, err_msg.value, blocker=True, code=err_msg.code) return None, None else: raise @@ -235,7 +236,8 @@ def parse_type_comment(type_comment: str, ignored: Optional[List[str]] = parse_type_ignore_tag(tag) if ignored is None: if errors is not None: - errors.report(line, column, INVALID_TYPE_IGNORE, code=codes.SYNTAX) + err_msg = message_registry.INVALID_TYPE_IGNORE + errors.report(line, column, err_msg.value, code=err_msg.code) else: raise SyntaxError else: @@ -313,12 +315,12 @@ def note(self, msg: str, line: int, column: int) -> None: self.errors.report(line, column, msg, severity='note', code=codes.SYNTAX) def fail(self, - msg: str, + msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None: if blocker or not self.options.ignore_errors: - self.errors.report(line, column, msg, blocker=blocker, code=codes.SYNTAX) + self.errors.report(line, column, msg.value, blocker=blocker, code=msg.code) def visit(self, node: Optional[AST]) -> Any: if node is None: @@ -501,7 +503,7 @@ def visit_Module(self, mod: ast3.Module) -> MypyFile: if parsed is not None: self.type_ignores[ti.lineno] = parsed else: - self.fail(INVALID_TYPE_IGNORE, ti.lineno, -1) + self.fail(message_registry.INVALID_TYPE_IGNORE, ti.lineno, -1) body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True)) return MypyFile(body, self.imports, @@ -574,7 +576,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], arg_types.insert(0, AnyType(TypeOfAny.special_form)) except SyntaxError: stripped_type = n.type_comment.split("#", 2)[0].strip() - err_msg = '{} "{}"'.format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type) + err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type) self.fail(err_msg, lineno, n.col_offset) if n.type_comment and n.type_comment[0] not in ["(", "#"]: self.note('Suggestion: wrap argument types in parentheses', @@ -593,13 +595,12 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], if any(arg_types) or return_type: if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types): - self.fail("Ellipses cannot accompany other argument types " - "in function type signature", lineno, n.col_offset) + self.fail(message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS, lineno, n.col_offset) elif len(arg_types) > len(arg_kinds): - self.fail('Type signature has too many arguments', lineno, n.col_offset, + self.fail(message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS, lineno, n.col_offset, blocker=False) elif len(arg_types) < len(arg_kinds): - self.fail('Type signature has too few arguments', lineno, n.col_offset, + self.fail(message_registry.TYPE_SIGNATURE_TOO_FEW_ARGS, lineno, n.col_offset, blocker=False) else: func_type = CallableType([a if a is not None else @@ -727,7 +728,7 @@ def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: ArgKi return Argument(Var(arg.arg), arg_type, self.visit(default), kind, pos_only) - def fail_arg(self, msg: str, arg: ast3.arg) -> None: + def fail_arg(self, msg: ErrorMessage, arg: ast3.arg) -> None: self.fail(msg, arg.lineno, arg.col_offset) # ClassDef(identifier name, @@ -1288,7 +1289,7 @@ def visit_Index(self, n: Index) -> Node: return self.visit(cast(Any, n).value) def visit_Match(self, n: Any) -> Node: - self.fail("Match statement is not supported", + self.errors.report(message="Match statement is not supported", line=n.lineno, column=n.col_offset, blocker=True) # Just return some valid node return PassStmt() @@ -1365,9 +1366,9 @@ def parent(self) -> Optional[AST]: return None return self.node_stack[-2] - def fail(self, msg: str, line: int, column: int) -> None: + def fail(self, msg: ErrorMessage, line: int, column: int) -> None: if self.errors: - self.errors.report(line, column, msg, blocker=True, code=codes.SYNTAX) + self.errors.report(line, column, msg.value, blocker=True, code=msg.code) def note(self, msg: str, line: int, column: int) -> None: if self.errors: @@ -1398,7 +1399,7 @@ def visit_Call(self, e: Call) -> Type: note = "Suggestion: use {0}[...] instead of {0}(...)".format(constructor) return self.invalid_type(e, note=note) if not constructor: - self.fail("Expected arg constructor name", e.lineno, e.col_offset) + self.fail(message_registry.ARG_CONSTRUCTOR_NAME_EXPECTED, e.lineno, e.col_offset) name: Optional[str] = None default_type = AnyType(TypeOfAny.special_form) @@ -1411,25 +1412,25 @@ def visit_Call(self, e: Call) -> Type: elif i == 1: name = self._extract_argument_name(arg) else: - self.fail("Too many arguments for argument constructor", + self.fail(message_registry.ARG_CONSTRUCTOR_TOO_MANY_ARGS, f.lineno, f.col_offset) for k in e.keywords: value = k.value if k.arg == "name": if name is not None: - self.fail('"{}" gets multiple values for keyword argument "name"'.format( + self.fail(message_registry.MULTIPLE_VALUES_FOR_NAME_KWARG.format( constructor), f.lineno, f.col_offset) name = self._extract_argument_name(value) elif k.arg == "type": if typ is not default_type: - self.fail('"{}" gets multiple values for keyword argument "type"'.format( + self.fail(message_registry.MULTIPLE_VALUES_FOR_TYPE_KWARG.format( constructor), f.lineno, f.col_offset) converted = self.visit(value) assert converted is not None typ = converted else: self.fail( - 'Unexpected argument "{}" for argument constructor'.format(k.arg), + message_registry.ARG_CONSTRUCTOR_UNEXPECTED_ARG.format(k.arg), value.lineno, value.col_offset) return CallableArgument(typ, name, constructor, e.lineno, e.col_offset) @@ -1441,7 +1442,7 @@ def _extract_argument_name(self, n: ast3.expr) -> Optional[str]: return n.s.strip() elif isinstance(n, NameConstant) and str(n.value) == 'None': return None - self.fail('Expected string literal for argument name, got {}'.format( + self.fail(message_registry.ARG_NAME_EXPECTED_STRING_LITERAL.format( type(n).__name__), self.line, 0) return None diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index bf3c09453ec0..933ccc826c80 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -44,6 +44,7 @@ Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny, Instance, ProperType ) +from mypy.message_registry import ErrorMessage from mypy import message_registry, errorcodes as codes from mypy.errors import Errors from mypy.fastparse import ( @@ -176,9 +177,9 @@ def __init__(self, self.type_ignores: Dict[int, List[str]] = {} - def fail(self, msg: str, line: int, column: int, blocker: bool = True) -> None: + def fail(self, msg: ErrorMessage, line: int, column: int, blocker: bool = True) -> None: if blocker or not self.options.ignore_errors: - self.errors.report(line, column, msg, blocker=blocker, code=codes.SYNTAX) + self.errors.report(line, column, msg.value, blocker=blocker, code=msg.code) def visit(self, node: Optional[AST]) -> Any: # same as in typed_ast stub if node is None: @@ -353,7 +354,7 @@ def visit_Module(self, mod: ast27.Module) -> MypyFile: if parsed is not None: self.type_ignores[ti.lineno] = parsed else: - self.fail(INVALID_TYPE_IGNORE, ti.lineno, -1) + self.fail(message_registry.INVALID_TYPE_IGNORE, ti.lineno, -1) body = self.fix_function_overloads(self.translate_stmt_list(mod.body, module=True)) return MypyFile(body, self.imports, @@ -408,7 +409,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: arg_types.insert(0, AnyType(TypeOfAny.special_form)) except SyntaxError: stripped_type = type_comment.split("#", 2)[0].strip() - err_msg = '{} "{}"'.format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type) + err_msg = message_registry.TYPE_COMMENT_SYNTAX_ERROR_VALUE.format(stripped_type) self.fail(err_msg, lineno, n.col_offset) arg_types = [AnyType(TypeOfAny.from_error)] * len(args) return_type = AnyType(TypeOfAny.from_error) @@ -423,13 +424,12 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: if any(arg_types) or return_type: if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types): - self.fail("Ellipses cannot accompany other argument types " - "in function type signature", lineno, n.col_offset) + self.fail(message_registry.ELLIPSIS_WITH_OTHER_TYPEARGS, lineno, n.col_offset) elif len(arg_types) > len(arg_kinds): - self.fail('Type signature has too many arguments', lineno, n.col_offset, + self.fail(message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS, lineno, n.col_offset, blocker=False) elif len(arg_types) < len(arg_kinds): - self.fail('Type signature has too few arguments', lineno, n.col_offset, + self.fail(message_registry.TYPE_SIGNATURE_TOO_FEW_ARGS, lineno, n.col_offset, blocker=False) else: any_type = AnyType(TypeOfAny.unannotated) @@ -526,7 +526,7 @@ def transform_args(self, arg.pos_only = True # We don't have any context object to give, but we have closed around the line num - def fail_arg(msg: str, arg: None) -> None: + def fail_arg(msg: ErrorMessage, arg: None) -> None: self.fail(msg, line, 0) check_arg_names(names, [None] * len(names), fail_arg) @@ -568,7 +568,7 @@ def get_type(self, tag: Optional[str] = cast(Any, extra_ignore).group(1) ignored = parse_type_ignore_tag(tag) if ignored is None: - self.fail(INVALID_TYPE_IGNORE, converter.line, -1) + self.fail(message_registry.INVALID_TYPE_IGNORE, converter.line, -1) else: self.type_ignores[converter.line] = ignored return typ @@ -705,7 +705,7 @@ def try_handler(self, elif isinstance(item.name, Name): vs.append(self.set_line(NameExpr(item.name.id), item)) else: - self.fail('Sorry, "except , " is not supported', + self.fail(message_registry.EXCEPT_EXPR_NOTNAME_UNSUPPORTED, item.lineno, item.col_offset) vs.append(None) types = [self.visit(h.type) for h in handlers] diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 77dff1154833..6a6e6f955db0 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -113,7 +113,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": "Expected TypedDict key to be string literal" ) MALFORMED_ASSERT: Final = ErrorMessage("Assertion is always true, perhaps remove parentheses?") -DUPLICATE_TYPE_SIGNATURES: Final = "Function has duplicate type signatures" +DUPLICATE_TYPE_SIGNATURES: Final = ErrorMessage("Function has duplicate type signatures") DESCRIPTOR_SET_NOT_CALLABLE: Final = ErrorMessage("{}.__set__ is not callable") DESCRIPTOR_GET_NOT_CALLABLE: Final = "{}.__get__ is not callable" MODULE_LEVEL_GETATTRIBUTE: Final = ErrorMessage( @@ -161,6 +161,43 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": TYPEVAR_BOUND_MUST_BE_TYPE: Final = 'TypeVar "bound" must be a type' TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"' +# FastParse +TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( + 'syntax error in type comment "{}"', codes.SYNTAX +) +INVALID_TYPE_IGNORE: Final = ErrorMessage('Invalid "type: ignore" comment', codes.SYNTAX) +ELLIPSIS_WITH_OTHER_TYPEARGS: Final = ErrorMessage( + "Ellipses cannot accompany other argument types in function type signature", codes.SYNTAX +) +TYPE_SIGNATURE_TOO_MANY_ARGS: Final = ErrorMessage( + "Type signature has too many arguments", codes.SYNTAX +) +TYPE_SIGNATURE_TOO_FEW_ARGS: Final = ErrorMessage( + "Type signature has too few arguments", codes.SYNTAX +) +ARG_CONSTRUCTOR_NAME_EXPECTED: Final = ErrorMessage("Expected arg constructor name", codes.SYNTAX) +ARG_CONSTRUCTOR_TOO_MANY_ARGS: Final = ErrorMessage( + "Too many arguments for argument constructor", codes.SYNTAX +) +MULTIPLE_VALUES_FOR_NAME_KWARG: Final = ErrorMessage( + '"{}" gets multiple values for keyword argument "name"', codes.SYNTAX +) +MULTIPLE_VALUES_FOR_TYPE_KWARG: Final = ErrorMessage( + '"{}" gets multiple values for keyword argument "type"', codes.SYNTAX +) +ARG_CONSTRUCTOR_UNEXPECTED_ARG: Final = ErrorMessage( + 'Unexpected argument "{}" for argument constructor', codes.SYNTAX +) +ARG_NAME_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "Expected string literal for argument name, got {}", codes.SYNTAX +) +EXCEPT_EXPR_NOTNAME_UNSUPPORTED: Final = ErrorMessage( + 'Sorry, "except , " is not supported', codes.SYNTAX +) + +# Nodes +DUPLICATE_ARGUMENT_IN_X: Final = ErrorMessage('Duplicate argument "{}" in {}') + # Super TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"') TOO_FEW_ARGS_FOR_SUPER: Final = ErrorMessage('Too few arguments for "super"', codes.CALL_ARG) diff --git a/mypy/nodes.py b/mypy/nodes.py index 78a018f94a78..f611ab3d38dd 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -12,6 +12,8 @@ from mypy_extensions import trait import mypy.strconv +from mypy import message_registry +from mypy.message_registry import ErrorMessage from mypy.util import short_type from mypy.visitor import NodeVisitor, StatementVisitor, ExpressionVisitor @@ -3408,12 +3410,12 @@ def check_arg_kinds( is_kw_arg = True -def check_arg_names(names: Sequence[Optional[str]], nodes: List[T], fail: Callable[[str, T], None], +def check_arg_names(names: Sequence[Optional[str]], nodes: List[T], fail: Callable[[ErrorMessage, T], None], description: str = 'function definition') -> None: seen_names: Set[Optional[str]] = set() for name, node in zip(names, nodes): if name is not None and name in seen_names: - fail('Duplicate argument "{}" in {}'.format(name, description), node) + fail(message_registry.DUPLICATE_ARGUMENT_IN_X.format(name, description), node) break seen_names.add(name) From 7eeefbeaa3d4f841e3a50423bbc46fc584e4dc3c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 00:52:07 +0530 Subject: [PATCH 02/20] Migrate typeanal.py --- mypy/message_registry.py | 114 ++++++++++++++++++++++++++++-- mypy/nodes.py | 13 ++-- mypy/plugin.py | 2 +- mypy/semanal_shared.py | 5 +- mypy/typeanal.py | 148 ++++++++++++++++++--------------------- 5 files changed, 190 insertions(+), 92 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 6a6e6f955db0..9af740345987 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -21,7 +21,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": # Invalid types -INVALID_TYPE_RAW_ENUM_VALUE: Final = "Invalid type: try using Literal[{}.{}] instead?" +INVALID_TYPE_RAW_ENUM_VALUE: Final = ErrorMessage( + "Invalid type: try using Literal[{}.{}] instead?" +) # Type checker error message constants NO_RETURN_VALUE_EXPECTED: Final = ErrorMessage("No return value expected", codes.RETURN_VALUE) @@ -146,9 +148,11 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": "Access to generic instance variables via class is ambiguous" ) GENERIC_CLASS_VAR_ACCESS: Final = "Access to generic class variables is ambiguous" -BARE_GENERIC: Final = "Missing type parameters for generic type {}" -IMPLICIT_GENERIC_ANY_BUILTIN: Final = ( - 'Implicit generic "Any". Use "{}" and specify generic parameters' +BARE_GENERIC: Final = ErrorMessage( + "Missing type parameters for generic type {}", codes.TYPE_ARG +) +IMPLICIT_GENERIC_ANY_BUILTIN: Final = ErrorMessage( + 'Implicit generic "Any". Use "{}" and specify generic parameters', codes.TYPE_ARG ) # TypeVar @@ -197,6 +201,108 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": # Nodes DUPLICATE_ARGUMENT_IN_X: Final = ErrorMessage('Duplicate argument "{}" in {}') +POS_ARGS_BEFORE_DEFAULT_NAMED_OR_VARARGS: Final = ErrorMessage( + "Required positional args may not appear after default, named or var args" +) +DEFAULT_ARGS_BEFORE_NAMED_OR_VARARGS: Final = ErrorMessage( + "Positional default args may not appear after named or var args" +) +VAR_ARGS_BEFORE_NAMED_OR_VARARGS: Final = ErrorMessage( + "Var args may not appear after named or var args" +) +KWARGS_MUST_BE_LAST: Final = ErrorMessage("A **kwargs argument must be the last argument") +MULTIPLE_KWARGS: Final = ErrorMessage("You may only have one **kwargs argument") + +# Type Analysis +TYPEANAL_INTERNAL_ERROR: Final = ErrorMessage("Internal error (node is None, kind={})") +NOT_SUBSCRIPTABLE: Final = ErrorMessage('"{}" is not subscriptable') +NOT_SUBSCRIPTABLE_REPLACEMENT: Final = ErrorMessage('"{}" is not subscriptable, use "{}" instead') +INVALID_LOCATION_FOR_PARAMSPEC: Final = ErrorMessage( + 'Invalid location for ParamSpec "{}"' +) +UNBOUND_PARAMSPEC: Final = ErrorMessage('ParamSpec "{}" is unbound') +PARAMSPEC_USED_WITH_ARGS: Final = ErrorMessage('ParamSpec "{}" used with arguments') +NO_BOUND_TYPEVAR_GENERIC_ALIAS: Final = ErrorMessage( + 'Can\'t use bound type variable "{}" to define generic alias' +) +TYPEVAR_USED_WITH_ARGS: Final = ErrorMessage('Type variable "{}" used with arguments') +ONLY_OUTERMOST_FINAL: Final = ErrorMessage( + "Final can be only used as an outermost qualifier in a variable annotation" +) +BUILTIN_TUPLE_NOT_DEFINED: Final = ErrorMessage('Name "tuple" is not defined') +SINGLE_TYPE_ARG: Final = ErrorMessage("{} must have exactly one type argument") +INVALID_NESTED_CLASSVAR: Final = ErrorMessage("Invalid type: ClassVar nested inside other type") +CLASSVAR_ATMOST_ONE_TYPE_ARG: Final = ErrorMessage( + "ClassVar[...] must have at most one type argument" +) +ANNOTATED_SINGLE_TYPE_ARG: Final = ErrorMessage( + "Annotated[...] must have exactly one type argument and at least one annotation" +) +REQUIRED_OUTSIDE_TYPEDDICT: Final = ErrorMessage( + "Required[] can be only used in a TypedDict definition" +) +NOTREQUIRED_OUTSIDE_TYPEDDICT: Final = ErrorMessage( + "NotRequired[] can be only used in a TypedDict definition" +) +REQUIRED_SINGLE_TYPE_ARG: Final = ErrorMessage("Required[] must have exactly one type argument") +NOTREQUIRED_SINGLE_TYPE_ARG: Final = ErrorMessage( + "NotRequired[] must have exactly one type argument" +) +GENERIC_TUPLE_UNSUPPORTED: Final = ErrorMessage("Generic tuple types not supported") +GENERIC_TYPED_DICT_UNSUPPORTED: Final = ErrorMessage("Generic TypedDict types not supported") +VARIABLE_NOT_VALID_TYPE: Final = ErrorMessage( + 'Variable "{}" is not valid as a type', codes.VALID_TYPE +) +FUNCTION_NOT_VALID_TYPE: Final = ErrorMessage( + 'Function "{}" is not valid as a type', codes.VALID_TYPE +) +MODULE_NOT_VALID_TYPE: Final = ErrorMessage('Module "{}" is not valid as a type', codes.VALID_TYPE) +UNBOUND_TYPEVAR: Final = ErrorMessage('Type variable "{}" is unbound', codes.VALID_TYPE) +CANNOT_INTERPRET_AS_TYPE: Final = ErrorMessage( + 'Cannot interpret reference "{}" as a type', codes.VALID_TYPE +) +INVALID_TYPE: Final = ErrorMessage("Invalid type") +BRACKETED_EXPR_INVALID_TYPE: Final = ErrorMessage( + 'Bracketed expression "[...]" is not valid as a type' +) +ANNOTATION_SYNTAX_ERROR: Final = ErrorMessage("Syntax error in type annotation", codes.SYNTAX) +TUPLE_SINGLE_STAR_TYPE: Final = ErrorMessage("At most one star type allowed in a tuple") +INVALID_TYPE_USE_LITERAL: Final = ErrorMessage( + "Invalid type: try using Literal[{}] instead?", codes.VALID_TYPE +) +INVALID_LITERAL_TYPE: Final = ErrorMessage( + "Invalid type: {} literals cannot be used as a type", codes.VALID_TYPE +) +INVALID_ANNOTATION: Final = ErrorMessage("Invalid type comment or annotation", codes.VALID_TYPE) +PIPE_UNION_REQUIRES_PY310: Final = ErrorMessage("X | Y syntax for unions requires Python 3.10") +UNEXPECTED_ELLIPSIS: Final = ErrorMessage('Unexpected "..."') +CALLABLE_INVALID_FIRST_ARG: Final = ErrorMessage( + 'The first argument to Callable must be a list of types or "..."' +) +CALLABLE_INVALID_ARGS: Final = ErrorMessage( + 'Please use "Callable[[], ]" or "Callable"' +) +INVALID_ARG_CONSTRUCTOR: Final = ErrorMessage('Invalid argument constructor "{}"') +ARGS_SHOULD_NOT_HAVE_NAMES: Final = ErrorMessage("{} arguments should not have names") +LITERAL_AT_LEAST_ONE_ARG: Final = ErrorMessage("Literal[...] must have at least one parameter") +LITERAL_INDEX_CANNOT_BE_ANY: Final = ErrorMessage( + 'Parameter {} of Literal[...] cannot be of type "Any"' +) +LITERAL_INDEX_INVALID_TYPE: Final = ErrorMessage( + 'Parameter {} of Literal[...] cannot be of type "{}"' +) +LITERAL_INVALID_EXPRESSION: Final = ErrorMessage( + "Invalid type: Literal[...] cannot contain arbitrary expressions" +) +LITERAL_INVALID_PARAMETER: Final = ErrorMessage("Parameter {} of Literal[...] is invalid") +TYPEVAR_BOUND_BY_OUTER_CLASS: Final = ErrorMessage('Type variable "{}" is bound by an outer class') +TYPE_ARG_COUNT_MISMATCH: Final = ErrorMessage('"{}" expects {}, but {} given', codes.TYPE_ARG) +TYPE_ALIAS_ARG_COUNT_MISMATCH: Final = ErrorMessage( + "Bad number of arguments for type alias, expected: {}, given: {}" +) +INVALID_TYPE_ALIAS: Final = ErrorMessage("Invalid type alias: expression is not a valid type") +CANNOT_RESOLVE_TYPE: Final = ErrorMessage('Cannot resolve {} "{}" (possible cyclic definition)') +UNION_SYNTAX_REQUIRES_PY310: Final = ErrorMessage("X | Y syntax for unions requires Python 3.10") # Super TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"') diff --git a/mypy/nodes.py b/mypy/nodes.py index f611ab3d38dd..ac7b28c41e7c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3376,7 +3376,7 @@ def get_member_expr_fullname(expr: MemberExpr) -> Optional[str]: def check_arg_kinds( - arg_kinds: List[ArgKind], nodes: List[T], fail: Callable[[str, T], None]) -> None: + arg_kinds: List[ArgKind], nodes: List[T], fail: Callable[[ErrorMessage, T], None]) -> None: is_var_arg = False is_kw_arg = False seen_named = False @@ -3384,28 +3384,27 @@ def check_arg_kinds( for kind, node in zip(arg_kinds, nodes): if kind == ARG_POS: if is_var_arg or is_kw_arg or seen_named or seen_opt: - fail("Required positional args may not appear " - "after default, named or var args", + fail(message_registry.POS_ARGS_BEFORE_DEFAULT_NAMED_OR_VARARGS, node) break elif kind == ARG_OPT: if is_var_arg or is_kw_arg or seen_named: - fail("Positional default args may not appear after named or var args", node) + fail(message_registry.DEFAULT_ARGS_BEFORE_NAMED_OR_VARARGS, node) break seen_opt = True elif kind == ARG_STAR: if is_var_arg or is_kw_arg or seen_named: - fail("Var args may not appear after named or var args", node) + fail(message_registry.VAR_ARGS_BEFORE_NAMED_OR_VARARGS, node) break is_var_arg = True elif kind == ARG_NAMED or kind == ARG_NAMED_OPT: seen_named = True if is_kw_arg: - fail("A **kwargs argument must be the last argument", node) + fail(message_registry.KWARGS_MUST_BE_LAST, node) break elif kind == ARG_STAR2: if is_kw_arg: - fail("You may only have one **kwargs argument", node) + fail(message_registry.MULTIPLE_KWARGS, node) break is_kw_arg = True diff --git a/mypy/plugin.py b/mypy/plugin.py index dfa446521548..b789e1db055f 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -152,7 +152,7 @@ class TypeAnalyzerPluginInterface: options: Options @abstractmethod - def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: + def fail(self, msg: ErrorMessage, ctx: Context) -> None: """Emit an error message at given location.""" raise NotImplementedError diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 85a6779ac9f3..388ca5006724 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -14,6 +14,7 @@ Type, FunctionLike, Instance, TupleType, TPDICT_FB_NAMES, ProperType, get_proper_type ) from mypy.tvar_scope import TypeVarLikeScope +from mypy.message_registry import ErrorMessage from mypy.errorcodes import ErrorCode from mypy import join @@ -45,8 +46,8 @@ def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode] raise NotImplementedError @abstractmethod - def fail(self, msg: str, ctx: Context, serious: bool = False, *, - blocker: bool = False, code: Optional[ErrorCode] = None) -> None: + def fail(self, msg: ErrorMessage, ctx: Context, serious: bool = False, *, + blocker: bool = False) -> None: raise NotImplementedError @abstractmethod diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2a14162cf558..f7d4437b416d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -33,6 +33,7 @@ from mypy.plugin import Plugin, TypeAnalyzerPluginInterface, AnalyzeTypeContext from mypy.semanal_shared import SemanticAnalyzerCoreInterface from mypy.errorcodes import ErrorCode +from mypy.message_registry import ErrorMessage from mypy import nodes, message_registry, errorcodes as codes T = TypeVar('T') @@ -81,7 +82,7 @@ def analyze_type_alias(node: Expression, try: type = expr_to_unanalyzed_type(node, options, api.is_stub_file) except TypeTranslationError: - api.fail('Invalid type alias: expression is not a valid type', node) + api.fail(message_registry.INVALID_TYPE_ALIAS, node) return None analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, defining_alias=True, @@ -92,14 +93,15 @@ def analyze_type_alias(node: Expression, return res, analyzer.aliases_used -def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str: - msg = '"{}" is not subscriptable'.format(name.split('.')[-1]) +def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> ErrorMessage: # This should never be called if the python_version is 3.9 or newer + variable_name = name.split('.')[-1] nongen_builtins = get_nongen_builtins((3, 8)) replacement = nongen_builtins[name] if replacement and propose_alt: - msg += ', use "{}" instead'.format(replacement) - return msg + return message_registry.NOT_SUBSCRIPTABLE_REPLACEMENT.format(variable_name, replacement) + + return message_registry.NOT_SUBSCRIPTABLE.format(variable_name) class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface): @@ -195,7 +197,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) self.api.record_incomplete_ref() return AnyType(TypeOfAny.special_form) if node is None: - self.fail('Internal error (node is None, kind={})'.format(sym.kind), t) + self.fail(message_registry.TYPEANAL_INTERNAL_ERROR.format(sym.kind), t) return AnyType(TypeOfAny.special_form) fullname = node.fullname hook = self.plugin.get_type_analyze_hook(fullname) @@ -209,24 +211,23 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def = self.tvar_scope.get_binding(sym) if isinstance(sym.node, ParamSpecExpr): if tvar_def is None: - self.fail('ParamSpec "{}" is unbound'.format(t.name), t) + self.fail(message_registry.UNBOUND_PARAMSPEC.format(t.name), t) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, ParamSpecType) if len(t.args) > 0: - self.fail('ParamSpec "{}" used with arguments'.format(t.name), t) + self.fail(message_registry.PARAMSPEC_USED_WITH_ARGS.format(t.name), t) # Change the line number return ParamSpecType( tvar_def.name, tvar_def.fullname, tvar_def.id, tvar_def.flavor, tvar_def.upper_bound, line=t.line, column=t.column, ) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None and self.defining_alias: - self.fail('Can\'t use bound type variable "{}"' - ' to define generic alias'.format(t.name), t) + self.fail(message_registry.NO_BOUND_TYPEVAR_GENERIC_ALIAS.format(t.name), t) return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None: assert isinstance(tvar_def, TypeVarType) if len(t.args) > 0: - self.fail('Type variable "{}" used with arguments'.format(t.name), t) + self.fail(message_registry.TYPEVAR_USED_WITH_ARGS.format(t.name), t) # Change the line number return TypeVarType( tvar_def.name, tvar_def.fullname, tvar_def.id, tvar_def.values, @@ -272,9 +273,7 @@ def cannot_resolve_type(self, t: UnboundType) -> None: # TODO: Move error message generation to messages.py. We'd first # need access to MessageBuilder here. Also move the similar # message generation logic in semanal.py. - self.api.fail( - 'Cannot resolve name "{}" (possible cyclic definition)'.format(t.name), - t) + self.api.fail(message_registry.CANNOT_RESOLVE_TYPE.format('name', t.name), t) def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Optional[Type]: """Bind special type that is recognized through magic name such as 'typing.Any'. @@ -286,8 +285,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt elif fullname == 'typing.Any' or fullname == 'builtins.Any': return AnyType(TypeOfAny.explicit) elif fullname in FINAL_TYPE_NAMES: - self.fail("Final can be only used as an outermost qualifier" - " in a variable annotation", t) + self.fail(message_registry.ONLY_OUTERMOST_FINAL, t) return AnyType(TypeOfAny.from_error) elif (fullname == 'typing.Tuple' or (fullname == 'builtins.tuple' @@ -299,7 +297,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if self.api.is_incomplete_namespace('builtins'): self.api.record_incomplete_ref() else: - self.fail('Name "tuple" is not defined', t) + self.fail(message_registry.BUILTIN_TUPLE_NOT_DEFINED, t) return AnyType(TypeOfAny.special_form) if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' @@ -317,7 +315,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return UnionType.make_union(items) elif fullname == 'typing.Optional': if len(t.args) != 1: - self.fail('Optional[...] must have exactly one type argument', t) + self.fail(message_registry.SINGLE_TYPE_ARG.format('Optional[...]'), t) return AnyType(TypeOfAny.from_error) item = self.anal_type(t.args[0]) return make_optional_type(item) @@ -336,16 +334,16 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return None if len(t.args) != 1: type_str = 'Type[...]' if fullname == 'typing.Type' else 'type[...]' - self.fail(type_str + ' must have exactly one type argument', t) + self.fail(message_registry.SINGLE_TYPE_ARG.format(type_str), t) item = self.anal_type(t.args[0]) return TypeType.make_normalized(item, line=t.line) elif fullname == 'typing.ClassVar': if self.nesting_level > 0: - self.fail('Invalid type: ClassVar nested inside other type', t) + self.fail(message_registry.INVALID_NESTED_CLASSVAR, t) if len(t.args) == 0: return AnyType(TypeOfAny.from_omitted_generics, line=t.line, column=t.column) if len(t.args) != 1: - self.fail('ClassVar[...] must have at most one type argument', t) + self.fail(message_registry.CLASSVAR_ATMOST_ONE_TYPE_ARG, t) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) elif fullname in ('mypy_extensions.NoReturn', 'typing.NoReturn'): @@ -354,24 +352,23 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return self.analyze_literal_type(t) elif fullname in ANNOTATED_TYPE_NAMES: if len(t.args) < 2: - self.fail("Annotated[...] must have exactly one type argument" - " and at least one annotation", t) + self.fail(message_registry.ANNOTATED_SINGLE_TYPE_ARG, t) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) elif fullname in ('typing_extensions.Required', 'typing.Required'): if not self.allow_required: - self.fail("Required[] can be only used in a TypedDict definition", t) + self.fail(message_registry.REQUIRED_OUTSIDE_TYPEDDICT, t) return AnyType(TypeOfAny.from_error) if len(t.args) != 1: - self.fail("Required[] must have exactly one type argument", t) + self.fail(message_registry.REQUIRED_SINGLE_TYPE_ARG, t) return AnyType(TypeOfAny.from_error) return RequiredType(self.anal_type(t.args[0]), required=True) elif fullname in ('typing_extensions.NotRequired', 'typing.NotRequired'): if not self.allow_required: - self.fail("NotRequired[] can be only used in a TypedDict definition", t) + self.fail(message_registry.NOTREQUIRED_OUTSIDE_TYPEDDICT, t) return AnyType(TypeOfAny.from_error) if len(t.args) != 1: - self.fail("NotRequired[] must have exactly one type argument", t) + self.fail(message_registry.NOTREQUIRED_SINGLE_TYPE_ARG, t) return AnyType(TypeOfAny.from_error) return RequiredType(self.anal_type(t.args[0]), required=False) elif self.anal_type_guard_arg(t, fullname) is not None: @@ -413,7 +410,7 @@ def analyze_type_with_type_info( # The class has a Tuple[...] base class so it will be # represented as a tuple type. if args: - self.fail('Generic tuple types not supported', ctx) + self.fail(message_registry.GENERIC_TUPLE_UNSUPPORTED, ctx) return AnyType(TypeOfAny.from_error) return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance) @@ -422,7 +419,7 @@ def analyze_type_with_type_info( # The class has a TypedDict[...] base class so it will be # represented as a typeddict type. if args: - self.fail('Generic TypedDict types not supported', ctx) + self.fail(message_registry.GENERIC_TYPED_DICT_UNSUPPORTED, ctx) return AnyType(TypeOfAny.from_error) # Create a named TypedDictType return td.copy_modified(item_types=self.anal_array(list(td.items.values())), @@ -470,9 +467,8 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl value = sym.node.name base_enum_short_name = sym.node.info.name if not defining_literal: - msg = message_registry.INVALID_TYPE_RAW_ENUM_VALUE.format( - base_enum_short_name, value) - self.fail(msg, t) + self.fail(message_registry.INVALID_TYPE_RAW_ENUM_VALUE.format( + base_enum_short_name, value), t) return AnyType(TypeOfAny.from_error) return LiteralType( value=value, @@ -489,23 +485,23 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl if isinstance(sym.node, Var): notes.append('See https://mypy.readthedocs.io/en/' 'stable/common_issues.html#variables-vs-type-aliases') - message = 'Variable "{}" is not valid as a type' + message = message_registry.VARIABLE_NOT_VALID_TYPE elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)): - message = 'Function "{}" is not valid as a type' + message = message_registry.FUNCTION_NOT_VALID_TYPE notes.append('Perhaps you need "Callable[...]" or a callback protocol?') elif isinstance(sym.node, MypyFile): # TODO: suggest a protocol when supported. - message = 'Module "{}" is not valid as a type' + message = message_registry.MODULE_NOT_VALID_TYPE elif unbound_tvar: - message = 'Type variable "{}" is unbound' + message = message_registry.UNBOUND_TYPEVAR short = name.split('.')[-1] notes.append(('(Hint: Use "Generic[{}]" or "Protocol[{}]" base class' ' to bind "{}" inside a class)').format(short, short, short)) notes.append('(Hint: Use "{}" in function signature to bind "{}"' ' inside a function)'.format(short, short)) else: - message = 'Cannot interpret reference "{}" as a type' - self.fail(message.format(name), t, code=codes.VALID_TYPE) + message = message_registry.CANNOT_INTERPRET_AS_TYPE + self.fail(message.format(name), t) for note in notes: self.note(note, t, code=codes.VALID_TYPE) @@ -532,12 +528,12 @@ def visit_deleted_type(self, t: DeletedType) -> Type: return t def visit_type_list(self, t: TypeList) -> Type: - self.fail('Bracketed expression "[...]" is not valid as a type', t) + self.fail(message_registry.BRACKETED_EXPR_INVALID_TYPE, t) self.note('Did you mean "List[...]"?', t) return AnyType(TypeOfAny.from_error) def visit_callable_argument(self, t: CallableArgument) -> Type: - self.fail('Invalid type', t) + self.fail(message_registry.INVALID_TYPE, t) return AnyType(TypeOfAny.from_error) def visit_instance(self, t: Instance) -> Type: @@ -591,7 +587,7 @@ def anal_type_guard(self, t: Type) -> Optional[Type]: def anal_type_guard_arg(self, t: UnboundType, fullname: str) -> Optional[Type]: if fullname in ('typing_extensions.TypeGuard', 'typing.TypeGuard'): if len(t.args) != 1: - self.fail("TypeGuard must have exactly one type argument", t) + self.fail(message_registry.SINGLE_TYPE_ARG.format('TypeGuard'), t) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) return None @@ -628,7 +624,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: # Types such as (t1, t2, ...) only allowed in assignment statements. They'll # generate errors elsewhere, and Tuple[t1, t2, ...] must be used instead. if t.implicit and not self.allow_tuple_literal: - self.fail('Syntax error in type annotation', t, code=codes.SYNTAX) + self.fail(message_registry.ANNOTATION_SYNTAX_ERROR, t) if len(t.items) == 0: self.note('Suggestion: Use Tuple[()] instead of () for an empty tuple, or ' 'None for a function without a return value', t, code=codes.SYNTAX) @@ -640,7 +636,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: return AnyType(TypeOfAny.from_error) star_count = sum(1 for item in t.items if isinstance(item, StarType)) if star_count > 1: - self.fail('At most one star type allowed in a tuple', t) + self.fail(message_registry.TUPLE_SINGLE_STAR_TYPE, t) if t.implicit: return TupleType([AnyType(TypeOfAny.from_error) for _ in t.items], self.named_type('builtins.tuple'), @@ -675,19 +671,19 @@ def visit_raw_expression_type(self, t: RawExpressionType) -> Type: if t.base_type_name in ('builtins.int', 'builtins.bool'): # The only time it makes sense to use an int or bool is inside of # a literal type. - msg = "Invalid type: try using Literal[{}] instead?".format(repr(t.literal_value)) + msg = message_registry.INVALID_TYPE_USE_LITERAL.format(repr(t.literal_value)) elif t.base_type_name in ('builtins.float', 'builtins.complex'): # We special-case warnings for floats and complex numbers. - msg = "Invalid type: {} literals cannot be used as a type".format(t.simple_name()) + msg = message_registry.INVALID_LITERAL_TYPE.format(t.simple_name()) else: # And in all other cases, we default to a generic error message. # Note: the reason why we use a generic error message for strings # but not ints or bools is because whenever we see an out-of-place # string, it's unclear if the user meant to construct a literal type # or just misspelled a regular type. So we avoid guessing. - msg = 'Invalid type comment or annotation' + msg = message_registry.INVALID_ANNOTATION - self.fail(msg, t, code=codes.VALID_TYPE) + self.fail(msg, t) if t.note is not None: self.note(t.note, t, code=codes.VALID_TYPE) @@ -704,14 +700,14 @@ def visit_union_type(self, t: UnionType) -> Type: and t.is_evaluated is True and not self.always_allow_new_syntax and not self.options.python_version >= (3, 10)): - self.fail("X | Y syntax for unions requires Python 3.10", t) + self.fail(message_registry.UNION_SYNTAX_REQUIRES_PY310, t) return UnionType(self.anal_array(t.items), t.line) def visit_partial_type(self, t: PartialType) -> Type: assert False, "Internal error: Unexpected partial type" def visit_ellipsis_type(self, t: EllipsisType) -> Type: - self.fail('Unexpected "..."', t) + self.fail(message_registry.UNEXPECTED_ELLIPSIS, t) return AnyType(TypeOfAny.from_error) def visit_type_type(self, t: TypeType) -> Type: @@ -792,11 +788,11 @@ def analyze_callable_type(self, t: UnboundType) -> Type: # Callable[?, RET] (where ? is something invalid) # TODO(PEP612): change error to mention paramspec, once we actually have some # support for it - self.fail('The first argument to Callable must be a list of types or "..."', t) + self.fail(message_registry.CALLABLE_INVALID_FIRST_ARG, t) return AnyType(TypeOfAny.from_error) ret = maybe_ret else: - self.fail('Please use "Callable[[], ]" or "Callable"', t) + self.fail(message_registry.CALLABLE_INVALID_ARGS, t) return AnyType(TypeOfAny.from_error) assert isinstance(ret, CallableType) return ret.accept(self) @@ -818,7 +814,7 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], # Looking it up already put an error message in return None elif found.fullname not in ARG_KINDS_BY_CONSTRUCTOR: - self.fail('Invalid argument constructor "{}"'.format( + self.fail(message_registry.INVALID_ARG_CONSTRUCTOR.format( found.fullname), arg) return None else: @@ -826,7 +822,7 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], kind = ARG_KINDS_BY_CONSTRUCTOR[found.fullname] kinds.append(kind) if arg.name is not None and kind.is_star(): - self.fail("{} arguments should not have names".format( + self.fail(message_registry.ARGS_SHOULD_NOT_HAVE_NAMES.format( arg.constructor), arg) return None else: @@ -840,7 +836,7 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], def analyze_literal_type(self, t: UnboundType) -> Type: if len(t.args) == 0: - self.fail('Literal[...] must have at least one parameter', t) + self.fail(message_registry.LITERAL_AT_LEAST_ONE_ARG, t) return AnyType(TypeOfAny.from_error) output: List[Type] = [] @@ -889,16 +885,16 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L # TODO: Once we start adding support for enums, make sure we report a custom # error for case 2 as well. if arg.type_of_any not in (TypeOfAny.from_error, TypeOfAny.special_form): - self.fail('Parameter {} of Literal[...] cannot be of type "Any"'.format(idx), ctx) + self.fail(message_registry.LITERAL_INDEX_CANNOT_BE_ANY.format(idx), ctx) return None elif isinstance(arg, RawExpressionType): # A raw literal. Convert it directly into a literal if we can. if arg.literal_value is None: name = arg.simple_name() if name in ('float', 'complex'): - msg = 'Parameter {} of Literal[...] cannot be of type "{}"'.format(idx, name) + msg = message_registry.LITERAL_INDEX_INVALID_TYPE.format(idx, name) else: - msg = 'Invalid type: Literal[...] cannot contain arbitrary expressions' + msg = message_registry.LITERAL_INVALID_EXPRESSION self.fail(msg, ctx) # Note: we deliberately ignore arg.note here: the extra info might normally be # helpful, but it generally won't make sense in the context of a Literal[...]. @@ -923,14 +919,14 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L out.extend(union_result) return out else: - self.fail('Parameter {} of Literal[...] is invalid'.format(idx), ctx) + self.fail(message_registry.LITERAL_INVALID_PARAMETER.format(idx), ctx) return None def analyze_type(self, t: Type) -> Type: return t.accept(self) - def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: - self.fail_func(msg, ctx, code=code) + def fail(self, msg: ErrorMessage, ctx: Context) -> None: + self.fail_func(msg, ctx) def note(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: self.note_func(msg, ctx, code=code) @@ -985,7 +981,7 @@ def bind_function_type_variables( defs: List[TypeVarLikeType] = [] for name, tvar in typevars: if not self.tvar_scope.allow_binding(tvar.fullname): - self.fail('Type variable "{}" is bound by an outer class'.format(name), defn) + self.fail(message_registry.TYPEVAR_BOUND_BY_OUTER_CLASS.format(name), defn) self.tvar_scope.bind_new(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname) assert binding is not None @@ -1022,7 +1018,7 @@ def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = Fa if (not allow_param_spec and isinstance(analyzed, ParamSpecType) and analyzed.flavor == ParamSpecFlavor.BARE): - self.fail('Invalid location for ParamSpec "{}"'.format(analyzed.name), t) + self.fail(message_registry.INVALID_LOCATION_FOR_PARAMSPEC.format(analyzed.name), t) self.note( 'You can use ParamSpec as the first argument to Callable, e.g., ' "'Callable[{}, int]'".format(analyzed.name), @@ -1076,10 +1072,11 @@ def tuple_type(self, items: List[Type]) -> TupleType: TypeVarLikeList = List[Tuple[str, TypeVarLikeExpr]] # Mypyc doesn't support callback protocols yet. -MsgCallback = Callable[[str, Context, DefaultNamedArg(Optional[ErrorCode], 'code')], None] +MsgCallback = Callable[[ErrorMessage, Context], None] +NoteCallback = Callable[[str, Context, DefaultNamedArg(Optional[ErrorCode], 'code')], None] -def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: MsgCallback, +def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: NoteCallback, orig_type: Type, python_version: Tuple[int, int], fullname: Optional[str] = None, unexpanded_type: Optional[Type] = None) -> AnyType: @@ -1089,16 +1086,12 @@ def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: MsgCallback, typ = orig_type # We use a dedicated error message for builtin generics (as the most common case). alternative = nongen_builtins[fullname] - fail(message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), typ, - code=codes.TYPE_ARG) + fail(message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), typ) else: typ = unexpanded_type or orig_type type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ) - fail( - message_registry.BARE_GENERIC.format(quote_type_string(type_str)), - typ, - code=codes.TYPE_ARG) + fail(message_registry.BARE_GENERIC.format(quote_type_string(type_str)), typ) base_type = get_proper_type(orig_type) base_fullname = ( base_type.type.fullname if isinstance(base_type, Instance) else fullname @@ -1123,7 +1116,7 @@ def get_omitted_any(disallow_any: bool, fail: MsgCallback, note: MsgCallback, return any_type -def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, +def fix_instance(t: Instance, fail: MsgCallback, note: NoteCallback, disallow_any: bool, python_version: Tuple[int, int], use_generic_error: bool = False, unexpanded_type: Optional[Type] = None,) -> None: @@ -1150,8 +1143,8 @@ def fix_instance(t: Instance, fail: MsgCallback, note: MsgCallback, act = str(len(t.args)) if act == '0': act = 'none' - fail('"{}" expects {}, but {} given'.format( - t.type.name, s, act), t, code=codes.TYPE_ARG) + fail(message_registry.TYPE_ARG_COUNT_MISMATCH.format( + t.type.name, s, act), t) # Construct the correct number of type arguments, as # otherwise the type checker may crash as it expects # things to be right. @@ -1195,8 +1188,7 @@ def expand_type_alias(node: TypeAlias, args: List[Type], tp.column = ctx.column return tp if act_len != exp_len: - fail('Bad number of arguments for type alias, expected: %s, given: %s' - % (exp_len, act_len), ctx) + fail(message_registry.TYPE_ALIAS_ARG_COUNT_MISMATCH.format(exp_len, act_len), ctx) return set_any_tvars(node, ctx.line, ctx.column, from_error=True) typ = TypeAliasType(node, args, ctx.line, ctx.column) assert typ.alias is not None @@ -1225,7 +1217,7 @@ def set_any_tvars(node: TypeAlias, type_str = otype.name if isinstance(otype, UnboundType) else format_type_bare(otype) fail(message_registry.BARE_GENERIC.format(quote_type_string(type_str)), - Context(newline, newcolumn), code=codes.TYPE_ARG) + Context(newline, newcolumn)) any_type = AnyType(type_of_any, line=newline, column=newcolumn) return TypeAliasType(node, [any_type] * len(node.alias_tvars), newline, newcolumn) @@ -1390,7 +1382,7 @@ def make_optional_type(t: Type) -> Type: return UnionType([t, NoneType()], t.line, t.column) -def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, +def fix_instance_types(t: Type, fail: MsgCallback, note: NoteCallback, python_version: Tuple[int, int]) -> None: """Recursively fix all instance types (type argument count) in a given type. @@ -1402,7 +1394,7 @@ def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, class InstanceFixer(TypeTraverserVisitor): def __init__( - self, fail: MsgCallback, note: MsgCallback, python_version: Tuple[int, int] + self, fail: MsgCallback, note: NoteCallback, python_version: Tuple[int, int] ) -> None: self.fail = fail self.note = note From 80200246dccdfaf5b49536914e01f2c9f5165008 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 00:53:52 +0530 Subject: [PATCH 03/20] Fix regression --- mypy/fastparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 979db7c10eac..dc8111e82f87 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -1290,7 +1290,7 @@ def visit_Index(self, n: Index) -> Node: def visit_Match(self, n: Any) -> Node: self.errors.report(message="Match statement is not supported", - line=n.lineno, column=n.col_offset, blocker=True) + line=n.lineno, column=n.col_offset, blocker=True, code=codes.SYNTAX) # Just return some valid node return PassStmt() From 211070175609d89cba1f0c7cbbeca74ff767db44 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 00:58:00 +0530 Subject: [PATCH 04/20] Fix lint issues --- mypy/fastparse2.py | 2 +- mypy/nodes.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 933ccc826c80..8d2671a02f33 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -49,7 +49,7 @@ from mypy.errors import Errors from mypy.fastparse import ( TypeConverter, parse_type_comment, parse_type_ignore_tag, - TYPE_IGNORE_PATTERN, INVALID_TYPE_IGNORE + TYPE_IGNORE_PATTERN ) from mypy.options import Options from mypy.util import bytes_to_human_readable_repr diff --git a/mypy/nodes.py b/mypy/nodes.py index ac7b28c41e7c..c5d8161d8664 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3409,8 +3409,9 @@ def check_arg_kinds( is_kw_arg = True -def check_arg_names(names: Sequence[Optional[str]], nodes: List[T], fail: Callable[[ErrorMessage, T], None], - description: str = 'function definition') -> None: +def check_arg_names( + names: Sequence[Optional[str]], nodes: List[T], fail: Callable[[ErrorMessage, T], None], + description: str = 'function definition') -> None: seen_names: Set[Optional[str]] = set() for name, node in zip(names, nodes): if name is not None and name in seen_names: From ecc552ce680a483d881f09ff7235f606969e6cd1 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 16:04:16 +0530 Subject: [PATCH 05/20] Migrate semanal --- mypy/checkmember.py | 2 +- mypy/fastparse.py | 4 - mypy/message_registry.py | 347 +++++++++++++++++++++++++++++++++++-- mypy/plugin.py | 2 +- mypy/semanal.py | 306 +++++++++++++++----------------- mypy/semanal_enum.py | 39 +++-- mypy/semanal_namedtuple.py | 68 ++++---- mypy/semanal_newtype.py | 25 +-- mypy/semanal_typeddict.py | 131 ++++---------- 9 files changed, 581 insertions(+), 343 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 1c66320bb562..29601840647f 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -714,7 +714,7 @@ def analyze_class_attribute_access(itype: Instance, if is_method: mx.msg.cant_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): - mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context) + mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE.value, mx.context) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. diff --git a/mypy/fastparse.py b/mypy/fastparse.py index dc8111e82f87..b96ff9554c3d 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -129,10 +129,6 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str, MISSING_FALLBACK: Final = FakeInfo("fallback can't be filled out until semanal") _dummy_fallback: Final = Instance(MISSING_FALLBACK, [], -1) -TYPE_COMMENT_SYNTAX_ERROR: Final = "syntax error in type comment" - -INVALID_TYPE_IGNORE: Final = 'Invalid "type: ignore" comment' - TYPE_IGNORE_PATTERN: Final = re.compile(r'[^#]*#\s*type:\s*ignore\s*(.*)') diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 9af740345987..308f7e12e411 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -76,7 +76,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": INVALID_NEW_TYPE: Final = ErrorMessage('Incompatible return type for "__new__"') BAD_CONSTRUCTOR_TYPE: Final = ErrorMessage("Unsupported decorated constructor type") CANNOT_ASSIGN_TO_METHOD: Final = "Cannot assign to a method" -CANNOT_ASSIGN_TO_TYPE: Final = "Cannot assign to a type" +CANNOT_ASSIGN_TO_TYPE: Final = ErrorMessage("Cannot assign to a type") INCONSISTENT_ABSTRACT_OVERLOAD: Final = ErrorMessage( "Overloaded method has both abstract and non-abstract variants" ) @@ -148,9 +148,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": "Access to generic instance variables via class is ambiguous" ) GENERIC_CLASS_VAR_ACCESS: Final = "Access to generic class variables is ambiguous" -BARE_GENERIC: Final = ErrorMessage( - "Missing type parameters for generic type {}", codes.TYPE_ARG -) +BARE_GENERIC: Final = ErrorMessage("Missing type parameters for generic type {}", codes.TYPE_ARG) IMPLICIT_GENERIC_ANY_BUILTIN: Final = ErrorMessage( 'Implicit generic "Any". Use "{}" and specify generic parameters', codes.TYPE_ARG ) @@ -161,9 +159,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()"' +TYPEVAR_VARIANCE_DEF: Final = ErrorMessage('TypeVar "{}" may only be a literal bool') +TYPEVAR_BOUND_MUST_BE_TYPE: Final = ErrorMessage('TypeVar "bound" must be a type') +TYPEVAR_UNEXPECTED_ARGUMENT: Final = ErrorMessage('Unexpected argument to "TypeVar()"') # FastParse TYPE_COMMENT_SYNTAX_ERROR_VALUE: Final = ErrorMessage( @@ -213,13 +211,336 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": KWARGS_MUST_BE_LAST: Final = ErrorMessage("A **kwargs argument must be the last argument") MULTIPLE_KWARGS: Final = ErrorMessage("You may only have one **kwargs argument") +# Semantic Analysis +METHOD_ATLEAST_ONE_ARG: Final = ErrorMessage('Method must have at least one argument') +OVERLOAD_IMPLEMENTATION_IN_STUB: Final = ErrorMessage( + "An implementation for an overloaded function is not allowed in a stub file" +) +OVERLOAD_IMPLEMENTATION_LAST: Final = ErrorMessage( + "The implementation for an overloaded function must come last" +) +OVERLOAD_IMPLEMENTATION_REQUIRED: Final = ErrorMessage( + "An overloaded function outside a stub file must have an implementation" +) +FINAL_DEC_ON_OVERLOAD_ONLY: Final = ErrorMessage( + "@final should be applied only to overload implementation" +) +FINAL_DEC_STUB_FIRST_OVERLOAD: Final = ErrorMessage( + "In a stub file @final must be applied only to the first overload" +) +DECORATED_PROPERTY_UNSUPPORTED: Final = ErrorMessage("Decorated property not supported") +UNEXPECTED_PROPERTY_DEFN: Final = ErrorMessage('Unexpected definition for property "{}"') +TOO_MANY_ARGS: Final = ErrorMessage('Too many arguments') +FINAL_DEC_WITH_METHODS_ONLY: Final = ErrorMessage( + "@final cannot be used with non-method functions" +) +DECORATOR_USED_WITH_NON_METHOD: Final = ErrorMessage('"{}" used with a non-method') +CANNOT_USE_FINAL_DEC_WITH_TYPEDDICT: Final = ErrorMessage("@final cannot be used with TypedDict") +RUNTIME_CHECKABLE_WITH_NON_PROPERTY: Final = ErrorMessage( + '@runtime_checkable can only be used with protocol classes' +) +BASES_MUST_HAVE_SINGLE_GENERIC_OR_PROTOCOL: Final = ErrorMessage( + 'Only single Generic[...] or Protocol[...] can be in bases' +) +DUPLICATE_TYPEVARS_IN_GENERIC_OR_PROTOCOL: Final = ErrorMessage( + "Duplicate type variables in Generic[...] or Protocol[...]" +) +GENERIC_PROTOCOL_NOT_ALL_TYPEVARS: Final = ErrorMessage( + "If Generic[...] or Protocol[...] is present it should list all type variables" +) +FREE_TYPEVAR_EXPECTED: Final = ErrorMessage('Free type variable expected in {}[...]') +UNSUPPORTED_DYNAMIC_BASE_CLASS: Final = ErrorMessage('Unsupported dynamic base class{}') +INVALID_BASE_CLASS: Final = ErrorMessage('Invalid base class{}') +CANNOT_SUBCLASS_NEWTYPE: Final = ErrorMessage('Cannot subclass "NewType"') +CANNOT_SUBCLASS_ANY_NAMED: Final = ErrorMessage('Class cannot subclass "{}" (has type "Any")') +CANNOT_SUBCLASS_ANY: Final = ErrorMessage('Class cannot subclass value of type "Any"') +INCOMPATIBLE_BASES: Final = ErrorMessage("Class has two incompatible bases derived from tuple") +CANNOT_DETERMINE_MRO: Final = ErrorMessage( + 'Cannot determine consistent method resolution order (MRO) for "{}"' +) +INNER_METACLASS_UNSUPPORTED: Final = ErrorMessage( + "Metaclasses defined as inner classes are not supported" +) +MULTIPLE_METACLASSES: Final = ErrorMessage("Multiple metaclass definitions") +INHERITANCE_CYCLE: Final = ErrorMessage('Cycle in inheritance hierarchy') +NAMED_INVALID_BASE_CLASS: Final = ErrorMessage('"{}" is not a valid base class') +DUPLICATE_BASE_CLASS: Final = ErrorMessage('Duplicate base class "{}"') +UNSUPPORTED_NAMED_DYNAMIC_BASE_CLASS: Final = ErrorMessage( + 'Dynamic metaclass not supported for "{}"' +) +INVALID_METACLASS: Final = ErrorMessage('Invalid metaclass "{}"') +METACLASS_MUST_INHERIT_TYPE: Final = ErrorMessage( + 'Metaclasses not inheriting from "type" are not supported' +) +INCONSISTENT_METACLAS_STRUCTURE: Final = ErrorMessage('Inconsistent metaclass structure for "{}"') +NO_GENERIC_ENUM: Final = ErrorMessage("Enum class cannot be generic") +MODULE_MISSING_ATTIRBUTE: Final = ErrorMessage( + 'Module "{}" has no attribute "{}"{}', codes.ATTR_DEFINED +) +NO_IMPLICIT_REEXPORT: Final = ErrorMessage( + 'Module "{}" does not explicitly export attribute "{}"; implicit reexport disabled' +) +INCORRECT_RELATIVE_IMPORT: Final = ErrorMessage("Relative import climbs too many namespaces") +INVALID_TYPE_ALIAS_TARGET: Final = ErrorMessage( + 'Type variable "{}" is invalid as target for type alias' +) +NAMEDTUPLE_ATTRIBUTE_UNSUPPORTED: Final = ErrorMessage( + "NamedTuple type as an attribute is not supported" +) +NAMEDTUPLE_INCORRECT_FIRST_ARG: Final = ErrorMessage( + 'First argument to namedtuple() should be "{}", not "{}"', codes.NAME_MATCH +) +TYPEDDICT_ATTRIBUTE_UNSUPPORTED: Final = ErrorMessage( + "TypedDict type as attribute is not supported" +) +FINAL_ATMOST_ONE_ARG: Final = ErrorMessage("Final[...] takes at most one type argument") +FINAL_INITIALIZER_REQUIRED: Final = ErrorMessage( + "Type in Final[...] can only be omitted if there is an initializer" +) +FINAL_CLASSVAR_DISALLOWED: Final = ErrorMessage( + "Variable should not be annotated with both ClassVar and Final" +) +INVALID_FINAL: Final = ErrorMessage("Invalid final declaration") +FINAL_IN_LOOP_DISALLOWED: Final = ErrorMessage("Cannot use Final inside a loop") +FINAL_ONLY_ON_SELF_MEMBER: Final = ErrorMessage( + "Final can be only applied to a name or an attribute on self" +) +FINAL_ONLY_IN_CLASS_BODY_OR_INIT: Final = ErrorMessage( + "Can only declare a final attribute in class body or __init__" +) +PROTOCOL_MEMBERS_MUST_BE_TYPED: Final = ErrorMessage( + 'All protocol members must have explicitly declared types' +) +MULTIPLE_TYPES_WITHOUT_EXPLICIT_TYPE: Final = ErrorMessage( + 'Cannot assign multiple types to name "{}" without an explicit "Type[...]" annotation' +) +TYPE_DECLARATION_IN_ASSIGNMENT: Final = ErrorMessage( + 'Type cannot be declared in assignment to non-self attribute' +) +UNEXPECTED_TYPE_DECLARATION: Final = ErrorMessage('Unexpected type declaration') +STAR_ASSIGNMENT_TARGET_LIST_OR_TUPLE: Final = ErrorMessage( + 'Starred assignment target must be in a list or tuple' +) +INVALID_ASSIGNMENT_TARGET: Final = ErrorMessage('Invalid assignment target') +REDEFINE_AS_FINAL: Final = ErrorMessage("Cannot redefine an existing name as final") +TWO_STAR_EXPRESSIONS_IN_ASSIGNMENT: Final = ErrorMessage('Two starred expressions in assignment') +PROTOCOL_ASSIGNMENT_TO_SELF: Final = ErrorMessage( + "Protocol members cannot be defined via assignment to self" +) +STARTYPE_ONLY_FOR_STAR_EXPRESSIONS: Final = ErrorMessage( + 'Star type only allowed for starred expressions' +) +INCOMPATIBLE_TUPLE_ITEM_COUNT: Final = ErrorMessage('Incompatible number of tuple items') +TUPLE_TYPE_EXPECTED: Final = ErrorMessage('Tuple type expected for multiple variables') +CANNOT_DECLARE_TYPE_OF_TYPEVAR: Final = ErrorMessage("Cannot declare the type of a type variable") +REDEFINE_AS_TYPEVAR: Final = ErrorMessage('Cannot redefine "{}" as a type variable') +TYPEVAR_CALL_TOO_FEW_ARGS: Final = ErrorMessage("Too few arguments for {}()") +TYPEVAR_CALL_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "{}() expects a string literal as first argument" +) +TYPEVAR_NAME_ARG_MISMATCH: Final = ErrorMessage( + 'String argument 1 "{}" to {}(...) does not match variable name "{}"' +) +TYPEVAR_UNEXPECTED_ARG: Final = ErrorMessage("Unexpected argument to TypeVar()") +TYPEVAR_UNEXPECTED_ARG_NAMED: Final = ErrorMessage('Unexpected argument to TypeVar(): "{}"') +TYPEVAR_VALUE_WITH_BOUND_DISALLOWED: Final = ErrorMessage( + "TypeVar cannot have both values and an upper bound" +) +TYPEVAR_VALUES_ARG_UNSUPPORTED: Final = ErrorMessage('TypeVar "values" argument not supported') +USE_NEW_TYPEVAR_SYNTAX: Final = ErrorMessage( + "Use TypeVar('T', t, ...) instead of TypeVar('T', values=(t, ...))" +) +TYPEVAR_COVARIANT_AND_CONTRAVARIANT: Final = ErrorMessage( + "TypeVar cannot be both covariant and contravariant" +) +TYPEVAR_SINGLE_CONSTRAINT: Final = ErrorMessage("TypeVar cannot have only a single constraint") +CANNOT_DECLARE_TYPE_OF_PARAMSPEC: Final = ErrorMessage( + "Cannot declare the type of a parameter specification" +) +TYPE_EXPECTED: Final = ErrorMessage('Type expected') +CLASSVAR_OUTSIDE_CLASS_BODY: Final = ErrorMessage( + 'ClassVar can only be used for assignments in class body' +) +MULTIPLE_MODULE_ASSIGNMENT: Final = ErrorMessage( + 'Cannot assign multiple modules to name "{}" without explicit "types.ModuleType" annotation' +) +DELETABLE_MUST_BE_WITH_LIST_OR_TUPLE: Final = ErrorMessage( + '"__deletable__" must be initialized with a list or tuple expression' +) +DELETABLE_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + 'Invalid "__deletable__" item; string literal expected' +) +RETURN_OUTSIDE_FUNCTION: Final = ErrorMessage('"return" outside function') +BREAK_OUTSIDE_LOOP: Final = ErrorMessage('"break" outside loop') +CONTINUE_OUTSIDE_LOOP: Final = ErrorMessage('"continue" outside loop') +WITH_HAS_NO_TARGETS: Final = ErrorMessage('Invalid type comment: "with" statement has no targets') +WITH_INCOMPATIBLE_TARGET_COUNT: Final = ErrorMessage( + 'Incompatible number of types for "with" targets' +) +WITH_MULTIPLE_TYPES_EXPECTED: Final = ErrorMessage( + 'Multiple types expected for multiple "with" targets' +) +INVALID_DELETE_TARGET: Final = ErrorMessage('Invalid delete target') +NAME_IS_NONLOCAL_AND_GLOBAL: Final = ErrorMessage('Name "{}" is nonlocal and global') +NONLOCAL_AT_MODULE_LEVEL: Final = ErrorMessage("nonlocal declaration not allowed at module level") +NONLOCAL_NO_BINDING_FOUND: Final = ErrorMessage('No binding for nonlocal "{}" found') +LOCAL_DEFINITION_BEFORE_NONLOCAL: Final = ErrorMessage( + 'Name "{}" is already defined in local scope before nonlocal declaration' +) +NAME_ONLY_VALID_IN_TYPE_CONTEXT: Final = ErrorMessage( + '"{}" is a type variable and only valid in type context' +) +SUPER_OUTSIDE_CLASS: Final = ErrorMessage('"super" used outside class') +INVALID_STAR_EXPRESSION: Final = ErrorMessage( + 'Can use starred expression only as assignment target' +) +YIELD_OUTSIDE_FUNC: Final = ErrorMessage('"yield" outside function') +YIELD_FROM_OUTSIDE_FUNC: Final = ErrorMessage('"yield from" outside function') +YIELD_IN_ASYNC_FUNC: Final = ErrorMessage('"yield" in async function') +YIELD_FROM_IN_ASYNC_FUNC: Final = ErrorMessage('"yield from" in async function') +CAST_TARGET_IS_NOT_TYPE: Final = ErrorMessage('Cast target is not a type') +ANY_CALL_UNSUPPORTED: Final = ErrorMessage( + 'Any(...) is no longer supported. Use cast(Any, ...) instead' +) +PROMOTE_ARG_EXPECTED_TYPE: Final = ErrorMessage('Argument 1 to _promote is not a type') +ARG_COUNT_MISMATCH: Final = ErrorMessage('"{}" expects {} argument{}') +POS_ARG_COUNT_MISMATCH: Final = ErrorMessage('"{}" must be called with {} positional argument{}') +TYPE_EXPECTED_IN_BRACKETS: Final = ErrorMessage('Type expected within [...]') +AWAIT_OUTSIDE_FUNC: Final = ErrorMessage('"await" outside function') +AWAIT_OUTSIDE_COROUTINE: Final = ErrorMessage('"await" outside coroutine ("async def")') +CANNOT_RESOLVE_NAME: Final = ErrorMessage('Cannot resolve {} "{}" (possible cyclic definition)') +NAME_NOT_DEFINED: Final = ErrorMessage('Name "{}" is not defined', codes.NAME_DEFINED) +NAME_ALREADY_DEFINED: Final = ErrorMessage('{} "{}" already defined{}', codes.NO_REDEF) + + +# Semantic Analysis: Enum +ENUM_ATTRIBUTE_UNSUPPORTED: Final = ErrorMessage("Enum type as attribute is not supported") +ENUM_CALL_UNEXPECTED_ARGS: Final = ErrorMessage("Unexpected arguments to {}()") +ENUM_CALL_UNEXPECTED_KWARG: Final = ErrorMessage('Unexpected keyword argument "{}"') +ENUM_CALL_TOO_MANY_ARGS: Final = ErrorMessage("Too many arguments for {}()") +ENUM_CALL_TOO_FEW_ARGS: Final = ErrorMessage("Too few arguments for {}()") +ENUM_CALL_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "{}() expects a string literal as the first argument" +) +ENUM_CALL_EXPECTED_STRINGS_OR_PAIRS: Final = ErrorMessage( + "{}() with tuple or list expects strings or (name, value) pairs" +) +ENUM_CALL_DICT_EXPECTED_STRING_KEYS: Final = ErrorMessage( + "{}() with dict literal requires string literals" +) +ENUM_CALL_EXPECTED_LITERAL: Final = ErrorMessage( + "{}() expects a string, tuple, list or dict literal as the second argument" +) +ENUM_CALL_ATLEAST_ONE_ITEM: Final = ErrorMessage("{}() needs at least one item") +ENUM_REUSED_MEMBER_IN_DEFN: Final = ErrorMessage( + 'Attempted to reuse member name "{}" in Enum definition "{}"' +) + +# Semantic Analysis: NamedTuple +NAMEDTUPLE_SUPPORTED_ABOVE_PY36: Final = ErrorMessage( + "NamedTuple class syntax is only supported in Python 3.6" +) +NAMEDTUPLE_SINGLE_BASE: Final = ErrorMessage("NamedTuple should be a single base") +NAMEDTUPLE_CLASS_ERROR: Final = ErrorMessage( + "Invalid statement in NamedTuple definition; " 'expected "field_name: field_type [= default]"' +) +NAMEDTUPLE_FIELD_NO_UNDERSCORE: Final = ErrorMessage( + "NamedTuple field name cannot start with an underscore: {}" +) +NAMEDTUPLE_FIELD_DEFAULT_AFTER_NONDEFAULT: Final = ErrorMessage( + "Non-default NamedTuple fields cannot follow default fields" +) +NAMEDTUPLE_TOO_FEW_ARGS: Final = ErrorMessage('Too few arguments for "{}()"') +NAMEDTUPLE_TOO_MANY_ARGS: Final = ErrorMessage('Too many arguments for "{}()"') +NAMEDTUPLE_EXPECTED_LIST_TUPLE_DEFAULTS: Final = ErrorMessage( + "List or tuple literal expected as the defaults argument to {}()" +) +NAMEDTUPLE_UNEXPECTED_ARGS: Final = ErrorMessage('Unexpected arguments to "{}()"') +NAMEDTUPLE_ARG_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + '"{}()" expects a string literal as the first argument' +) +NAMEDTUPLE_ARG_EXPECTED_LIST_TUPLE: Final = ErrorMessage( + "List or tuple literal expected as the second argument to namedtuple()" +) +NAMEDTUPLE_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "String literal expected as namedtuple() item" +) +NAMEDTUPLE_FIELDS_NO_UNDERSCORE: Final = ErrorMessage( + '"{}()" field names cannot start with an underscore: {}' +) +NAMEDTUPLE_TOO_MANY_DEFAULTS: Final = ErrorMessage('Too many defaults given in call to "{}()"') +NAMEDTUPLE_INVALID_FIELD_DEFINITION: Final = ErrorMessage("Invalid NamedTuple field definition") +NAMEDTUPLE_INVALID_FIELD_NAME: Final = ErrorMessage("Invalid NamedTuple() field name") +NAMEDTUPLE_INVALID_FIELD_TYPE: Final = ErrorMessage("Invalid field type") +NAMEDTUPLE_TUPLE_EXPECTED: Final = ErrorMessage("Tuple expected as NamedTuple() field") +NAMEDTUPLE_CANNOT_OVERWRITE_ATTRIBUTE: Final = ErrorMessage( + 'Cannot overwrite NamedTuple attribute "{}"' +) + +# Semantic Analysis: NewType +NEWTYPE_USED_WITH_PROTOCOL: Final = ErrorMessage("NewType cannot be used with protocol classes") +NEWTYPE_ARG_MUST_BE_SUBCLASSABLE: Final = ErrorMessage( + "Argument 2 to NewType(...) must be subclassable (got {})", codes.VALID_NEWTYPE +) +CANNOT_DECLARE_TYPE_OF_NEWTYPE: Final = ErrorMessage( + "Cannot declare the type of a NewType declaration" +) +CANNOT_REDEFINE_AS_NEWTYPE: Final = ErrorMessage('Cannot redefine "{}" as a NewType') +NEWTYPE_EXPECTS_TWO_ARGS: Final = ErrorMessage( + "NewType(...) expects exactly two positional arguments" +) +NEWTYPE_ARG_STRING_LITERAL: Final = ErrorMessage( + "Argument 1 to NewType(...) must be a string literal" +) +NEWTYPE_ARG_VARNAME_MISMATCH: Final = ErrorMessage( + 'String argument 1 "{}" to NewType(...) does not match variable name "{}"' +) +NEWTYPE_ARG_INVALID_TYPE: Final = ErrorMessage("Argument 2 to NewType(...) must be a valid type") + +# Semantic Analysis: TypedDict +TYPEDDICT_BASES_MUST_BE_TYPEDDICTS: Final = ErrorMessage( + "All bases of a new TypedDict must be TypedDict types" +) +TYPEDDICT_OVERWRITE_FIELD_IN_MERGE: Final = ErrorMessage( + 'Overwriting TypedDict field "{}" while merging' +) +TYPEDDICT_OVERWRITE_FIELD_IN_EXTEND: Final = ErrorMessage( + 'Overwriting TypedDict field "{}" while extending' +) +TYPEDDICT_CLASS_ERROR: Final = ErrorMessage( + "Invalid statement in TypedDict definition; " 'expected "field_name: field_type"' +) +TYPEDDICT_ARG_NAME_MISMATCH: Final = ErrorMessage( + 'First argument "{}" to TypedDict() does not match variable name "{}"', codes.NAME_MATCH +) +TYPEDDICT_TOO_FEW_ARGS: Final = ErrorMessage("Too few arguments for TypedDict()") +TYPEDDICT_TOO_MANY_ARGS: Final = ErrorMessage("Too many arguments for TypedDict()") +TYPEDDICT_UNEXPECTED_ARGS: Final = ErrorMessage("Unexpected arguments to TypedDict()") +TYPEDDICT_CALL_UNEXPECTED_KWARG: Final = ErrorMessage( + 'Unexpected keyword argument "{}" for "TypedDict"' +) +TYPEDDICT_CALL_EXPECTED_STRING_LITERAL: Final = ErrorMessage( + "TypedDict() expects a string literal as the first argument" +) +TYPEDDICT_CALL_EXPECTED_DICT: Final = ErrorMessage( + "TypedDict() expects a dictionary literal as the second argument" +) +TYPEDDICT_RHS_VALUE_UNSUPPORTED: Final = ErrorMessage( + "Right hand side values are not supported in TypedDict" +) +TYPEDDICT_TOTAL_MUST_BE_BOOL: Final = ErrorMessage( + 'TypedDict() "total" argument must be True or False' +) +TYPEDDICT_TOTAL_MUST_BE_BOOL_2: Final = ErrorMessage('Value of "total" must be True or False') +TYPEDDICT_DUPLICATE_KEY: Final = ErrorMessage('Duplicate TypedDict key "{}"') +TYPEDDICT_INVALID_FIELD_NAME: Final = ErrorMessage("Invalid TypedDict() field name") +TYPEDDICT_INVALID_FIELD_TYPE: Final = ErrorMessage("Invalid field type") + # Type Analysis TYPEANAL_INTERNAL_ERROR: Final = ErrorMessage("Internal error (node is None, kind={})") NOT_SUBSCRIPTABLE: Final = ErrorMessage('"{}" is not subscriptable') NOT_SUBSCRIPTABLE_REPLACEMENT: Final = ErrorMessage('"{}" is not subscriptable, use "{}" instead') -INVALID_LOCATION_FOR_PARAMSPEC: Final = ErrorMessage( - 'Invalid location for ParamSpec "{}"' -) +INVALID_LOCATION_FOR_PARAMSPEC: Final = ErrorMessage('Invalid location for ParamSpec "{}"') UNBOUND_PARAMSPEC: Final = ErrorMessage('ParamSpec "{}" is unbound') PARAMSPEC_USED_WITH_ARGS: Final = ErrorMessage('ParamSpec "{}" used with arguments') NO_BOUND_TYPEVAR_GENERIC_ALIAS: Final = ErrorMessage( @@ -355,10 +676,8 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": 'Cannot override class variable (previously declared on base class "{}") with instance ' "variable" ) -CLASS_VAR_WITH_TYPEVARS: Final = 'ClassVar cannot contain type variables' -CLASS_VAR_OUTSIDE_OF_CLASS: Final = ( - 'ClassVar can only be used for assignments in class body' -) +CLASS_VAR_WITH_TYPEVARS: Final = ErrorMessage('ClassVar cannot contain type variables') +CLASS_VAR_OUTSIDE_OF_CLASS: Final = 'ClassVar can only be used for assignments in class body' # Protocol RUNTIME_PROTOCOL_EXPECTED: Final = ErrorMessage( diff --git a/mypy/plugin.py b/mypy/plugin.py index b789e1db055f..9f74cb353bbb 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -284,7 +284,7 @@ def parse_bool(self, expr: Expression) -> Optional[bool]: raise NotImplementedError @abstractmethod - def fail(self, msg: str, ctx: Context, serious: bool = False, *, + def fail(self, msg: Union[str, ErrorMessage], ctx: Context, serious: bool = False, *, blocker: bool = False, code: Optional[ErrorCode] = None) -> None: """Emit an error message at given location.""" raise NotImplementedError diff --git a/mypy/semanal.py b/mypy/semanal.py index 22d6b2e571ba..6ee98ffba161 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -87,6 +87,7 @@ best_matches, MessageBuilder, pretty_seq, SUGGESTED_TEST_FIXTURES, TYPES_FOR_UNIMPORTED_HINTS ) from mypy.errorcodes import ErrorCode +from mypy.message_registry import ErrorMessage from mypy import message_registry, errorcodes as codes from mypy.types import ( FunctionLike, UnboundType, TypeVarType, TupleType, UnionType, StarType, @@ -688,7 +689,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: if func.name in ['__init_subclass__', '__class_getitem__']: func.is_class = True if not func.arguments: - self.fail('Method must have at least one argument', func) + self.fail(message_registry.METHOD_ATLEAST_ONE_ARG, func) elif isinstance(functype, CallableType): self_type = get_proper_type(functype.arg_types[0]) if isinstance(self_type, AnyType): @@ -853,11 +854,9 @@ def handle_missing_overload_decorators(self, # Some of them were overloads, but not all. for idx in non_overload_indexes: if self.is_stub_file: - self.fail("An implementation for an overloaded function " - "is not allowed in a stub file", defn.items[idx]) + self.fail(message_registry.OVERLOAD_IMPLEMENTATION_IN_STUB, defn.items[idx]) else: - self.fail("The implementation for an overloaded function " - "must come last", defn.items[idx]) + self.fail(message_registry.OVERLOAD_IMPLEMENTATION_LAST, defn.items[idx]) else: for idx in non_overload_indexes[1:]: self.name_already_defined(defn.name, defn.items[idx], defn.items[0]) @@ -878,9 +877,7 @@ def handle_missing_overload_implementation(self, defn: OverloadedFuncDef) -> Non else: item.is_abstract = True else: - self.fail( - "An overloaded function outside a stub file must have an implementation", - defn, code=codes.NO_OVERLOAD_IMPL) + self.fail(message_registry.OVERLOAD_IMPLEMENTATION_REQUIRED, defn) def process_final_in_overload(self, defn: OverloadedFuncDef) -> None: """Detect the @final status of an overloaded function (and perform checks).""" @@ -892,12 +889,10 @@ def process_final_in_overload(self, defn: OverloadedFuncDef) -> None: # Only show the error once per overload bad_final = next(ov for ov in defn.items if ov.is_final) if not self.is_stub_file: - self.fail("@final should be applied only to overload implementation", - bad_final) + self.fail(message_registry.FINAL_DEC_ON_OVERLOAD_ONLY, bad_final) elif any(item.is_final for item in defn.items[1:]): bad_final = next(ov for ov in defn.items[1:] if ov.is_final) - self.fail("In a stub file @final must be applied only to the first overload", - bad_final) + self.fail(message_registry.FINAL_DEC_STUB_FIRST_OVERLOAD, bad_final) if defn.impl is not None and defn.impl.is_final: defn.is_final = True @@ -952,10 +947,10 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - # Get abstractness from the original definition. item.func.is_abstract = first_item.func.is_abstract else: - self.fail("Decorated property not supported", item) + self.fail(message_registry.DECORATED_PROPERTY_UNSUPPORTED, item) item.func.accept(self) else: - self.fail('Unexpected definition for property "{}"'.format(first_item.func.name), + self.fail(message_registry.UNEXPECTED_PROPERTY_DEFN.format(first_item.func.name), item) deleted_items.append(i + 1) for i in reversed(deleted_items): @@ -1013,13 +1008,13 @@ def check_function_signature(self, fdef: FuncItem) -> None: sig = fdef.type assert isinstance(sig, CallableType) if len(sig.arg_types) < len(fdef.arguments): - self.fail('Type signature has too few arguments', fdef) + self.fail(message_registry.TYPE_SIGNATURE_TOO_FEW_ARGS, fdef) # Add dummy Any arguments to prevent crashes later. num_extra_anys = len(fdef.arguments) - len(sig.arg_types) extra_anys = [AnyType(TypeOfAny.from_error)] * num_extra_anys sig.arg_types.extend(extra_anys) elif len(sig.arg_types) > len(fdef.arguments): - self.fail('Type signature has too many arguments', fdef, blocker=True) + self.fail(message_registry.TYPE_SIGNATURE_TOO_MANY_ARGS, fdef, blocker=True) def visit_decorator(self, dec: Decorator) -> None: self.statement = dec @@ -1065,7 +1060,7 @@ def visit_decorator(self, dec: Decorator) -> None: dec.var.is_settable_property = True self.check_decorated_function_is_method('property', dec) if len(dec.func.arguments) > 1: - self.fail('Too many arguments', dec.func) + self.fail(message_registry.TOO_MANY_ARGS, dec.func) elif refers_to_fullname(d, 'typing.no_type_check'): dec.var.type = AnyType(TypeOfAny.special_form) no_type_check = True @@ -1079,7 +1074,7 @@ def visit_decorator(self, dec: Decorator) -> None: dec.var.is_final = True removed.append(i) else: - self.fail("@final cannot be used with non-method functions", d) + self.fail(message_registry.FINAL_DEC_WITH_METHODS_ONLY, d) for i in reversed(removed): del dec.decorators[i] if (not dec.is_overload or dec.var.is_property) and self.type: @@ -1088,12 +1083,12 @@ def visit_decorator(self, dec: Decorator) -> None: if not no_type_check and self.recurse_into_functions: dec.func.accept(self) if dec.decorators and dec.var.is_property: - self.fail('Decorated property not supported', dec) + self.fail(message_registry.DECORATED_PROPERTY_UNSUPPORTED, dec) def check_decorated_function_is_method(self, decorator: str, context: Context) -> None: if not self.type or self.is_func_scope(): - self.fail('"%s" used with a non-method' % decorator, context) + self.fail(message_registry.DECORATOR_USED_WITH_NON_METHOD.format(decorator), context) # # Classes @@ -1156,7 +1151,7 @@ def analyze_class(self, defn: ClassDef) -> None: decorator.accept(self) if isinstance(decorator, RefExpr): if decorator.fullname in FINAL_DECORATOR_NAMES: - self.fail("@final cannot be used with TypedDict", decorator) + self.fail(message_registry.CANNOT_USE_FINAL_DEC_WITH_TYPEDDICT, decorator) if info is None: self.mark_incomplete(defn.name, defn) else: @@ -1280,8 +1275,7 @@ def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None if defn.info.is_protocol: defn.info.runtime_protocol = True else: - self.fail('@runtime_checkable can only be used with protocol classes', - defn) + self.fail(message_registry.RUNTIME_CHECKABLE_WITH_NON_PROPERTY, defn) elif decorator.fullname in FINAL_DECORATOR_NAMES: defn.info.is_final = True @@ -1319,7 +1313,7 @@ class Foo(Bar, Generic[T]): ... result = self.analyze_class_typevar_declaration(base) if result is not None: if declared_tvars: - self.fail('Only single Generic[...] or Protocol[...] can be in bases', context) + self.fail(message_registry.BASES_MUST_HAVE_SINGLE_GENERIC_OR_PROTOCOL, context) removed.append(i) tvars = result[0] is_protocol |= result[1] @@ -1335,11 +1329,10 @@ class Foo(Bar, Generic[T]): ... all_tvars = self.get_all_bases_tvars(base_type_exprs, removed) if declared_tvars: if len(remove_dups(declared_tvars)) < len(declared_tvars): - self.fail("Duplicate type variables in Generic[...] or Protocol[...]", context) + self.fail(message_registry.DUPLICATE_TYPEVARS_IN_GENERIC_OR_PROTOCOL, context) declared_tvars = remove_dups(declared_tvars) if not set(all_tvars).issubset(set(declared_tvars)): - self.fail("If Generic[...] or Protocol[...] is present" - " it should list all type variables", context) + self.fail(message_registry.GENERIC_PROTOCOL_NOT_ALL_TYPEVARS, context) # In case of error, Generic tvars will go first declared_tvars = remove_dups(declared_tvars + all_tvars) else: @@ -1384,8 +1377,7 @@ def analyze_class_typevar_declaration( if tvar: tvars.append(tvar) elif not self.found_incomplete_ref(tag): - self.fail('Free type variable expected in %s[...]' % - sym.node.name, base) + self.fail(message_registry.FREE_TYPEVAR_EXPECTED.format(sym.node.name), base) return tvars, is_proto return None @@ -1515,12 +1507,14 @@ def analyze_base_classes( except TypeTranslationError: name = self.get_name_repr_of_expr(base_expr) if isinstance(base_expr, CallExpr): - msg = 'Unsupported dynamic base class' + msg = message_registry.UNSUPPORTED_DYNAMIC_BASE_CLASS else: - msg = 'Invalid base class' + msg = message_registry.INVALID_BASE_CLASS + extra = '' if name: - msg += ' "{}"'.format(name) - self.fail(msg, base_expr) + extra += ' "{}"'.format(name) + + self.fail(msg.format(extra), base_expr) is_error = True continue if base is None: @@ -1548,29 +1542,24 @@ def configure_base_classes(self, base_types.append(actual_base) elif isinstance(base, Instance): if base.type.is_newtype: - self.fail('Cannot subclass "NewType"', defn) - if self.enum_has_final_values(base): - # This means that are trying to subclass a non-default - # Enum class, with defined members. This is not possible. - # In runtime, it will raise. We need to mark this type as final. - # However, methods can be defined on a type: only values can't. - # We also don't count values with annotations only. - base.type.is_final = True + self.fail(message_registry.CANNOT_SUBCLASS_NEWTYPE, defn) base_types.append(base) elif isinstance(base, AnyType): if self.options.disallow_subclassing_any: if isinstance(base_expr, (NameExpr, MemberExpr)): - msg = 'Class cannot subclass "{}" (has type "Any")'.format(base_expr.name) + msg = message_registry.CANNOT_SUBCLASS_ANY_NAMED.format(base_expr.name) else: - msg = 'Class cannot subclass value of type "Any"' + msg = message_registry.CANNOT_SUBCLASS_ANY self.fail(msg, base_expr) info.fallback_to_any = True else: - msg = 'Invalid base class' + msg = message_registry.INVALID_BASE_CLASS name = self.get_name_repr_of_expr(base_expr) + extra = '' if name: - msg += ' "{}"'.format(name) - self.fail(msg, base_expr) + extra += ' "{}"'.format(name) + + self.fail(msg.format(extra), base_expr) info.fallback_to_any = True if self.options.disallow_any_unimported and has_any_from_unimported_type(base): if isinstance(base_expr, (NameExpr, MemberExpr)): @@ -1621,7 +1610,7 @@ def configure_tuple_base_class(self, # There may be an existing valid tuple type from previous semanal iterations. # Use equality to check if it is the case. if info.tuple_type and info.tuple_type != base: - self.fail("Class has two incompatible bases derived from tuple", defn) + self.fail(message_registry.INCOMPATIBLE_BASES, defn) defn.has_incompatible_baseclass = True info.tuple_type = base if isinstance(base_expr, CallExpr): @@ -1652,8 +1641,7 @@ def calculate_class_mro(self, defn: ClassDef, try: calculate_mro(defn.info, obj_type) except MroError: - self.fail('Cannot determine consistent method resolution ' - 'order (MRO) for "%s"' % defn.name, defn) + self.fail(message_registry.CANNOT_DETERMINE_MRO.format(defn.name), defn) self.set_dummy_mro(defn.info) # Allow plugins to alter the MRO to handle the fact that `def mro()` # on metaclasses permits MRO rewriting. @@ -1677,7 +1665,7 @@ def update_metaclass(self, defn: ClassDef) -> None: if self.options.python_version[0] == 2: for body_node in defn.defs.body: if isinstance(body_node, ClassDef) and body_node.name == "__metaclass__": - self.fail("Metaclasses defined as inner classes are not supported", body_node) + self.fail(message_registry.INNER_METACLASS_UNSUPPORTED, body_node) break elif isinstance(body_node, AssignmentStmt) and len(body_node.lvalues) == 1: lvalue = body_node.lvalues[0] @@ -1713,7 +1701,7 @@ def update_metaclass(self, defn: ClassDef) -> None: if len(metas) == 0: return if len(metas) > 1: - self.fail("Multiple metaclass definitions", defn) + self.fail(message_registry.MULTIPLE_METACLASSES, defn) return defn.metaclass = metas.pop() @@ -1723,15 +1711,16 @@ def verify_base_classes(self, defn: ClassDef) -> bool: for base in info.bases: baseinfo = base.type if self.is_base_class(info, baseinfo): - self.fail('Cycle in inheritance hierarchy', defn) + self.fail(message_registry.INHERITANCE_CYCLE, defn) cycle = True if baseinfo.fullname == 'builtins.bool': - self.fail('"%s" is not a valid base class' % - baseinfo.name, defn, blocker=True) + self.fail(message_registry.NAMED_INVALID_BASE_CLASS.format(baseinfo.name), + defn, + blocker=True) return False dup = find_duplicate(info.direct_base_classes()) if dup: - self.fail('Duplicate base class "%s"' % dup.name, defn, blocker=True) + self.fail(message_registry.DUPLICATE_BASE_CLASS.format(dup.name), defn, blocker=True) return False return not cycle @@ -1758,7 +1747,8 @@ def analyze_metaclass(self, defn: ClassDef) -> None: elif isinstance(defn.metaclass, MemberExpr): metaclass_name = get_member_expr_fullname(defn.metaclass) if metaclass_name is None: - self.fail('Dynamic metaclass not supported for "%s"' % defn.name, defn.metaclass) + self.fail(message_registry.UNSUPPORTED_NAMED_DYNAMIC_BASE_CLASS.format(defn.name), + defn.metaclass) return sym = self.lookup_qualified(metaclass_name, defn.metaclass) if sym is None: @@ -1775,10 +1765,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None: self.defer(defn) return if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None: - self.fail('Invalid metaclass "%s"' % metaclass_name, defn.metaclass) + self.fail(message_registry.INVALID_METACLASS.format(metaclass_name), + defn.metaclass) return if not sym.node.is_metaclass(): - self.fail('Metaclasses not inheriting from "type" are not supported', + self.fail(message_registry.METACLASS_MUST_INHERIT_TYPE, defn.metaclass) return inst = fill_typevars(sym.node) @@ -1797,12 +1788,12 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # Inconsistency may happen due to multiple baseclasses even in classes that # do not declare explicit metaclass, but it's harder to catch at this stage if defn.metaclass is not None: - self.fail('Inconsistent metaclass structure for "%s"' % defn.name, defn) + self.fail(message_registry.INCONSISTENT_METACLAS_STRUCTURE.format(defn.name), defn) else: if defn.info.metaclass_type.type.has_base('enum.EnumMeta'): defn.info.is_enum = True if defn.type_vars: - self.fail("Enum class cannot be generic", defn) + self.fail(message_registry.NO_GENERIC_ENUM, defn) # # Imports @@ -1951,20 +1942,19 @@ def report_missing_module_attribute( imported_id, context, module_public=module_public, module_hidden=module_hidden ) return - message = 'Module "{}" has no attribute "{}"'.format(import_id, source_id) + message = message_registry.MODULE_MISSING_ATTIRBUTE # Suggest alternatives, if any match is found. module = self.modules.get(import_id) if module: if not self.options.implicit_reexport and source_id in module.names.keys(): - message = ('Module "{}" does not explicitly export attribute "{}"' - '; implicit reexport disabled'.format(import_id, source_id)) + message = (message_registry.NO_IMPLICIT_REEXPORT.format(import_id, source_id)) else: alternatives = set(module.names.keys()).difference({source_id}) matches = best_matches(source_id, alternatives)[:3] if matches: suggestion = "; maybe {}?".format(pretty_seq(matches, "or")) - message += "{}".format(suggestion) - self.fail(message, context, code=codes.ATTR_DEFINED) + message = message.format(import_id, source_id, suggestion) + self.fail(message, context) self.add_unknown_imported_symbol( imported_id, context, target_name=None, module_public=module_public, module_hidden=not module_public @@ -2010,7 +2000,7 @@ def correct_relative_import(self, node: Union[ImportFrom, ImportAll]) -> str: import_id, ok = correct_relative_import(self.cur_mod_id, node.relative, node.id, self.cur_mod_node.is_package_init_file()) if not ok: - self.fail("Relative import climbs too many namespaces", node) + self.fail(message_registry.INCORRECT_RELATIVE_IMPORT, node) return import_id def visit_import_all(self, i: ImportAll) -> None: @@ -2226,7 +2216,7 @@ def is_type_ref(self, rv: Expression, bare: bool = False) -> bool: if not isinstance(rv, RefExpr): return False if isinstance(rv.node, TypeVarExpr): - self.fail('Type variable "{}" is invalid as target for type alias'.format( + self.fail(message_registry.INVALID_TYPE_ALIAS_TARGET.format( rv.fullname), rv) return False @@ -2313,11 +2303,11 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool: if internal_name is None: return False if isinstance(lvalue, MemberExpr): - self.fail("NamedTuple type as an attribute is not supported", lvalue) + self.fail(message_registry.NAMEDTUPLE_ATTRIBUTE_UNSUPPORTED, lvalue) return False if internal_name != name: - self.fail('First argument to namedtuple() should be "{}", not "{}"'.format( - name, internal_name), s.rvalue, code=codes.NAME_MATCH) + self.fail(message_registry.NAMEDTUPLE_INCORRECT_FIRST_ARG.format( + name, internal_name), s.rvalue) return True # Yes, it's a valid namedtuple, but defer if it is not ready. if not info: @@ -2337,7 +2327,7 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool: if not is_typed_dict: return False if isinstance(lvalue, MemberExpr): - self.fail("TypedDict type as attribute is not supported", lvalue) + self.fail(message_registry.TYPEDDICT_ATTRIBUTE_UNSUPPORTED, lvalue) return False # Yes, it's a valid typed dict, but defer if it is not ready. if not info: @@ -2411,22 +2401,22 @@ def unwrap_final(self, s: AssignmentStmt) -> bool: return False assert isinstance(s.unanalyzed_type, UnboundType) if len(s.unanalyzed_type.args) > 1: - self.fail("Final[...] takes at most one type argument", s.unanalyzed_type) + self.fail(message_registry.FINAL_ATMOST_ONE_ARG, s.unanalyzed_type) invalid_bare_final = False if not s.unanalyzed_type.args: s.type = None if isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs: invalid_bare_final = True - self.fail("Type in Final[...] can only be omitted if there is an initializer", s) + self.fail(message_registry.FINAL_INITIALIZER_REQUIRED, s) else: s.type = s.unanalyzed_type.args[0] if s.type is not None and self.is_classvar(s.type): - self.fail("Variable should not be annotated with both ClassVar and Final", s) + self.fail(message_registry.FINAL_CLASSVAR_DISALLOWED, s) return False if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], RefExpr): - self.fail("Invalid final declaration", s) + self.fail(message_registry.INVALID_FINAL, s) return False lval = s.lvalues[0] assert isinstance(lval, RefExpr) @@ -2438,7 +2428,7 @@ def unwrap_final(self, s: AssignmentStmt) -> bool: lval.is_inferred_def = s.type is None if self.loop_depth > 0: - self.fail("Cannot use Final inside a loop", s) + self.fail(message_registry.FINAL_IN_LOOP_DISALLOWED, s) if self.type and self.type.is_protocol: self.msg.protocol_members_cant_be_final(s) if (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs and @@ -2458,13 +2448,13 @@ def check_final_implicit_def(self, s: AssignmentStmt) -> None: assert isinstance(lval, RefExpr) if isinstance(lval, MemberExpr): if not self.is_self_member_ref(lval): - self.fail("Final can be only applied to a name or an attribute on self", s) + self.fail(message_registry.FINAL_ONLY_ON_SELF_MEMBER, s) s.is_final_def = False return else: assert self.function_stack if self.function_stack[-1].name != '__init__': - self.fail("Can only declare a final attribute in class body or __init__", s) + self.fail(message_registry.FINAL_ONLY_IN_CLASS_BODY_OR_INIT, s) s.is_final_def = False return @@ -2550,7 +2540,7 @@ def process_type_annotation(self, s: AssignmentStmt) -> None: else: if (self.type and self.type.is_protocol and self.is_annotated_protocol_member(s) and not self.is_func_scope()): - self.fail('All protocol members must have explicitly declared types', s) + self.fail(message_registry.PROTOCOL_MEMBERS_MUST_BE_TYPED, s) # Set the type if the rvalue is a simple literal (even if the above error occurred). if len(s.lvalues) == 1 and isinstance(s.lvalues[0], RefExpr): if s.lvalues[0].is_inferred_def: @@ -2689,8 +2679,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # TODO: find a more robust way to track the order of definitions. # Note: if is_alias_def=True, this is just a node from previous iteration. if isinstance(existing.node, TypeAlias) and not s.is_alias_def: - self.fail('Cannot assign multiple types to name "{}"' - ' without an explicit "Type[...]" annotation' + self.fail(message_registry.MULTIPLE_TYPES_WITHOUT_EXPLICIT_TYPE .format(lvalue.name), lvalue) return False @@ -2827,11 +2816,10 @@ def analyze_lvalue(self, elif isinstance(lval, MemberExpr): self.analyze_member_lvalue(lval, explicit_type, is_final) if explicit_type and not self.is_self_member_ref(lval): - self.fail('Type cannot be declared in assignment to non-self ' - 'attribute', lval) + self.fail(message_registry.TYPE_DECLARATION_IN_ASSIGNMENT, lval) elif isinstance(lval, IndexExpr): if explicit_type: - self.fail('Unexpected type declaration', lval) + self.fail(message_registry.UNEXPECTED_TYPE_DECLARATION, lval) lval.accept(self) elif isinstance(lval, TupleExpr): self.analyze_tuple_or_list_lvalue(lval, explicit_type) @@ -2839,9 +2827,9 @@ def analyze_lvalue(self, if nested: self.analyze_lvalue(lval.expr, nested, explicit_type) else: - self.fail('Starred assignment target must be in a list or tuple', lval) + self.fail(message_registry.STAR_ASSIGNMENT_TARGET_LIST_OR_TUPLE, lval) else: - self.fail('Invalid assignment target', lval) + self.fail(message_registry.INVALID_ASSIGNMENT_TARGET, lval) def analyze_name_lvalue(self, lvalue: NameExpr, @@ -2860,7 +2848,7 @@ def analyze_name_lvalue(self, name = lvalue.name if self.is_alias_for_final_name(name): if is_final: - self.fail("Cannot redefine an existing name as final", lvalue) + self.fail(message_registry.REDEFINE_AS_FINAL, lvalue) else: self.msg.cant_assign_to_final(name, self.type is not None, lvalue) @@ -2872,9 +2860,8 @@ def analyze_name_lvalue(self, if kind == MDEF and isinstance(self.type, TypeInfo) and self.type.is_enum: # Special case: we need to be sure that `Enum` keys are unique. if existing is not None and not isinstance(existing.node, PlaceholderNode): - self.fail('Attempted to reuse member name "{}" in Enum definition "{}"'.format( - name, self.type.name, - ), lvalue) + self.fail(message_registry.ENUM_REUSED_MEMBER_IN_DEFN.format(name, self.type.name), + lvalue) if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer: # Define new variable. @@ -2896,7 +2883,7 @@ def analyze_name_lvalue(self, typ = AnyType(TypeOfAny.special_form) self.store_declared_types(lvalue, typ) if is_final and self.is_final_redefinition(kind, name): - self.fail("Cannot redefine an existing name as final", lvalue) + self.fail(message_registry.REDEFINE_AS_FINAL, lvalue) else: self.make_name_lvalue_point_to_existing_def(lvalue, explicit_type, is_final) @@ -2962,7 +2949,7 @@ def make_name_lvalue_point_to_existing_def( """ if is_final: # Redefining an existing name with final is always an error. - self.fail("Cannot redefine an existing name as final", lval) + self.fail(message_registry.REDEFINE_AS_FINAL, lval) original_def = self.lookup(lval.name, lval, suppress_errors=True) if original_def is None and self.type and not self.is_func_scope(): # Workaround to allow "x, x = ..." in class body. @@ -2985,7 +2972,7 @@ def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, star_exprs = [item for item in items if isinstance(item, StarExpr)] if len(star_exprs) > 1: - self.fail('Two starred expressions in assignment', lval) + self.fail(message_registry.TWO_STAR_EXPRESSIONS_IN_ASSIGNMENT, lval) else: if len(star_exprs) == 1: star_exprs[0].valid = True @@ -3017,7 +3004,7 @@ def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: node = self.type.get(lval.name) if cur_node and is_final: # Overrides will be checked in type checker. - self.fail("Cannot redefine an existing name as final", lval) + self.fail(message_registry.REDEFINE_AS_FINAL, lval) # On first encounter with this definition, if this attribute was defined before # with an inferred type and it's marked with an explicit type now, give an error. if (not lval.node and cur_node and isinstance(cur_node.node, Var) and @@ -3031,7 +3018,7 @@ def analyze_member_lvalue(self, lval: MemberExpr, explicit_type: bool, is_final: # so we also check if `is_final` is passed. or (cur_node is None and (explicit_type or is_final))): if self.type.is_protocol and node is None: - self.fail("Protocol members cannot be defined via assignment to self", lval) + self.fail(message_registry.PROTOCOL_ASSIGNMENT_TO_SELF, lval) else: # Implicit attribute definition in __init__. lval.is_new_def = True @@ -3058,13 +3045,13 @@ def is_self_member_ref(self, memberexpr: MemberExpr) -> bool: def check_lvalue_validity(self, node: Union[Expression, SymbolNode, None], ctx: Context) -> None: if isinstance(node, TypeVarExpr): - self.fail('Invalid assignment target', ctx) + self.fail(message_registry.INVALID_ASSIGNMENT_TARGET, ctx) elif isinstance(node, TypeInfo): self.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, ctx) def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None: if isinstance(typ, StarType) and not isinstance(lvalue, StarExpr): - self.fail('Star type only allowed for starred expressions', lvalue) + self.fail(message_registry.STARTYPE_ONLY_FOR_STAR_EXPRESSIONS, lvalue) if isinstance(lvalue, RefExpr): lvalue.is_inferred_def = False if isinstance(lvalue.node, Var): @@ -3076,13 +3063,12 @@ def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None: typ = get_proper_type(typ) if isinstance(typ, TupleType): if len(lvalue.items) != len(typ.items): - self.fail('Incompatible number of tuple items', lvalue) + self.fail(message_registry.INCOMPATIBLE_TUPLE_ITEM_COUNT, lvalue) return for item, itemtype in zip(lvalue.items, typ.items): self.store_declared_types(item, itemtype) else: - self.fail('Tuple type expected for multiple variables', - lvalue) + self.fail(message_registry.TUPLE_TYPE_EXPECTED, lvalue) elif isinstance(lvalue, StarExpr): # Historical behavior for the old parser if isinstance(typ, StarType): @@ -3106,7 +3092,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: lvalue = s.lvalues[0] assert isinstance(lvalue, NameExpr) if s.type: - self.fail("Cannot declare the type of a type variable", s) + self.fail(message_registry.CANNOT_DECLARE_TYPE_OF_TYPEVAR, s) return False name = lvalue.name @@ -3131,7 +3117,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool: # Also give error for another type variable with the same name. (isinstance(existing.node, TypeVarExpr) and existing.node is call.analyzed)): - self.fail('Cannot redefine "%s" as a type variable' % name, s) + self.fail(message_registry.REDEFINE_AS_TYPEVAR.format(name), s) return False if self.options.disallow_any_unimported: @@ -3178,15 +3164,15 @@ def check_typevarlike_name(self, call: CallExpr, name: str, context: Context) -> call.callee.name if isinstance(call.callee, NameExpr) else call.callee.fullname ) if len(call.args) < 1: - self.fail("Too few arguments for {}()".format(typevarlike_type), context) + self.fail(message_registry.TYPEVAR_CALL_TOO_FEW_ARGS.format(typevarlike_type), context) return False if (not isinstance(call.args[0], (StrExpr, BytesExpr, UnicodeExpr)) or not call.arg_kinds[0] == ARG_POS): - self.fail("{}() expects a string literal as first argument".format(typevarlike_type), - context) + self.fail(message_registry.TYPEVAR_CALL_EXPECTED_STRING_LITERAL.format( + typevarlike_type), context) return False elif call.args[0].value != name: - msg = 'String argument 1 "{}" to {}(...) does not match variable name "{}"' + msg = message_registry.TYPEVAR_NAME_ARG_MISMATCH self.fail(msg.format(call.args[0].value, typevarlike_type, name), context) return False return True @@ -3239,7 +3225,7 @@ def process_typevar_parameters(self, args: List[Expression], return None elif param_name == 'bound': if has_values: - self.fail("TypeVar cannot have both values and an upper bound", context) + self.fail(message_registry.TYPEVAR_VALUE_WITH_BOUND_DISALLOWED, context) return None try: # We want to use our custom error message below, so we suppress @@ -3265,21 +3251,19 @@ def process_typevar_parameters(self, args: List[Expression], return None elif param_name == 'values': # Probably using obsolete syntax with values=(...). Explain the current syntax. - self.fail('TypeVar "values" argument not supported', context) - self.fail("Use TypeVar('T', t, ...) instead of TypeVar('T', values=(t, ...))", - context) + self.fail(message_registry.TYPEVAR_VALUES_ARG_UNSUPPORTED, context) + self.fail(message_registry.USE_NEW_TYPEVAR_SYNTAX, context) return None else: - self.fail('{}: "{}"'.format( - message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, param_name, - ), context) + self.fail(message_registry.TYPEVAR_UNEXPECTED_ARG_NAMED.format(param_name), + context) return None if covariant and contravariant: - self.fail("TypeVar cannot be both covariant and contravariant", context) + self.fail(message_registry.TYPEVAR_COVARIANT_AND_CONTRAVARIANT, context) return None elif num_values == 1: - self.fail("TypeVar cannot have only a single constraint", context) + self.fail(message_registry.TYPEVAR_SINGLE_CONSTRAINT, context) return None elif covariant: variance = COVARIANT @@ -3306,7 +3290,7 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool: lvalue = s.lvalues[0] assert isinstance(lvalue, NameExpr) if s.type: - self.fail("Cannot declare the type of a parameter specification", s) + self.fail(message_registry.CANNOT_DECLARE_TYPE_OF_PARAMSPEC, s) return False name = lvalue.name @@ -3367,7 +3351,7 @@ def analyze_value_types(self, items: List[Expression]) -> List[Type]: analyzed = PlaceholderType(None, [], node.line) result.append(analyzed) except TypeTranslationError: - self.fail('Type expected', node) + self.fail(message_registry.TYPE_EXPECTED, node) result.append(AnyType(TypeOfAny.from_error)) return result @@ -3410,7 +3394,7 @@ def is_final_type(self, typ: Optional[Type]) -> bool: return sym.node.fullname in FINAL_TYPE_NAMES def fail_invalid_classvar(self, context: Context) -> None: - self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context) + self.fail(message_registry.CLASSVAR_OUTSIDE_CLASS_BODY, context) def process_module_assignment(self, lvals: List[Lvalue], rval: Expression, ctx: AssignmentStmt) -> None: @@ -3472,10 +3456,9 @@ def process_module_assignment(self, lvals: List[Lvalue], rval: Expression, if lnode: if isinstance(lnode.node, MypyFile) and lnode.node is not rnode.node: assert isinstance(lval, (NameExpr, MemberExpr)) - self.fail( - 'Cannot assign multiple modules to name "{}" ' - 'without explicit "types.ModuleType" annotation'.format(lval.name), - ctx) + self.fail(message_registry.MULTIPLE_MODULE_ASSIGNMENT + .format(lval.name), + ctx) # never create module alias except on initial var definition elif lval.is_inferred_def: assert rnode.node is not None @@ -3495,13 +3478,13 @@ def process__deletable__(self, s: AssignmentStmt) -> None: s.lvalues[0].name == '__deletable__' and s.lvalues[0].kind == MDEF): rvalue = s.rvalue if not isinstance(rvalue, (ListExpr, TupleExpr)): - self.fail('"__deletable__" must be initialized with a list or tuple expression', s) + self.fail(message_registry.DELETABLE_MUST_BE_WITH_LIST_OR_TUPLE, s) return items = rvalue.items attrs = [] for item in items: if not isinstance(item, StrExpr): - self.fail('Invalid "__deletable__" item; string literal expected', item) + self.fail(message_registry.DELETABLE_EXPECTED_STRING_LITERAL, item) else: attrs.append(item.value) assert self.type @@ -3586,7 +3569,7 @@ def visit_expression_stmt(self, s: ExpressionStmt) -> None: def visit_return_stmt(self, s: ReturnStmt) -> None: self.statement = s if not self.is_func_scope(): - self.fail('"return" outside function', s) + self.fail(message_registry.RETURN_OUTSIDE_FUNCTION, s) if s.expr: s.expr.accept(self) @@ -3645,12 +3628,12 @@ def visit_for_stmt(self, s: ForStmt) -> None: def visit_break_stmt(self, s: BreakStmt) -> None: self.statement = s if self.loop_depth == 0: - self.fail('"break" outside loop', s, serious=True, blocker=True) + self.fail(message_registry.BREAK_OUTSIDE_LOOP, s, serious=True, blocker=True) def visit_continue_stmt(self, s: ContinueStmt) -> None: self.statement = s if self.loop_depth == 0: - self.fail('"continue" outside loop', s, serious=True, blocker=True) + self.fail(message_registry.CONTINUE_OUTSIDE_LOOP, s, serious=True, blocker=True) def visit_if_stmt(self, s: IfStmt) -> None: self.statement = s @@ -3686,7 +3669,7 @@ def visit_with_stmt(self, s: WithStmt) -> None: actual_targets = [t for t in s.target if t is not None] if len(actual_targets) == 0: # We have a type for no targets - self.fail('Invalid type comment: "with" statement has no targets', s) + self.fail(message_registry.WITH_HAS_NO_TARGETS, s) elif len(actual_targets) == 1: # We have one target and one type types = [s.unanalyzed_type] @@ -3696,10 +3679,10 @@ def visit_with_stmt(self, s: WithStmt) -> None: types = s.unanalyzed_type.items.copy() else: # But it's the wrong number of items - self.fail('Incompatible number of types for "with" targets', s) + self.fail(message_registry.WITH_INCOMPATIBLE_TARGET_COUNT, s) else: # We have multiple targets and one type - self.fail('Multiple types expected for multiple "with" targets', s) + self.fail(message_registry.WITH_MULTIPLE_TYPES_EXPECTED, s) new_types: List[Type] = [] for e, n in zip(s.expr, s.target): @@ -3727,7 +3710,7 @@ def visit_del_stmt(self, s: DelStmt) -> None: self.statement = s s.expr.accept(self) if not self.is_valid_del_target(s.expr): - self.fail('Invalid delete target', s) + self.fail(message_registry.INVALID_DELETE_TARGET, s) def is_valid_del_target(self, s: Expression) -> bool: if isinstance(s, (IndexExpr, NameExpr, MemberExpr)): @@ -3741,27 +3724,26 @@ def visit_global_decl(self, g: GlobalDecl) -> None: self.statement = g for name in g.names: if name in self.nonlocal_decls[-1]: - self.fail('Name "{}" is nonlocal and global'.format(name), g) + self.fail(message_registry.NAME_IS_NONLOCAL_AND_GLOBAL.format(name), g) self.global_decls[-1].add(name) def visit_nonlocal_decl(self, d: NonlocalDecl) -> None: self.statement = d if not self.is_func_scope(): - self.fail("nonlocal declaration not allowed at module level", d) + self.fail(message_registry.NONLOCAL_AT_MODULE_LEVEL, d) else: for name in d.names: for table in reversed(self.locals[:-1]): if table is not None and name in table: break else: - self.fail('No binding for nonlocal "{}" found'.format(name), d) + self.fail(message_registry.NONLOCAL_NO_BINDING_FOUND.format(name), d) if self.locals[-1] is not None and name in self.locals[-1]: - self.fail('Name "{}" is already defined in local ' - 'scope before nonlocal declaration'.format(name), d) + self.fail(message_registry.LOCAL_DEFINITION_BEFORE_NONLOCAL.format(name), d) if name in self.global_decls[-1]: - self.fail('Name "{}" is nonlocal and global'.format(name), d) + self.fail(message_registry.NAME_IS_NONLOCAL_AND_GLOBAL.format(name), d) self.nonlocal_decls[-1].add(name) def visit_print_stmt(self, s: PrintStmt) -> None: @@ -3791,8 +3773,7 @@ def visit_name_expr(self, expr: NameExpr) -> None: def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: """Bind name expression to a symbol table node.""" if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym): - self.fail('"{}" is a type variable and only valid in type ' - 'context'.format(expr.name), expr) + self.fail(message_registry.NAME_ONLY_VALID_IN_TYPE_CONTEXT.format(expr.name), expr) elif isinstance(sym.node, PlaceholderNode): self.process_placeholder(expr.name, 'name', expr) else: @@ -3802,7 +3783,7 @@ def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: def visit_super_expr(self, expr: SuperExpr) -> None: if not self.type and not expr.call.args: - self.fail('"super" used outside class', expr) + self.fail(message_registry.SUPER_OUTSIDE_CLASS, expr) return expr.info = self.type for arg in expr.call.args: @@ -3835,16 +3816,16 @@ def visit_dict_expr(self, expr: DictExpr) -> None: def visit_star_expr(self, expr: StarExpr) -> None: if not expr.valid: # XXX TODO Change this error message - self.fail('Can use starred expression only as assignment target', expr) + self.fail(message_registry.INVALID_STAR_EXPRESSION, expr) else: expr.expr.accept(self) def visit_yield_from_expr(self, e: YieldFromExpr) -> None: if not self.is_func_scope(): # not sure - self.fail('"yield from" outside function', e, serious=True, blocker=True) + self.fail(message_registry.YIELD_FROM_OUTSIDE_FUNC, e, serious=True, blocker=True) else: if self.function_stack[-1].is_coroutine: - self.fail('"yield from" in async function', e, serious=True, blocker=True) + self.fail(message_registry.YIELD_FROM_IN_ASYNC_FUNC, e, serious=True, blocker=True) else: self.function_stack[-1].is_generator = True if e.expr: @@ -3865,7 +3846,7 @@ def visit_call_expr(self, expr: CallExpr) -> None: try: target = self.expr_to_unanalyzed_type(expr.args[0]) except TypeTranslationError: - self.fail('Cast target is not a type', expr) + self.fail(message_registry.CAST_TARGET_IS_NOT_TYPE, expr) return # Piggyback CastExpr object to the CallExpr object; it takes # precedence over the CallExpr semantics. @@ -3914,7 +3895,7 @@ def visit_call_expr(self, expr: CallExpr) -> None: expr.analyzed.accept(self) elif refers_to_fullname(expr.callee, 'typing.Any'): # Special form Any(...) no longer supported. - self.fail('Any(...) is no longer supported. Use cast(Any, ...) instead', expr) + self.fail(message_registry.ANY_CALL_UNSUPPORTED, expr) elif refers_to_fullname(expr.callee, 'typing._promote'): # Special form _promote(...). if not self.check_fixed_args(expr, 1, '_promote'): @@ -3923,7 +3904,7 @@ def visit_call_expr(self, expr: CallExpr) -> None: try: target = self.expr_to_unanalyzed_type(expr.args[0]) except TypeTranslationError: - self.fail('Argument 1 to _promote is not a type', expr) + self.fail(message_registry.PROMOTE_ARG_EXPECTED_TYPE, expr) return expr.analyzed = PromoteExpr(target) expr.analyzed.line = expr.line @@ -3978,12 +3959,10 @@ def check_fixed_args(self, expr: CallExpr, numargs: int, if numargs == 1: s = '' if len(expr.args) != numargs: - self.fail('"%s" expects %d argument%s' % (name, numargs, s), - expr) + self.fail(message_registry.ARG_COUNT_MISMATCH.format(name, numargs, s), expr) return False if expr.arg_kinds != [ARG_POS] * numargs: - self.fail('"%s" must be called with %s positional argument%s' % - (name, numargs, s), expr) + self.fail(message_registry.POS_ARG_COUNT_MISMATCH.format(name, numargs, s), expr) return False return True @@ -4120,7 +4099,7 @@ def analyze_type_application_args(self, expr: IndexExpr) -> Optional[List[Type]] try: typearg = self.expr_to_unanalyzed_type(item) except TypeTranslationError: - self.fail('Type expected within [...]', expr) + self.fail(message_registry.TYPE_EXPECTED_IN_BRACKETS, expr) return None # We always allow unbound type variables in IndexExpr, since we # may be analysing a type alias definition rvalue. The error will be @@ -4226,11 +4205,12 @@ def visit__promote_expr(self, expr: PromoteExpr) -> None: def visit_yield_expr(self, expr: YieldExpr) -> None: if not self.is_func_scope(): - self.fail('"yield" outside function', expr, serious=True, blocker=True) + self.fail(message_registry.YIELD_OUTSIDE_FUNC, expr, serious=True, blocker=True) else: if self.function_stack[-1].is_coroutine: if self.options.python_version < (3, 6): - self.fail('"yield" in async function', expr, serious=True, blocker=True) + self.fail(message_registry.YIELD_IN_ASYNC_FUNC, expr, serious=True, + blocker=True) else: self.function_stack[-1].is_generator = True self.function_stack[-1].is_async_generator = True @@ -4241,9 +4221,9 @@ def visit_yield_expr(self, expr: YieldExpr) -> None: def visit_await_expr(self, expr: AwaitExpr) -> None: if not self.is_func_scope(): - self.fail('"await" outside function', expr) + self.fail(message_registry.AWAIT_OUTSIDE_FUNC, expr) elif not self.function_stack[-1].is_coroutine: - self.fail('"await" outside coroutine ("async def")', expr) + self.fail(message_registry.AWAIT_OUTSIDE_COROUTINE, expr) expr.expr.accept(self) # @@ -4892,7 +4872,7 @@ def process_placeholder(self, name: str, kind: str, ctx: Context) -> None: self.defer(ctx) def cannot_resolve_name(self, name: str, kind: str, ctx: Context) -> None: - self.fail('Cannot resolve {} "{}" (possible cyclic definition)'.format(kind, name), ctx) + self.fail(message_registry.CANNOT_RESOLVE_NAME.format(kind, name), ctx) def qualified_name(self, name: str) -> str: if self.type is not None: @@ -5000,8 +4980,7 @@ def name_not_defined(self, name: str, ctx: Context, namespace: Optional[str] = N # later on. Defer current target. self.record_incomplete_ref() return - message = 'Name "{}" is not defined'.format(name) - self.fail(message, ctx, code=codes.NAME_DEFINED) + self.fail(message_registry.NAME_NOT_DEFINED.format(name), ctx) if 'builtins.{}'.format(name) in SUGGESTED_TEST_FIXTURES: # The user probably has a missing definition in a test fixture. Let's verify. @@ -5052,8 +5031,8 @@ def already_defined(self, extra_msg = ' on line {}'.format(node.line) else: extra_msg = ' (possibly by an import)' - self.fail('{} "{}" already defined{}'.format(noun, unmangle(name), extra_msg), ctx, - code=codes.NO_REDEF) + self.fail(message_registry.NAME_ALREADY_DEFINED.format(noun, unmangle(name), extra_msg), + ctx) def name_already_defined(self, name: str, @@ -5103,7 +5082,7 @@ def in_checked_function(self) -> bool: return True def fail(self, - msg: str, + msg: Union[str, ErrorMessage], ctx: Context, serious: bool = False, *, @@ -5113,7 +5092,10 @@ def fail(self, return # In case it's a bug and we don't really have context assert ctx is not None, msg - self.errors.report(ctx.get_line(), ctx.get_column(), msg, blocker=blocker, code=code) + assert isinstance(msg, ErrorMessage) + + self.errors.report(ctx.get_line(), ctx.get_column(), msg.value, code=msg.code, + blocker=blocker) def note(self, msg: str, ctx: Context, code: Optional[ErrorCode] = None) -> None: if not self.in_checked_function(): diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index bb88b4df4716..fadd58078706 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -14,6 +14,8 @@ from mypy.semanal_shared import SemanticAnalyzerInterface from mypy.options import Options from mypy.types import get_proper_type, LiteralType +from mypy.message_registry import ErrorMessage +from mypy import message_registry # Note: 'enum.EnumMeta' is deliberately excluded from this list. Classes that directly use # enum.EnumMeta do not necessarily automatically have the 'name' and 'value' attributes. @@ -46,7 +48,7 @@ def process_enum_call(self, s: AssignmentStmt, is_func_scope: bool) -> bool: if enum_call is None: return False if isinstance(lvalue, MemberExpr): - self.fail("Enum type as attribute is not supported", lvalue) + self.fail(message_registry.ENUM_ATTRIBUTE_UNSUPPORTED, lvalue) return False # Yes, it's a valid Enum definition. Add it to the symbol table. self.api.add_symbol(name, enum_call, s) @@ -119,15 +121,19 @@ def parse_enum_call_args(self, call: CallExpr, """ args = call.args if not all([arg_kind in [ARG_POS, ARG_NAMED] for arg_kind in call.arg_kinds]): - return self.fail_enum_call_arg("Unexpected arguments to %s()" % class_name, call) + return self.fail_enum_call_arg(message_registry.ENUM_CALL_UNEXPECTED_ARGS.format( + class_name), call) if len(args) < 2: - return self.fail_enum_call_arg("Too few arguments for %s()" % class_name, call) + return self.fail_enum_call_arg(message_registry.ENUM_CALL_TOO_FEW_ARGS.format( + class_name), call) if len(args) > 6: - return self.fail_enum_call_arg("Too many arguments for %s()" % class_name, call) + return self.fail_enum_call_arg(message_registry.ENUM_CALL_TOO_MANY_ARGS.format( + class_name), call) valid_name = [None, 'value', 'names', 'module', 'qualname', 'type', 'start'] for arg_name in call.arg_names: if arg_name not in valid_name: - self.fail_enum_call_arg('Unexpected keyword argument "{}"'.format(arg_name), call) + self.fail_enum_call_arg(message_registry.ENUM_CALL_UNEXPECTED_KWARG.format( + arg_name), call) value, names = None, None for arg_name, arg in zip(call.arg_names, args): if arg_name == 'value': @@ -140,7 +146,7 @@ def parse_enum_call_args(self, call: CallExpr, names = args[1] if not isinstance(value, (StrExpr, UnicodeExpr)): return self.fail_enum_call_arg( - "%s() expects a string literal as the first argument" % class_name, call) + message_registry.ENUM_CALL_EXPECTED_STRING_LITERAL.format(class_name), call) items = [] values: List[Optional[Expression]] = [] if isinstance(names, (StrExpr, UnicodeExpr)): @@ -164,14 +170,13 @@ def parse_enum_call_args(self, call: CallExpr, values.append(value) else: return self.fail_enum_call_arg( - "%s() with tuple or list expects strings or (name, value) pairs" % - class_name, - call) + message_registry.ENUM_CALL_EXPECTED_STRINGS_OR_PAIRS.format(class_name), call) elif isinstance(names, DictExpr): for key, value in names.items: if not isinstance(key, (StrExpr, UnicodeExpr)): return self.fail_enum_call_arg( - "%s() with dict literal requires string literals" % class_name, call) + message_registry.ENUM_CALL_DICT_EXPECTED_STRING_KEYS.format(class_name), + call) items.append(key.value) values.append(value) elif isinstance(args[1], RefExpr) and isinstance(args[1].node, Var): @@ -188,23 +193,21 @@ def parse_enum_call_args(self, call: CallExpr, items.append(field) else: return self.fail_enum_call_arg( - "%s() expects a string, tuple, list or dict literal as the second argument" % - class_name, - call) + message_registry.ENUM_CALL_EXPECTED_LITERAL.format(class_name), call) else: # TODO: Allow dict(x=1, y=2) as a substitute for {'x': 1, 'y': 2}? return self.fail_enum_call_arg( - "%s() expects a string, tuple, list or dict literal as the second argument" % - class_name, + message_registry.ENUM_CALL_EXPECTED_LITERAL.format(class_name), call) if len(items) == 0: - return self.fail_enum_call_arg("%s() needs at least one item" % class_name, call) + return self.fail_enum_call_arg(message_registry.ENUM_CALL_ATLEAST_ONE_ITEM.format( + class_name), call) if not values: values = [None] * len(items) assert len(items) == len(values) return items, values, True - def fail_enum_call_arg(self, message: str, + def fail_enum_call_arg(self, message: ErrorMessage, context: Context) -> Tuple[List[str], List[Optional[Expression]], bool]: self.fail(message, context) @@ -212,5 +215,5 @@ def fail_enum_call_arg(self, message: str, # Helpers - def fail(self, msg: str, ctx: Context) -> None: + def fail(self, msg: ErrorMessage, ctx: Context) -> None: self.api.fail(msg, ctx) diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py index 8930c63d2bef..10e69b231650 100644 --- a/mypy/semanal_namedtuple.py +++ b/mypy/semanal_namedtuple.py @@ -23,6 +23,8 @@ from mypy.options import Options from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.util import get_unique_redefinition_name +from mypy.message_registry import ErrorMessage +from mypy import message_registry # Matches "_prohibited" in typing.py, but adds __annotations__, which works at runtime but can't # easily be supported in a static checker. @@ -41,10 +43,6 @@ "__annotations__", ) -NAMEDTUP_CLASS_ERROR: Final = ( - "Invalid statement in NamedTuple definition; " 'expected "field_name: field_type [= default]"' -) - SELF_TVAR_NAME: Final = "_NT" @@ -94,10 +92,10 @@ def check_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool or None, if any of the types are not ready. """ if self.options.python_version < (3, 6) and not is_stub_file: - self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) + self.fail(message_registry.NAMEDTUPLE_SUPPORTED_ABOVE_PY36, defn) return [], [], {} if len(defn.base_type_exprs) > 1: - self.fail('NamedTuple should be a single base', defn) + self.fail(message_registry.NAMEDTUPLE_SINGLE_BASE, defn) items: List[str] = [] types: List[Type] = [] default_items: Dict[str, Expression] = {} @@ -115,10 +113,10 @@ def check_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool if (isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr)): continue - self.fail(NAMEDTUP_CLASS_ERROR, stmt) + self.fail(message_registry.NAMEDTUPLE_CLASS_ERROR, stmt) elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): # An assignment, but an invalid one. - self.fail(NAMEDTUP_CLASS_ERROR, stmt) + self.fail(message_registry.NAMEDTUPLE_CLASS_ERROR, stmt) else: # Append name and type in this case... name = stmt.lvalues[0].name @@ -133,15 +131,13 @@ def check_namedtuple_classdef(self, defn: ClassDef, is_stub_file: bool types.append(analyzed) # ...despite possible minor failures that allow further analyzis. if name.startswith('_'): - self.fail('NamedTuple field name cannot start with an underscore: {}' - .format(name), stmt) + self.fail(message_registry.NAMEDTUPLE_FIELD_NO_UNDERSCORE.format(name), stmt) if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax: - self.fail(NAMEDTUP_CLASS_ERROR, stmt) + self.fail(message_registry.NAMEDTUPLE_CLASS_ERROR, stmt) elif isinstance(stmt.rvalue, TempNode): # x: int assigns rvalue to TempNode(AnyType()) if default_items: - self.fail('Non-default NamedTuple fields cannot follow default fields', - stmt) + self.fail(message_registry.NAMEDTUPLE_FIELD_DEFAULT_AFTER_NONDEFAULT, stmt) else: default_items[name] = stmt.rvalue return items, types, default_items @@ -266,13 +262,13 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str # TODO: Share code with check_argument_count in checkexpr.py? args = call.args if len(args) < 2: - self.fail('Too few arguments for "{}()"'.format(type_name), call) + self.fail(message_registry.NAMEDTUPLE_TOO_FEW_ARGS.format(type_name), call) return None defaults: List[Expression] = [] if len(args) > 2: # Typed namedtuple doesn't support additional arguments. if fullname == 'typing.NamedTuple': - self.fail('Too many arguments for "NamedTuple()"', call) + self.fail(message_registry.NAMEDTUPLE_TOO_MANY_ARGS.format(type_name), call) return None for i, arg_name in enumerate(call.arg_names[2:], 2): if arg_name == 'defaults': @@ -283,17 +279,16 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str defaults = list(arg.items) else: self.fail( - "List or tuple literal expected as the defaults argument to " - "{}()".format(type_name), - arg - ) + message_registry.NAMEDTUPLE_EXPECTED_LIST_TUPLE_DEFAULTS.format( + type_name + ), arg) break if call.arg_kinds[:2] != [ARG_POS, ARG_POS]: - self.fail('Unexpected arguments to "{}()"'.format(type_name), call) + self.fail(message_registry.NAMEDTUPLE_UNEXPECTED_ARGS.format(type_name), call) return None if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): self.fail( - '"{}()" expects a string literal as the first argument'.format(type_name), call) + message_registry.NAMEDTUPLE_ARG_EXPECTED_STRING_LITERAL.format(type_name), call) return None typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value types: List[Type] = [] @@ -304,11 +299,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str items = str_expr.value.replace(',', ' ').split() else: self.fail( - 'List or tuple literal expected as the second argument to "{}()"'.format( - type_name, - ), - call, - ) + message_registry.NAMEDTUPLE_ARG_EXPECTED_LIST_TUPLE.format(type_name), call) return None else: listexpr = args[1] @@ -316,7 +307,7 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str # The fields argument contains just names, with implicit Any types. if any(not isinstance(item, (StrExpr, BytesExpr, UnicodeExpr)) for item in listexpr.items): - self.fail('String literal expected as "namedtuple()" item', call) + self.fail(message_registry.NAMEDTUPLE_EXPECTED_STRING_LITERAL, call) return None items = [cast(Union[StrExpr, BytesExpr, UnicodeExpr], item).value for item in listexpr.items] @@ -333,10 +324,12 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str types = [AnyType(TypeOfAny.unannotated) for _ in items] underscore = [item for item in items if item.startswith('_')] if underscore: - self.fail('"{}()" field names cannot start with an underscore: '.format(type_name) - + ', '.join(underscore), call) + self.fail( + message_registry.NAMEDTUPLE_FIELDS_NO_UNDERSCORE.format( + type_name, ', '.join(underscore) + ), call) if len(defaults) > len(items): - self.fail('Too many defaults given in call to "{}()"'.format(type_name), call) + self.fail(message_registry.NAMEDTUPLE_TOO_MANY_DEFAULTS.format(type_name), call) defaults = defaults[:len(items)] return items, types, defaults, typename, True @@ -352,18 +345,18 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: C for item in nodes: if isinstance(item, TupleExpr): if len(item.items) != 2: - self.fail('Invalid "NamedTuple()" field definition', item) + self.fail(message_registry.NAMEDTUPLE_INVALID_FIELD_DEFINITION, item) return None name, type_node = item.items if isinstance(name, (StrExpr, BytesExpr, UnicodeExpr)): items.append(name.value) else: - self.fail('Invalid "NamedTuple()" field name', item) + self.fail(message_registry.NAMEDTUPLE_INVALID_FIELD_NAME, item) return None try: type = expr_to_unanalyzed_type(type_node, self.options, self.api.is_stub_file) except TypeTranslationError: - self.fail('Invalid field type', type_node) + self.fail(message_registry.NAMEDTUPLE_INVALID_FIELD_TYPE, type_node) return None analyzed = self.api.anal_type(type) # Workaround #4987 and avoid introducing a bogus UnboundType @@ -374,7 +367,7 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: C return [], [], [], False types.append(analyzed) else: - self.fail('Tuple expected as "NamedTuple()" field', item) + self.fail(message_registry.NAMEDTUPLE_TUPLE_EXPECTED, item) return None return items, types, [], True @@ -524,8 +517,9 @@ def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]: continue ctx = named_tuple_info.names[prohibited].node assert ctx is not None - self.fail('Cannot overwrite NamedTuple attribute "{}"'.format(prohibited), - ctx) + self.fail(message_registry.NAMEDTUPLE_CANNOT_OVERWRITE_ATTRIBUTE.format( + prohibited + ), ctx) # Restore the names in the original symbol table. This ensures that the symbol # table contains the field objects created by build_namedtuple_typeinfo. Exclude @@ -546,5 +540,5 @@ def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]: # Helpers - def fail(self, msg: str, ctx: Context) -> None: + def fail(self, msg: ErrorMessage, ctx: Context) -> None: self.api.fail(msg, ctx) diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index 047df3d85b83..dc49f0f1d23f 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -19,7 +19,8 @@ from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type from mypy.messages import MessageBuilder, format_type -from mypy.errorcodes import ErrorCode +from mypy import message_registry +from mypy.message_registry import ErrorMessage from mypy import errorcodes as codes @@ -76,12 +77,12 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: newtype_class_info.tuple_type = old_type elif isinstance(old_type, Instance): if old_type.type.is_protocol: - self.fail("NewType cannot be used with protocol classes", s) + self.fail(message_registry.NEWTYPE_USED_WITH_PROTOCOL, s) newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type, s.line) else: if old_type is not None: - message = "Argument 2 to NewType(...) must be subclassable (got {})" - self.fail(message.format(format_type(old_type)), s, code=codes.VALID_NEWTYPE) + message = message_registry.NEWTYPE_ARG_MUST_BE_SUBCLASSABLE + self.fail(message.format(format_type(old_type)), s) # Otherwise the error was already reported. old_type = AnyType(TypeOfAny.from_error) object_type = self.api.named_type('builtins.object') @@ -120,14 +121,14 @@ def analyze_newtype_declaration(self, name = s.lvalues[0].name if s.type: - self.fail("Cannot declare the type of a NewType declaration", s) + self.fail(message_registry.CANNOT_DECLARE_TYPE_OF_NEWTYPE, s) names = self.api.current_symbol_table() existing = names.get(name) # Give a better error message than generic "Name already defined". if (existing and not isinstance(existing.node, PlaceholderNode) and not s.rvalue.analyzed): - self.fail('Cannot redefine "%s" as a NewType' % name, s) + self.fail(message_registry.CANNOT_REDEFINE_AS_NEWTYPE.format(name), s) # This dummy NewTypeExpr marks the call as sufficiently analyzed; it will be # overwritten later with a fully complete NewTypeExpr if there are no other @@ -145,20 +146,20 @@ def check_newtype_args(self, name: str, call: CallExpr, has_failed = False args, arg_kinds = call.args, call.arg_kinds if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS: - self.fail("NewType(...) expects exactly two positional arguments", context) + self.fail(message_registry.NEWTYPE_EXPECTS_TWO_ARGS, context) return None, False # Check first argument if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): - self.fail("Argument 1 to NewType(...) must be a string literal", context) + self.fail(message_registry.NEWTYPE_ARG_STRING_LITERAL, context) has_failed = True elif args[0].value != name: - msg = 'String argument 1 "{}" to NewType(...) does not match variable name "{}"' + msg = message_registry.NEWTYPE_ARG_VARNAME_MISMATCH self.fail(msg.format(args[0].value, name), context) has_failed = True # Check second argument - msg = "Argument 2 to NewType(...) must be a valid type" + msg = message_registry.NEWTYPE_ARG_INVALID_TYPE try: unanalyzed_type = expr_to_unanalyzed_type(args[1], self.options, self.api.is_stub_file) except TypeTranslationError: @@ -208,5 +209,5 @@ def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance, def make_argument(self, name: str, type: Type) -> Argument: return Argument(Var(name), type, None, ARG_POS) - def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: - self.api.fail(msg, ctx, code=code) + def fail(self, msg: ErrorMessage, ctx: Context) -> None: + self.api.fail(msg, ctx) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index ffc6a7df3931..137f023ff248 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -4,9 +4,7 @@ from typing import Optional, List, Set, Tuple from typing_extensions import Final -from mypy.types import ( - Type, AnyType, TypeOfAny, TypedDictType, TPDICT_NAMES, RequiredType, -) +from mypy.types import Type, AnyType, TypeOfAny, TypedDictType, TPDICT_NAMES from mypy.nodes import ( CallExpr, TypedDictExpr, Expression, NameExpr, Context, StrExpr, BytesExpr, UnicodeExpr, ClassDef, RefExpr, TypeInfo, AssignmentStmt, PassStmt, ExpressionStmt, EllipsisExpr, TempNode, @@ -17,12 +15,10 @@ from mypy.options import Options from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type from mypy.messages import MessageBuilder -from mypy.errorcodes import ErrorCode -from mypy import errorcodes as codes +from mypy.message_registry import ErrorMessage +from mypy import message_registry, errorcodes as codes + -TPDICT_CLASS_ERROR: Final = ( - "Invalid statement in TypedDict definition; " 'expected "field_name: field_type"' -) class TypedDictAnalyzer: @@ -69,30 +65,16 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ defn.analyzed.line = defn.line defn.analyzed.column = defn.column return True, info - # Extending/merging existing TypedDicts - typeddict_bases = [] - typeddict_bases_set = set() - for expr in defn.base_type_exprs: - if isinstance(expr, RefExpr) and expr.fullname in TPDICT_NAMES: - if 'TypedDict' not in typeddict_bases_set: - typeddict_bases_set.add('TypedDict') - else: - self.fail('Duplicate base class "TypedDict"', defn) - elif isinstance(expr, RefExpr) and self.is_typeddict(expr): - assert expr.fullname - if expr.fullname not in typeddict_bases_set: - typeddict_bases_set.add(expr.fullname) - typeddict_bases.append(expr) - else: - assert isinstance(expr.node, TypeInfo) - self.fail('Duplicate base class "%s"' % expr.node.name, defn) - else: - self.fail("All bases of a new TypedDict must be TypedDict types", defn) - + if any(not isinstance(expr, RefExpr) or + expr.fullname not in TPDICT_NAMES and + not self.is_typeddict(expr) for expr in defn.base_type_exprs): + self.fail(message_registry.TYPEDDICT_BASES_MUST_BE_TYPEDDICTS, defn) + typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) keys: List[str] = [] types = [] required_keys = set() + # Iterate over bases in reverse order so that leftmost base class' keys take precedence for base in reversed(typeddict_bases): assert isinstance(base, RefExpr) @@ -103,7 +85,7 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ valid_items = base_items.copy() for key in base_items: if key in keys: - self.fail('Overwriting TypedDict field "{}" while merging' + self.fail(message_registry.TYPEDDICT_OVERWRITE_FIELD_IN_MERGE .format(key), defn) keys.extend(valid_items.keys()) types.extend(valid_items.values()) @@ -146,55 +128,40 @@ def analyze_typeddict_classdef_fields( if (not isinstance(stmt, PassStmt) and not (isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, (EllipsisExpr, StrExpr)))): - self.fail(TPDICT_CLASS_ERROR, stmt) + self.fail(message_registry.TYPEDDICT_CLASS_ERROR, stmt) elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): # An assignment, but an invalid one. - self.fail(TPDICT_CLASS_ERROR, stmt) + self.fail(message_registry.TYPEDDICT_CLASS_ERROR, stmt) else: name = stmt.lvalues[0].name if name in (oldfields or []): - self.fail('Overwriting TypedDict field "{}" while extending' + self.fail(message_registry.TYPEDDICT_OVERWRITE_FIELD_IN_EXTEND .format(name), stmt) if name in fields: - self.fail('Duplicate TypedDict key "{}"'.format(name), stmt) + self.fail(message_registry.TYPEDDICT_DUPLICATE_KEY.format(name), stmt) continue # Append name and type in this case... fields.append(name) if stmt.type is None: types.append(AnyType(TypeOfAny.unannotated)) else: - analyzed = self.api.anal_type(stmt.type, allow_required=True) + analyzed = self.api.anal_type(stmt.type) if analyzed is None: return None, [], set() # Need to defer types.append(analyzed) # ...despite possible minor failures that allow further analyzis. if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax: - self.fail(TPDICT_CLASS_ERROR, stmt) + self.fail(message_registry.TYPEDDICT_CLASS_ERROR, stmt) elif not isinstance(stmt.rvalue, TempNode): # x: int assigns rvalue to TempNode(AnyType()) - self.fail('Right hand side values are not supported in TypedDict', stmt) + self.fail(message_registry.TYPEDDICT_RHS_VALUE_UNSUPPORTED, stmt) total: Optional[bool] = True if 'total' in defn.keywords: total = self.api.parse_bool(defn.keywords['total']) if total is None: - self.fail('Value of "total" must be True or False', defn) + self.fail(message_registry.TYPEDDICT_TOTAL_MUST_BE_BOOL_2, defn) total = True - required_keys = { - field - for (field, t) in zip(fields, types) - if (total or ( - isinstance(t, RequiredType) and # type: ignore[misc] - t.required - )) and not ( - isinstance(t, RequiredType) and # type: ignore[misc] - not t.required - ) - } - types = [ # unwrap Required[T] to just T - t.item if isinstance(t, RequiredType) else t # type: ignore[misc] - for t in types - ] - + required_keys = set(fields) if total else set() return fields, types, required_keys def check_typeddict(self, @@ -233,26 +200,12 @@ def check_typeddict(self, else: if var_name is not None and name != var_name: self.fail( - 'First argument "{}" to TypedDict() does not match variable name "{}"'.format( - name, var_name), node, code=codes.NAME_MATCH) + message_registry.TYPEDDICT_ARG_NAME_MISMATCH.format( + name, var_name), node) if name != var_name or is_func_scope: # Give it a unique name derived from the line number. name += '@' + str(call.line) - required_keys = { - field - for (field, t) in zip(items, types) - if (total or ( - isinstance(t, RequiredType) and # type: ignore[misc] - t.required - )) and not ( - isinstance(t, RequiredType) and # type: ignore[misc] - not t.required - ) - } - types = [ # unwrap Required[T] to just T - t.item if isinstance(t, RequiredType) else t # type: ignore[misc] - for t in types - ] + required_keys = set(items) if total else set() info = self.build_typeddict_typeinfo(name, items, types, required_keys, call.line) info.line = node.line # Store generated TypeInfo under both names, see semanal_namedtuple for more details. @@ -274,27 +227,27 @@ def parse_typeddict_args( # TODO: Share code with check_argument_count in checkexpr.py? args = call.args if len(args) < 2: - return self.fail_typeddict_arg("Too few arguments for TypedDict()", call) + return self.fail_typeddict_arg(message_registry.TYPEDDICT_TOO_FEW_ARGS, call) if len(args) > 3: - return self.fail_typeddict_arg("Too many arguments for TypedDict()", call) + return self.fail_typeddict_arg(message_registry.TYPEDDICT_TOO_MANY_ARGS, call) # TODO: Support keyword arguments if call.arg_kinds not in ([ARG_POS, ARG_POS], [ARG_POS, ARG_POS, ARG_NAMED]): - return self.fail_typeddict_arg("Unexpected arguments to TypedDict()", call) + return self.fail_typeddict_arg(message_registry.TYPEDDICT_UNEXPECTED_ARGS, call) if len(args) == 3 and call.arg_names[2] != 'total': return self.fail_typeddict_arg( - 'Unexpected keyword argument "{}" for "TypedDict"'.format(call.arg_names[2]), call) + message_registry.TYPEDDICT_CALL_UNEXPECTED_KWARG.format(call.arg_names[2]), call) if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): return self.fail_typeddict_arg( - "TypedDict() expects a string literal as the first argument", call) + message_registry.TYPEDDICT_CALL_EXPECTED_STRING_LITERAL, call) if not isinstance(args[1], DictExpr): return self.fail_typeddict_arg( - "TypedDict() expects a dictionary literal as the second argument", call) + message_registry.TYPEDDICT_CALL_EXPECTED_DICT, call) total: Optional[bool] = True if len(args) == 3: total = self.api.parse_bool(call.args[2]) if total is None: return self.fail_typeddict_arg( - 'TypedDict() "total" argument must be True or False', call) + message_registry.TYPEDDICT_TOTAL_MUST_BE_BOOL, call) dictexpr = args[1] res = self.parse_typeddict_fields_with_types(dictexpr.items, call) if res is None: @@ -328,32 +281,25 @@ def parse_typeddict_fields_with_types( key = field_name_expr.value items.append(key) if key in seen_keys: - self.fail('Duplicate TypedDict key "{}"'.format(key), field_name_expr) + self.fail(message_registry.TYPEDDICT_DUPLICATE_KEY.format(key), field_name_expr) seen_keys.add(key) else: name_context = field_name_expr or field_type_expr - self.fail_typeddict_arg("Invalid TypedDict() field name", name_context) + self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_NAME, name_context) return [], [], False try: type = expr_to_unanalyzed_type(field_type_expr, self.options, self.api.is_stub_file) except TypeTranslationError: - if (isinstance(field_type_expr, CallExpr) and - isinstance(field_type_expr.callee, RefExpr) and - field_type_expr.callee.fullname in TPDICT_NAMES): - self.fail_typeddict_arg( - 'Inline TypedDict types not supported; use assignment to define TypedDict', - field_type_expr) - else: - self.fail_typeddict_arg('Invalid field type', field_type_expr) + self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_TYPE, field_type_expr) return [], [], False - analyzed = self.api.anal_type(type, allow_required=True) + analyzed = self.api.anal_type(type) if analyzed is None: return None types.append(analyzed) return items, types, True - def fail_typeddict_arg(self, message: str, + def fail_typeddict_arg(self, message: ErrorMessage, context: Context) -> Tuple[str, List[str], List[Type], bool, bool]: self.fail(message, context) return '', [], [], True, False @@ -378,8 +324,5 @@ def is_typeddict(self, expr: Expression) -> bool: return (isinstance(expr, RefExpr) and isinstance(expr.node, TypeInfo) and expr.node.typeddict_type is not None) - def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: - self.api.fail(msg, ctx, code=code) - - def note(self, msg: str, ctx: Context) -> None: - self.api.note(msg, ctx) + def fail(self, msg: ErrorMessage, ctx: Context) -> None: + self.api.fail(msg, ctx) From e3e2845d048c3294989ea570109f76f3b62e038c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 16:44:04 +0530 Subject: [PATCH 06/20] Fix porting mistakes --- mypy/semanal.py | 9 +++++- mypy/semanal_typeddict.py | 61 +++++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 962278e3589c..6a744069690c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1548,6 +1548,13 @@ def configure_base_classes(self, elif isinstance(base, Instance): if base.type.is_newtype: self.fail(message_registry.CANNOT_SUBCLASS_NEWTYPE, defn) + if self.enum_has_final_values(base): + # This means that are trying to subclass a non-default + # Enum class, with defined members. This is not possible. + # In runtime, it will raise. We need to mark this type as final. + # However, methods can be defined on a type: only values can't. + # We also don't count values with annotations only. + base.type.is_final = True base_types.append(base) elif isinstance(base, AnyType): if self.options.disallow_subclassing_any: @@ -1563,7 +1570,6 @@ def configure_base_classes(self, extra = '' if name: extra += ' "{}"'.format(name) - self.fail(msg.format(extra), base_expr) info.fallback_to_any = True if self.options.disallow_any_unimported and has_any_from_unimported_type(base): @@ -1948,6 +1954,7 @@ def report_missing_module_attribute( ) return message = message_registry.MODULE_MISSING_ATTIRBUTE + suggestion = '' # Suggest alternatives, if any match is found. module = self.modules.get(import_id) if module: diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 137f023ff248..f65a981ace88 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -4,7 +4,9 @@ from typing import Optional, List, Set, Tuple from typing_extensions import Final -from mypy.types import Type, AnyType, TypeOfAny, TypedDictType, TPDICT_NAMES +from mypy.types import ( + Type, AnyType, TypeOfAny, TypedDictType, TPDICT_NAMES, RequiredType, +) from mypy.nodes import ( CallExpr, TypedDictExpr, Expression, NameExpr, Context, StrExpr, BytesExpr, UnicodeExpr, ClassDef, RefExpr, TypeInfo, AssignmentStmt, PassStmt, ExpressionStmt, EllipsisExpr, TempNode, @@ -65,12 +67,28 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ defn.analyzed.line = defn.line defn.analyzed.column = defn.column return True, info + + # Extending/merging existing TypedDicts - if any(not isinstance(expr, RefExpr) or - expr.fullname not in TPDICT_NAMES and - not self.is_typeddict(expr) for expr in defn.base_type_exprs): - self.fail(message_registry.TYPEDDICT_BASES_MUST_BE_TYPEDDICTS, defn) - typeddict_bases = list(filter(self.is_typeddict, defn.base_type_exprs)) + typeddict_bases = [] + typeddict_bases_set = set() + for expr in defn.base_type_exprs: + if isinstance(expr, RefExpr) and expr.fullname in TPDICT_NAMES: + if 'TypedDict' not in typeddict_bases_set: + typeddict_bases_set.add('TypedDict') + else: + self.fail(message_registry.DUPLICATE_BASE_CLASS.format('TypedDict'), defn) + elif isinstance(expr, RefExpr) and self.is_typeddict(expr): + assert expr.fullname + if expr.fullname not in typeddict_bases_set: + typeddict_bases_set.add(expr.fullname) + typeddict_bases.append(expr) + else: + assert isinstance(expr.node, TypeInfo) + self.fail(message_registry.DUPLICATE_BASE_CLASS.format(expr.node.name), + defn) + else: + self.fail(message_registry.TYPEDDICT_BASES_MUST_BE_TYPEDDICTS, defn) keys: List[str] = [] types = [] required_keys = set() @@ -161,7 +179,22 @@ def analyze_typeddict_classdef_fields( if total is None: self.fail(message_registry.TYPEDDICT_TOTAL_MUST_BE_BOOL_2, defn) total = True - required_keys = set(fields) if total else set() + required_keys = { + field + for (field, t) in zip(fields, types) + if (total or ( + isinstance(t, RequiredType) and # type: ignore[misc] + t.required + )) and not ( + isinstance(t, RequiredType) and # type: ignore[misc] + not t.required + ) + } + types = [ # unwrap Required[T] to just T + t.item if isinstance(t, RequiredType) else t # type: ignore[misc] + for t in types + ] + return fields, types, required_keys def check_typeddict(self, @@ -291,9 +324,16 @@ def parse_typeddict_fields_with_types( type = expr_to_unanalyzed_type(field_type_expr, self.options, self.api.is_stub_file) except TypeTranslationError: - self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_TYPE, field_type_expr) + if (isinstance(field_type_expr, CallExpr) and + isinstance(field_type_expr.callee, RefExpr) and + field_type_expr.callee.fullname in TPDICT_NAMES): + self.fail_typeddict_arg( + 'Inline TypedDict types not supported; use assignment to define TypedDict', + field_type_expr) + else: + self.fail_typeddict_arg('Invalid field type', field_type_expr) return [], [], False - analyzed = self.api.anal_type(type) + analyzed = self.api.anal_type(type, allow_required=True) if analyzed is None: return None types.append(analyzed) @@ -326,3 +366,6 @@ def is_typeddict(self, expr: Expression) -> bool: def fail(self, msg: ErrorMessage, ctx: Context) -> None: self.api.fail(msg, ctx) + + def note(self, msg: str, ctx: Context) -> None: + self.api.note(msg, ctx) From c5215ca7067a9bf36479dc7311d562cf5a389d29 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 16:50:33 +0530 Subject: [PATCH 07/20] More porting fixes --- mypy/message_registry.py | 4 +++- mypy/semanal_typeddict.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 1208e22325f0..e99a05b05b58 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -536,7 +536,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": TYPEDDICT_DUPLICATE_KEY: Final = ErrorMessage('Duplicate TypedDict key "{}"') TYPEDDICT_INVALID_FIELD_NAME: Final = ErrorMessage("Invalid TypedDict() field name") TYPEDDICT_INVALID_FIELD_TYPE: Final = ErrorMessage("Invalid field type") - +TYPEDDICT_INLINE_UNSUPPORTED: Final = ErrorMessage( + 'Inline TypedDict types not supported; use assignment to define TypedDict' +) # Type Analysis TYPEANAL_INTERNAL_ERROR: Final = ErrorMessage("Internal error (node is None, kind={})") NOT_SUBSCRIPTABLE: Final = ErrorMessage('"{}" is not subscriptable') diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index f65a981ace88..796a926baa22 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -328,10 +328,10 @@ def parse_typeddict_fields_with_types( isinstance(field_type_expr.callee, RefExpr) and field_type_expr.callee.fullname in TPDICT_NAMES): self.fail_typeddict_arg( - 'Inline TypedDict types not supported; use assignment to define TypedDict', + message_registry.TYPEDDICT_INLINE_UNSUPPORTED, field_type_expr) else: - self.fail_typeddict_arg('Invalid field type', field_type_expr) + self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_TYPE, field_type_expr) return [], [], False analyzed = self.api.anal_type(type, allow_required=True) if analyzed is None: From e4ead79a3ec271fad7f4bc0c089e437c79680f84 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 16:55:21 +0530 Subject: [PATCH 08/20] Remove unnecessary changes --- mypy/semanal_typeddict.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 796a926baa22..46e1a00ba255 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -92,7 +92,6 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ keys: List[str] = [] types = [] required_keys = set() - # Iterate over bases in reverse order so that leftmost base class' keys take precedence for base in reversed(typeddict_bases): assert isinstance(base, RefExpr) @@ -163,7 +162,7 @@ def analyze_typeddict_classdef_fields( if stmt.type is None: types.append(AnyType(TypeOfAny.unannotated)) else: - analyzed = self.api.anal_type(stmt.type) + analyzed = self.api.anal_type(stmt.type, allow_required=True) if analyzed is None: return None, [], set() # Need to defer types.append(analyzed) @@ -238,7 +237,21 @@ def check_typeddict(self, if name != var_name or is_func_scope: # Give it a unique name derived from the line number. name += '@' + str(call.line) - required_keys = set(items) if total else set() + required_keys = { + field + for (field, t) in zip(items, types) + if (total or ( + isinstance(t, RequiredType) and # type: ignore[misc] + t.required + )) and not ( + isinstance(t, RequiredType) and # type: ignore[misc] + not t.required + ) + } + types = [ # unwrap Required[T] to just T + t.item if isinstance(t, RequiredType) else t # type: ignore[misc] + for t in types + ] info = self.build_typeddict_typeinfo(name, items, types, required_keys, call.line) info.line = node.line # Store generated TypeInfo under both names, see semanal_namedtuple for more details. From afd8f8ded2b2a05027c24f57e1454a4b77e0ca0f Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 16:58:12 +0530 Subject: [PATCH 09/20] Plugins still use the old call signature --- mypy/semanal.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6a744069690c..9c94055e9cd9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5155,10 +5155,11 @@ def fail(self, return # In case it's a bug and we don't really have context assert ctx is not None, msg - assert isinstance(msg, ErrorMessage) - - self.errors.report(ctx.get_line(), ctx.get_column(), msg.value, code=msg.code, - blocker=blocker) + if isinstance(msg, ErrorMessage): + self.errors.report(ctx.get_line(), ctx.get_column(), msg.value, code=msg.code, + blocker=blocker) + return + self.errors.report(ctx.get_line(), ctx.get_column(), msg, blocker=blocker, code=code) def note(self, msg: str, ctx: Context, code: Optional[ErrorCode] = None) -> None: if not self.in_checked_function(): From a9d00e6d4bd47e1129189b5fafbbc63c29416afa Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 17:12:52 +0530 Subject: [PATCH 10/20] Formatting --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 9c94055e9cd9..b98cc37fa478 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5157,7 +5157,7 @@ def fail(self, assert ctx is not None, msg if isinstance(msg, ErrorMessage): self.errors.report(ctx.get_line(), ctx.get_column(), msg.value, code=msg.code, - blocker=blocker) + blocker=blocker) return self.errors.report(ctx.get_line(), ctx.get_column(), msg, blocker=blocker, code=code) From bbaa5193b84b76142914dd2d66934e68f414e42d Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 18:26:22 +0530 Subject: [PATCH 11/20] Small fixes --- mypy/message_registry.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index e99a05b05b58..a3a1ed5ab9e1 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -221,7 +221,8 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": "The implementation for an overloaded function must come last" ) OVERLOAD_IMPLEMENTATION_REQUIRED: Final = ErrorMessage( - "An overloaded function outside a stub file must have an implementation" + "An overloaded function outside a stub file must have an implementation", + codes.NO_OVERLOAD_IMPL, ) FINAL_DEC_ON_OVERLOAD_ONLY: Final = ErrorMessage( "@final should be applied only to overload implementation" @@ -343,7 +344,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": 'String argument 1 "{}" to {}(...) does not match variable name "{}"' ) TYPEVAR_UNEXPECTED_ARG: Final = ErrorMessage("Unexpected argument to TypeVar()") -TYPEVAR_UNEXPECTED_ARG_NAMED: Final = ErrorMessage('Unexpected argument to TypeVar(): "{}"') +TYPEVAR_UNEXPECTED_ARG_NAMED: Final = ErrorMessage('Unexpected argument to "TypeVar()": "{}"') TYPEVAR_VALUE_WITH_BOUND_DISALLOWED: Final = ErrorMessage( "TypeVar cannot have both values and an upper bound" ) @@ -461,10 +462,10 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": '"{}()" expects a string literal as the first argument' ) NAMEDTUPLE_ARG_EXPECTED_LIST_TUPLE: Final = ErrorMessage( - "List or tuple literal expected as the second argument to namedtuple()" + 'List or tuple literal expected as the second argument to "{}()"' ) NAMEDTUPLE_EXPECTED_STRING_LITERAL: Final = ErrorMessage( - "String literal expected as namedtuple() item" + 'String literal expected as "namedtuple()" item' ) NAMEDTUPLE_FIELDS_NO_UNDERSCORE: Final = ErrorMessage( '"{}()" field names cannot start with an underscore: {}' @@ -473,7 +474,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": NAMEDTUPLE_INVALID_FIELD_DEFINITION: Final = ErrorMessage("Invalid NamedTuple field definition") NAMEDTUPLE_INVALID_FIELD_NAME: Final = ErrorMessage("Invalid NamedTuple() field name") NAMEDTUPLE_INVALID_FIELD_TYPE: Final = ErrorMessage("Invalid field type") -NAMEDTUPLE_TUPLE_EXPECTED: Final = ErrorMessage("Tuple expected as NamedTuple() field") +NAMEDTUPLE_TUPLE_EXPECTED: Final = ErrorMessage('Tuple expected as "NamedTuple()" field') NAMEDTUPLE_CANNOT_OVERWRITE_ATTRIBUTE: Final = ErrorMessage( 'Cannot overwrite NamedTuple attribute "{}"' ) From ea4fe578d7af1886be323cf75947f4606c059b12 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 18:27:59 +0530 Subject: [PATCH 12/20] Another small fix --- mypy/message_registry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index a3a1ed5ab9e1..ca6c95324343 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -280,7 +280,8 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": 'Module "{}" has no attribute "{}"{}', codes.ATTR_DEFINED ) NO_IMPLICIT_REEXPORT: Final = ErrorMessage( - 'Module "{}" does not explicitly export attribute "{}"; implicit reexport disabled' + 'Module "{}" does not explicitly export attribute "{}"; implicit reexport disabled', + codes.ATTR_DEFINED ) INCORRECT_RELATIVE_IMPORT: Final = ErrorMessage("Relative import climbs too many namespaces") INVALID_TYPE_ALIAS_TARGET: Final = ErrorMessage( From cf4a8bd7b9884b1a28fbeacf41d13ea181609c69 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 18:36:49 +0530 Subject: [PATCH 13/20] Fix failing tests --- mypy/errorcodes.py | 26 +++++++++++++++++--------- mypy/semanal_newtype.py | 1 - mypy/semanal_typeddict.py | 15 +++++++-------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index ba716608ae56..79dff526ca59 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -28,8 +28,10 @@ def __str__(self) -> str: return ''.format(self.code) -ATTR_DEFINED: Final = ErrorCode("attr-defined", "Check that attribute exists", "General") -NAME_DEFINED: Final = ErrorCode("name-defined", "Check that name is defined", "General") +ATTR_DEFINED: Final[ErrorCode] = ErrorCode( + "attr-defined", "Check that attribute exists", "General" +) +NAME_DEFINED: Final[ErrorCode] = ErrorCode("name-defined", "Check that name is defined", "General") CALL_ARG: Final[ErrorCode] = ErrorCode( "call-arg", "Check number, names and kinds of arguments in calls", "General" ) @@ -37,7 +39,9 @@ def __str__(self) -> str: CALL_OVERLOAD: Final = ErrorCode( "call-overload", "Check that an overload variant matches arguments", "General" ) -VALID_TYPE: Final = ErrorCode("valid-type", "Check that type (annotation) is valid", "General") +VALID_TYPE: Final[ErrorCode] = ErrorCode( + "valid-type", "Check that type (annotation) is valid", "General" +) VAR_ANNOTATED: Final = ErrorCode( "var-annotated", "Require variable annotation if type can't be inferred", "General" ) @@ -53,7 +57,9 @@ def __str__(self) -> str: ASSIGNMENT: Final = ErrorCode( "assignment", "Check that assigned value is compatible with target", "General" ) -TYPE_ARG: Final = ErrorCode("type-arg", "Check that generic type arguments are present", "General") +TYPE_ARG: Final[ErrorCode] = ErrorCode( + "type-arg", "Check that generic type arguments are present", "General" +) TYPE_VAR: Final = ErrorCode("type-var", "Check that type variable values are valid", "General") UNION_ATTR: Final = ErrorCode( "union-attr", "Check that attribute exists in each item of a union", "General" @@ -75,14 +81,16 @@ def __str__(self) -> str: IMPORT: Final = ErrorCode( "import", "Require that imported module can be found or has stubs", "General" ) -NO_REDEF: Final = ErrorCode("no-redef", "Check that each name is defined once", "General") +NO_REDEF: Final[ErrorCode] = ErrorCode( + "no-redef", "Check that each name is defined once", "General" +) FUNC_RETURNS_VALUE: Final = ErrorCode( "func-returns-value", "Check that called function returns a value in value context", "General" ) ABSTRACT: Final = ErrorCode( "abstract", "Prevent instantiation of classes with abstract attributes", "General" ) -VALID_NEWTYPE: Final = ErrorCode( +VALID_NEWTYPE: Final[ErrorCode] = ErrorCode( "valid-newtype", "Check that argument 2 to NewType is valid", "General" ) STRING_FORMATTING: Final = ErrorCode( @@ -133,10 +141,10 @@ def __str__(self) -> str: "General", default_enabled=False, ) -NAME_MATCH: Final = ErrorCode( +NAME_MATCH: Final[ErrorCode] = ErrorCode( "name-match", "Check that type definition has consistent naming", "General" ) -NO_OVERLOAD_IMPL: Final = ErrorCode( +NO_OVERLOAD_IMPL: Final[ErrorCode] = ErrorCode( "no-overload-impl", "Check that overloaded functions outside stub files have an implementation", "General", @@ -144,7 +152,7 @@ def __str__(self) -> str: # Syntax errors are often blocking. -SYNTAX: Final = ErrorCode("syntax", "Report syntax errors", "General") +SYNTAX: Final[ErrorCode] = ErrorCode("syntax", "Report syntax errors", "General") # This is a catch-all for remaining uncategorized errors. MISC: Final = ErrorCode("misc", "Miscellaneous other checks", "General") diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index dc49f0f1d23f..3dc359cae22a 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -21,7 +21,6 @@ from mypy.messages import MessageBuilder, format_type from mypy import message_registry from mypy.message_registry import ErrorMessage -from mypy import errorcodes as codes class NewTypeAnalyzer: diff --git a/mypy/semanal_typeddict.py b/mypy/semanal_typeddict.py index 46e1a00ba255..625c5e6b7cb6 100644 --- a/mypy/semanal_typeddict.py +++ b/mypy/semanal_typeddict.py @@ -2,7 +2,6 @@ from mypy.backports import OrderedDict from typing import Optional, List, Set, Tuple -from typing_extensions import Final from mypy.types import ( Type, AnyType, TypeOfAny, TypedDictType, TPDICT_NAMES, RequiredType, @@ -18,9 +17,7 @@ from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type from mypy.messages import MessageBuilder from mypy.message_registry import ErrorMessage -from mypy import message_registry, errorcodes as codes - - +from mypy import message_registry class TypedDictAnalyzer: @@ -68,7 +65,6 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> Tuple[bool, Optional[Typ defn.analyzed.column = defn.column return True, info - # Extending/merging existing TypedDicts typeddict_bases = [] typeddict_bases_set = set() @@ -327,11 +323,13 @@ def parse_typeddict_fields_with_types( key = field_name_expr.value items.append(key) if key in seen_keys: - self.fail(message_registry.TYPEDDICT_DUPLICATE_KEY.format(key), field_name_expr) + self.fail(message_registry.TYPEDDICT_DUPLICATE_KEY.format(key), + field_name_expr) seen_keys.add(key) else: name_context = field_name_expr or field_type_expr - self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_NAME, name_context) + self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_NAME, + name_context) return [], [], False try: type = expr_to_unanalyzed_type(field_type_expr, self.options, @@ -344,7 +342,8 @@ def parse_typeddict_fields_with_types( message_registry.TYPEDDICT_INLINE_UNSUPPORTED, field_type_expr) else: - self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_TYPE, field_type_expr) + self.fail_typeddict_arg(message_registry.TYPEDDICT_INVALID_FIELD_TYPE, + field_type_expr) return [], [], False analyzed = self.api.anal_type(type, allow_required=True) if analyzed is None: From ab6c756f2097a8f3a3aa51f138545a1abfb0d621 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 19:19:00 +0530 Subject: [PATCH 14/20] Fix weird failing test --- mypy/typeanal.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f7d4437b416d..e83797c7ed41 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,9 @@ from contextlib import contextmanager from mypy.backports import OrderedDict -from typing import Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable, Sequence +from typing import ( + Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable, Sequence, Union +) from typing_extensions import Final from mypy_extensions import DefaultNamedArg @@ -925,7 +927,7 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L def analyze_type(self, t: Type) -> Type: return t.accept(self) - def fail(self, msg: ErrorMessage, ctx: Context) -> None: + def fail(self, msg: Union[str, ErrorMessage], ctx: Context) -> None: self.fail_func(msg, ctx) def note(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None: From a396e4d4c3f86dbd76f7f210684be8776540cfa3 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 18 Jan 2022 19:25:33 +0530 Subject: [PATCH 15/20] Fix interface --- mypy/semanal_shared.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 388ca5006724..84c0b79a8e9c 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -2,7 +2,7 @@ from abc import abstractmethod -from typing import Optional, List, Callable +from typing import Optional, List, Callable, Union from typing_extensions import Final from mypy_extensions import trait @@ -46,8 +46,8 @@ def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode] raise NotImplementedError @abstractmethod - def fail(self, msg: ErrorMessage, ctx: Context, serious: bool = False, *, - blocker: bool = False) -> None: + def fail(self, msg: Union[str, ErrorMessage], ctx: Context, serious: bool = False, *, + blocker: bool = False, code: Optional[ErrorCode] = None) -> None: raise NotImplementedError @abstractmethod From 0a42056c55a6ec9814b003ede619144564d017d3 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 20 Jan 2022 13:10:58 +0530 Subject: [PATCH 16/20] Add todos --- mypy/plugin.py | 1 + mypy/semanal.py | 1 + mypy/semanal_shared.py | 1 + mypy/typeanal.py | 1 + 4 files changed, 4 insertions(+) diff --git a/mypy/plugin.py b/mypy/plugin.py index 9f74cb353bbb..cfac2e85bc04 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -223,6 +223,7 @@ def type_context(self) -> List[Optional[Type]]: """Return the type context of the plugin""" raise NotImplementedError + # TODO(tushar): remove `str` type and `code` property from here @abstractmethod def fail(self, msg: Union[str, ErrorMessage], ctx: Context, *, code: Optional[ErrorCode] = None) -> None: diff --git a/mypy/semanal.py b/mypy/semanal.py index 2ea1c9ae20da..8b63d8e6c354 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5118,6 +5118,7 @@ def in_checked_function(self) -> bool: # no regular functions. return True + # TODO(tushar): remove `str` type and `code` property from here def fail(self, msg: Union[str, ErrorMessage], ctx: Context, diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 84c0b79a8e9c..337bc0f50a16 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -45,6 +45,7 @@ def lookup_fully_qualified(self, name: str) -> SymbolTableNode: def lookup_fully_qualified_or_none(self, name: str) -> Optional[SymbolTableNode]: raise NotImplementedError + # TODO(tushar): remove `str` type and `code` property from here @abstractmethod def fail(self, msg: Union[str, ErrorMessage], ctx: Context, serious: bool = False, *, blocker: bool = False, code: Optional[ErrorCode] = None) -> None: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e83797c7ed41..7b114af26dc9 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -927,6 +927,7 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L def analyze_type(self, t: Type) -> Type: return t.accept(self) + # TODO(tushar): remove `str` type and `code` property from here def fail(self, msg: Union[str, ErrorMessage], ctx: Context) -> None: self.fail_func(msg, ctx) From 0039d3d8393e7f3a9f872a24b5cef1cd6b34e653 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Mon, 7 Feb 2022 16:34:51 +0530 Subject: [PATCH 17/20] Update mypy/semanal.py --- mypy/semanal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f82fa53cc834..b0b11796659a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3830,8 +3830,11 @@ def visit_star_expr(self, expr: StarExpr) -> None: expr.expr.accept(self) def visit_yield_from_expr(self, e: YieldFromExpr) -> None: - if not self.is_func_scope(): # not sure + if not self.is_func_scope(): self.fail(message_registry.YIELD_FROM_OUTSIDE_FUNC, e, serious=True, blocker=True) + elif self.is_comprehension_stack[-1]: + self.fail('"yield from" inside comprehension or generator expression', + e, serious=True, blocker=True) elif self.function_stack[-1].is_coroutine: self.fail(message_registry.YIELD_FROM_IN_ASYNC_FUNC, e, serious=True, blocker=True) else: From 07dadfeaf2f5f3e0a2ebd563afc1fc17c598bcbc Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Mon, 7 Feb 2022 17:17:25 +0530 Subject: [PATCH 18/20] Fix failing test --- mypy/message_registry.py | 6 ++++++ mypy/semanal.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index ca6c95324343..fb9c63f64357 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -401,6 +401,12 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": YIELD_FROM_OUTSIDE_FUNC: Final = ErrorMessage('"yield from" outside function') YIELD_IN_ASYNC_FUNC: Final = ErrorMessage('"yield" in async function') YIELD_FROM_IN_ASYNC_FUNC: Final = ErrorMessage('"yield from" in async function') +YIELD_IN_LISTCOMP_GENEXP: Final = ErrorMessage( + '"yield" inside comprehension or generator expression' +) +YIELD_FROM_IN_LISTCOMP_GENEXP: Final = ErrorMessage( + '"yield from" inside comprehension or generator expression' +) CAST_TARGET_IS_NOT_TYPE: Final = ErrorMessage('Cast target is not a type') ANY_CALL_UNSUPPORTED: Final = ErrorMessage( 'Any(...) is no longer supported. Use cast(Any, ...) instead' diff --git a/mypy/semanal.py b/mypy/semanal.py index b0b11796659a..07144e9e97ca 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3833,7 +3833,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> None: if not self.is_func_scope(): self.fail(message_registry.YIELD_FROM_OUTSIDE_FUNC, e, serious=True, blocker=True) elif self.is_comprehension_stack[-1]: - self.fail('"yield from" inside comprehension or generator expression', + self.fail(message_registry.YIELD_FROM_IN_LISTCOMP_GENEXP, e, serious=True, blocker=True) elif self.function_stack[-1].is_coroutine: self.fail(message_registry.YIELD_FROM_IN_ASYNC_FUNC, e, serious=True, blocker=True) @@ -4218,7 +4218,7 @@ def visit_yield_expr(self, e: YieldExpr) -> None: if not self.is_func_scope(): self.fail(message_registry.YIELD_OUTSIDE_FUNC, e, serious=True, blocker=True) elif self.is_comprehension_stack[-1]: - self.fail(message_registry.YIELD_IN_ASYNC_FUNC, e, serious=True, blocker=True) + self.fail(message_registry.YIELD_IN_LISTCOMP_GENEXP, e, serious=True, blocker=True) elif self.function_stack[-1].is_coroutine: if self.options.python_version < (3, 6): self.fail(message_registry.YIELD_IN_ASYNC_FUNC, e, serious=True, blocker=True) From f2d0b3bba6015fe6d3d9f3f6fdb7a506b3500738 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 22 Feb 2022 14:50:16 +0530 Subject: [PATCH 19/20] Remove duplicate line --- mypy/typeanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index dd81795ba832..937091b72a39 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -490,7 +490,6 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl message = message_registry.VARIABLE_NOT_VALID_TYPE elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)): message = message_registry.FUNCTION_NOT_VALID_TYPE - notes.append('Perhaps you need "Callable[...]" or a callback protocol?') if name == 'builtins.any': notes.append('Perhaps you meant "typing.Any" instead of "any"?') elif name == 'builtins.callable': From 6ea2f651638d8aa859ff00cb74bcaaf0c9c406fb Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Mon, 25 Apr 2022 13:42:48 +0530 Subject: [PATCH 20/20] Fix type errors --- mypy/fastparse.py | 3 +-- mypy/message_registry.py | 6 ++++++ mypy/semanal.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 490dd463ea39..a8b292257076 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -349,11 +349,10 @@ def fail(self, def fail_merge_overload(self, node: IfStmt) -> None: self.fail( - "Condition can't be inferred, unable to merge overloads", + message_registry.FAILED_TO_MERGE_OVERLOADS, line=node.line, column=node.column, blocker=False, - code=codes.MISC, ) def visit(self, node: Optional[AST]) -> Any: diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 1cd4f7916946..47ceae4da502 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -203,6 +203,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": EXCEPT_EXPR_NOTNAME_UNSUPPORTED: Final = ErrorMessage( 'Sorry, "except , " is not supported', codes.SYNTAX ) +FAILED_TO_MERGE_OVERLOADS: Final = ErrorMessage( + "Condition can't be inferred, unable to merge overloads", +) # Nodes DUPLICATE_ARGUMENT_IN_X: Final = ErrorMessage('Duplicate argument "{}" in {}') @@ -343,6 +346,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": TUPLE_TYPE_EXPECTED: Final = ErrorMessage('Tuple type expected for multiple variables') CANNOT_DECLARE_TYPE_OF_TYPEVAR: Final = ErrorMessage("Cannot declare the type of a type variable") REDEFINE_AS_TYPEVAR: Final = ErrorMessage('Cannot redefine "{}" as a type variable') +CANNOT_REDEFINE_TYPEVAR_TYPE: Final = ErrorMessage( + "Cannot declare the type of a TypeVar or similar construct" +) TYPEVAR_CALL_TOO_FEW_ARGS: Final = ErrorMessage("Too few arguments for {}()") TYPEVAR_CALL_EXPECTED_STRING_LITERAL: Final = ErrorMessage( "{}() expects a string literal as first argument" diff --git a/mypy/semanal.py b/mypy/semanal.py index b445f6e8c2c9..7406125965b1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3276,7 +3276,7 @@ def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> Optiona lvalue = s.lvalues[0] assert isinstance(lvalue, NameExpr) if s.type: - self.fail("Cannot declare the type of a TypeVar or similar construct", s) + self.fail(message_registry.CANNOT_REDEFINE_TYPEVAR_TYPE, s) return None if not self.check_typevarlike_name(call, lvalue.name, s):