microsoft / microsoft/pyright

"key" in typed_dict checks for non-closed TypedDict doesn't allow typed_dict["key"] afterwards

Open
#11,116 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
15.6k
Forks
1.8k
Avg merge
12h 13m
Merged PRs (30d)
52

Description

Note: This is very related to #10376 and the regressed (?) fix thereof. The behavior in versions other than 1.1.401 is to simply deduce `Never`, but even 1.1.401 seems less than helpful.

Consider the following ([Pyright-Play](https://pyright-play.net/?pyrightVersion=1.1.401&pythonVersion=3.15&code=GYJw9gtgBALgngBwJYDsDmUkQWEMoAqiApgCYAiSAxjADRQByYMASsQI4CuSIZAsAChBVADYBDAM4SoAQQAURBGUo0AlAC5BUbVDHrMKfAB9GYFMS06ARvqasO3XqQDaqGAF1Bg0sWBRgnChUcjD6MhqW2kh%2BMM4ARGJx7poCOmlQvABuxGIiAPrwSiHxie6qUADEUADCuLw0InBQKGIg4ADuZLBgUAAGbr1eqTpZOfmFxMVxVknlVbVtxA1NvBKcIjDSqLooUMRtuJGYftNxBrAp6SPE2bkFJFMzZZU1dUswjc2tHV0wPf2GQbDbRHUZ3CZTKizF4LeofFbENYbLa7MS7fbgEBHaJQOJQ86hI5pMHjB4wOZQADKAAswOtSFArMQ%2BjJevQUMw%2BgwbvsgVdtCT7kVYnjoVUaXSRAyOfgEOBSJwqMy0XsDiAgA) - as per the note above, Pyright is set to 1.1.401 to see any "non-trivial" type deduction)

```python
from typing import TypedDict, NotRequired

class A(TypedDict):
a: int | None
b: NotRequired[int]

def func(t: A):
if t["a"]:
reveal_type(t["a"]) # Correctly narrowed to `int`

reveal_type(t["b"]) # Correctly results in an error
if "b" in t:
reveal_type(t["b"]) # Correctly narrowed to `int`

reveal_type(t["c"]) # Correctly results in an error
if "c" in t:
reveal_type(t) # Should be `A`, not `Never`
reveal_type(t["c"]) # Should not produce an error
```

* As the top `if` shows, items within a `TypedDict` can properly be narrowed (which is very useful)
* As the middle `if` shows, optional keys can correctly be accessed once a `"key" in typed_dict` check has passed
* However, the same type of guard does not work for unknown keys, which should in principle be allowed: Ignoring the fact that the latest versions once again incorrectly infer `t` to be `Never`, it would be very helpful if `t["c"]` didn't throw an error (and instead simply resolved to `Any`). Otherwise, it's effectively impossible to ever convince Pyright that the dict has some additional key. Note that adding `extra_items = Any` correctly fixes the last line to reveal `Any` (in 1.1.407), but it also makes the line before the `if` guard pass without errors (this might be a separate bug?). It was my interpretation that in versions before `extra_items` was added, the behavior should be equivalent to `extra_items=Any`?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the TypedDict example in Pyright-Play with versions 1.1.401 and 1.1.407, then trace the handling of the `"key" in typed_dict` guard and unknown-key access. Done means the guard no longer narrows the TypedDict to `Never`, and `t["c"]` resolves to `Any` after the guard while still erroring before it.

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
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.