Skip to content

Inheriting from a generic TypedDict with a T | None key inexplicably narrows the that key type to T #16395

@daniil-berg

Description

@daniil-berg

Bug Report

Inheriting from a generic TypedDict with a T | None key inexplicably narrows the that key type to T.

To Reproduce

from typing import Generic, TypeVar, TypedDict

T = TypeVar("T")


class Foo(TypedDict, Generic[T], total=False):
    a: str | None
    ...


class Bar(Foo[T], total=False):
    ...


class Baz(Foo[int], total=False):
    ...


a_value: str | None

foo: Foo[int]
bar: Bar[int]
baz: Baz

reveal_type(foo)  # TypedDict('tests.Foo', {'a'?: Union[builtins.str, None]})
reveal_type(bar)  # TypedDict('tests.Bar', {'a'?: builtins.str})
reveal_type(baz)  # TypedDict('tests.Baz', {'a'?: builtins.str})

foo["a"] = a_value  # passes
bar["a"] = a_value  # error: Value of "a" has incompatible type "str | None"; expected "str"  [typeddict-item]
baz["a"] = a_value  # error: Value of "a" has incompatible type "str | None"; expected "str"  [typeddict-item]

Expected Behavior

No errors. For all three variables foo, bar, baz the revealed type of the a-key of the TypedDict should be Union[builtins.str, None].

Actual Behavior

The type of the a-key of bar and baz is revealed to be builtins.str instead of Union[builtins.str, None].

Thus, the last two assignments cause the errors:

error: Value of "a" has incompatible type "int | None"; expected "int"  [typeddict-item]
error: Value of "a" has incompatible type "int | None"; expected "int"  [typeddict-item]

Notable obsevations

  • It does not matter, if any key in Foo is actually declared to be of type T.
  • Declaring for example a: T | None and adjusting the a_value type accordingly makes no difference.
  • As soon as you make Foo non-generic, i.e. remove Generic[T] from the bases (and adjust Bar and Baz declarations accordingly), the error disappears.
  • If you declare the a-key to be of any other union type e.g. str | int (and adjust the a_value type accordingly), the error seems to disappear. (Obviously I have only tested a few combinations.)

Your Environment

  • Mypy version used: 1.6.1
  • Mypy command-line flags: -
  • Mypy configuration options from mypy.ini (and other config files): strict = True
  • Python version used: 3.11.4 (also reproduced with 3.9.17)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions