Skip to content

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

Closed

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)

Activity

  1. JelleZijlstra commented on Nov 2, 2023

    @JelleZijlstra
    Member

    Confirmed in mypy playground: https://mypy-play.net/?mypy=latest&python=3.10&gist=4d53c16a3b06bad9014022b24b490d08

    Feels like something is going wrong with strict-optional handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions