Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Infer dict(TypedDict) as dict[str, object]#24852

Merged
charliermarsh merged 4 commits into
mainfrom
charlie/dict-obj
May 21, 2026
Merged

[ty] Infer dict(TypedDict) as dict[str, object]#24852
charliermarsh merged 4 commits into
mainfrom
charlie/dict-obj

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Apr 26, 2026

Copy link
Copy Markdown
Member

Summary

Given:

from typing import TypedDict

class TD(TypedDict):
    x: int
    y: str

def _(data: TD):
    reveal_type(dict(data))

We now reveal dict[str, object].

We could consider using a narrower value type (e.g., infer dict[str, int] if all values are int), though Mypy and Pyright don't do that (I suppose it wouldn't be sound for open TypedDicts?).

Closes astral-sh/ty#3340.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Apr 26, 2026
@astral-sh-bot

astral-sh-bot Bot commented Apr 26, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 89.36%. The percentage of expected errors that received a diagnostic held steady at 85.49%. The number of fully passing files held steady at 88/134.

@astral-sh-bot

astral-sh-bot Bot commented Apr 26, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Apr 26, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
no-matching-overload 0 23 0
invalid-argument-type 0 3 0
Total 0 26 0
Raw diff (26 changes)
aioredis (https://github.com/aio-libs/aioredis)
- aioredis/connection.py:1301:9 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

core (https://github.com/home-assistant/core)
- homeassistant/components/weather/__init__.py:701:46 error[no-matching-overload] No overload of `dict.__init__` matches arguments
- homeassistant/components/auth/mfa_setup_flow.py:146:16 error[no-matching-overload] No overload of `dict.__init__` matches arguments
- homeassistant/components/fronius/config_flow.py:117:53 error[no-matching-overload] No overload of `dict.__init__` matches arguments
- homeassistant/components/mqtt/config_flow.py:3777:65 error[no-matching-overload] No overload of `dict.__init__` matches arguments
- homeassistant/components/mqtt/config_flow.py:4891:13 error[no-matching-overload] No overload of `dict.__init__` matches arguments
- homeassistant/components/mqtt/config_flow.py:4996:13 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/config_flow.py:4997:13 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/config_flow.py:5035:9 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/config_flow.py:5047:13 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/config_flow.py:5048:13 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/entity.py:386:17 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments
- homeassistant/components/mqtt/entity.py:387:17 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

cwltool (https://github.com/common-workflow-language/cwltool)
- cwltool/cwlprov/ro.py:359:21 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ddtrace/llmobs/_prompt_optimization.py:316:49 error[no-matching-overload] No overload of `dict.__init__` matches arguments

discord.py (https://github.com/Rapptz/discord.py)
- discord/client.py:2717:93 error[invalid-argument-type] Argument to bound method `str.format_map` is incorrect: Expected `_FormatMapMapping`, found `TextChannel | NewsChannel | VoiceChannel | ... omitted 7 union elements`
- discord/guild.py:2626:93 error[invalid-argument-type] Argument to bound method `str.format_map` is incorrect: Expected `_FormatMapMapping`, found `TextChannel | NewsChannel | VoiceChannel | ... omitted 7 union elements`
- discord/interactions.py:260:92 error[invalid-argument-type] Argument to bound method `str.format_map` is incorrect: Expected `_FormatMapMapping`, found `TextChannel | NewsChannel | VoiceChannel | ... omitted 7 union elements`

hydpy (https://github.com/hydpy-dev/hydpy)
- hydpy/models/rconc/rconc_control.py:249:23 error[no-matching-overload] No overload of `dict.__init__` matches arguments

packaging (https://github.com/pypa/packaging)
- src/packaging/pylock.py:780:13 error[no-matching-overload] No overload of `dict.__init__` matches arguments

pip (https://github.com/pypa/pip)
- src/pip/_vendor/packaging/pylock.py:775:13 error[no-matching-overload] No overload of `dict.__init__` matches arguments

poetry (https://github.com/python-poetry/poetry)
- src/poetry/utils/authenticator.py:233:9 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

pydantic (https://github.com/pydantic/pydantic)
- pydantic/_internal/_mock_val_ser.py:41:16 error[no-matching-overload] No overload matches arguments

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- sklearn/externals/array_api_compat/dask/array/_info.py:378:17 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

scipy (https://github.com/scipy/scipy)
- subprojects/array_api_compat/array_api_compat/array_api_compat/dask/array/_info.py:378:17 error[no-matching-overload] No overload of bound method `MutableMapping.update` matches arguments

zulip (https://github.com/zulip/zulip)
- zerver/views/auth.py:379:30 error[no-matching-overload] No overload of `dict.__init__` matches arguments

Full report with detailed diff (timing results)

@AlexWaygood

Copy link
Copy Markdown
Member

(I suppose it wouldn't be sound for open TypedDicts?)

Indeed it wouldn't haha

@charliermarsh

Copy link
Copy Markdown
Member Author

It looks like there are some false positives from cases in which we're relying on the inferred rather than the annotated type, e.g.:

forecast_entry: dict[str, Any] = dict(_forecast_entry)

We then treat entries in this dict as object rather than Any, which leads to downstream errors.

@charliermarsh charliermarsh marked this pull request as ready for review April 27, 2026 00:17
@carljm

carljm commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

It looks like there are some false positives from cases in which we're relying on the inferred rather than the annotated type

That'll be fixed by the PR I'm working on for astral-sh/ty#136

@charliermarsh

Copy link
Copy Markdown
Member Author

Figured as much, thanks!

@carljm carljm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I am not totally convinced that special-casing is the right way out here, though maybe in the end it will be. (Especially since IIRC we have other diffs in the pipeline that do special-cased handling of even more dict(...) signatures?)

The normal (non-special-cased) inference path for dict.__init__ matches a single positional argument against the SupportsKeysAndGetItem protocol, and infers key and value types from that. It looks like if we completed our TypedDict __getitem__ overloads with a final overload for (str) -> object (which is technically correct for a non-closed TypedDict -- but would mean we no longer error on subscripting a non-closed TypedDict with unexpected key names, unless we added a special-cased rule to check for that) then the SupportsKeysAndGetIem assignability check would infer the right types here.

Throwing this into the playground or multiplay might clarify what I mean:

from typing import TypedDict, Iterable, Literal, overload

class TD(TypedDict):
    x: int
    y: str
    
class ManualBad:
    def keys(self) -> Iterable[str]: ...
    @overload
    def __getitem__(self, key: Literal["x"]) -> int: ...
    @overload
    def __getitem__(self, key: Literal["y"]) -> str: ...
    def __getitem__(self, key: str) -> object: ...
    
class Manual:
    def keys(self) -> Iterable[str]: ...
    @overload
    def __getitem__(self, key: Literal["x"]) -> int: ...
    @overload
    def __getitem__(self, key: Literal["y"]) -> str: ...
    @overload
    def __getitem__(self, key: str) -> object: ...
    def __getitem__(self, key: str) -> object: ...
    
def takes_dict(value: dict[str, object]) -> None: ...
def takes_kwargs(**kwargs: object) -> None: ...
def _(data: TD, bad: ManualBad, man: Manual):
    reveal_type(dict(bad))
    takes_dict(dict(bad))
    
    reveal_type(dict(man))
    takes_dict(dict(man))
    
    reveal_type(data.__getitem__)
    reveal_type(dict(data))
    takes_dict(dict(data))


// Probe silently so non-TypedDict arguments fall back to normal call binding without
// duplicate diagnostics; merge the inference only after accepting the fast path.
let mut speculative_builder = self.speculate();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously I think we've always used speculative building when we really need to infer the same thing with different type contexts. I don't love starting down this slope of using it not because we need different type context, but just because it's too much of a pain to reorganize things to avoid double-inference. It's not super likely, but certainly possible, that someone wraps dict(...) around a sizable expression that's actually costly to double infer.

@charliermarsh charliermarsh marked this pull request as draft April 27, 2026 01:52
@charliermarsh

Copy link
Copy Markdown
Member Author

Your summary makes sense to me, and I pushed up the change, but I haven't reviewed the ecosystem diff yet.

@charliermarsh

This comment was marked as outdated.

@charliermarsh charliermarsh marked this pull request as ready for review April 27, 2026 14:39
@charliermarsh charliermarsh assigned carljm and unassigned dcreager Apr 27, 2026
@charliermarsh

charliermarsh commented Apr 27, 2026

Copy link
Copy Markdown
Member Author

One downside (?) is that movie.__getitem__(key) where key: str no longer emits a diagnostic even if key isn't a valid key, but that does match Pyright.

We do still emit a diagnostic when subscripting with invalid keys via this special case.

@charliermarsh charliermarsh marked this pull request as draft May 21, 2026 11:46
@charliermarsh charliermarsh force-pushed the charlie/dict-obj branch 2 times, most recently from 13122b1 to b985e26 Compare May 21, 2026 11:52
@charliermarsh charliermarsh marked this pull request as ready for review May 21, 2026 12:00
@astral-sh-bot astral-sh-bot Bot requested a review from carljm May 21, 2026 12:00

@carljm carljm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thank you!

@charliermarsh charliermarsh merged commit 425d4f0 into main May 21, 2026
60 checks passed
@charliermarsh charliermarsh deleted the charlie/dict-obj branch May 21, 2026 14:24
thejchap pushed a commit to thejchap/ruff that referenced this pull request May 23, 2026
## Summary

Given:

```python
from typing import TypedDict

class TD(TypedDict):
    x: int
    y: str

def _(data: TD):
    reveal_type(dict(data))
```

We now reveal `dict[str, object]`.

We could consider using a narrower value type (e.g., infer `dict[str,
int]` if all values are `int`), though Mypy and Pyright don't do that (I
suppose it wouldn't be sound for open TypedDicts?).

Closes astral-sh/ty#3340.
anishgirianish pushed a commit to anishgirianish/ruff that referenced this pull request May 28, 2026
## Summary

Given:

```python
from typing import TypedDict

class TD(TypedDict):
    x: int
    y: str

def _(data: TD):
    reveal_type(dict(data))
```

We now reveal `dict[str, object]`.

We could consider using a narrower value type (e.g., infer `dict[str,
int]` if all values are `int`), though Mypy and Pyright don't do that (I
suppose it wouldn't be sound for open TypedDicts?).

Closes astral-sh/ty#3340.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

type context causes failed call to dict.__init__ with TypedDict argument

4 participants