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

False positive for overload parameters overlapping with specific recursive types #9514

Closed
Kajiih opened this issue Nov 28, 2024 · 4 comments
Closed
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working

Comments

@Kajiih
Copy link

Kajiih commented Nov 28, 2024

Describe the bug
There seems to be a false positive for overload parameters overlapping, happening specifically with some recursive types.

I couldn't point out the exact issue, so here's an example:
Code or Screenshots
It is not really a minimal example, but I tried to keep some context. In particular, the protocol CallableArbitraryNbArgs was originally generic, but the false positive happens in both generic and non-generic cases.

The false positive happens when using NestedMapping which is a recursive type that involves NestedMappingNode, but not when using NestedMapping2, that doesn't.

from collections.abc import Callable, Mapping
from typing import Any, Protocol, overload

type NestedMapping[K, V] = Mapping[K, NestedMappingNode[K, V]]
type NestedMappingNode[K, V] = V | NestedMapping[K, V]


type NestedMapping2[K, V] = Mapping[K, NestedMapping2[K, V]]


class CallableArbitraryNbArgs(Protocol):
    """Protocol for a callable that accepts an arbitrary number of arguments."""

    def __call__(self, *args: Any) -> Any: ...  # noqa: D102


# === With NestedMapping: false positive ===
@overload
def map_leaves_1[K, V, W](
    func: CallableArbitraryNbArgs,
    *nested_dicts: NestedMapping[K, V],
) -> NestedMapping[K, W]: ...


# Overload 2 for "map_leaves_no_generic_protocol" will never be used because its parameters overlap overload 1
@overload
def map_leaves_1[K, W](
    func: Callable[..., W],
    *nested_dicts: NestedMapping[K, Any],
) -> NestedMapping[K, W]: ...


def map_leaves_1[K, W](
    func: Callable[..., W] | CallableArbitraryNbArgs, *nested_dicts: NestedMapping[K, Any]
) -> NestedMapping[K, W]:
    del nested_dicts
    del func
    raise NotImplementedError


# === With NestedMapping2: no error ===
@overload
def map_leaves_2[K, V, W](
    func: CallableArbitraryNbArgs,
    *nested_dicts: NestedMapping2[K, V],
) -> NestedMapping[K, W]: ...


@overload
def map_leaves_2[K, W](
    func: Callable[..., W],
    *nested_dicts: NestedMapping2[K, Any],
) -> NestedMapping[K, W]: ...


def map_leaves_2[K, W](
    func: Callable[..., W] | CallableArbitraryNbArgs, *nested_dicts: NestedMapping2[K, Any]
) -> NestedMapping[K, W]:
    del nested_dicts
    del func
    raise NotImplementedError

If I'm not mistaking, the overload parameters don't overlap in either case.

VS Code extension or command-line
Both extension and command line, Pylance and Pyright version 1.1.389

@Kajiih Kajiih added the bug Something isn't working label Nov 28, 2024
@loing-cdut

This comment has been minimized.

@erictraut
Copy link
Collaborator

Thanks for the bug report. I agree that this should not be considered a fully-overlapping override, but perhaps not for the reason you think. It has nothing to do with recursive types. It's because CallableArbitraryNbArgs does not accept keyword arguments.

Here's a code sample that demonstrates how a callback function with keyword parameters would not be accepted by the first overload but would be accepted by the second.

from typing import Any, Callable, Protocol

class CB(Protocol):
    def __call__(self, *args: Any) -> Any: ...

def overload1(func: CB) -> None: ...
def overload2(func: Callable[..., Any]) -> None: ...

def cb(*, x: int) -> None: ...

overload1(cb)  # Error
overload2(cb)

@Kajiih
Copy link
Author

Kajiih commented Nov 29, 2024

Thank you.
Indeed, there is the same error when we remove the *nested_dicts parameters altogether. But another thing that's confusing here is that the behavior changes when the type of *nested_dicts changes in the example. Specifically, it is not considered fully overlapping in the second case.

erictraut added a commit that referenced this issue Nov 29, 2024
…n the overload accepts a `Callable[..., T]` form. This addresses #9514.
erictraut added a commit that referenced this issue Nov 29, 2024
…n the overload accepts a `Callable[..., T]` form. This addresses #9514. (#9516)
@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Nov 29, 2024
@erictraut
Copy link
Collaborator

This is addressed in pyright 1.1.390.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants