Skip to content

Narrowing of isinstance(..., dict) #1130

@JelleZijlstra

Description

@JelleZijlstra

Summary

We just closed #456, but there's one special case that needs more attention: isinstance(x, dict). The tricky part is that TypedDicts are instances of dict at runtime, but are not (generally) assignable to any materialization of dict[Any, Any]. (See the proposed spec change in python/typing#2072 for the details.)

Current behavior is as follows (playground):

from typing import TypedDict

class X(TypedDict):
    a: int

def _(x: list | X):
    if isinstance(x, dict):
        reveal_type(x)  # X & Top[dict[Unknown, Unknown]]
    else:
        reveal_type(x)  # list[Unknown] | (X & ~Top[dict[Unknown, Unknown]])

Mypy and pyright both infer X in the positive branch and list[Any] in the negative branch, which is what we'd want.

I know TypedDict support is still missing a lot of functionality, so things will change, but I see a few options:

  • Make TypedDict types into subtypes of Top[dict[Unknown, Unknown]]. That would make this sample work correctly, but it's technically wrong in that TypedDicts aren't materializations of dict[Any, Any]. As such, it may cause issues with other possible use cases for Top[]. It would also allow some unsafe operations (https://github.com/JelleZijlstra/unsoundness/blob/main/examples/narrowing/isinstance_dict.py).
  • Change the spec so that TypedDict types are assignable to dict[str, Any]. I think that's a defensible change, but it's not what the current spec says and I don't know if such a change would see a positive reception.
  • Add a new special form ty_extensions.AnyTypedDict, with the semantics that any TypedDict type is a subtype of AnyTypedDict. The only allowed operations on AnyTypedDict are those that are allowed on any TypedDict (so no .clear(); .keys() returns an iterable of str; .values() an iterable of object; etc.). isinstance(x, dict) would narrow to AnyTypedDict | Top[dict[Unknown, Unknown]]. This is technically correct and would fix the soundness hole in https://github.com/JelleZijlstra/unsoundness/blob/main/examples/narrowing/isinstance_dict.py, but it would introduce a new user-visible concept.

Version

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    narrowingrelated to flow-sensitive type narrowing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions