Skip to content

Commit

Permalink
typeanal: add error codes for many errors (#13550)
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja authored Sep 8, 2022
1 parent 91a6613 commit cc59b56
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 41 deletions.
4 changes: 3 additions & 1 deletion mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
4 changes: 3 additions & 1 deletion mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
128 changes: 92 additions & 36 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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 (
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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] = []
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 0 additions & 3 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit cc59b56

Please sign in to comment.