Skip to content

Commit 3e43a57

Browse files
Refactor checker error messages (python#10959)
1 parent b431be6 commit 3e43a57

File tree

4 files changed

+143
-93
lines changed

4 files changed

+143
-93
lines changed

mypy/checker.py

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
is_literal_type_like,
5757
)
5858
from mypy import message_registry
59+
from mypy.message_registry import ErrorMessage
5960
from mypy.subtypes import (
6061
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
6162
restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_compatible,
@@ -1019,10 +1020,9 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
10191020
# entirely pass/Ellipsis/raise NotImplementedError.
10201021
if isinstance(return_type, UninhabitedType):
10211022
# This is a NoReturn function
1022-
self.msg.fail(message_registry.INVALID_IMPLICIT_RETURN, defn)
1023+
self.fail(message_registry.INVALID_IMPLICIT_RETURN, defn)
10231024
else:
1024-
self.msg.fail(message_registry.MISSING_RETURN_STATEMENT, defn,
1025-
code=codes.RETURN)
1025+
self.fail(message_registry.MISSING_RETURN_STATEMENT, defn)
10261026

10271027
self.return_types.pop()
10281028

@@ -1078,31 +1078,25 @@ def is_unannotated_any(t: Type) -> bool:
10781078
if fdef.type is None and self.options.disallow_untyped_defs:
10791079
if (not fdef.arguments or (len(fdef.arguments) == 1 and
10801080
(fdef.arg_names[0] == 'self' or fdef.arg_names[0] == 'cls'))):
1081-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1082-
code=codes.NO_UNTYPED_DEF)
1081+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
10831082
if not has_return_statement(fdef) and not fdef.is_generator:
10841083
self.note('Use "-> None" if function does not return a value', fdef,
10851084
code=codes.NO_UNTYPED_DEF)
10861085
else:
1087-
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef,
1088-
code=codes.NO_UNTYPED_DEF)
1086+
self.fail(message_registry.FUNCTION_TYPE_EXPECTED, fdef)
10891087
elif isinstance(fdef.type, CallableType):
10901088
ret_type = get_proper_type(fdef.type.ret_type)
10911089
if is_unannotated_any(ret_type):
1092-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1093-
code=codes.NO_UNTYPED_DEF)
1090+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
10941091
elif fdef.is_generator:
10951092
if is_unannotated_any(self.get_generator_return_type(ret_type,
10961093
fdef.is_coroutine)):
1097-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1098-
code=codes.NO_UNTYPED_DEF)
1094+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
10991095
elif fdef.is_coroutine and isinstance(ret_type, Instance):
11001096
if is_unannotated_any(self.get_coroutine_return_type(ret_type)):
1101-
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef,
1102-
code=codes.NO_UNTYPED_DEF)
1097+
self.fail(message_registry.RETURN_TYPE_EXPECTED, fdef)
11031098
if any(is_unannotated_any(t) for t in fdef.type.arg_types):
1104-
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef,
1105-
code=codes.NO_UNTYPED_DEF)
1099+
self.fail(message_registry.ARGUMENT_TYPE_EXPECTED, fdef)
11061100

11071101
def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
11081102
self_type = fill_typevars_with_any(fdef.info)
@@ -1387,7 +1381,7 @@ def check_getattr_method(self, typ: Type, context: Context, name: str) -> None:
13871381
if len(self.scope.stack) == 1:
13881382
# module scope
13891383
if name == '__getattribute__':
1390-
self.msg.fail(message_registry.MODULE_LEVEL_GETATTRIBUTE, context)
1384+
self.fail(message_registry.MODULE_LEVEL_GETATTRIBUTE, context)
13911385
return
13921386
# __getattr__ is fine at the module level as of Python 3.7 (PEP 562). We could
13931387
# show an error for Python < 3.7, but that would be annoying in code that supports
@@ -2608,7 +2602,7 @@ def check_assignment_to_slots(self, lvalue: Lvalue) -> None:
26082602
return
26092603

26102604
self.fail(
2611-
'Trying to assign name "{}" that is not in "__slots__" of type "{}"'.format(
2605+
message_registry.NAME_NOT_IN_SLOTS.format(
26122606
lvalue.name, inst.type.fullname,
26132607
),
26142608
lvalue,
@@ -2652,16 +2646,16 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Ex
26522646
elif self.type_is_iterable(typs) and isinstance(typs, Instance):
26532647
if (iterable_type is not None
26542648
and iterable_type != self.iterable_item_type(typs)):
2655-
self.fail("Contiguous iterable with same type expected", context)
2649+
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
26562650
else:
26572651
if last_idx is None or last_idx + 1 == idx_rval:
26582652
rvalues.append(rval)
26592653
last_idx = idx_rval
26602654
iterable_type = self.iterable_item_type(typs)
26612655
else:
2662-
self.fail("Contiguous iterable with same type expected", context)
2656+
self.fail(message_registry.CONTIGUOUS_ITERABLE_EXPECTED, context)
26632657
else:
2664-
self.fail("Invalid type '{}' for *expr (iterable expected)".format(typs),
2658+
self.fail(message_registry.ITERABLE_TYPE_EXPECTED.format(typs),
26652659
context)
26662660
else:
26672661
rvalues.append(rval)
@@ -3194,8 +3188,7 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type,
31943188

31953189
dunder_set = attribute_type.type.get_method('__set__')
31963190
if dunder_set is None:
3197-
self.msg.fail(message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(attribute_type),
3198-
context)
3191+
self.fail(message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(attribute_type), context)
31993192
return AnyType(TypeOfAny.from_error), get_type, False
32003193

32013194
function = function_type(dunder_set, self.named_type('builtins.function'))
@@ -3378,8 +3371,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
33783371
# Functions returning a value of type None are allowed to have a None return.
33793372
if is_lambda or isinstance(typ, NoneType):
33803373
return
3381-
self.fail(message_registry.NO_RETURN_VALUE_EXPECTED, s,
3382-
code=codes.RETURN_VALUE)
3374+
self.fail(message_registry.NO_RETURN_VALUE_EXPECTED, s)
33833375
else:
33843376
self.check_subtype(
33853377
subtype_label='got',
@@ -3401,7 +3393,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
34013393
return
34023394

34033395
if self.in_checked_function():
3404-
self.fail(message_registry.RETURN_VALUE_EXPECTED, s, code=codes.RETURN_VALUE)
3396+
self.fail(message_registry.RETURN_VALUE_EXPECTED, s)
34053397

34063398
def visit_if_stmt(self, s: IfStmt) -> None:
34073399
"""Type check an if statement."""
@@ -4251,23 +4243,16 @@ def format_expr_type() -> str:
42514243
return f'Expression has type "{t}"'
42524244

42534245
if isinstance(t, FunctionLike):
4254-
self.msg.fail(
4255-
f'Function "{t}" could always be true in boolean context', expr,
4256-
code=codes.TRUTHY_BOOL,
4257-
)
4246+
self.fail(message_registry.FUNCTION_ALWAYS_TRUE.format(t), expr)
42584247
elif isinstance(t, UnionType):
4259-
self.msg.fail(
4260-
f"{format_expr_type()} of which no members implement __bool__ or __len__ "
4261-
"so it could always be true in boolean context",
4248+
self.fail(
4249+
message_registry.TYPE_ALWAYS_TRUE_UNIONTYPE.format(format_expr_type()),
42624250
expr,
4263-
code=codes.TRUTHY_BOOL,
42644251
)
42654252
else:
4266-
self.msg.fail(
4267-
f'{format_expr_type()} which does not implement __bool__ or __len__ '
4268-
'so it could always be true in boolean context',
4253+
self.fail(
4254+
message_registry.TYPE_ALWAYS_TRUE.format(format_expr_type()),
42694255
expr,
4270-
code=codes.TRUTHY_BOOL,
42714256
)
42724257

42734258
def find_type_equals_check(self, node: ComparisonExpr, expr_indices: List[int]
@@ -4400,7 +4385,7 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM
44004385
if node.callee.type_guard is not None:
44014386
# TODO: Follow keyword args or *args, **kwargs
44024387
if node.arg_kinds[0] != nodes.ARG_POS:
4403-
self.fail("Type guard requires positional argument", node)
4388+
self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node)
44044389
return {}, {}
44054390
if literal(expr) == LITERAL_TYPE:
44064391
# Note: we wrap the target type, so that we can special case later.
@@ -4946,7 +4931,7 @@ def check_subtype(self,
49464931
subtype: Type,
49474932
supertype: Type,
49484933
context: Context,
4949-
msg: str = message_registry.INCOMPATIBLE_TYPES,
4934+
msg: Union[str, ErrorMessage] = message_registry.INCOMPATIBLE_TYPES,
49504935
subtype_label: Optional[str] = None,
49514936
supertype_label: Optional[str] = None,
49524937
*,
@@ -4956,9 +4941,14 @@ def check_subtype(self,
49564941
if is_subtype(subtype, supertype):
49574942
return True
49584943

4944+
if isinstance(msg, ErrorMessage):
4945+
msg_text = msg.value
4946+
code = msg.code
4947+
else:
4948+
msg_text = msg
49594949
subtype = get_proper_type(subtype)
49604950
supertype = get_proper_type(supertype)
4961-
if self.msg.try_report_long_tuple_assignment_error(subtype, supertype, context, msg,
4951+
if self.msg.try_report_long_tuple_assignment_error(subtype, supertype, context, msg_text,
49624952
subtype_label, supertype_label, code=code):
49634953
return False
49644954
if self.should_suppress_optional_error([subtype]):
@@ -4977,8 +4967,9 @@ def check_subtype(self,
49774967
if isinstance(subtype, Instance) and isinstance(supertype, Instance):
49784968
notes = append_invariance_notes([], subtype, supertype)
49794969
if extra_info:
4980-
msg += ' (' + ', '.join(extra_info) + ')'
4981-
self.fail(msg, context, code=code)
4970+
msg_text += ' (' + ', '.join(extra_info) + ')'
4971+
4972+
self.fail(ErrorMessage(msg_text, code=code), context)
49824973
for note in notes:
49834974
self.msg.note(note, context, code=code)
49844975
if note_msg:
@@ -5249,8 +5240,12 @@ def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode:
52495240
"""Create a temporary node with the given, fixed type."""
52505241
return TempNode(t, context=context)
52515242

5252-
def fail(self, msg: str, context: Context, *, code: Optional[ErrorCode] = None) -> None:
5243+
def fail(self, msg: Union[str, ErrorMessage], context: Context, *,
5244+
code: Optional[ErrorCode] = None) -> None:
52535245
"""Produce an error message."""
5246+
if isinstance(msg, ErrorMessage):
5247+
self.msg.fail(msg.value, context, code=msg.code)
5248+
return
52545249
self.msg.fail(msg, context, code=code)
52555250

52565251
def note(self,

mypy/errorcodes.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ def __str__(self) -> str:
4444
OVERRIDE: Final = ErrorCode(
4545
"override", "Check that method override is compatible with base class", "General"
4646
)
47-
RETURN: Final = ErrorCode("return", "Check that function always returns a value", "General")
48-
RETURN_VALUE: Final = ErrorCode(
47+
RETURN: Final[ErrorCode] = ErrorCode(
48+
"return", "Check that function always returns a value", "General"
49+
)
50+
RETURN_VALUE: Final[ErrorCode] = ErrorCode(
4951
"return-value", "Check that return value is compatible with signature", "General"
5052
)
5153
ASSIGNMENT: Final = ErrorCode(
@@ -94,11 +96,11 @@ def __str__(self) -> str:
9496
)
9597

9698
# These error codes aren't enabled by default.
97-
NO_UNTYPED_DEF: Final = ErrorCode(
99+
NO_UNTYPED_DEF: Final[ErrorCode] = ErrorCode(
98100
"no-untyped-def", "Check that every function has an annotation", "General"
99101
)
100102
NO_UNTYPED_CALL: Final = ErrorCode(
101-
'no-untyped-call',
103+
"no-untyped-call",
102104
"Disallow calling functions without type annotations from annotated functions",
103105
"General",
104106
)
@@ -122,11 +124,11 @@ def __str__(self) -> str:
122124
REDUNDANT_EXPR: Final = ErrorCode(
123125
"redundant-expr", "Warn about redundant expressions", "General", default_enabled=False
124126
)
125-
TRUTHY_BOOL: Final = ErrorCode(
126-
'truthy-bool',
127+
TRUTHY_BOOL: Final[ErrorCode] = ErrorCode(
128+
"truthy-bool",
127129
"Warn about expressions that could always evaluate to true in boolean contexts",
128-
'General',
129-
default_enabled=False
130+
"General",
131+
default_enabled=False,
130132
)
131133
NAME_MATCH: Final = ErrorCode(
132134
"name-match", "Check that type definition has consistent naming", "General"

mypy/errors.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from mypy.options import Options
1212
from mypy.version import __version__ as mypy_version
1313
from mypy.errorcodes import ErrorCode, IMPORT
14+
from mypy.message_registry import ErrorMessage
1415
from mypy import errorcodes as codes
1516
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
1617

@@ -677,7 +678,12 @@ def render_messages(self,
677678
result.append((file, -1, -1, 'note',
678679
'In class "{}":'.format(e.type), e.allow_dups, None))
679680

680-
result.append((file, e.line, e.column, e.severity, e.message, e.allow_dups, e.code))
681+
if isinstance(e.message, ErrorMessage):
682+
result.append(
683+
(file, e.line, e.column, e.severity, e.message.value, e.allow_dups, e.code))
684+
else:
685+
result.append(
686+
(file, e.line, e.column, e.severity, e.message, e.allow_dups, e.code))
681687

682688
prev_import_context = e.import_ctx
683689
prev_function_or_member = e.function_or_member

0 commit comments

Comments
 (0)