Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
7 changes: 1 addition & 6 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,13 @@ def infer_constraints_for_callable(
if param_spec and callee.arg_kinds[i] in (ARG_STAR, ARG_STAR2):
# If actual arguments are mapped to ParamSpec type, we can't infer individual
# constraints, instead store them and infer single constraint at the end.
# It is impossible to map actual kind to formal kind, so use some heuristic.
# This inference is used as a fallback, so relying on heuristic should be OK.
if not incomplete_star_mapping:
param_spec_arg_types.append(
mapper.expand_actual_type(
actual_arg_type, arg_kinds[actual], None, arg_kinds[actual]
)
)
actual_kind = arg_kinds[actual]
param_spec_arg_kinds.append(
ARG_POS if actual_kind not in (ARG_STAR, ARG_STAR2) else actual_kind
)
param_spec_arg_kinds.append(arg_kinds[actual])
param_spec_arg_names.append(arg_names[actual] if arg_names else None)
else:
actual_type = mapper.expand_actual_type(
Expand Down
23 changes: 11 additions & 12 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,21 +458,20 @@ def callable_corresponding_argument(
if by_name is not None and by_pos is not None:
if by_name == by_pos:
return by_name
# If we're dealing with an optional pos-only and an optional
# name-only arg, merge them. This is the case for all functions
# taking both *args and **args, or a pair of functions like so:
# If we're dealing with an optional pos and an optional
# name arg, merge them. This is the case for all functions
# taking both *args and **args, or a functions like so:

# def right(a: int = ...) -> None: ...
# def left(__a: int = ..., *, a: int = ...) -> None: ...
from mypy.subtypes import is_equivalent
# def left1(__a: int = ..., *, a: int = ...) -> None: ...
# def left2(x: int = ..., a: int = ...) -> None: ...

if (
not (by_name.required or by_pos.required)
and by_pos.name is None
and by_name.pos is None
and is_equivalent(by_name.typ, by_pos.typ)
):
return FormalArgument(by_name.name, by_pos.pos, by_name.typ, False)
from mypy.meet import meet_types

if not (by_name.required or by_pos.required):
return FormalArgument(
by_name.name, by_pos.pos, meet_types(by_pos.typ, by_name.typ), False
)
return by_name if by_name is not None else by_pos


Expand Down
31 changes: 31 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -6768,3 +6768,34 @@ class D(Generic[T]):
a: D[str] # E: Type argument "str" of "D" must be a subtype of "C"
reveal_type(a.f(1)) # N: Revealed type is "builtins.int"
reveal_type(a.f("x")) # N: Revealed type is "builtins.str"

[case testOverloadWithTwoRelevantArgsWithDifferentType]
from typing import overload, Union

@overload
def set(year: int) -> None:
...

@overload
def set() -> None:
...

# no error here:
def set(*args: object, **kw: int) -> None:
pass
[builtins fixtures/tuple.pyi]

[case testOverloadWithTwoRelevantOptionalArgs]
from typing import overload

@overload
def set(year: int) -> None:
...

@overload
def set() -> None:
...

# no error:
def set(x: int = 42, year: int = 42) -> None:
pass
33 changes: 27 additions & 6 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -439,13 +439,16 @@ def f(x: int) -> None: pass
def g(x: int, y: str) -> None: pass

reveal_type(register(lambda: f(1))) # N: Revealed type is "def ()"
reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (x: Literal[1]?)"
reveal_type(register(lambda x: f(x), x=1)) # N: Revealed type is "def (*, x: Literal[1]?)" \
# E: Cannot infer type of lambda
register(lambda x: f(x)) # E: Cannot infer type of lambda \
# E: Argument 1 to "register" has incompatible type "Callable[[Any], None]"; expected "Callable[[], None]"
register(lambda x: f(x), y=1) # E: Argument 1 to "register" has incompatible type "Callable[[Arg(int, 'x')], None]"; expected "Callable[[Arg(int, 'y')], None]"
register(lambda x: f(x), y=1) # E: Cannot infer type of lambda \
# E: Argument 1 to "register" has incompatible type "Callable[[Any], None]"; expected "Callable[[NamedArg(int, 'y')], None]"
reveal_type(register(lambda x: f(x), 1)) # N: Revealed type is "def (Literal[1]?)"
reveal_type(register(lambda x, y: g(x, y), 1, "a")) # N: Revealed type is "def (Literal[1]?, Literal['a']?)"
reveal_type(register(lambda x, y: g(x, y), 1, y="a")) # N: Revealed type is "def (Literal[1]?, y: Literal['a']?)"
reveal_type(register(lambda x, y: g(x, y), 1, y="a")) # N: Revealed type is "def (Literal[1]?, *, y: Literal['a']?)" \
# E: Cannot infer type of lambda
[builtins fixtures/dict.pyi]

[case testParamSpecInvalidCalls]
Expand Down Expand Up @@ -1677,7 +1680,7 @@ class Foo(Generic[P]):
def test(*args: P.args, **kwargs: P.kwargs) -> Foo[P]: ...

reveal_type(test(1, 2)) # N: Revealed type is "__main__.Foo[[Literal[1]?, Literal[2]?]]"
reveal_type(test(x=1, y=2)) # N: Revealed type is "__main__.Foo[[x: Literal[1]?, y: Literal[2]?]]"
reveal_type(test(x=1, y=2)) # N: Revealed type is "__main__.Foo[[*, x: Literal[1]?, y: Literal[2]?]]"
ints = [1, 2, 3]
reveal_type(test(*ints)) # N: Revealed type is "__main__.Foo[[*builtins.int]]"
[builtins fixtures/paramspec.pyi]
Expand Down Expand Up @@ -1732,7 +1735,7 @@ apply(apply, test2, 42, "yes")
apply(apply, test2, "no", 42) # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], str, int], None]"
apply(apply, test2, x=42, y="yes")
apply(apply, test2, y="yes", x=42)
apply(apply, test2, y=42, x="no") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], int, str], None]"
apply(apply, test2, y=42, x="no") # E: Argument 1 to "apply" has incompatible type "Callable[[Callable[P, T], **P], None]"; expected "Callable[[Callable[[int, str], None], NamedArg(int, 'y'), NamedArg(str, 'x')], None]"
[builtins fixtures/paramspec.pyi]

[case testParamSpecApplyPosVsNamedOptional]
Expand Down Expand Up @@ -2157,7 +2160,7 @@ reveal_type(submit( # N: Revealed type is "__main__.Result"
backend="asyncio",
))
submit(
run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], R], VarArg(object), DefaultNamedArg(str, 'backend')], R]"; expected "Callable[[Callable[[], Result], int], Result]"
run, # E: Argument 1 to "submit" has incompatible type "Callable[[Callable[[], R], VarArg(object), DefaultNamedArg(str, 'backend')], R]"; expected "Callable[[Callable[[], Result], NamedArg(int, 'backend')], Result]"
run_portal,
backend=int(),
)
Expand Down Expand Up @@ -2532,3 +2535,21 @@ class GenericWrapper(Generic[P]):
def contains(c: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None: ...
def inherits(*args: P.args, **kwargs: P.kwargs) -> None: ...
[builtins fixtures/paramspec.pyi]

[case testParamSpecInferenceFromArgs]
from typing_extensions import ParamSpec
from typing import Any, Callable, Union

P = ParamSpec("P")

def into(f: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None:
return None

class C:
def f(self, y: bool = False, *, x: int = 42) -> None:
return None

ex: Union[C, Any] = C()

into(ex.f, x=-1)
[builtins fixtures/paramspec.pyi]