Skip to content

isinstance(..., dict) and top materialization result in false positive on .get() call #2374

@yilei

Description

@yilei

Summary

Given the following code:

def foo(body: object) -> str | None:
    if isinstance(body, dict):
        value = body.get("key")  # reveal_type(body) shows `Top[dict[Unknown, Unknown]]`
        if isinstance(value, str):
            return value
    return None

ty reports:

error[invalid-argument-type]: Argument to bound method `get` is incorrect
 --> isinstance_dict_narrowing.py:3:26
  |
1 | def foo(body: object) -> str | None:
2 |     if isinstance(body, dict):
3 |         value = body.get("key")
  |                          ^^^^^ Expected `Never`, found `Literal["key"]`
4 |         if isinstance(value, str):
5 |             return value
  |
info: Matching overload defined here
    --> stdlib/builtins.pyi:3015:9
     |
3013 |     # Positional-only in dict, but not in MutableMapping
3014 |     @overload  # type: ignore[override]
3015 |     def get(self, key: _KT, default: None = None, /) -> _VT | None:
     |         ^^^       -------- Parameter declared here
3016 |         """Return the value for key if key is in the dictionary, else default."""
     |
info: Non-matching overloads for bound method `get`:
info:   (self, key: _KT@dict, default: _VT@dict, /) -> _VT@dict
info:   (self, key: _KT@dict, default: _T@get, /) -> _VT@dict | _T@get
info: rule `invalid-argument-type` is enabled by default

After reading #456 and its implementation astral-sh/ruff#20256, this seems to be working as designed? Top materialization means we can't call the .get method here since there is no type for the key would satisfy all possible dicts of any key type, thus the Never type. The ecosystem analysis result on astral-sh/ruff#20256 also shows a few examples like the theme_config variable here.

However, I couldn't wrap my head around this behavior. Is there a good or pedantic example that ty is catching real errors? And at least, could we add better diagnostics or documentation?

(I also found the relevant open issue #1130, but it's talking specifically about TypedDict.)

Version

ty 0.0.9 (f1652f0 2026-01-05)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions