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

Strange behavior using Tagged Union #10659

Open
msdrigg opened this issue Jun 16, 2021 · 6 comments
Open

Strange behavior using Tagged Union #10659

msdrigg opened this issue Jun 16, 2021 · 6 comments
Labels
bug mypy got something wrong topic-typed-dict

Comments

@msdrigg
Copy link

msdrigg commented Jun 16, 2021

Bug Report

When using tagged unions, mypy infers correctly with with instance["tag"],
but fails with instance.get("tag")

To Reproduce

  1. Write the following code into tagged_union.py:
from typing import Literal, TypedDict, Union


class TaggedTypeA(TypedDict):
    tag: Literal["A"]
    is_a: bool


class TaggedTypeB(TypedDict):
    tag: Literal["B"]


TaggedUnion = Union[TaggedTypeA, TaggedTypeB]

instance_a: TaggedUnion = {"tag": "A", "is_a": True}

if instance_a["tag"] == "A":
    print(f"Am I type a? {instance_a['is_a']}")

if instance_a.get("tag") == "A":
    print(f"Am I type a this time? {instance_a['is_a']}")
  1. Run mypy tagged_union.py
  2. Get only one error

tagged_union.py:21: error: TypedDict "TaggedTypeB" has no key "is_a"

Error occurs in second if statement

Expected Behavior

I can see that mypy correctly infers the type of instance_a when I use instance_a["tag"] == "A" (the first if expression).

Therefore, I would expect the same behavior when using instance_a.get("tag") == "A" (the second if clause).

Actual Behavior
mypy correctly infers the type of instance_a when I use instance_a["tag"] == "A" (the first if expression).

mypy fails to infer the type when I use instance_a.get("tag") == "A".

In fact, mypy doesn't only not infer the correct type, it infers the opposite type TaggedTypeB which does not make sense at all

Your Environment

  • Mypy version used: 0.902
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.8.10
  • Operating system and version: Ubuntu 21.04
@msdrigg msdrigg added the bug mypy got something wrong label Jun 16, 2021
@ethframe
Copy link
Contributor

It's not that mypy narrows the type incorrectly - it's just an imprecise error message:

...

# No narrowing occurs here since mypy does not understand this pattern
if instance_a.get("tag") == "A":
    reveal_type(instance_a)  # Revealed type is "Union[TaggedTypeA, TaggedTypeB]"
    print(f"Am I type a this time? {instance_a['is_a']}")  # TypedDict "TaggedTypeB" has no key "is_a"

Compare this to accessing an attribute of union type:

from typing import Union


class A:
    a: int
    b: bool


class B:
    a: int


ab: Union[A, B] = A()
ab.b  # Item "B" of "Union[A, B]" has no attribute "b"

@msdrigg
Copy link
Author

msdrigg commented Jun 20, 2021

Thank you for the explanation. This error message makes sense given the context.

Two follow up questions before I close this:

  1. Should the error message be clarified to clearly indicate that the error is due to Union including B, not that the type has been transformed into B? I dont know if this change should be made to account for a small subset of errors.
  2. Should we be checking for TypedDict.get(Literal) to convert unions? The single argument .get(Literal) case is equivalent to the indexing operator for the purposes of automatic casting. Should this case be implemented?

@levrik
Copy link

levrik commented Jun 21, 2021

Since the upgrade to mypy 0.900 I'm also seeing this behavior while before the narrowing was working correctly.
I'm accessing attributes on a class. Pyright (alternative type checker by Microsoft) for example gets it right as mypy before.

@msdrigg
Copy link
Author

msdrigg commented Jun 26, 2021

I would be willing to work on a pull request on this issue if someone could point me to where in the codebase is responsible for handling the transformation between union and specific types.

@hauntsaninja
Copy link
Collaborator

@levrik if you're seeing a regression, please open another issue with your code. Previous mypy versions complain about the examples in this issue, so seems separate from what you're describing.

As for implementing narrowing for tagged unions using .get maybe try looking at

def refine_parent_types(self,

@levrik
Copy link

levrik commented Jul 5, 2021

@hauntsaninja I've opened #10764 for the issue I'm seeing.

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-typed-dict
Projects
None yet
Development

No branches or pull requests

4 participants