Skip to content

Improved error message for incompatible default argument #3773

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,9 +740,7 @@ def is_implicit_any(t: Type) -> bool:

# Type check initialization expressions.
for arg in item.arguments:
init = arg.initialization_statement
if init:
self.accept(init)
self.expr_checker.check_default_arg(arg)

# Type check body in a new scope.
with self.binder.top_frame_context():
Expand Down
16 changes: 15 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import OrderedDict
from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional

from mypy.errors import report_internal_error
from mypy.errors import Errors, report_internal_error
from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any, set_any_tvars
from mypy.types import (
Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef,
Expand Down Expand Up @@ -1011,6 +1011,20 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
messages.incompatible_argument(n, m, callee, original_caller_type,
caller_kind, context)

def check_default_arg(self, arg: mypy.nodes.Argument) -> None:
rvalue = arg.initializer
lval_type = arg.variable.type
if rvalue and lval_type and not (self.chk.is_stub and isinstance(rvalue, EllipsisExpr)):
single = CallableType([lval_type], [ARG_POS], [None],
AnyType(), self.named_type('builtins.function'))
msg = MessageBuilder(Errors(), self.chk.modules)
self.check_call(single, [rvalue], [ARG_POS], arg, arg_messages=msg)
if msg.is_errors():
rval_type = self.accept(rvalue, lval_type)
varname = arg.variable.name()
self.msg.incompatible_default_argument(
varname, lval_type, rval_type, arg)

def overload_call_target(self, arg_types: List[Type], arg_kinds: List[int],
arg_names: List[str],
overload: Overloaded, context: Context,
Expand Down
10 changes: 10 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,16 @@ def untyped_function_call(self, callee: CallableType, context: Context) -> Type:
self.fail('Call to untyped function {} in typed context'.format(name), context)
return AnyType()

def incompatible_default_argument(self, name: str, lvalue: Type, rvalue: Type,
context: Context) -> None:
if name.startswith('__tuple_arg_'):
fmt = "Incompatible default for tuple argument no' {}".format(name[12:])
else:
fmt = 'Incompatible default for argument "{}"'.format(name)
fmt += ' (argument has type {}, default has type {})'
lvalt, rvalt = self.format_distinctly(lvalue, rvalue)
self.fail(fmt.format(lvalt, rvalt), context)

def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type,
arg_kind: int, context: Context) -> None:
"""Report an error about an incompatible argument type.
Expand Down
35 changes: 30 additions & 5 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -388,17 +388,42 @@ class A: pass

[case testDefaultArgumentExpressions2]
import typing
def f(x: 'A' = B()) -> None: # E: Incompatible types in assignment (expression has type "B", variable has type "A")
def f(x: 'A' = B()) -> None: # E: Incompatible default for argument "x" (argument has type "A", default has type "B")
b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B")
a = x # type: A

class B: pass
class A: pass
[out]

[case testDefaultArgumentExpressionsGeneric]
from typing import TypeVar
T = TypeVar('T', bound='A')
def f(x: T = B()) -> None: # E: Incompatible default for argument "x" (argument has type "T", default has type "B")
b = x # type: B # E: Incompatible types in assignment (expression has type "T", variable has type "B")
a = x # type: A

class B: pass
class A: pass

[case testDefaultArgumentExpressionsPython2]
# flags: --python-version 2.7
from typing import Tuple
def f(x = B()): # E: Incompatible default for argument "x" (argument has type "A", default has type "B")
# type: (A) -> None
b = x # type: B # E: Incompatible types in assignment (expression has type "A", variable has type "B")
a = x # type: A

def g((x, y) = (A(), B())): # E: Incompatible default for tuple argument no' 1 (argument has type "Tuple[B, B]", default has type "Tuple[A, B]")
# type: ( Tuple[B, B] ) -> None
b = x # type: B
a = x # type: A # E: Incompatible types in assignment (expression has type "B", variable has type "A")

class B: pass
class A: pass

[case testDefaultArgumentsWithSubtypes]
import typing
def f(x: 'B' = A()) -> None: # E: Incompatible types in assignment (expression has type "A", variable has type "B")
def f(x: 'B' = A()) -> None: # E: Incompatible default for argument "x" (argument has type "B", default has type "A")
pass
def g(x: 'A' = B()) -> None:
pass
Expand All @@ -409,7 +434,7 @@ class B(A): pass

[case testMultipleDefaultArgumentExpressions]
import typing
def f(x: 'A' = B(), y: 'B' = B()) -> None: # E: Incompatible types in assignment (expression has type "B", variable has type "A")
def f(x: 'A' = B(), y: 'B' = B()) -> None: # E: Incompatible default for argument "x" (argument has type "A", default has type "B")
pass
def h(x: 'A' = A(), y: 'B' = B()) -> None:
pass
Expand All @@ -420,7 +445,7 @@ class B: pass

[case testMultipleDefaultArgumentExpressions2]
import typing
def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible types in assignment (expression has type "A", variable has type "B")
def g(x: 'A' = A(), y: 'B' = A()) -> None: # E: Incompatible default for argument "y" (argument has type "B", default has type "A")
pass

class A: pass
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -1119,7 +1119,7 @@ from typing import Callable
def f(a: Callable[..., None] = lambda *a, **k: None):
pass

def g(a: Callable[..., None] = lambda *a, **k: 1): # E: Incompatible types in assignment (expression has type Callable[[VarArg(Any), KwArg(Any)], int], variable has type Callable[..., None])
def g(a: Callable[..., None] = lambda *a, **k: 1): # E: Incompatible default for argument "a" (argument has type Callable[..., None], default has type Callable[[VarArg(Any), KwArg(Any)], int])
pass
[builtins fixtures/dict.pyi]

Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -475,11 +475,11 @@ def f(x: int = ...) -> None: pass
[file m.pyi]
def g(x: int = '') -> None: pass
[out]
tmp/m.pyi:1: error: Incompatible types in assignment (expression has type "str", variable has type "int")
main:2: error: Incompatible types in assignment (expression has type "ellipsis", variable has type "int")
tmp/m.pyi:1: error: Incompatible default for argument "x" (argument has type "int", default has type "str")
main:2: error: Incompatible default for argument "x" (argument has type "int", default has type "ellipsis")

[case testEllipsisDefaultArgValueInNonStub]
def f(x: int = ...) -> None: pass # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "int")
def f(x: int = ...) -> None: pass # E: Incompatible default for argument "x" (argument has type "int", default has type "ellipsis")
[out]

[case testStarImportOverlapping]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ f(None)

[case testNoInferOptionalFromDefaultNone]
# flags: --no-implicit-optional
def f(x: int = None) -> None: # E: Incompatible types in assignment (expression has type None, variable has type "int")
def f(x: int = None) -> None: # E: Incompatible default for argument "x" (argument has type "int", default has type None)
pass
[out]

Expand All @@ -140,7 +140,7 @@ f(None)

[case testNoInferOptionalFromDefaultNoneComment]
# flags: --no-implicit-optional
def f(x=None): # E: Incompatible types in assignment (expression has type None, variable has type "int")
def f(x=None): # E: Incompatible default for argument "x" (argument has type "int", default has type None)
# type: (int) -> None
pass
[out]
Expand Down