Josverl / micropython-stubs

Stubs of most MicroPython ports, boards and versions to make writing code that much simpler.
https://micropython-stubs.readthedocs.io
MIT License
130 stars 21 forks source link

Attempt to create a subclass of a generic class results in TypeError: '_AnyCall' object isn't subscriptable #755

Closed ANogin closed 8 hours ago

ANogin commented 1 month ago

I have

T = TypeVar('T')

class Foo(Generic[T]):
   ...

class Bar(Foo[Something]):
   ...

And micropython complains:

TypeError: '_AnyCall' object isn't subscriptable

for the Foo[Something]...

Josverl commented 1 month ago

Can you add some more detail on the type checker you are using, the python version on your host and the stubs package and version you are using?

Josverl commented 1 month ago

If I test your sample in Pyright's playground it also shows an error ( but a different one )

Code sample in pyright playground


from typing import TypeVar, Generic, List, Tuple

T = TypeVar('T')

class Foo(Generic[T]):
   ...

# Something does not exist

class Baz(Foo[Something]):
   ...

# Something needs to exist 
Something = List[Tuple]

class Bar(Foo[Something]):
   ...
Josverl commented 1 month ago

And a quick test does seem not show an obvious error.

from typing import Dict, Generic, TypeVar
from typing_extensions import reveal_type

T = TypeVar("T")
from machine import Pin, Signal

class Registry(Generic[T]):
    def __init__(self) -> None:
        self._store: Dict[str, T] = {}

    def set_item(self, k: str, v: T) -> None:
        self._store[k] = v

    def get_item(self, k: str) -> T:
        return self._store[k]

inputs = Registry[Pin]()
inputs.set_item("button", Pin(4, Pin.IN))
inputs.set_item("X", Pin(18, Pin.IN))
inputs.set_item("Y", Pin(19, Pin.IN))

outputs = Registry[Signal]()
outputs.set_item("Player-A", Signal(Pin(5, Pin.OUT)))
outputs.set_item("Player-B", Signal(Pin(6, Pin.OUT)))
outputs.set_item("Game over", Signal(Pin(8, Pin.OUT), invert=True))

b = inputs.get_item("button")
reveal_type(b)  # Expected `Pin`, got `Pin`

x = inputs.get_item("X")
reveal_type(x)  # Expected `Pin`, got `Pin`

game_over = outputs.get_item("Game over")
reveal_type(game_over)  # Expected `Signal`, got `Signal`
ANogin commented 1 month ago

Couple of things, sorry for not being sufficiently clear:

Josverl commented 1 month ago

Then it may be that you need to add the round brackets , that I tend to forget as well So rather than inputs = Registry[Pin] use inputs = Registry[Pin]()

Or in your sample foo = Foo[Something]()

ANogin commented 1 month ago

@js

Then it may be that you need to add the round brackets , that I tend to forget as well So rather than inputs = Registry[Pin] use inputs = Registry[Pin]()

Or in your sample foo = Foo[Something]()

Note that I do not have any foo - I have a subclass definition. Again, note that this works correctly with mypy and the syntax is correct. The issue is that the hack you use to define Generic causes subclasses to fail in micropython.

ANogin commented 1 month ago

E.g.

% cat test.py

from typing import TypeVar, Generic, Any

T = TypeVar('T')

class Foo(Generic[T]):
   pass

class Bar(Foo[Any]):
   pass

% mypy test.py Success: no issues found in 1 source file % python3 test.py % wget https://github.com/Josverl/micropython-stubs/raw/main/mip/typing.mpy --2024-05-28 21:12:05-- https://github.com/Josverl/micropython-stubs/raw/main/mip/typing.mpy Resolving github.com (github.com)... 140.82.116.3 Connecting to github.com (github.com)|140.82.116.3|:443... connected. HTTP request sent, awaiting response... 302 Found Location: https://raw.githubusercontent.com/Josverl/micropython-stubs/main/mip/typing.mpy [following] --2024-05-28 21:12:06-- https://raw.githubusercontent.com/Josverl/micropython-stubs/main/mip/typing.mpy Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.111.133, ... Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1669 (1.6K) [application/octet-stream] Saving to: ‘typing.mpy’

typing.mpy 100%[=================================================>] 1.63K --.-KB/s in 0s

2024-05-28 21:12:06 (15.9 MB/s) - ‘typing.mpy’ saved [1669/1669]

% micropython
MicroPython v1.22.2 on 2024-02-20; darwin [GCC 4.2.1] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> import test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 8, in <module>
TypeError: '_AnyCall' object isn't subscriptable
Josverl commented 1 month ago

Thanks , now I know what to look for . Likely _AnyCall needs a __getitem__ method

andrewleech commented 1 month ago

Ah, Generic is subscriptable, but doing so returns AnyCall which isn't. Maybe subscriptable should inherit from AnyCall, but return a new subscriptable so it can be used recursively like this.

Josverl commented 1 day ago

@ANogin ,

I think I can solve your problem based on Andrew`s suggestion. But the only testing in this thusfar has been on your code snippet so please let me know if this works for you.

typings.py

class _AnyCall:
    def __init__(*args, **kwargs):
        pass

    def __call__(*args, **kwargs):
        pass
    def __getitem__(self, arg): # <--
        return _anyCall

_anyCall = _AnyCall()

class _SubscriptableType:
    def __getitem__(self, arg):
        return _anyCall
Josverl commented 8 hours ago

I have merged the updated typings.py/.mpy and added a number of test cases. See: https://github.com/Josverl/micropython-stubs/tree/main/tests/quality_tests/feat_typing