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

Merge two different applicable argument types when synthesizing corresponding argument #18627

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
16 changes: 16 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2278,6 +2278,22 @@ def get_arg_infer_passes(
# run(test, 1, 2)
# we will use `test` for inference, since it will allow to infer also
# argument *names* for P <: [x: int, y: int].
if isinstance(p_actual, UnionType):
new_items = []
for item in p_actual.items:
# narrow the union based on some approximations
p_item = get_proper_type(item)
if isinstance(p_item, CallableType) or (
isinstance(p_item, Instance)
and find_member("__call__", p_item, p_item, is_operator=True)
is not None
):
new_items.append(p_item)
if len(new_items) == 2:
break

if len(new_items) == 1:
p_actual = new_items[0]
if isinstance(p_actual, Instance):
call_method = find_member("__call__", p_actual, p_actual, is_operator=True)
if call_method is not None:
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
18 changes: 18 additions & 0 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -2560,3 +2560,21 @@ def fn(f: MiddlewareFactory[P]) -> Capture[P]: ...

reveal_type(fn(ServerErrorMiddleware)) # N: Revealed type is "__main__.Capture[[handler: Union[builtins.str, None] =, debug: builtins.bool =]]"
[builtins fixtures/paramspec.pyi]

[case testParamSpecInferenceWithAny]
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]