Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point error to incompatible argument instead of call expression #7470

Merged
merged 14 commits into from Sep 10, 2019
22 changes: 12 additions & 10 deletions mypy/checker.py
Expand Up @@ -1683,7 +1683,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
continue

dec = self.expr_checker.accept(decorator)
temp = self.temp_node(sig)
temp = self.temp_node(sig, context=decorator)
fullname = None
if isinstance(decorator, RefExpr):
fullname = decorator.fullname
Expand Down Expand Up @@ -2796,15 +2796,20 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type,
# For this we use the rvalue as type context.
self.msg.disable_errors()
_, inferred_dunder_set_type = self.expr_checker.check_call(
dunder_set_type, [TempNode(instance_type), rvalue],
[nodes.ARG_POS, nodes.ARG_POS], context)
dunder_set_type,
[TempNode(instance_type, context=context), rvalue],
[nodes.ARG_POS, nodes.ARG_POS],
context)
self.msg.enable_errors()

# And now we type check the call second time, to show errors related
# to wrong arguments count, etc.
self.expr_checker.check_call(
dunder_set_type, [TempNode(instance_type), TempNode(AnyType(TypeOfAny.special_form))],
[nodes.ARG_POS, nodes.ARG_POS], context)
dunder_set_type,
[TempNode(instance_type, context=context),
TempNode(AnyType(TypeOfAny.special_form), context=context)],
[nodes.ARG_POS, nodes.ARG_POS],
context)

