Skip to content

Commit 7fb7e26

Browse files
authored
Add error codes to many additional messages (#7334)
This adds error codes to the most common messages seen at Dropbox plus a few others that seem useful. There are still some others I'm planning to add, but this should cover the essentials.
1 parent 2002ae1 commit 7fb7e26

12 files changed

+583
-91
lines changed

mypy/build.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from mypy.renaming import VariableRenameVisitor
5858
from mypy.config_parser import parse_mypy_comments
5959
from mypy.freetree import free_tree
60+
from mypy import errorcodes as codes
6061

6162

6263
# Switch to True to produce debug output related to fine-grained incremental
@@ -2422,14 +2423,16 @@ def module_not_found(manager: BuildManager, line: int, caller_state: State,
24222423
or (manager.options.python_version[0] >= 3
24232424
and moduleinfo.is_py3_std_lib_module(target))):
24242425
errors.report(
2425-
line, 0, "No library stub file for standard library module '{}'".format(target))
2426+
line, 0, "No library stub file for standard library module '{}'".format(target),
2427+
code=codes.IMPORT)
24262428
errors.report(line, 0, stub_msg, severity='note', only_once=True)
24272429
elif moduleinfo.is_third_party_module(target):
2428-
errors.report(line, 0, "No library stub file for module '{}'".format(target))
2430+
errors.report(line, 0, "No library stub file for module '{}'".format(target),
2431+
code=codes.IMPORT)
24292432
errors.report(line, 0, stub_msg, severity='note', only_once=True)
24302433
else:
24312434
note = "See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports"
2432-
errors.report(line, 0, "Cannot find module named '{}'".format(target))
2435+
errors.report(line, 0, "Cannot find module named '{}'".format(target), code=codes.IMPORT)
24332436
errors.report(line, 0, note, severity='note', only_once=True)
24342437
errors.set_import_context(save_import_context)
24352438

mypy/checker.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
from mypy.sharedparse import BINARY_MAGIC_METHODS
7171
from mypy.scope import Scope
7272
from mypy.typeops import tuple_fallback
73-
from mypy import state
73+
from mypy import state, errorcodes as codes
7474
from mypy.traverser import has_return_statement
7575
from mypy.errorcodes import ErrorCode
7676

@@ -961,7 +961,8 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
961961
# This is a NoReturn function
962962
self.msg.fail(message_registry.INVALID_IMPLICIT_RETURN, defn)
963963
else:
964-
self.msg.fail(message_registry.MISSING_RETURN_STATEMENT, defn)
964+
self.msg.fail(message_registry.MISSING_RETURN_STATEMENT, defn,
965+
code=codes.RETURN)
965966

966967
self.return_types.pop()
967968

@@ -980,7 +981,8 @@ def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None:
980981
else:
981982
msg += 'argument "{}"'.format(name)
982983
self.check_simple_assignment(arg.variable.type, arg.initializer,
983-
context=arg, msg=msg, lvalue_name='argument', rvalue_name='default')
984+
context=arg, msg=msg, lvalue_name='argument', rvalue_name='default',
985+
code=codes.ASSIGNMENT)
984986

