Skip to content

Commit f799344

Browse files
committed
respect final-ity of TypedDict
1 parent 88e2c9f commit f799344

File tree

3 files changed

+53
-6
lines changed

3 files changed

+53
-6
lines changed

mypy/checker.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5037,7 +5037,7 @@ def _contains_string_right_operand_type_map(
50375037
for key in item_strs:
50385038
if key in t.required_keys:
50395039
if_types.append(t)
5040-
elif key in t.items:
5040+
elif key in t.items or not t.is_final:
50415041
if_types.append(t)
50425042
else_types.append(t)
50435043
else:

mypy/types.py

+4
Original file line numberDiff line numberDiff line change
@@ -2290,6 +2290,10 @@ def deserialize(cls, data: JsonDict) -> TypedDictType:
22902290
Instance.deserialize(data["fallback"]),
22912291
)
22922292

2293+
@property
2294+
def is_final(self) -> bool:
2295+
return self.fallback.type.is_final
2296+
22932297
def is_anonymous(self) -> bool:
22942298
return self.fallback.type.fullname in TPDICT_FB_NAMES
22952299

test-data/unit/check-typeddict.test

+48-5
Original file line numberDiff line numberDiff line change
@@ -2017,7 +2017,6 @@ from __future__ import annotations
20172017
from typing import assert_type, TypedDict, Union
20182018
from typing_extensions import final
20192019

2020-
20212020
@final
20222021
class D(TypedDict):
20232022
foo: int
@@ -2030,10 +2029,10 @@ if 'foo' in d:
20302029
else:
20312030
assert_type(d, list[str])
20322031

2033-
[builtins fixtures / dict.pyi]
2034-
[typing fixtures / typing - typeddict.pyi]
2032+
[builtins fixtures/dict.pyi]
2033+
[typing fixtures/typing-typeddict.pyi]
20352034

2036-
[case testOperatorContainsNarrowsTotalTypedDicts]
2035+
[case testOperatorContainsNarrowsTypedDicts_total]
20372036
from __future__ import annotations
20382037
from typing import assert_type, Literal, TypedDict, TypeVar, Union
20392038
from typing_extensions import final
@@ -2048,6 +2047,13 @@ class D2(TypedDict):
20482047
bar: int
20492048

20502049
d: D1 | D2
2050+
opt_d: D1 | None
2051+
2052+
if 'foo' in opt_d:
2053+
assert_type(opt_d, D1)
2054+
else:
2055+
assert_type(opt_d, None)
2056+
20512057

20522058
if 'foo' in d:
20532059
assert_type(d, D1)
@@ -2081,7 +2087,44 @@ def f(arg: TD) -> None:
20812087
[builtins fixtures/dict.pyi]
20822088
[typing fixtures/typing-typeddict.pyi]
20832089

2084-
[case testOperatorContainsNarrowsPartialTypedDicts]
2090+
[case testOperatorContainsNarrowsTypedDicts_final]
2091+
# flags: --warn-unreachable
2092+
from __future__ import annotations
2093+
from typing import assert_type, Literal, TypedDict, TypeVar, Union
2094+
from typing_extensions import final
2095+
2096+
@final
2097+
class DFinal(TypedDict):
2098+
foo: int
2099+
2100+
2101+
class DNotFinal(TypedDict):
2102+
bar: int
2103+
2104+
2105+
d_not_final: DNotFinal
2106+
2107+
if 'bar' in d_not_final:
2108+
assert_type(d_not_final, DNotFinal)
2109+
else:
2110+
spam = 'ham' # E: Statement is unreachable
2111+
2112+
if 'spam' in d_not_final:
2113+
assert_type(d_not_final, DNotFinal)
2114+
else:
2115+
assert_type(d_not_final, DNotFinal)
2116+
2117+
d_union: DFinal | DNotFinal
2118+
2119+
if 'foo' in d_union:
2120+
assert_type(d_union, Union[DFinal, DNotFinal])
2121+
else:
2122+
assert_type(d_union, DNotFinal)
2123+
2124+
[builtins fixtures/dict.pyi]
2125+
[typing fixtures/typing-typeddict.pyi]
2126+
2127+
[case testOperatorContainsNarrowsTypedDicts_partial]
20852128
from __future__ import annotations
20862129
from typing import assert_type, Literal, TypedDict, Union
20872130
from typing_extensions import final

0 commit comments

Comments
 (0)