From cc59b563ce63d80c61ff8ddcbb410fd1f1dbcdcc Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Wed, 7 Sep 2022 20:37:37 -0700 Subject: [PATCH] typeanal: add error codes for many errors (#13550) --- mypy/errorcodes.py | 4 +- mypy/message_registry.py | 4 +- mypy/typeanal.py | 128 +++++++++++++++++++-------- test-data/unit/check-errorcodes.test | 3 - 4 files changed, 98 insertions(+), 41 deletions(-) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 0d25e88c45db..88bd25262e8b 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -33,7 +33,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" ) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 78a7ec2e8382..6dfc11be5c12 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -26,7 +26,9 @@ def with_additional_msg(self, info: str) -> 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?", codes.VALID_TYPE +) # Type checker error message constants NO_RETURN_VALUE_EXPECTED: Final = ErrorMessage("No return value expected", codes.RETURN_VALUE) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 5af12a5b68c8..37f00841562f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -138,7 +138,7 @@ def analyze_type_alias( 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("Invalid type alias: expression is not a valid type", node, code=codes.VALID_TYPE) return None analyzer = TypeAnalyser( api, @@ -281,11 +281,13 @@ 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(f'ParamSpec "{t.name}" is unbound', t) + self.fail(f'ParamSpec "{t.name}" is unbound', t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, ParamSpecType) if len(t.args) > 0: - self.fail(f'ParamSpec "{t.name}" used with arguments', t) + self.fail( + f'ParamSpec "{t.name}" used with arguments', t, code=codes.VALID_TYPE + ) # Change the line number return ParamSpecType( tvar_def.name, @@ -298,15 +300,17 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) ) 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), + f'Can\'t use bound type variable "{t.name}" to define generic alias', t, + code=codes.VALID_TYPE, ) 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(f'Type variable "{t.name}" used with arguments', t) + self.fail( + f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE + ) # Change the line number return TypeVarType( tvar_def.name, @@ -322,18 +326,20 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) tvar_def is not None and self.defining_alias ): self.fail( - 'Can\'t use bound type variable "{}"' - " to define generic alias".format(t.name), + f'Can\'t use bound type variable "{t.name}" to define generic alias', t, + code=codes.VALID_TYPE, ) return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarTupleExpr): if tvar_def is None: - self.fail(f'TypeVarTuple "{t.name}" is unbound', t) + self.fail(f'TypeVarTuple "{t.name}" is unbound', t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) assert isinstance(tvar_def, TypeVarTupleType) if len(t.args) > 0: - self.fail(f'Type variable "{t.name}" used with arguments', t) + self.fail( + f'Type variable "{t.name}" used with arguments', t, code=codes.VALID_TYPE + ) # Change the line number return TypeVarTupleType( tvar_def.name, @@ -401,19 +407,23 @@ def cannot_resolve_type(self, t: UnboundType) -> None: def apply_concatenate_operator(self, t: UnboundType) -> Type: if len(t.args) == 0: - self.api.fail("Concatenate needs type arguments", t) + self.api.fail("Concatenate needs type arguments", t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) # last argument has to be ParamSpec ps = self.anal_type(t.args[-1], allow_param_spec=True) if not isinstance(ps, ParamSpecType): - self.api.fail("The last parameter to Concatenate needs to be a ParamSpec", t) + self.api.fail( + "The last parameter to Concatenate needs to be a ParamSpec", + t, + code=codes.VALID_TYPE, + ) return AnyType(TypeOfAny.from_error) # TODO: this may not work well with aliases, if those worked. # Those should be special-cased. elif ps.prefix.arg_types: - self.api.fail("Nested Concatenates are invalid", t) + self.api.fail("Nested Concatenates are invalid", t, code=codes.VALID_TYPE) args = self.anal_array(t.args[:-1]) pre = ps.prefix @@ -437,7 +447,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ 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 + "Final can be only used as an outermost qualifier in a variable annotation", + t, + code=codes.VALID_TYPE, ) return AnyType(TypeOfAny.from_error) elif fullname == "typing.Tuple" or ( @@ -468,7 +480,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ 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( + "Optional[...] must have exactly one type argument", t, code=codes.VALID_TYPE + ) return AnyType(TypeOfAny.from_error) item = self.anal_type(t.args[0]) return make_optional_type(item) @@ -488,19 +502,25 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ 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( + type_str + " must have exactly one type argument", t, code=codes.VALID_TYPE + ) item = self.anal_type(t.args[0]) if bad_type_type_item(item): - self.fail("Type[...] can't contain another Type[...]", t) + self.fail("Type[...] can't contain another Type[...]", t, code=codes.VALID_TYPE) item = AnyType(TypeOfAny.from_error) return TypeType.make_normalized(item, line=t.line, column=t.column) elif fullname == "typing.ClassVar": if self.nesting_level > 0: - self.fail("Invalid type: ClassVar nested inside other type", t) + self.fail( + "Invalid type: ClassVar nested inside other type", t, code=codes.VALID_TYPE + ) 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( + "ClassVar[...] must have at most one type argument", t, code=codes.VALID_TYPE + ) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) elif fullname in NEVER_NAMES: @@ -513,23 +533,36 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ "Annotated[...] must have exactly one type argument" " and at least one annotation", t, + code=codes.VALID_TYPE, ) 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( + "Required[] can be only used in a TypedDict definition", + t, + code=codes.VALID_TYPE, + ) return AnyType(TypeOfAny.from_error) if len(t.args) != 1: - self.fail("Required[] must have exactly one type argument", t) + self.fail( + "Required[] must have exactly one type argument", t, code=codes.VALID_TYPE + ) 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( + "NotRequired[] can be only used in a TypedDict definition", + t, + code=codes.VALID_TYPE, + ) return AnyType(TypeOfAny.from_error) if len(t.args) != 1: - self.fail("NotRequired[] must have exactly one type argument", t) + self.fail( + "NotRequired[] must have exactly one type argument", t, code=codes.VALID_TYPE + ) 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: @@ -627,7 +660,11 @@ def analyze_type_with_type_info( ) if info.fullname == "types.NoneType": - self.fail("NoneType should not be used as a type, please use None instead", ctx) + self.fail( + "NoneType should not be used as a type, please use None instead", + ctx, + code=codes.VALID_TYPE, + ) return NoneType(ctx.line, ctx.column) return instance @@ -680,7 +717,7 @@ def analyze_unbound_type_without_type_info( msg = message_registry.INVALID_TYPE_RAW_ENUM_VALUE.format( base_enum_short_name, value ) - self.fail(msg, t) + self.fail(msg.value, t, code=msg.code) return AnyType(TypeOfAny.from_error) return LiteralType( value=value, @@ -763,12 +800,14 @@ def visit_type_list(self, t: TypeList) -> Type: else: return AnyType(TypeOfAny.from_error) else: - self.fail('Bracketed expression "[...]" is not valid as a type', t) + self.fail( + 'Bracketed expression "[...]" is not valid as a type', t, code=codes.VALID_TYPE + ) 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("Invalid type", t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) def visit_instance(self, t: Instance) -> Type: @@ -831,7 +870,9 @@ def anal_type_guard(self, t: Type) -> Type | None: def anal_type_guard_arg(self, t: UnboundType, fullname: str) -> Type | None: 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( + "TypeGuard must have exactly one type argument", t, code=codes.VALID_TYPE + ) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) return None @@ -848,11 +889,19 @@ def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type: if kind == ARG_STAR: make_paramspec = paramspec_args if components[-1] != "args": - self.fail(f'Use "{tvar_name}.args" for variadic "*" parameter', t) + self.fail( + f'Use "{tvar_name}.args" for variadic "*" parameter', + t, + code=codes.VALID_TYPE, + ) elif kind == ARG_STAR2: make_paramspec = paramspec_kwargs if components[-1] != "kwargs": - self.fail(f'Use "{tvar_name}.kwargs" for variadic "**" parameter', t) + self.fail( + f'Use "{tvar_name}.kwargs" for variadic "**" parameter', + t, + code=codes.VALID_TYPE, + ) else: assert False, kind return make_paramspec( @@ -966,7 +1015,7 @@ def visit_union_type(self, t: UnionType) -> Type: 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("X | Y syntax for unions requires Python 3.10", t, code=codes.SYNTAX) return UnionType(self.anal_array(t.items), t.line) def visit_partial_type(self, t: PartialType) -> Type: @@ -1096,6 +1145,7 @@ def analyze_callable_type(self, t: UnboundType) -> Type: "The first argument to Callable must be a " 'list of types, parameter specification, or "..."', t, + code=codes.VALID_TYPE, ) self.note( "See https://mypy.readthedocs.io/en/stable/kinds_of_types.html#callable-types-and-lambdas", # noqa: E501 @@ -1149,7 +1199,7 @@ def analyze_callable_args( 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("Literal[...] must have at least one parameter", t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) output: list[Type] = [] @@ -1210,7 +1260,7 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] msg = f'Parameter {idx} of Literal[...] cannot be of type "{name}"' else: msg = "Invalid type: Literal[...] cannot contain arbitrary expressions" - self.fail(msg, ctx) + self.fail(msg, ctx, code=codes.VALID_TYPE) # 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[...]. return None @@ -1296,7 +1346,11 @@ def bind_function_type_variables( defs: list[TypeVarLikeType] = [] for name, tvar in typevars: if not self.tvar_scope.allow_binding(tvar.fullname): - self.fail(f'Type variable "{name}" is bound by an outer class', defn) + self.fail( + f'Type variable "{name}" is bound by an outer class', + defn, + code=codes.VALID_TYPE, + ) self.tvar_scope.bind_new(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname) assert binding is not None @@ -1335,10 +1389,12 @@ def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = Fa and analyzed.flavor == ParamSpecFlavor.BARE ): if analyzed.prefix.arg_types: - self.fail("Invalid location for Concatenate", t) + self.fail("Invalid location for Concatenate", t, code=codes.VALID_TYPE) self.note("You can use Concatenate as the first argument to Callable", t) else: - self.fail(f'Invalid location for ParamSpec "{analyzed.name}"', t) + self.fail( + f'Invalid location for ParamSpec "{analyzed.name}"', t, code=codes.VALID_TYPE + ) self.note( "You can use ParamSpec as the first argument to Callable, e.g., " "'Callable[{}, int]'".format(analyzed.name), diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index a599a6e75418..24684c84b76d 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -632,9 +632,6 @@ def g() -> int: [case testErrorCodeIgnoreNamedDefinedNote] x: List[int] # type: ignore[name-defined] -[case testErrorCodeIgnoreMiscNote] -x: [int] # type: ignore[misc] - [case testErrorCodeProtocolProblemsIgnore] from typing_extensions import Protocol