Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c8a2289

Browse files
Fix type narrowing on TypedDict with key name in Final variable (#11813)
* Add support for Final in literal_hash() * Add test for tuple
1 parent d6c56cf commit c8a2289

3 files changed

Lines changed: 34 additions & 6 deletions

File tree

mypy/literals.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
TypeVarExpr,
4949
TypeVarTupleExpr,
5050
UnaryExpr,
51+
Var,
5152
YieldExpr,
5253
YieldFromExpr,
5354
)
@@ -112,6 +113,8 @@ def literal(e: Expression) -> int:
112113
return LITERAL_NO
113114

114115
elif isinstance(e, NameExpr):
116+
if isinstance(e.node, Var) and e.node.is_final and e.node.final_value is not None:
117+
return LITERAL_YES
115118
return LITERAL_TYPE
116119

117120
if isinstance(e, (IntExpr, FloatExpr, ComplexExpr, StrExpr, BytesExpr)):
@@ -154,6 +157,8 @@ def visit_star_expr(self, e: StarExpr) -> Key:
154157
return ("Star", literal_hash(e.expr))
155158

156159
def visit_name_expr(self, e: NameExpr) -> Key:
160+
if isinstance(e.node, Var) and e.node.is_final and e.node.final_value is not None:
161+
return ("Literal", e.node.final_value)
157162
# N.B: We use the node itself as the key, and not the name,
158163
# because using the name causes issues when there is shadowing
159164
# (for example, in list comprehensions).

test-data/unit/check-literal.test

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,8 @@ reveal_type(unify(func)) # N: Revealed type is "<nothing>"
18301830
--
18311831

18321832
[case testLiteralIntelligentIndexingTuples]
1833-
from typing import Tuple, NamedTuple
1833+
# flags: --strict-optional
1834+
from typing import Tuple, NamedTuple, Optional, Final
18341835
from typing_extensions import Literal
18351836

18361837
class A: pass
@@ -1846,17 +1847,23 @@ idx3: Literal[3]
18461847
idx4: Literal[4]
18471848
idx5: Literal[5]
18481849
idx_neg1: Literal[-1]
1850+
idx_final: Final = 2
18491851

1850-
tup1: Tuple[A, B, C, D, E]
1852+
tup1: Tuple[A, B, Optional[C], D, E]
18511853
reveal_type(tup1[idx0]) # N: Revealed type is "__main__.A"
18521854
reveal_type(tup1[idx1]) # N: Revealed type is "__main__.B"
1853-
reveal_type(tup1[idx2]) # N: Revealed type is "__main__.C"
1855+
reveal_type(tup1[idx2]) # N: Revealed type is "Union[__main__.C, None]"
1856+
reveal_type(tup1[idx_final]) # N: Revealed type is "Union[__main__.C, None]"
18541857
reveal_type(tup1[idx3]) # N: Revealed type is "__main__.D"
18551858
reveal_type(tup1[idx4]) # N: Revealed type is "__main__.E"
18561859
reveal_type(tup1[idx_neg1]) # N: Revealed type is "__main__.E"
18571860
tup1[idx5] # E: Tuple index out of range
1858-
reveal_type(tup1[idx2:idx4]) # N: Revealed type is "Tuple[__main__.C, __main__.D]"
1859-
reveal_type(tup1[::idx2]) # N: Revealed type is "Tuple[__main__.A, __main__.C, __main__.E]"
1861+
reveal_type(tup1[idx2:idx4]) # N: Revealed type is "Tuple[Union[__main__.C, None], __main__.D]"
1862+
reveal_type(tup1[::idx2]) # N: Revealed type is "Tuple[__main__.A, Union[__main__.C, None], __main__.E]"
1863+
if tup1[idx2] is not None:
1864+
reveal_type(tup1[idx2]) # N: Revealed type is "Union[__main__.C, None]"
1865+
if tup1[idx_final] is not None:
1866+
reveal_type(tup1[idx_final]) # N: Revealed type is "__main__.C"
18601867

18611868
Tup2Class = NamedTuple('Tup2Class', [('a', A), ('b', B), ('c', C), ('d', D), ('e', E)])
18621869
tup2: Tup2Class
@@ -1870,7 +1877,6 @@ tup2[idx5] # E: Tuple index out of range
18701877
reveal_type(tup2[idx2:idx4]) # N: Revealed type is "Tuple[__main__.C, __main__.D, fallback=__main__.Tup2Class]"
18711878
reveal_type(tup2[::idx2]) # N: Revealed type is "Tuple[__main__.A, __main__.C, __main__.E, fallback=__main__.Tup2Class]"
18721879
[builtins fixtures/slice.pyi]
1873-
[out]
18741880

18751881
[case testLiteralIntelligentIndexingTypedDict]
18761882
from typing_extensions import Literal

test-data/unit/check-typeddict.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,23 @@ class DummyTypedDict(TypedDict):
20252025
[builtins fixtures/dict.pyi]
20262026
[typing fixtures/typing-typeddict.pyi]
20272027

2028+
[case testTypedDictTypeNarrowingWithFinalKey]
2029+
from typing import Final, Optional, TypedDict
2030+
2031+
KEY_NAME: Final = "bar"
2032+
class Foo(TypedDict):
2033+
bar: Optional[str]
2034+
2035+
foo = Foo(bar="hello")
2036+
if foo["bar"] is not None:
2037+
reveal_type(foo["bar"]) # N: Revealed type is "builtins.str"
2038+
reveal_type(foo[KEY_NAME]) # N: Revealed type is "builtins.str"
2039+
if foo[KEY_NAME] is not None:
2040+
reveal_type(foo["bar"]) # N: Revealed type is "builtins.str"
2041+
reveal_type(foo[KEY_NAME]) # N: Revealed type is "builtins.str"
2042+
[builtins fixtures/dict.pyi]
2043+
[typing fixtures/typing-typeddict.pyi]
2044+
20282045
[case testTypedDictDoubleForwardClass]
20292046
from mypy_extensions import TypedDict
20302047
from typing import Any, List

0 commit comments

Comments
 (0)