From 6a25a7bf0b5cb3721a06d0e0d6245b2ebfbf053b Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 17 Jul 2023 08:01:52 -0700 Subject: [PATCH] Removed the requirement that a `TypedDict` be marked `@final` for it to be considered for type narrowing as part of a `S in D` type guard pattern. This requirement wasn't sound because TypedDict is a structural type (i.e. a protocol), so `@final` doesn't have any real meaning. --- docs/type-concepts-advanced.md | 2 +- .../pyright-internal/src/analyzer/typeGuards.ts | 6 +----- .../tests/samples/typeNarrowingTypedDict1.py | 17 +---------------- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/docs/type-concepts-advanced.md b/docs/type-concepts-advanced.md index cd7a9c395511..0c7b2a7ac630 100644 --- a/docs/type-concepts-advanced.md +++ b/docs/type-concepts-advanced.md @@ -72,7 +72,7 @@ In addition to assignment-based type narrowing, Pyright supports the following t * `x[I] is None` and `x[I] is not None` (where I is a literal expression and x is a known-length tuple that is distinguished by the index indicated by I) * `len(x) == L` and `len(x) != L` (where x is tuple and L is a literal integer) * `x in y` or `x not in y` (where y is instance of list, set, frozenset, deque, tuple, dict, defaultdict, or OrderedDict) -* `S in D` and `S not in D` (where S is a string literal and D is a final TypedDict) +* `S in D` and `S not in D` (where S is a string literal and D is a TypedDict) * `isinstance(x, T)` (where T is a type or a tuple of types) * `issubclass(x, T)` (where T is a type or a tuple of types) * `callable(x)` diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 57d4cfd0493c..3ab508cfbc66 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -1816,11 +1816,7 @@ function narrowTypeForTypedDictKey( if (isPositiveTest) { if (!tdEntry) { - // If the class is final, we can say with certainty that if - // the TypedDict doesn't define this entry, it is not this type. - // If it's not final, we can't say this because it could be a - // subclass of this TypedDict that adds more fields. - return ClassType.isFinal(subtype) ? undefined : subtype; + return undefined; } // If the entry is currently not required and not marked provided, we can mark diff --git a/packages/pyright-internal/src/tests/samples/typeNarrowingTypedDict1.py b/packages/pyright-internal/src/tests/samples/typeNarrowingTypedDict1.py index 9d6fbe5bf0ce..30f477f9a051 100644 --- a/packages/pyright-internal/src/tests/samples/typeNarrowingTypedDict1.py +++ b/packages/pyright-internal/src/tests/samples/typeNarrowingTypedDict1.py @@ -1,32 +1,24 @@ # This sample tests type narrowing for TypedDict types based # on whether a key is in or not in the dict. -from typing import TypedDict, final +from typing import TypedDict -@final class TD1(TypedDict): a: str b: int -@final class TD2(TypedDict): a: int c: str -@final class TD3(TypedDict, total=False): a: int d: str -class TD4(TypedDict): - a: int - c: str - - def f1(p: TD1 | TD2): if "b" in p: reveal_type(p, expected_text="TD1") @@ -91,10 +83,3 @@ def f7(p: TD3): def f8(p: TD3): if "a" in p: f7(p) - - -def f9(p: TD1 | TD4): - if "b" in p: - reveal_type(p, expected_text="TD1 | TD4") - else: - reveal_type(p, expected_text="TD4")