Skip to content

False positive about self argument with union of namedtuple and Any #15600

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
JukkaL opened this issue Jul 5, 2023 · 1 comment · Fixed by #18666
Closed

False positive about self argument with union of namedtuple and Any #15600

JukkaL opened this issue Jul 5, 2023 · 1 comment · Fixed by #18666
Labels
bug mypy got something wrong topic-named-tuple

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Jul 5, 2023

The following code produces a false positive:

from collections import namedtuple
from typing import Any

T = namedtuple("T", ["x"])

class C(T):
    def f(self) -> bool:
        return True

c: C | Any
c.f()  # Error: Invalid self argument "C" to attribute function "f" with type "Callable[[C], bool]"

c2: C
c2.f()  # Ok

It looks like the self argument check fails when we have a union with a namedtuple type. It's okay if the base class is a regular class, or if there is no union.

@JukkaL JukkaL added the bug mypy got something wrong label Jul 5, 2023
@ilevkivskyi
Copy link
Member

Note that the bug is specific to a union with Any, union with another type (that has the attribute) doesn't cause the problem. This is happening because self argument check does something like is_subtype(meet_types(base_type, instance_type), erase_type_vars(declared_self_type)), and meet_types() is broken for Any. Namely, in this case meet_types(Union[tuple[Any, fallback=C], Any], C) returns C, because meet_types(Any, C) returns C. And then is_subtype(C, tuple[Any, fallback=C]) obviously returns False, causing the error.

But the problem is actually much wider, meet_types(C, Any) returning C is both wrong and inconsistent (with joins returning Any). Here is a more abstract example: imagine arbitrary pair of types C <: B, then if meet(X, Any) = X, and meet(X | Y, Z) = meet(X, Z) | meet(Y, Z), we get a contradiction meet(C | Any, B) = meet(C, B) | meet(Any, B) = C | B = B, while by definition meet must be a subtype of both arguments. Btw @JukkaL proposed to switch meet(X, Any) from X to Any a long time ago in #3194 (comment). This may be a good argument to actually do this.

Unfortunately, switching meet(X, Any) to return Any causes 22 test failures, most of them look like something that just needs to be updated, but also there are few tests that fail because if isinstance(x, list): ... behaves differently (since in such cases both meet and Any are present). So this is not a super-easy fix as I hoped.

hauntsaninja pushed a commit that referenced this issue Feb 12, 2025
The hack to use `meet_types(original_type, itype)` to select a correct
element from a union appeared before we added proper handling of unions
in various places related to `checkmember.py`. This is error prone,
since `meet_types()` is one of least precise type ops (for good and bad
reasons), and results in obscure bugs, see e.g.
#15600

This hack should not be needed anymore, now we have three-level
information available everywhere we needed it:
* `original_type` - as the name says, a type from which everything
started. This is used for error messages and for plugin hooks.
* `self_type` - a specific element of the union is the original type is
a union. The name is because this is what will be ultimately used by
`bind_self()`
* `itype` the actual instance type where we look up the attribute (this
will be e.g. a fallback if the `self_type` is not an instance)
ericmarkmartin pushed a commit to ericmarkmartin/mypy that referenced this issue Feb 19, 2025
The hack to use `meet_types(original_type, itype)` to select a correct
element from a union appeared before we added proper handling of unions
in various places related to `checkmember.py`. This is error prone,
since `meet_types()` is one of least precise type ops (for good and bad
reasons), and results in obscure bugs, see e.g.
python#15600

This hack should not be needed anymore, now we have three-level
information available everywhere we needed it:
* `original_type` - as the name says, a type from which everything
started. This is used for error messages and for plugin hooks.
* `self_type` - a specific element of the union is the original type is
a union. The name is because this is what will be ultimately used by
`bind_self()`
* `itype` the actual instance type where we look up the attribute (this
will be e.g. a fallback if the `self_type` is not an instance)
ericmarkmartin pushed a commit to ericmarkmartin/mypy that referenced this issue Feb 19, 2025
Fixes python#15600

The issue was previously "fixed" because of another bug. Now that
everything is properly fixed, we can add this "regression" test just in
case.
ericmarkmartin pushed a commit to ericmarkmartin/mypy that referenced this issue Feb 19, 2025
The hack to use `meet_types(original_type, itype)` to select a correct
element from a union appeared before we added proper handling of unions
in various places related to `checkmember.py`. This is error prone,
since `meet_types()` is one of least precise type ops (for good and bad
reasons), and results in obscure bugs, see e.g.
python#15600

This hack should not be needed anymore, now we have three-level
information available everywhere we needed it:
* `original_type` - as the name says, a type from which everything
started. This is used for error messages and for plugin hooks.
* `self_type` - a specific element of the union is the original type is
a union. The name is because this is what will be ultimately used by
`bind_self()`
* `itype` the actual instance type where we look up the attribute (this
will be e.g. a fallback if the `self_type` is not an instance)
ericmarkmartin pushed a commit to ericmarkmartin/mypy that referenced this issue Feb 19, 2025
Fixes python#15600

The issue was previously "fixed" because of another bug. Now that
everything is properly fixed, we can add this "regression" test just in
case.
x612skm pushed a commit to x612skm/mypy-dev that referenced this issue Feb 24, 2025
The hack to use `meet_types(original_type, itype)` to select a correct
element from a union appeared before we added proper handling of unions
in various places related to `checkmember.py`. This is error prone,
since `meet_types()` is one of least precise type ops (for good and bad
reasons), and results in obscure bugs, see e.g.
python#15600

This hack should not be needed anymore, now we have three-level
information available everywhere we needed it:
* `original_type` - as the name says, a type from which everything
started. This is used for error messages and for plugin hooks.
* `self_type` - a specific element of the union is the original type is
a union. The name is because this is what will be ultimately used by
`bind_self()`
* `itype` the actual instance type where we look up the attribute (this
will be e.g. a fallback if the `self_type` is not an instance)
x612skm pushed a commit to x612skm/mypy-dev that referenced this issue Feb 24, 2025
Fixes python#15600

The issue was previously "fixed" because of another bug. Now that
everything is properly fixed, we can add this "regression" test just in
case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-named-tuple
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants