LibCST icon indicating copy to clipboard operation
LibCST copied to clipboard

pyright + `__or__` in generic type annotations gives unknown type

Open jakkdl opened this issue 9 months ago • 1 comments

pyright gets confused by your custom __or__ methods on matcher classes, giving "Expected type expression but received UnionType". It does work with mypy, though I suspect that might simply be because mypy doesn't try to parse it for type hints. It's possible to work around it by using Union in types, but that's on its way out & inconvenient.

repro

from __future__ import annotations

from libcst.matchers import Name
from typing_extensions import reveal_type

# Name.__or__ is broken
l1: list[Name|int] = [Name("foo"), 5]
reveal_type(l1)
# int.__or__ works, but if you want to union multiple matcher types it's not going to help
l2: list[int|Name] = [Name("foo"), 5]
reveal_type(l2)
$ pyright foo.py
./foo.py
  ./foo.py:8:1 - error: Type of "l1" is partially unknown
    Type of "l1" is "list[Unknown]" (reportUnknownVariableType)
  ./foo.py:8:10 - error: Expected type expression but received "UnionType" (reportGeneralTypeIssues)
  ./foo.py:9:13 - information: Type of "l1" is "list[Unknown]"
  ./foo.py:11:13 - information: Type of "l2" is "list[int | Name]"
2 errors, 0 warnings, 2 informations 
$ mypy foo.py
foo.py:7: note: Revealed type is "builtins.list[Union[libcst.matchers.Name, builtins.int]]"
foo.py:9: note: Revealed type is "builtins.list[Union[builtins.int, libcst.matchers.Name]]"
Success: no issues found in 1 source file

versions

libcst 1.3.1 pyright 1.1.362 mypy 1.9.0

jakkdl avatar May 10 '24 11:05 jakkdl

I think Pyright's behavior is correct here; essentially we're running into the incompatible changes section in PEP 604.

Other than changing the matcher syntax to use something other than | to delineate alternatives, the only other way I can see us resolving this is if we make types.UnionType work with matchers the same way as TypeOf does - then we could remove this override

https://github.com/Instagram/LibCST/blob/6783244eab6f38bf456cd60090201c31c5ec9357/libcst/matchers/_matcher_base.py#L73-L74

which I believe is causing the problem.

We also override __or__ to produce OneOf matchers, but these are generally instance method overrides, so I don't think they would produce type errors like above

zsol avatar May 13 '24 09:05 zsol