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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

(馃悶) 1.7 regression, (x: int) considered a valid subtype of (*args: Any) #16569

Closed
KotlinIsland opened this issue Nov 26, 2023 · 6 comments
Closed
Labels
bug mypy got something wrong

Comments

@KotlinIsland
Copy link
Contributor

KotlinIsland commented Nov 26, 2023

# mypy: disable-error-code=empty-body

from typing import Any, Protocol

class Base:
    def f(self, *args: Any) -> None: ...

class Derived(Base):
    def f(self, x: int) -> None: ...  # no error

See:

mypy/mypy/subtypes.py

Lines 1597 to 1607 in 1200d1d

if (
right.arg_kinds == [ARG_STAR]
and isinstance(get_proper_type(right.arg_types[0]), AnyType)
and not is_proper_subtype
):
# Similar to how (*Any, **Any) is considered a supertype of all callables, we consider
# (*Any) a supertype of all callables with positional arguments. This is needed in
# particular because we often refuse to try type inference if actual type is not
# a subtype of erased template type.
if all(k.is_positional() for k in left.arg_kinds) and ignore_pos_arg_names:
return True

This is inconsistent with how (*args: Any, **kwargs: Any) works:

# mypy: disable-error-code=empty-body
from __future__ import annotations

from typing import Any, Protocol

class Base:
    def f1(self, x: int, *args: Any) -> None: ...
    def f2(self, x: int, *args: Any, **kwargs: Any) -> None: ...

class Derived(Base):
    def f1(self, x: int, y: str) -> None: ...  # Signature of "f1" incompatible with supertype "Base"
    def f2(self, x: int, y: str) -> None: ...

@ilevkivskyi

@KotlinIsland KotlinIsland added the bug mypy got something wrong label Nov 26, 2023
@JelleZijlstra
Copy link
Member

That error seems correct. The protocol demands that the function take any number of arguments. A callable that takes exactly one argument is not compatible.

@KotlinIsland
Copy link
Contributor Author

KotlinIsland commented Nov 26, 2023

@JelleZijlstra Intuitively, I thought the same thing, but I think this is intentional, when it is not the subject of protocol compatability, mypy likes this subtype:

# mypy: disable-error-code=empty-body
from __future__ import annotations

from typing import Any, Protocol

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

class B(A): 
    def __call__(self, x: int) -> None: ...  # no error

a: A = B()  # no error

Although, it could be that this is the defective behavior...

See

mypy/mypy/subtypes.py

Lines 1597 to 1607 in 1200d1d

if (
right.arg_kinds == [ARG_STAR]
and isinstance(get_proper_type(right.arg_types[0]), AnyType)
and not is_proper_subtype
):
# Similar to how (*Any, **Any) is considered a supertype of all callables, we consider
# (*Any) a supertype of all callables with positional arguments. This is needed in
# particular because we often refuse to try type inference if actual type is not
# a subtype of erased template type.
if all(k.is_positional() for k in left.arg_kinds) and ignore_pos_arg_names:
return True

@erictraut
Copy link

Jelle's analysis above is correct. Mypy is correct in emitting an error in the first code sample because protocol A requires that the method accept an arbitrary number of positional arguments, but B does not.

it could be that this is the defective behavior...

Yes, I think this is a false negative. This should be flagged as an incompatible method override. For comparison, pyright emits an error here.

The justification provided in the comment seems weak. This introduces a hole in the type system. Likewise, I don't think it's correct to consider (*Any, **Any) a supertype of all callables. However, ... (when used in a Callable or as as a type argument to a ParamSpec) is both a supertype and a subtype of all callables. (It's the moral equivalent of Any when used with a callable signature.)

These would be good points to discuss and clarify in the context of the typing spec shepherded by the newly-formed typing council (TC).

@KotlinIsland KotlinIsland changed the title (馃悶) Procotol compatability for __call__ methods are broken for a *args: Any signature (馃悶) 1.7 regression, (x: int) considered a valid subtype of (*args: Any) Nov 26, 2023
@KotlinIsland
Copy link
Contributor Author

KotlinIsland commented Nov 26, 2023

Ah yes, after some investigation, it appears that this is an unintended regression in 1.7, although all versions of mypy since 0.980 consider this as valid:

from typing import Any

class Base:
    def f(self, *args: Any, **kwargs: Any) -> None: ...

class Derived(Base):
    def f(self, x: int) -> None: ...  # no error

@ilevkivskyi ilevkivskyi closed this as not planned Won't fix, can't repro, duplicate, stale Nov 27, 2023
@KotlinIsland
Copy link
Contributor Author

@ilevkivskyi Was this closed as it is working as intended? if so, could you comment on the second example in the OP.

@hauntsaninja
Copy link
Collaborator

Regarding the previous comment / whether (*Any, **Any) is a supertype of all callables
This is obviously not sound, but at least users are explicitly using Any. The decision was made in #5876, which was a top ten all time most popular mypy issue.
Note mypy will still do the sound thing for (*object, **object)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

5 participants