From a8dbd22c04c124cc4fa652ae9971df6fc92d0668 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Mon, 17 Jul 2023 23:36:12 -0400 Subject: [PATCH 1/2] TypedDict 'in' narrowing w/o @final --- mypy/checker.py | 4 +-- test-data/unit/check-typeddict.test | 48 ----------------------------- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f2873c7d58e4..d0d11f80d8c2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5283,9 +5283,7 @@ def conditional_types_for_iterable( for key in item_str_literals: if key in possible_iterable_type.required_keys: if_types.append(possible_iterable_type) - elif ( - key in possible_iterable_type.items or not possible_iterable_type.is_final - ): + elif key in possible_iterable_type.items: if_types.append(possible_iterable_type) else_types.append(possible_iterable_type) else: diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 7de8e6416f35..1202988b274e 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -2017,9 +2017,7 @@ v = {bad2: 2} # E: Missing key "num" for TypedDict "Value" \ [case testOperatorContainsNarrowsTypedDicts_unionWithList] from __future__ import annotations from typing import assert_type, TypedDict, Union -from typing_extensions import final -@final class D(TypedDict): foo: int @@ -2041,12 +2039,10 @@ from __future__ import annotations from typing import assert_type, Literal, TypedDict, TypeVar, Union from typing_extensions import final -@final class D1(TypedDict): foo: int -@final class D2(TypedDict): bar: int @@ -2087,50 +2083,6 @@ def f(arg: TD) -> None: [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testOperatorContainsNarrowsTypedDicts_final] -# flags: --warn-unreachable -from __future__ import annotations -from typing import assert_type, TypedDict, Union -from typing_extensions import final - -@final -class DFinal(TypedDict): - foo: int - - -class DNotFinal(TypedDict): - bar: int - - -d_not_final: DNotFinal - -if 'bar' in d_not_final: - assert_type(d_not_final, DNotFinal) -else: - spam = 'ham' # E: Statement is unreachable - -if 'spam' in d_not_final: - assert_type(d_not_final, DNotFinal) -else: - assert_type(d_not_final, DNotFinal) - -d_final: DFinal - -if 'spam' in d_final: - spam = 'ham' # E: Statement is unreachable -else: - assert_type(d_final, DFinal) - -d_union: DFinal | DNotFinal - -if 'foo' in d_union: - assert_type(d_union, Union[DFinal, DNotFinal]) -else: - assert_type(d_union, DNotFinal) - -[builtins fixtures/dict.pyi] -[typing fixtures/typing-typeddict.pyi] - [case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse] from __future__ import annotations from typing import assert_type, Literal, TypedDict, Union From a68b87abff4f38d0ebf469e986f1aad98cbe0de5 Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Sat, 12 Aug 2023 22:40:21 -0400 Subject: [PATCH 2/2] remove more @finals + improve tests --- test-data/unit/check-typeddict.test | 36 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 1202988b274e..c57f34ce2f7e 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -2035,18 +2035,16 @@ else: [typing fixtures/typing-typeddict.pyi] [case testOperatorContainsNarrowsTypedDicts_total] +# flags: --warn-unreachable from __future__ import annotations from typing import assert_type, Literal, TypedDict, TypeVar, Union -from typing_extensions import final class D1(TypedDict): foo: int - class D2(TypedDict): bar: int - d: D1 | D2 if 'foo' in d: @@ -2054,6 +2052,11 @@ if 'foo' in d: else: assert_type(d, D2) +if 'unknown_key' in d: + object() # E: Statement is unreachable +else: + assert_type(d, Union[D1, D2]) + foo_or_bar: Literal['foo', 'bar'] if foo_or_bar in d: assert_type(d, Union[D1, D2]) @@ -2079,25 +2082,20 @@ def f(arg: TD) -> None: else: assert_type(arg['bar'], int) - [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testOperatorContainsNarrowsTypedDicts_partialThroughTotalFalse] +[case testOperatorContainsNarrowsTypedDicts_totalFalse] +# flags: --warn-unreachable from __future__ import annotations from typing import assert_type, Literal, TypedDict, Union -from typing_extensions import final -@final class DTotal(TypedDict): required_key: int - -@final class DNotTotal(TypedDict, total=False): optional_key: int - d: DTotal | DNotTotal if 'required_key' in d: @@ -2110,6 +2108,11 @@ if 'optional_key' in d: else: assert_type(d, Union[DTotal, DNotTotal]) +if 'unknown_key' in d: + object() # E: Statement is unreachable +else: + assert_type(d, Union[DTotal, DNotTotal]) + key: Literal['optional_key', 'required_key'] if key in d: assert_type(d, Union[DTotal, DNotTotal]) @@ -2119,23 +2122,19 @@ else: [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] -[case testOperatorContainsNarrowsTypedDicts_partialThroughNotRequired] +[case testOperatorContainsNarrowsTypedDicts_NotRequired] +# flags: --warn-unreachable from __future__ import annotations from typing import assert_type, Required, NotRequired, TypedDict, Union -from typing_extensions import final -@final class D1(TypedDict): required_key: Required[int] optional_key: NotRequired[int] - -@final class D2(TypedDict): abc: int xyz: int - d: D1 | D2 if 'required_key' in d: @@ -2148,6 +2147,11 @@ if 'optional_key' in d: else: assert_type(d, Union[D1, D2]) +if 'unknown_key' in d: + object() # E: Statement is unreachable +else: + assert_type(d, Union[D1, D2]) + [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi]