microsoft / microsoft/pyright

Truthy type-narrowing of None bound or constraint TypeVar not happening

Open
#10,618 2 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
15.6k
Forks
1.8k
Avg merge
12h 13m
Merged PRs (30d)
52

Description

**Describe the bug**

👋 I have a generic variable `d: T` of a `dict` or `None`. For the issue it does not matter if the type var is bound to union or has two constraints.

When I have a normal annotation `var: dict | None` and do a truthy `if var` I would expect that `None` is no longer an option. Likewise I would expect the same for `d: T` in this case. However pyright reports an error that `d` could be still `None` after the truthy `if`. I would need to write `if d is not None and d:` as a check to get correct type-narrowing and runtime equivalence which is not intuitive.

**Code or Screenshots**
Code sample in [pyright playground](https://pyright-play.net/?code=CYUwZgBGD20NoBUBcEAUwCWBjALgGggDloA7EASgF10UECA3AQwCcVNcIAfI0ipAWABQEERADEEALTSIAUWbNozKdKGjxcgB4AHELhDAIOaBBCasAGwCuoHmSMBPXRADuGHAAsjzK54cQsDz0AawIAI18IYGgQAGcIEmgcCA9GbV0SNVEMSGABYXVRZhB6EEYLAH0cJxB0chEJBCzCjWJ7WOgrC0MwkFNza1BDIOKIMP9PPpwfPwCgrGDmwsYIAF4ouAAiDoBbEGCQB03KBrs%2BrEYSROTeiFirMNisZgxtHAMl9TC1qIA6AHMQDhUJskiNNvUJIDkokIMFEi4SBAAII4aYYCLvCDQSBtEBLJYSADC0B22hYGFiGFIrncXkSJEkgLILywjl0kg8GBIyRcSmC8UY8TMun0wCWOQgTFYnyKJTKlWqulQ0vqGnYOFlIhW62lW12%2B0Ox1OAHkANJasY-aUAoEgsEgZgQjTmoSElSSCAAdX5LE6JGAsRQ0kk7uRFnezBIjBwGFKcxC8U8Mdc-PyEty%2BRa2tisUdyUMlISSTOp1iHk63WLMJABiMXNilp1G22pMNRxOLotBRa33WwFtwNBkydkIgrsEGaiECL1zOKFaLEULli4UimEMc8CIRnuWwyVnpEkIDJ1UtxVK5SqNTqpw1TZ%2BwH1bYOHa7lr7f2h9pHzokE6nQt4jnPEIEuQw8lOWQAEcrDjcoQB5BMFiMExNklPJNnPeUryVWpgDVCR7x7ZZH2fPZX2Nf9u2zK1%2B0HH9wTHCd1DdEigA)

```python
def foo[T: (dict, None)](d: T, var: dict | None):
# --- Error ---
# Expected to exclude None type with truthy check, but does not happen
if d:
reveal_type(d) # T
# None sould be excluded here by the truthy check
a = d["somekey"] # None cannot be subscripted
b = d.get("other") # get no known Attribute of None

# Comparison with non-generic type-hint works as expected
if var:
reveal_type(var) # dict
a = var["somekey"] # OK
b = var.get("other") # OK

# --- Workarounds: ---
# Alternative checks that work:

if d:
assert d is not None # should not need this
a = d["somekey"] # OK
b = d.get("other") # OK

if d is not None: # Narrows, but did not check if dict is non-empty
reveal_type(d) # dict
a = d["somekey"] # OK
b = d.get("other") # OK

if d is not None and d: # Equivalent check to "if d:"
reveal_type(d) # dict
a = d["somekey"] # OK
b = d.get("other") # OK
```

[mypy](https://mypy-play.net/?mypy=latest&python=3.12&gist=8244d224b9873246c07743c3f90d160f) behaves as expected here.

**VS Code extension or command-line**
1.1.402+, still in 1.1.407

Contributor guide

Open the contributing guide

Research direction

Reproduce the generic TypeVar narrowing case in the linked Pyright playground and compare it with the non-generic example and workarounds. Trace the type-narrowing logic for truthy checks on a bound or constrained TypeVar; done when `if d:` excludes `None`, reveals the narrowed dictionary type, and accepts the subscripting and `.get` operations.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.