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

Assignment to TypedDict field does not handle bidirectional type inference properly #1645

Closed
A-UNDERSCORE-D opened this issue Aug 6, 2021 · 4 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

@A-UNDERSCORE-D
Copy link

Environment data

  • Language Server version: 2021.7.7
  • OS and version: linux x64
  • Python version (and distribution if applicable, e.g. Anaconda): 3.9.4 -- pyenv and a normal venv based virtual environment
  • python.analysis.indexing: undefined
  • python.analysis.typeCheckingMode: basic

Expected behaviour

Pylance understands that TypedDict is a wrapper around dict at runtime and __call__ on any TypedDict is just a call to dict()
which will happily accept a dict as its argument.

Actual behaviour

Pylance complains that you cannot pass a dict to SomeTypedDict(), stating that instead one must use key/value args

Logs

Python Language Server Log

[BG(1)] getSemanticTokens delta previousResultId:1628246633795 at /home/ad/development/python/EDMarketConnector/test-pycodestyle.py ...
[BG(1)]   parsing: /home/ad/development/python/EDMarketConnector/test-pycodestyle.py (1ms)
[BG(1)]   binding: /home/ad/development/python/EDMarketConnector/test-pycodestyle.py (0ms)
[BG(1)] getSemanticTokens delta previousResultId:1628246633795 at /home/ad/development/python/EDMarketConnector/test-pycodestyle.py (7ms)
[FG] parsing: /home/ad/development/python/EDMarketConnector/test-pycodestyle.py (0ms)
[FG] binding: /home/ad/development/python/EDMarketConnector/test-pycodestyle.py (0ms)
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
Background analysis message: setFileOpened
Background analysis message: markFilesDirty

Code Snippet / Additional information

from typing import TypedDict


class Test(TypedDict):
    thing: bool
    stuff: str


x = Test({"thing": True, "stuff": 'test'})

print(x, type(x), dict({"test": "stuff"}))
@github-actions github-actions bot added the triage label Aug 6, 2021
@erictraut
Copy link
Contributor

I think you mean __init__ rather than __call__. The latter is used only when calling an instance of the class, but in your example, you're calling the class itself (i.e. invoking its constructor), so __call__ is not involved.

PEP 589 is very clear that a TypedDict class should not allow non-keyword arguments passed to its constructor, so pylance is correct in reporting an error here.

The created TypedDict type object is not a real class object. Here are the only uses of the type a type checker is expected to allow: ... It can be used as a callable object with keyword arguments corresponding to the TypedDict items. Non-keyword arguments are not allowed.

Passing a dict to the constructor of a TypedDict class doesn't make sense because that's redundant. In your code example, you're allocating a dict object and then attempting to construct a second copy of that same dict. It's the equivalent of dict({"thing": True, "stuff": "test"}) rather than simply using {"thing": True, "stuff": "test"}.

Here's how a TypedDict type is meant to be used.

x: Test = {"thing": True, "stuff": "test"}

@A-UNDERSCORE-D
Copy link
Author

Sorry, I misread the source for the metaclass. You are correct I wanted __init__.

Runtime behavior is however as I described (and thus against the PEP, but thats an argument for the core devs).

I wanted to use it due to I guess a different edgecase or misunderstanding in behaviour where:

from typing import Optional, TypedDict


class Thing(TypedDict):
    thing: bool
    stuff: str


class Thing2(TypedDict):
    other_thing: Optional[Thing]
    stuff: str


thing2: Thing2 = {"other_thing": None, "stuff": "a"}
thing2['other_thing'] = {"thing": False, "stuff": "a"}
# ^^^^^^^^^^^^^^^^ 
# Could not assign item in TypedDict
#   Type "dict[str, Unknown]" cannot be assigned to type "Thing | None"
#     "dict[str, Unknown]" is incompatible with "Thing"
#     Type cannot be assigned to type "None"
#   PylancereportTypedDictNotRequiredAccess

Thus my first solution would be to wrap it in Thing to make the type checker happy (though it should work anyway. I see no reason why it wouldnt?).
Perhaps I should open an issue regarding that behaviour instead?

In the code Im working on I had gone with creating a new var and annotating it appropriately, then using that in the assignment, but its an extra step that feels quite pointless.

@erictraut
Copy link
Contributor

I agree that this code should work. I've fixed the underlying reason for this error message. This will be fixed in the next release of pylance.

@erictraut erictraut added bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version and removed triage labels Aug 6, 2021
@erictraut erictraut changed the title TypedDict subclasses __call__ method incorrectly typed Assignment to TypedDict field does not handle bidirectional type inference properly Aug 6, 2021
@heejaechang
Copy link
Contributor

fixed in 2021.8.1

@heejaechang heejaechang removed the fixed in next version (main) A fix has been implemented and will appear in an upcoming version label Aug 11, 2021
@jakebailey jakebailey added the fixed in next version (main) A fix has been implemented and will appear in an upcoming version label Aug 23, 2021
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

4 participants