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
133 stars 21 forks source link

Pylance reports: Dictionary key must be hashable: Type "int" is not hashable. #723

Closed glenn20 closed 9 months ago

glenn20 commented 10 months ago

I have just installed micropython_esp32_stubs-1.20.0.post2 to use with Pylance and VSCode (version 1.81.1) and this code i = 0; d = {i: "a"} generates the following problem report from Pylance (version v2023.8.50):

Dictionary key must be hashable
  Type "int" is not hashable"

A similar error is generated for "str" keys.

I installed with pip install -U micropython-esp32-stubs --target ~/.micropy/micropython-esp32-stubs --no-user.

My relevant parts of my workspace settings.json is:

{
    "python.autoComplete.extraPaths": [
        "~/.micropy/latest",
        "~/.micropy/extra"
    ],
    "python.analysis.typeshedPaths": [
        "~/.micropy/latest",
        "~/.micropy/extra"
    ],
    "python.analysis.typeCheckingMode": "basic",
    "python.analysis.diagnosticSeverityOverrides": {
        "reportMissingModuleSource": "none"
    },
}

where ~/.micropy/latest is a symlink to the micropython-esp32-stubs folder.

Oh - and I should add how much I enjoy micropython dev with these micropython stubs - thanks for putting this together.

Josverl commented 10 months ago

Thanks for the report Glen, I'll have a look and hope I can find what is causing this.

Jos

glenn20 commented 10 months ago

Thanks. Oh and another data point: {2: "two"} generates:

Dictionary key must be hashable
  Type "Literal[2]" is not hashable"

I did try adding the __hash__ attribute to the int class in stdlib/builtin.pyi, but it didn't resolve the issue.

glenn20 commented 10 months ago

Correction: Adding

    def __hash__(self) -> int: ...

to the end of class int (and class str) in stdlib.builtins.pyi removed the warnings from pylance. Copied from ~/.vscode/extensions/ms-python.vscode-pylance-2023.8.50/dist/typeshed-fallback/stdlib/builtins.pyi.

This would presumably be also needed for other hashable builtin types like float, complex etc....

Josverl commented 10 months ago

I can reproduce this in a configuration without micropy

But rather than just fixing this one - I think it makes sense to update the stdlib stubs to a current version. as they are pretty tied together this is a bit of a tangle ,and only updating stdlib/buildins.pyi breaks other things.

the issue is that modules such as os, io an sys are quite different on the MicroPython and CPython level , but are also deeply entwined in the stdlib typing. But I think I have found a way to combine both the both by adding an additional glue import to the micropython typestub to import the corresponding stdlib stypestub. In os.pyi add from stdlib.os import * seems to work well in exploratory testing so far.

I'll integrate this into micropython-stubber and let you know when I have updated stub packages to try.

Josverl commented 10 months ago

@glenn20

Could you give these a test : Change the target to wherever you prefer.

pip install git+https://github.com/josverl/micropython-stubs.git@stdlib-updates#subdirectory=publish/micropython-stdlib-stubs --target typings --no-user  
pip install git+https://github.com/josverl/micropython-stubs.git@stdlib-updates#subdirectory=publish/micropython-v1_20_0-esp32-stubs --target typings --no-user  

there is a warning regarding stdlib that i still need to look into.

If I test using pyright -t typings src where src has your sample in it, pyright is clean , and so is pylance

my reproduction https://github.com/Josverl/hashable

Josverl commented 10 months ago

note to self : also add to stdlib/sys.pyi:

# micropython specific functions
def atexit(func:Optional[Callable[[],Any]]) -> Any: 
    """\
    Register func to be called upon termination. func must be a callable that takes no arguments, 
    or None to disable the call. The atexit function will return the previous value set by this function, 
    which is initially None.
    """
    ...

def print_exception(exc, file=sys.stdout, /):
    """Print exception with a traceback to a file-like object file (or sys.stdout by default)."""
    ...