Should narrowing3 type check? If not, why? (Tested with mypy/pyright).
One workaround is using the approach in narrowing2 but it is verbose and e.g. ruff wants to nudge you to rewrite it.
from typing import Literal
from pydantic import BaseModel
class A(BaseModel):
type: Literal["a"] = "a"
value: int
class B(BaseModel):
type: Literal["b"] = "b"
value: int
class C(BaseModel):
type: Literal["c"] = "c"
def narrowing1(model: A | B | C) -> None:
if model.type == "a":
model.value # OK
def narrowing2(model: A | B | C) -> None:
if model.type == "a" or model.type == "b": #OK for mypy/pyright but Ruff wants to rewrite it.
model.value # OK
def narrowing3(model: A | B | C) -> None:
if model.type in ("a", "b"):
model.value # :( Item "C" of "A | B | C" has no attribute "value" [union-attr]
Specific type narrowing behaviors are not specified or mandated in the typing spec, so you’ll see different behaviors from different tools.
I don’t think that mypy has a documented list of type guard forms that it supports, but you can find such a list for pyright here. Your code in narrowing1 and narrowing2 maps to the type guard pattern x.y == LN. The code in narrowing3 does not map to any of the supported forms.
In general, each supported type guard form requires specialized logic in a type checker. There’s an infinite number of potential forms, so it’s not practical to support them all. Type checkers typically implement support for the most common forms. For unsupported forms, you can typically write your own user-defined type guard function
using TypeGuard or TypeIs.
For pyright, I would typically defer such an enhancement request until I saw good evidence for it being a common usage pattern. I did a quick search of the pyright and mypy issue trackers, and I found requests for several dozen other type guard forms (each with varying numbers of upvotes), but I couldn’t find any requests for this pattern. That would seem to indicate that it’s not a common use case.
And this issue attracted multiple people wanting something similar and getting confused (which you correctly redirected to other places).
So I do think there is repeated requests for something like this, especially if you check the mypy issue and see that multiple issues have been closed as duplicates of it.
Unless I am misunderstanding what you mean with type guard here?
@MegaIng, that’s a different form. Pyright already supports the form x in y (where y is one of several stdlib container types).
def func(x: str):
assert x in ("a", "b", "c")
reveal_type(x) # Pyright: Literal['a', 'b', 'c'], Mypy: str
Roderick is requesting support for a different form that handles discriminated unions based on an attribute access (x.y in <literal tuple of literal strings>). I wasn’t able to find any existing requests for this form.