985987
def is_forward_op_method(self, method_name: str) -> bool:
986988
if self.options.python_version[0] == 2 and method_name == '__div__':
@@ -1009,24 +1011,30 @@ def is_unannotated_any(t: Type) -> bool:
10091011
if fdef.type is None and self.options.disallow_untyped_defs:
10101012
if (not fdef.arguments or (len(fdef.arguments) == 1 and
10111013
(fdef.arg_names[0] == 'self' or fdef.arg_names[0] == 'cls'))):
1012-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
1014+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1015+
code=codes.NO_UNTYPED_DEF)
10131016
if not has_return_statement(fdef):
10141017
self.note('Use "-> None" if function does not return a value', fdef)
10151018
else:
1016-
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)
1019+
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef,
1020+
code=codes.NO_UNTYPED_DEF)
10171021
elif isinstance(fdef.type, CallableType):
10181022
ret_type = fdef.type.ret_type
10191023
if is_unannotated_any(ret_type):
1020-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
1024+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1025+
code=codes.NO_UNTYPED_DEF)
10211026
elif fdef.is_generator:
10221027
if is_unannotated_any(self.get_generator_return_type(ret_type,
10231028
fdef.is_coroutine)):
1024-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
1029+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1030+
code=codes.NO_UNTYPED_DEF)
10251031
elif fdef.is_coroutine and isinstance(ret_type, Instance):
10261032
if is_unannotated_any(self.get_coroutine_return_type(ret_type)):
1027-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
1033+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1034+
code=codes.NO_UNTYPED_DEF)
10281035
if any(is_unannotated_any(t) for t in fdef.type.arg_types):
1029-
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef)
1036+
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef,
1037+
code=codes.NO_UNTYPED_DEF)
10301038

10311039
def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
10321040
self_type = fill_typevars_with_any(fdef.info)
@@ -1975,7 +1983,8 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
19751983
rvalue_type, lvalue_type, infer_lvalue_type = self.check_member_assignment(
19761984
instance_type, lvalue_type, rvalue, lvalue)
19771985
else:
1978-
rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue)
1986+
rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue,
1987+
code=codes.ASSIGNMENT)
19791988

19801989
# Special case: only non-abstract non-protocol classes can be assigned to
19811990
# variables with explicit type Type[A], where A is protocol or abstract.
@@ -2111,7 +2120,8 @@ def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type]
21112120
return self.check_subtype(compare_type, base_type, lvalue,
21122121
message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT,
21132122
'expression has type',
2114-
'base class "%s" defined the type as' % base.name())
2123+
'base class "%s" defined the type as' % base.name(),
2124+
code=codes.ASSIGNMENT)
21152125
return True
21162126

21172127
def lvalue_type_from_base(self, expr_node: Var,
@@ -2675,7 +2685,8 @@ def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expressio
26752685
context: Context,
26762686
msg: str = message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT,
26772687
lvalue_name: str = 'variable',
2678-
rvalue_name: str = 'expression') -> Type:
2688+
rvalue_name: str = 'expression', *,
2689+
code: Optional[ErrorCode] = None) -> Type:
26792690
if self.is_stub and isinstance(rvalue, EllipsisExpr):
26802691
# '...' is always a valid initializer in a stub.
26812692
return AnyType(TypeOfAny.special_form)
@@ -2690,7 +2701,7 @@ def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expressio
26902701
elif lvalue_type:
26912702
self.check_subtype(rvalue_type, lvalue_type, context, msg,
26922703
'{} has type'.format(rvalue_name),
2693-
'{} has type'.format(lvalue_name))
2704+
'{} has type'.format(lvalue_name), code=code)
26942705
return rvalue_type
26952706

26962707
def check_member_assignment(self, instance_type: Type, attribute_type: Type,
@@ -2892,7 +2903,8 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
28922903
supertype_label='expected',
28932904
supertype=return_type,
28942905
context=s,
2895-
msg=message_registry.INCOMPATIBLE_RETURN_VALUE_TYPE)
2906+
msg=message_registry.INCOMPATIBLE_RETURN_VALUE_TYPE,
2907+
code=codes.RETURN_VALUE)
28962908
else:
28972909
# Empty returns are valid in Generators with Any typed returns, but not in
28982910
# coroutines.
@@ -3674,7 +3686,8 @@ def find_isinstance_check(self, node: Expression
36743686
def check_subtype(self, subtype: Type, supertype: Type, context: Context,
36753687
msg: str = message_registry.INCOMPATIBLE_TYPES,
36763688
subtype_label: Optional[str] = None,
3677-
supertype_label: Optional[str] = None) -> bool:
3689+
supertype_label: Optional[str] = None, *,
3690+
code: Optional[ErrorCode] = None) -> bool:
36783691
"""Generate an error if the subtype is not compatible with
36793692
supertype."""
36803693
if is_subtype(subtype, supertype):
@@ -3697,7 +3710,7 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
36973710
notes = append_invariance_notes([], subtype, supertype)
36983711
if extra_info:
36993712
msg += ' (' + ', '.join(extra_info) + ')'
3700-
self.fail(msg, context)
3713+
self.fail(msg, context, code=code)
37013714
for note in notes:
37023715
self.msg.note(note, context)
37033716
if note_msg:

mypy/checkexpr.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from mypy.visitor import ExpressionVisitor
6060
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
6161
from mypy.typeops import tuple_fallback
62+
import mypy.errorcodes as codes
6263

6364
# Type of callback user for checking individual function arguments. See
6465
# check_args() below for details.
@@ -483,7 +484,8 @@ def check_typeddict_call_with_kwargs(self, callee: TypedDictType,
483484
lvalue_type=item_expected_type, rvalue=item_value, context=item_value,
484485
msg=message_registry.INCOMPATIBLE_TYPES,
485486
lvalue_name='TypedDict item "{}"'.format(item_name),
486-
rvalue_name='expression')
487+
rvalue_name='expression',
488+
code=codes.TYPEDDICT_ITEM)
487489

488490
return callee
489491

@@ -3214,7 +3216,7 @@ def _super_arg_types(self, e: SuperExpr) -> Union[Type, Tuple[Type, Type]]:
32143216
return AnyType(TypeOfAny.unannotated)
32153217
elif len(e.call.args) == 0:
32163218
if self.chk.options.python_version[0] == 2:
3217-
self.chk.fail(message_registry.TOO_FEW_ARGS_FOR_SUPER, e)
3219+
self.chk.fail(message_registry.TOO_FEW_ARGS_FOR_SUPER, e, code=codes.CALL_ARG)
32183220
return AnyType(TypeOfAny.from_error)
32193221
elif not e.info:
32203222
# This has already been reported by the semantic analyzer.

mypy/errorcodes.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,77 @@ def __str__(self) -> str:
2929
'call-arg', "Check number, names and kinds of arguments in calls", 'General') # type: Final
3030
ARG_TYPE = ErrorCode(
3131
'arg-type', "Check argument types in calls", 'General') # type: Final
32+
CALL_OVERLOAD = ErrorCode(
33+
'call-overload', "Check that an overload variant matches arguments", 'General') # type: Final
3234
VALID_TYPE = ErrorCode(
3335
'valid-type', "Check that type (annotation) is valid", 'General') # type: Final
34-
MISSING_ANN = ErrorCode(
36+
VAR_ANNOTATED = ErrorCode(
3537
'var-annotated', "Require variable annotation if type can't be inferred",
3638
'General') # type: Final
3739
OVERRIDE = ErrorCode(
3840
'override', "Check that method override is compatible with base class",
3941
'General') # type: Final
42+
RETURN = ErrorCode(
43+
'return', "Check that function always returns a value", 'General') # type: Final
4044
RETURN_VALUE = ErrorCode(
4145
'return-value', "Check that return value is compatible with signature",
4246
'General') # type: Final
4347
ASSIGNMENT = ErrorCode(
4448
'assignment', "Check that assigned value is compatible with target", 'General') # type: Final
49+
TYPE_ARG = ErrorCode(
50+
'type-arg', "Check that generic type arguments are present", 'General') # type: Final
51+
TYPE_VAR = ErrorCode(
52+
'type-var', "Check that type variable values are valid", 'General') # type: Final
53+
UNION_ATTR = ErrorCode(
54+
'union-attr', "Check that attribute exists in each item of a union", 'General') # type: Final
55+
INDEX = ErrorCode(
56+
'index', "Check indexing operations", 'General') # type: Final
57+
OPERATOR = ErrorCode(
58+
'operator', "Check that operator is valid for operands", 'General') # type: Final
59+
LIST_ITEM = ErrorCode(
60+
'list-item', "Check list items in a list expression [item, ...]", 'General') # type: Final
61+
DICT_ITEM = ErrorCode(
62+
'dict-item',
63+
"Check dict items in a dict expression {key: value, ...}", 'General') # type: Final
64+
TYPEDDICT_ITEM = ErrorCode(
65+
'typeddict-item', "Check items when constructing TypedDict", 'General') # type: Final
66+
HAS_TYPE = ErrorCode(
67+
'has-type', "Check that type of reference can be determined", 'General') # type: Final
68+
IMPORT = ErrorCode(
69+
'import', "Require that imported module can be found or has stubs", 'General') # type: Final
70+
NO_REDEF = ErrorCode(
71+
'no-redef', "Check that each name is defined once", 'General') # type: Final
72+
FUNC_RETURNS_VALUE = ErrorCode(
73+
'func-returns-value', "Check that called function returns a value in value context",
74+
'General') # type: Final
75+
ABSTRACT = ErrorCode(
76+
'abstract', "Prevent instantiation of classes with abstract attributes",
77+
'General') # type: Final
78+
VALID_NEWTYPE = ErrorCode(
79+
'valid-newtype', "Check that argument 2 to NewType is valid", 'General') # type: Final
80+
81+
# These error codes aren't enable by default.
82+
NO_UNTYPED_DEF = ErrorCode(
83+
'no-untyped-def', "Check that every function has an annotation", 'General') # type: Final
84+
NO_UNTYPED_CALL = ErrorCode(
85+
'no-untyped-call',
86+
"Disallow calling functions without type annotations from annotated functions",
87+
'General') # type: Final
88+
REDUNDANT_CAST = ErrorCode(
89+
'redundant-cast', "Check that cast changes type of expression", 'General') # type: Final
90+
COMPARISON_OVERLAP = ErrorCode(
91+
'comparison-overlap',
92+
"Check that types in comparisons and 'in' expressions overlap", 'General') # type: Final
93+
NO_ANY_UNIMPORTED = ErrorCode(
94+
'no-any-unimported', 'Reject "Any" types from unfollowed imports', 'General') # type: Final
95+
NO_ANY_RETURN = ErrorCode(
96+
'no-any-return', 'Reject returning value with "Any" type if return type is not "Any"',
97+
'General') # type: Final
4598

99+
# Syntax errors are often blocking.
46100
SYNTAX = ErrorCode(
47101
'syntax', "Report syntax errors", 'General') # type: Final
48102

103+
# This is a catch-all for remaining uncategorized errors.
49104
MISC = ErrorCode(
50105
'misc', "Miscenallenous other checks", 'General') # type: Final

mypy/fastparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def parse_type_comment(type_comment: str,
208208
if errors is not None:
209209
stripped_type = type_comment.split("#", 2)[0].strip()
210210
err_msg = "{} '{}'".format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type)
211-
errors.report(line, e.offset, err_msg, blocker=True)
211+
errors.report(line, e.offset, err_msg, blocker=True, code=codes.SYNTAX)
212212
return None, None
213213
else:
214214
raise

mypy/message_registry.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@
8989
GENERIC_INSTANCE_VAR_CLASS_ACCESS = \
9090
'Access to generic instance variables via class is ambiguous' # type: Final
9191
BARE_GENERIC = 'Missing type parameters for generic type {}' # type: Final
92-
# TODO: remove when the old semantic analyzer is gone
93-
BARE_GENERIC_OLD = 'Missing type parameters for generic type' # type: Final
9492
IMPLICIT_GENERIC_ANY_BUILTIN = \
9593
'Implicit generic "Any". Use "{}" and specify generic parameters' # type: Final
9694

0 commit comments

Comments
 (0)