# should be handled by get_method above
assert isinstance(inferred_dunder_set_type, CallableType) # type: ignore
Expand Down Expand Up @@ -3307,7 +3312,7 @@ def visit_decorator(self, e: Decorator) -> None:
self.fail(message_registry.MULTIPLE_OVERLOADS_REQUIRED, e)
continue
dec = self.expr_checker.accept(d)
temp = self.temp_node(sig)
temp = self.temp_node(sig, context=e)
fullname = None
if isinstance(d, RefExpr):
fullname = d.fullname
Expand Down Expand Up @@ -4041,10 +4046,7 @@ def find_partial_types_in_all_scopes(

def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode:
"""Create a temporary node with the given, fixed type."""
temp = TempNode(t)
if context:
temp.set_line(context.get_line())
return temp
return TempNode(t, context=context)

def fail(self, msg: str, context: Context, *, code: Optional[ErrorCode] = None) -> None:
"""Produce an error message."""
Expand Down
91 changes: 67 additions & 24 deletions mypy/checkexpr.py
Expand Up @@ -64,7 +64,16 @@

# Type of callback user for checking individual function arguments. See
# check_args() below for details.
ArgChecker = Callable[[Type, Type, int, Type, int, int, CallableType, Context, MessageBuilder],
ArgChecker = Callable[[Type,
Type,
int,
Type,
int,
int,
CallableType,
Context,
Context,
MessageBuilder],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you trying black? :-)

None]

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

self.check_argument_types(arg_types, arg_kinds, callee, formal_to_actual, context,
self.check_argument_types(arg_types, arg_kinds, args, callee, formal_to_actual, context,
messages=arg_messages)

if (callee.is_type_obj() and (len(arg_types) == 1)
Expand Down Expand Up @@ -1169,7 +1178,8 @@ def check_argument_count(self,
if messages:
assert context, "Internal error: messages given without context"
elif context is None:
context = TempNode(AnyType(TypeOfAny.special_form)) # Avoid "is None" checks
# Avoid "is None" checks
context = TempNode(AnyType(TypeOfAny.special_form), context=context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context=context is redundant since it is anyway None.


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

Expand Down Expand Up @@ -1271,6 +1281,7 @@ def check_for_extra_actual_arguments(self,
def check_argument_types(self,
arg_types: List[Type],
arg_kinds: List[int],
args: List[Expression],
callee: CallableType,
formal_to_actual: List[List[int]],
context: Context,
Expand Down Expand Up @@ -1303,12 +1314,19 @@ def check_argument_types(self,
callee.arg_names[i], callee.arg_kinds[i])
check_arg(expanded_actual, actual_type, arg_kinds[actual],
callee.arg_types[i],
actual + 1, i + 1, callee, context, messages)
actual + 1, i + 1, callee, args[actual], context, messages)

def check_arg(self, caller_type: Type, original_caller_type: Type,
def check_arg(self,
caller_type: Type,
original_caller_type: Type,
caller_kind: int,
callee_type: Type, n: int, m: int, callee: CallableType,
context: Context, messages: MessageBuilder) -> None:
callee_type: Type,
n: int,
m: int,
callee: CallableType,
context: Context,
outer_context: Context,
messages: MessageBuilder) -> None:
"""Check the type of a single argument in a call."""
caller_type = get_proper_type(caller_type)
original_caller_type = get_proper_type(original_caller_type)
Expand All @@ -1326,8 +1344,13 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
elif not is_subtype(caller_type, callee_type):
if self.chk.should_suppress_optional_error([caller_type, callee_type]):
return
code = messages.incompatible_argument(n, m, callee, original_caller_type,
caller_kind, context)
code = messages.incompatible_argument(n,
m,
callee,
original_caller_type,
caller_kind,
context=context,
outer_context=outer_context)
messages.incompatible_argument_note(original_caller_type, callee_type, context,
code=code)

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

# Step 5: We try and infer a second-best alternative if possible. If not, fall back
# to using 'Any'.
Expand Down Expand Up @@ -1569,14 +1592,16 @@ def overload_erased_call_targets(self,
arg_types: List[Type],
arg_kinds: List[int],
arg_names: Optional[Sequence[Optional[str]]],
args: List[Expression],
context: Context) -> List[CallableType]:
"""Returns a list of all targets that match the caller after erasing types.

Assumes all of the given targets have argument counts compatible with the caller.
"""
matches = [] # type: List[CallableType]
for typ in plausible_targets:
if self.erased_signature_similarity(arg_types, arg_kinds, arg_names, typ, context):
if self.erased_signature_similarity(arg_types, arg_kinds, arg_names, args, typ,
context):
matches.append(typ)
return matches

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

def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int],
def erased_signature_similarity(self,
arg_types: List[Type],
arg_kinds: List[int],
arg_names: Optional[Sequence[Optional[str]]],
args: List[Expression],
callee: CallableType,
context: Context) -> bool:
"""Determine whether arguments could match the signature at runtime, after
Expand All @@ -1772,16 +1800,23 @@ def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int
# Too few or many arguments -> no match.
return False

def check_arg(caller_type: Type, original_caller_type: Type, caller_kind: int,
callee_type: Type, n: int, m: int, callee: CallableType,
context: Context, messages: MessageBuilder) -> None:
def check_arg(caller_type: Type,
original_ccaller_type: Type,
caller_kind: int,
callee_type: Type,
n: int,
m: int,
callee: CallableType,
context: Context,
outer_context: Context,
messages: MessageBuilder) -> None:
if not arg_approximate_similarity(caller_type, callee_type):
# No match -- exit early since none of the remaining work can change
# the result.
raise Finished

try:
self.check_argument_types(arg_types, arg_kinds, callee,
self.check_argument_types(arg_types, arg_kinds, args, callee,
formal_to_actual, context=context, check_arg=check_arg)
return True
except Finished:
Expand Down Expand Up @@ -2428,7 +2463,7 @@ def check_op(self, method: str, base_type: Type,
result, inferred = self.check_op_reversible(
op_name=method,
left_type=left_possible_type,
left_expr=TempNode(left_possible_type),
left_expr=TempNode(left_possible_type, context=context),
right_type=right_type,
right_expr=arg,
context=context,
Expand All @@ -2455,7 +2490,8 @@ def check_op(self, method: str, base_type: Type,
right_variants = [(right_type, arg)]
right_type = get_proper_type(right_type)
if isinstance(right_type, UnionType):
right_variants = [(item, TempNode(item)) for item in right_type.relevant_items()]
right_variants = [(item, TempNode(item, context=context))
for item in right_type.relevant_items()]

msg = self.msg.clean_copy()
msg.disable_count = 0
Expand All @@ -2467,7 +2503,7 @@ def check_op(self, method: str, base_type: Type,
result, inferred = self.check_op_reversible(
op_name=method,
left_type=left_possible_type,
left_expr=TempNode(left_possible_type),
left_expr=TempNode(left_possible_type, context=context),
right_type=right_possible_type,
right_expr=right_expr,
context=context,
Expand All @@ -2480,9 +2516,9 @@ def check_op(self, method: str, base_type: Type,
if len(left_variants) >= 2 and len(right_variants) >= 2:
self.msg.warn_both_operands_are_from_unions(context)
elif len(left_variants) >= 2:
self.msg.warn_operand_was_from_union("Left", base_type, context)
self.msg.warn_operand_was_from_union("Left", base_type, context=right_expr)
elif len(right_variants) >= 2:
self.msg.warn_operand_was_from_union("Right", right_type, context)
self.msg.warn_operand_was_from_union("Right", right_type, context=right_expr)

# See the comment in 'check_overload_call' for more details on why
# we call 'combine_function_signature' instead of just unioning the inferred
Expand Down Expand Up @@ -2811,10 +2847,10 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type:
assert expr.expr is not None
revealed_type = self.accept(expr.expr, type_context=self.type_context[-1])
if not self.chk.current_node_deferred:
self.msg.reveal_type(revealed_type, expr)
self.msg.reveal_type(revealed_type, expr.expr)
if not self.chk.in_checked_function():
self.msg.note("'reveal_type' always outputs 'Any' in unchecked functions",
expr)
expr.expr)
return revealed_type
else:
# REVEAL_LOCALS
Expand Down Expand Up @@ -3063,7 +3099,14 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
if key is None:
stargs.append(value)
else:
args.append(TupleExpr([key, value]))
tup = TupleExpr([key, value])
if key.line >= 0:
tup.line = key.line
tup.column = key.column
else:
tup.line = value.line
tup.column = value.column
args.append(tup)
# Define type variables (used in constructors below).
ktdef = TypeVarDef('KT', 'KT', -1, [], self.object_type())
vtdef = TypeVarDef('VT', 'VT', -2, [], self.object_type())
Expand Down
3 changes: 2 additions & 1 deletion mypy/checkmember.py
Expand Up @@ -469,7 +469,8 @@ def analyze_descriptor_access(instance_type: Type,

_, inferred_dunder_get_type = chk.expr_checker.check_call(
dunder_get_type,
[TempNode(instance_type), TempNode(TypeType.make_normalized(owner_type))],
[TempNode(instance_type, context=context),
TempNode(TypeType.make_normalized(owner_type), context=context)],
[ARG_POS, ARG_POS], context)

inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type)
Expand Down
11 changes: 8 additions & 3 deletions mypy/fastparse.py
Expand Up @@ -944,8 +944,8 @@ def visit_UnaryOp(self, n: ast3.UnaryOp) -> UnaryExpr:
# Lambda(arguments args, expr body)
def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr:
body = ast3.Return(n.body)
body.lineno = n.lineno
body.col_offset = n.col_offset
body.lineno = n.body.lineno
body.col_offset = n.body.col_offset

e = LambdaExpr(self.transform_args(n.args, n.lineno),
self.as_required_block([body], n.lineno))
Expand Down Expand Up @@ -1169,7 +1169,12 @@ def visit_Attribute(self, n: Attribute) -> Union[MemberExpr, SuperExpr]:
# Subscript(expr value, slice slice, expr_context ctx)
def visit_Subscript(self, n: ast3.Subscript) -> IndexExpr:
e = IndexExpr(self.visit(n.value), self.visit(n.slice))
return self.set_line(e, n)
self.set_line(e, n)
if isinstance(e.index, SliceExpr):
# Slice has no line/column in the raw ast.
e.index.line = e.line
e.index.column = e.column
return e

# Starred(expr value, expr_context ctx)
def visit_Starred(self, n: Starred) -> StarExpr:
Expand Down
11 changes: 8 additions & 3 deletions mypy/fastparse2.py
Expand Up @@ -834,8 +834,8 @@ def visit_Lambda(self, n: ast27.Lambda) -> LambdaExpr:
args, decompose_stmts = self.transform_args(n.args, n.lineno)

n_body = ast27.Return(n.body)
n_body.lineno = n.lineno
n_body.col_offset = n.col_offset
n_body.lineno = n.body.lineno
n_body.col_offset = n.body.col_offset
body = self.as_required_block([n_body], n.lineno)
if decompose_stmts:
body.body = decompose_stmts + body.body
Expand Down Expand Up @@ -1009,7 +1009,12 @@ def visit_Attribute(self, n: Attribute) -> Expression:
# Subscript(expr value, slice slice, expr_context ctx)
def visit_Subscript(self, n: ast27.Subscript) -> IndexExpr:
e = IndexExpr(self.visit(n.value), self.visit(n.slice))
return self.set_line(e, n)
self.set_line(e, n)
if isinstance(e.index, SliceExpr):
# Slice has no line/column in the raw ast.
e.index.line = e.line
e.index.column = e.column
return e

# Name(identifier id, expr_context ctx)
def visit_Name(self, n: Name) -> NameExpr:
Expand Down
14 changes: 10 additions & 4 deletions mypy/messages.py
Expand Up @@ -324,8 +324,14 @@ def untyped_function_call(self, callee: CallableType, context: Context) -> Type:
code=codes.NO_UNTYPED_CALL)
return AnyType(TypeOfAny.from_error)

def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type,
arg_kind: int, context: Context) -> Optional[ErrorCode]:
def incompatible_argument(self,
n: int,
m: int,
callee: CallableType,
arg_type: Type,
arg_kind: int,
context: Context,
outer_context: Context) -> Optional[ErrorCode]:
"""Report an error about an incompatible argument type.

The argument type is arg_type, argument number is n and the
Expand Down Expand Up @@ -456,8 +462,8 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
# For function calls with keyword arguments, display the argument name rather than the
# number.
arg_label = str(n)
if isinstance(context, CallExpr) and len(context.arg_names) >= n:
arg_name = context.arg_names[n - 1]
if isinstance(outer_context, CallExpr) and len(outer_context.arg_names) >= n:
arg_name = outer_context.arg_names[n - 1]
if arg_name is not None:
arg_label = '"{}"'.format(arg_name)

Expand Down
12 changes: 10 additions & 2 deletions mypy/nodes.py
Expand Up @@ -2224,13 +2224,21 @@ class TempNode(Expression):
# (e.g. for 'x: int' the rvalue is TempNode(AnyType(TypeOfAny.special_form), no_rhs=True))
no_rhs = False # type: bool

def __init__(self, typ: 'mypy.types.Type', no_rhs: bool = False) -> None:
def __init__(self,
typ: 'mypy.types.Type',
no_rhs: bool = False,
*,
context: Optional[Context] = None) -> None:
"""Construct a dummy node; optionally borrow line/column from context object."""
super().__init__()
self.type = typ
self.no_rhs = no_rhs
if context is not None:
self.line = context.line
self.column = context.column

def __repr__(self) -> str:
return 'TempNode(%s)' % str(self.type)
return 'TempNode:%d(%s)' % (self.line, str(self.type))

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