Skip to content

better gradual guarantee for un-annotated dict (and other container?) literals #1248

@carljm

Description

@carljm

In the ecosystem, we see this kind of pattern:

def f(a: int, b: str): ...

x = { "a": 1, "b": "2" }

# Expected `int` found `Unknown | int | str`
f(x["a"], x["b"])
# or
f(**x)

In this case, an un-annotated dict literal is used implicitly as a heterogeneous TypedDict. We (along with mypy and pyrefly) throw errors on these calls, because we infer the type of x as dict[str, Unknown | int | str]. (Pyrefly infers dict[str, int | str], mypy dict[str, object].)

Pyright avoids this problem by falling back to dict[str, Unknown] rather than inferring a union value type, when the dict contents look heterogeneous.

A similar problem can occur with list literals (e.g. x = [1, "a"]; f(x[0], x[1])). We do see this in the ecosystem too, but it's less common than with dictionaries (probably because tuples are an attractive alternative, and implicit heterogeneity is supported for tuples).

Perhaps ideally this would be solved by inferring a more precise heterogeneous "implicit TypedDict" type for these literals, but this gets very difficult to handle correctly with mutations.

If we do implement a "gradual mode" vs "strict mode", it may be worth emulating pyright's behavior in the "gradual mode".

Metadata

Metadata

Assignees

No one assigned

    Labels

    gradual-guaranteeIssues relating to avoiding false positives on working untyped codeneeds-decisionAwaiting a decision from a maintainer

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions