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

a descriptor object returned from a __getattr__ accessor is incorrectly typed as the descriptor's class-based result #2282

Closed
zzzeek opened this issue Jan 24, 2022 · 5 comments
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version

Comments

@zzzeek
Copy link

zzzeek commented Jan 24, 2022

Environment data

Pylance v2022.1.4-pre.1
Pylance language server 2022.1.4-pre.1 (pyright 98868f26) starting
Fedora 34
Python 3.10.0

I'm able to get the correct answer in mypy, wrong answer in pyright / pylance. pylance is being fooled by an object's __getattr__ scheme returning a descriptor, but the descriptor is not bound to that object:

import typing
from typing import Any
from typing import Generic
from typing import overload
from typing import TypeVar
from typing import Union

_T = TypeVar("_T")


class OtherThing(Generic[_T]):
    """a random thing."""

class Descriptor(Generic[_T]):
    """An object that holds a random thing and is also a descriptor."""

    def __init__(self, otherthing: OtherThing[_T]):
        self.otherthing = otherthing

    @overload
    def __get__(
        self, instance: None, owner: Any
    ) -> "OtherThing[_T]":
        ...

    @overload
    def __get__(self, instance: object, owner: Any) -> _T:
        ...

    def __get__(
        self, instance: object, owner: Any
    ) -> Union["OtherThing[_T]", _T]:
        return self.otherthing



class CollectionThing(Generic[_T]):
    """A collection of anything that returns it using both dict-style
    and getattr access."""
    thing: _T

    def __init__(self, thing: _T):
        self.thing = thing

    def __getitem__(self, key: str) -> _T:
        return self.thing

    def __getattr__(self, key: str) -> _T:
        return self.thing


some_otherthing: OtherThing[int] = OtherThing()
some_descriptor: Descriptor[int] = Descriptor(some_otherthing)
c1: CollectionThing[Descriptor[int]] = CollectionThing(some_descriptor)

# these two methods return the same object.
# The Descriptor is not resolved by the Python interpreter, so we
# do not get an OtherThing object.  pylance says we do though

# (variable) a1: Descriptor[int]
a1 = c1["key"]

# (variable) a2: OtherThing[int]  # <--- incorrect
a2 = c1.key

assert a1 is a2
assert a1 is some_descriptor
assert isinstance(a1, Descriptor)

if typing.TYPE_CHECKING:
    reveal_type(a1)
    reveal_type(a2)

pyright output:

Found 1 source file
/home/classic/dev/sqlalchemy/test3.py
  /home/classic/dev/sqlalchemy/test3.py:71:17 - information: Type of "a1" is "Descriptor[int]"
  /home/classic/dev/sqlalchemy/test3.py:72:17 - information: Type of "a2" is "OtherThing[int]"
0 errors, 0 warnings, 2 informations 

mypy ouutput:

$ mypy test3.py 
test3.py:71: note: Revealed type is "test3.Descriptor[builtins.int]"
test3.py:72: note: Revealed type is "test3.Descriptor*[builtins.int]"
@erictraut
Copy link
Contributor

Thanks for the bug report. Since this is a core type checking issue, I've opened a tracking bug in the pyright project: microsoft/pyright#2933

@erictraut erictraut added bug Something isn't working and removed triage labels Jan 24, 2022
@zzzeek
Copy link
Author

zzzeek commented Jan 24, 2022

ah OK wasnt sure which tracker to open it in. ill do pyright for the next one (but of course there wont be any more bugs! :) )

@erictraut
Copy link
Contributor

No worries. Feel free to file issues in either pylance or pyright, and we'll route them appropriately.

@erictraut
Copy link
Contributor

This will be fixed in the next release.

The code in pyright was doing extra work to bind the descriptor to the object. I've confirmed that's not how the runtime works, so I was able to delete this extra unnecessary logic.

@erictraut erictraut added the fixed in next version (main) A fix has been implemented and will appear in an upcoming version label Jan 25, 2022
@debonte
Copy link
Contributor

debonte commented Feb 4, 2022

This issue was fixed in version 2022.1.5. You can find the changelog here: CHANGELOG.md

@debonte debonte closed this as completed Feb 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version
Projects
None yet
Development

No branches or pull requests

3 participants