Skip to content

Commit 09c243d

Browse files
authored
Point error to incompatible argument instead of call expression (#7470)
Previously we pointed to the beginning of the call expression if there was an incompatible argument, which is not great, particularly if there are multiple arguments. Also point to incompatible list/dict/set item instead of the beginning of a list/dict/set expression. Note that this is a breaking change. Some `# type: ignore` comments will have to be moved to new lines. I had to update the output of many test cases, mostly because error ordering was changed.
1 parent 88e2b67 commit 09c243d

28 files changed

+366
-243
lines changed

mypy/checker.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
16811681
continue
16821682

16831683
dec = self.expr_checker.accept(decorator)
1684-
temp = self.temp_node(sig)
1684+
temp = self.temp_node(sig, context=decorator)
16851685
fullname = None
16861686
if isinstance(decorator, RefExpr):
16871687
fullname = decorator.fullname
@@ -2794,15 +2794,20 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type,
27942794
# For this we use the rvalue as type context.
27952795
self.msg.disable_errors()
27962796
_, inferred_dunder_set_type = self.expr_checker.check_call(
2797-
dunder_set_type, [TempNode(instance_type), rvalue],
2798-
[nodes.ARG_POS, nodes.ARG_POS], context)
2797+
dunder_set_type,
2798+
[TempNode(instance_type, context=context), rvalue],
2799+
[nodes.ARG_POS, nodes.ARG_POS],
2800+
context)
27992801
self.msg.enable_errors()
28002802

28012803
# And now we type check the call second time, to show errors related
28022804
# to wrong arguments count, etc.
28032805
self.expr_checker.check_call(
2804-
dunder_set_type, [TempNode(instance_type), TempNode(AnyType(TypeOfAny.special_form))],
2805-
[nodes.ARG_POS, nodes.ARG_POS], context)
2806+
dunder_set_type,
2807+
[TempNode(instance_type, context=context),
2808+
TempNode(AnyType(TypeOfAny.special_form), context=context)],
2809+
[nodes.ARG_POS, nodes.ARG_POS],
2810+
context)
28062811

28072812
# should be handled by get_method above
28082813
assert isinstance(inferred_dunder_set_type, CallableType) # type: ignore
@@ -3305,7 +3310,7 @@ def visit_decorator(self, e: Decorator) -> None:
33053310
self.fail(message_registry.MULTIPLE_OVERLOADS_REQUIRED, e)
33063311
continue
33073312
dec = self.expr_checker.accept(d)
3308-
temp = self.temp_node(sig)
3313+
temp = self.temp_node(sig, context=e)
33093314
fullname = None
33103315
if isinstance(d, RefExpr):
33113316
fullname = d.fullname
@@ -4039,10 +4044,7 @@ def find_partial_types_in_all_scopes(
40394044

40404045
def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode:
40414046
"""Create a temporary node with the given, fixed type."""
4042-
temp = TempNode(t)
4043-
if context:
4044-
temp.set_line(context.get_line())
4045-
return temp
4047+
return TempNode(t, context=context)
40464048

40474049
def fail(self, msg: str, context: Context, *, code: Optional[ErrorCode] = None) -> None:
40484050
"""Produce an error message."""

mypy/checkexpr.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,16 @@
6464

6565
# Type of callback user for checking individual function arguments. See
6666
# check_args() below for details.
67-
ArgChecker = Callable[[Type, Type, int, Type, int, int, CallableType, Context, MessageBuilder],
67+
ArgChecker = Callable[[Type,
68+
Type,
69+
int,
70+
Type,
71+
int,
72+
int,
73+
CallableType,
74+
Context,
75+
Context,
76+
MessageBuilder],
6877
None]
6978

7079
# Maximum nesting level for math union in overloads, setting this to large values
@@ -842,7 +851,7 @@ def check_callable_call(self,
842851
self.check_argument_count(callee, arg_types, arg_kinds,
843852
arg_names, formal_to_actual, context, self.msg)
844853

845-
self.check_argument_types(arg_types, arg_kinds, callee, formal_to_actual, context,
854+
self.check_argument_types(arg_types, arg_kinds, args, callee, formal_to_actual, context,
846855
messages=arg_messages)
847856

848857
if (callee.is_type_obj() and (len(arg_types) == 1)
@@ -1169,7 +1178,8 @@ def check_argument_count(self,
11691178
if messages:
11701179
assert context, "Internal error: messages given without context"
11711180
elif context is None:
1172-
context = TempNode(AnyType(TypeOfAny.special_form)) # Avoid "is None" checks
1181+
# Avoid "is None" checks
1182+
context = TempNode(AnyType(TypeOfAny.special_form))
11731183

11741184
# TODO(jukka): We could return as soon as we find an error if messages is None.
11751185

@@ -1271,6 +1281,7 @@ def check_for_extra_actual_arguments(self,
12711281
def check_argument_types(self,
12721282
arg_types: List[Type],
12731283
arg_kinds: List[int],
1284+
args: List[Expression],
12741285
callee: CallableType,
12751286
formal_to_actual: List[List[int]],
12761287
context: Context,
@@ -1303,12 +1314,19 @@ def check_argument_types(self,
13031314
callee.arg_names[i], callee.arg_kinds[i])
13041315
check_arg(expanded_actual, actual_type, arg_kinds[actual],
13051316
callee.arg_types[i],
1306-
actual + 1, i + 1, callee, context, messages)
1317+
actual + 1, i + 1, callee, args[actual], context, messages)
13071318

1308-
def check_arg(self, caller_type: Type, original_caller_type: Type,
1319+
def check_arg(self,
1320+
caller_type: Type,
1321+
original_caller_type: Type,
13091322
caller_kind: int,
1310-
callee_type: Type, n: int, m: int, callee: CallableType,
1311-
context: Context, messages: MessageBuilder) -> None:
1323+
callee_type: Type,
1324+
n: int,
1325+
m: int,
1326+
callee: CallableType,
1327+
context: Context,
1328+
outer_context: Context,
1329+
messages: MessageBuilder) -> None:
13121330
"""Check the type of a single argument in a call."""
13131331
caller_type = get_proper_type(caller_type)
13141332
original_caller_type = get_proper_type(original_caller_type)
@@ -1326,8 +1344,13 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
13261344
elif not is_subtype(caller_type, callee_type):
13271345
if self.chk.should_suppress_optional_error([caller_type, callee_type]):
13281346
return
1329-
code = messages.incompatible_argument(n, m, callee, original_caller_type,
1330-
caller_kind, context)
1347+
code = messages.incompatible_argument(n,
1348+
m,
1349+
callee,
1350+
original_caller_type,
1351+
caller_kind,
1352+
context=context,
1353+
outer_context=outer_context)
13311354
messages.incompatible_argument_note(original_caller_type, callee_type, context,
13321355
code=code)
13331356

@@ -1404,7 +1427,7 @@ def check_overload_call(self,
14041427
# Neither alternative matches, but we can guess the user probably wants the
14051428
# second one.
14061429
erased_targets = self.overload_erased_call_targets(plausible_targets, arg_types,
1407-
arg_kinds, arg_names, context)
1430+
arg_kinds, arg_names, args, context)
14081431

14091432
# Step 5: We try and infer a second-best alternative if possible. If not, fall back
14101433
# to using 'Any'.
@@ -1569,14 +1592,16 @@ def overload_erased_call_targets(self,
15691592
arg_types: List[Type],
15701593
arg_kinds: List[int],
15711594
arg_names: Optional[Sequence[Optional[str]]],
1595+
args: List[Expression],
15721596
context: Context) -> List[CallableType]:
15731597
"""Returns a list of all targets that match the caller after erasing types.
15741598
15751599
Assumes all of the given targets have argument counts compatible with the caller.
15761600
"""
15771601
matches = [] # type: List[CallableType]
15781602
for typ in plausible_targets:
1579-
if self.erased_signature_similarity(arg_types, arg_kinds, arg_names, typ, context):
1603+
if self.erased_signature_similarity(arg_types, arg_kinds, arg_names, args, typ,
1604+
context):
15801605
matches.append(typ)
15811606
return matches
15821607

@@ -1755,8 +1780,11 @@ def combine_function_signatures(self, types: Sequence[Type]) -> Union[AnyType, C
17551780
variables=variables,
17561781
implicit=True)
17571782

1758-
def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int],
1783+
def erased_signature_similarity(self,
1784+
arg_types: List[Type],
1785+
arg_kinds: List[int],
17591786
arg_names: Optional[Sequence[Optional[str]]],
1787+
args: List[Expression],
17601788
callee: CallableType,
17611789
context: Context) -> bool:
17621790
"""Determine whether arguments could match the signature at runtime, after
@@ -1772,16 +1800,23 @@ def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int
17721800
# Too few or many arguments -> no match.
17731801
return False
17741802

1775-
def check_arg(caller_type: Type, original_caller_type: Type, caller_kind: int,
1776-
callee_type: Type, n: int, m: int, callee: CallableType,
1777-
context: Context, messages: MessageBuilder) -> None:
1803+
def check_arg(caller_type: Type,
1804+
original_ccaller_type: Type,
1805+
caller_kind: int,
1806+
callee_type: Type,
1807+
n: int,
1808+
m: int,
1809+
callee: CallableType,
1810+
context: Context,
1811+
outer_context: Context,
1812+
messages: MessageBuilder) -> None:
17781813
if not arg_approximate_similarity(caller_type, callee_type):
17791814
# No match -- exit early since none of the remaining work can change
17801815
# the result.
17811816
raise Finished
17821817

17831818
try:
1784-
self.check_argument_types(arg_types, arg_kinds, callee,
1819+
self.check_argument_types(arg_types, arg_kinds, args, callee,
17851820
formal_to_actual, context=context, check_arg=check_arg)
17861821
return True
17871822
except Finished:
@@ -2429,7 +2464,7 @@ def check_op(self, method: str, base_type: Type,
24292464
result, inferred = self.check_op_reversible(
24302465
op_name=method,
24312466
left_type=left_possible_type,
2432-
left_expr=TempNode(left_possible_type),
2467+
left_expr=TempNode(left_possible_type, context=context),
24332468
right_type=right_type,
24342469
right_expr=arg,
24352470
context=context,
@@ -2456,7 +2491,8 @@ def check_op(self, method: str, base_type: Type,
24562491
right_variants = [(right_type, arg)]
24572492
right_type = get_proper_type(right_type)
24582493
if isinstance(right_type, UnionType):
2459-
right_variants = [(item, TempNode(item)) for item in right_type.relevant_items()]
2494+
right_variants = [(item, TempNode(item, context=context))
2495+
for item in right_type.relevant_items()]
24602496

24612497
msg = self.msg.clean_copy()
24622498
msg.disable_count = 0
@@ -2468,7 +2504,7 @@ def check_op(self, method: str, base_type: Type,
24682504
result, inferred = self.check_op_reversible(
24692505
op_name=method,
24702506
left_type=left_possible_type,
2471-
left_expr=TempNode(left_possible_type),
2507+
left_expr=TempNode(left_possible_type, context=context),
24722508
right_type=right_possible_type,
24732509
right_expr=right_expr,
24742510
context=context,
@@ -2481,9 +2517,9 @@ def check_op(self, method: str, base_type: Type,
24812517
if len(left_variants) >= 2 and len(right_variants) >= 2:
24822518
self.msg.warn_both_operands_are_from_unions(context)
24832519
elif len(left_variants) >= 2:
2484-
self.msg.warn_operand_was_from_union("Left", base_type, context)
2520+
self.msg.warn_operand_was_from_union("Left", base_type, context=right_expr)
24852521
elif len(right_variants) >= 2:
2486-
self.msg.warn_operand_was_from_union("Right", right_type, context)
2522+
self.msg.warn_operand_was_from_union("Right", right_type, context=right_expr)
24872523

24882524
# See the comment in 'check_overload_call' for more details on why
24892525
# we call 'combine_function_signature' instead of just unioning the inferred
@@ -2812,10 +2848,10 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type:
28122848
assert expr.expr is not None
28132849
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
28142850
if not self.chk.current_node_deferred:
2815-
self.msg.reveal_type(revealed_type, expr)
2851+
self.msg.reveal_type(revealed_type, expr.expr)
28162852
if not self.chk.in_checked_function():
28172853
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions",
2818-
expr)
2854+
expr.expr)
28192855
return revealed_type
28202856
else:
28212857
# REVEAL_LOCALS
@@ -3064,7 +3100,14 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
30643100
if key is None:
30653101
stargs.append(value)
30663102
else:
3067-
args.append(TupleExpr([key, value]))
3103+
tup = TupleExpr([key, value])
3104+
if key.line >= 0:
3105+
tup.line = key.line
3106+
tup.column = key.column
3107+
else:
3108+
tup.line = value.line
3109+
tup.column = value.column
3110+
args.append(tup)
30683111
# Define type variables (used in constructors below).
30693112
ktdef = TypeVarDef('KT', 'KT', -1, [], self.object_type())
30703113
vtdef = TypeVarDef('VT', 'VT', -2, [], self.object_type())

mypy/checkmember.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,8 @@ def analyze_descriptor_access(instance_type: Type,
469469

470470
_, inferred_dunder_get_type = chk.expr_checker.check_call(
471471
dunder_get_type,
472-
[TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type))],
472+
[TempNode(instance_type, context=context),
473+
TempNode(TypeType.make_normalized(owner_type), context=context)],
473474
[ARG_POS, ARG_POS], context)
474475

475476
inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type)

mypy/fastparse.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,8 @@ def visit_UnaryOp(self, n: ast3.UnaryOp) -> UnaryExpr:
944944
# Lambda(arguments args, expr body)
945945
def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr:
946946
body = ast3.Return(n.body)
947-
body.lineno = n.lineno
948-
body.col_offset = n.col_offset
947+
body.lineno = n.body.lineno
948+
body.col_offset = n.body.col_offset
949949

950950
e = LambdaExpr(self.transform_args(n.args, n.lineno),
951951
self.as_required_block([body], n.lineno))
@@ -1169,7 +1169,12 @@ def visit_Attribute(self, n: Attribute) -> Union[MemberExpr, SuperExpr]:
11691169
# Subscript(expr value, slice slice, expr_context ctx)
11701170
def visit_Subscript(self, n: ast3.Subscript) -> IndexExpr:
11711171
e = IndexExpr(self.visit(n.value), self.visit(n.slice))
1172-
return self.set_line(e, n)
1172+
self.set_line(e, n)
1173+
if isinstance(e.index, SliceExpr):
1174+
# Slice has no line/column in the raw ast.
1175+
e.index.line = e.line
1176+
e.index.column = e.column
1177+
return e
11731178

11741179
# Starred(expr value, expr_context ctx)
11751180
def visit_Starred(self, n: Starred) -> StarExpr:

mypy/fastparse2.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,8 +834,8 @@ def visit_Lambda(self, n: ast27.Lambda) -> LambdaExpr:
834834
args, decompose_stmts = self.transform_args(n.args, n.lineno)
835835

836836
n_body = ast27.Return(n.body)
837-
n_body.lineno = n.lineno
838-
n_body.col_offset = n.col_offset
837+
n_body.lineno = n.body.lineno
838+
n_body.col_offset = n.body.col_offset
839839
body = self.as_required_block([n_body], n.lineno)
840840
if decompose_stmts:
841841
body.body = decompose_stmts + body.body
@@ -1009,7 +1009,12 @@ def visit_Attribute(self, n: Attribute) -> Expression:
10091009
# Subscript(expr value, slice slice, expr_context ctx)
10101010
def visit_Subscript(self, n: ast27.Subscript) -> IndexExpr:
10111011
e = IndexExpr(self.visit(n.value), self.visit(n.slice))
1012-
return self.set_line(e, n)
1012+
self.set_line(e, n)
1013+
if isinstance(e.index, SliceExpr):
1014+
# Slice has no line/column in the raw ast.
1015+
e.index.line = e.line
1016+
e.index.column = e.column
1017+
return e
10131018

10141019
# Name(identifier id, expr_context ctx)
10151020
def visit_Name(self, n: Name) -> NameExpr:

mypy/messages.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,14 @@ def untyped_function_call(self, callee: CallableType, context: Context) -> Type:
324324
code=codes.NO_UNTYPED_CALL)
325325
return AnyType(TypeOfAny.from_error)
326326

327-
def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type,
328-
arg_kind: int, context: Context) -> Optional[ErrorCode]:
327+
def incompatible_argument(self,
328+
n: int,
329+
m: int,
330+
callee: CallableType,
331+
arg_type: Type,
332+
arg_kind: int,
333+
context: Context,
334+
outer_context: Context) -> Optional[ErrorCode]:
329335
"""Report an error about an incompatible argument type.
330336
331337
The argument type is arg_type, argument number is n and the
@@ -456,8 +462,8 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
456462
# For function calls with keyword arguments, display the argument name rather than the
457463
# number.
458464
arg_label = str(n)
459-
if isinstance(context, CallExpr) and len(context.arg_names) >= n:
460-
arg_name = context.arg_names[n - 1]
465+
if isinstance(outer_context, CallExpr) and len(outer_context.arg_names) >= n:
466+
arg_name = outer_context.arg_names[n - 1]
461467
if arg_name is not None:
462468
arg_label = '"{}"'.format(arg_name)
463469

mypy/nodes.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,13 +2224,21 @@ class TempNode(Expression):
22242224
# (e.g. for 'x: int' the rvalue is TempNode(AnyType(TypeOfAny.special_form), no_rhs=True))
22252225
no_rhs = False # type: bool
22262226

2227-
def __init__(self, typ: 'mypy.types.Type', no_rhs: bool = False) -> None:
2227+
def __init__(self,
2228+
typ: 'mypy.types.Type',
2229+
no_rhs: bool = False,
2230+
*,
2231+
context: Optional[Context] = None) -> None:
2232+
"""Construct a dummy node; optionally borrow line/column from context object."""
22282233
super().__init__()
22292234
self.type = typ
22302235
self.no_rhs = no_rhs
2236+
if context is not None:
2237+
self.line = context.line
2238+
self.column = context.column
22312239

22322240
def __repr__(self) -> str:
2233-
return 'TempNode(%s)' % str(self.type)
2241+
return 'TempNode:%d(%s)' % (self.line, str(self.type))
22342242

22352243
def accept(self, visitor: ExpressionVisitor[T]) -> T:
22362244
return visitor.visit_temp_node(self)

0 commit comments

Comments
 (0)