Skip to content

Commit 75306f0

Browse files
initial draft
1 parent e089abc commit 75306f0

File tree

5 files changed

+59
-16
lines changed

5 files changed

+59
-16
lines changed

mypy/meet.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,36 @@ def meet_types(s: Type, t: Type) -> ProperType:
9494
return s
9595
return t
9696

97+
# special casing for dealing with last known values
98+
if is_proper_subtype(s, t, ignore_promotions=True) and is_proper_subtype(
99+
t, s, ignore_promotions=True
100+
):
101+
lkv: LiteralType | None
102+
if s.last_known_value is None and t.last_known_value is None:
103+
# Both types have no last known value, so we return the original type.
104+
lkv = None
105+
elif s.last_known_value is None and t.last_known_value is not None:
106+
lkv = t.last_known_value
107+
elif s.last_known_value is not None and t.last_known_value is None:
108+
lkv = s.last_known_value
109+
elif s.last_known_value is not None and t.last_known_value is not None:
110+
lkv_meet = meet_types(s.last_known_value, t.last_known_value)
111+
if isinstance(lkv_meet, UninhabitedType):
112+
lkv = None
113+
elif isinstance(lkv_meet, LiteralType):
114+
lkv = lkv_meet
115+
else:
116+
msg = (
117+
f"Unexpected meet result for last known values: "
118+
f"{s.last_known_value=} and {t.last_known_value=} "
119+
f"resulted in {lkv_meet=}"
120+
)
121+
raise ValueError(msg)
122+
else:
123+
assert False
124+
assert lkv is None or isinstance(lkv, LiteralType)
125+
return t.copy_modified(last_known_value=lkv)
126+
97127
if not isinstance(s, UnboundType) and not isinstance(t, UnboundType):
98128
if is_proper_subtype(s, t, ignore_promotions=True):
99129
return s
@@ -1114,8 +1144,12 @@ def visit_typeddict_type(self, t: TypedDictType) -> ProperType:
11141144
def visit_literal_type(self, t: LiteralType) -> ProperType:
11151145
if isinstance(self.s, LiteralType) and self.s == t:
11161146
return t
1117-
elif isinstance(self.s, Instance) and is_subtype(t.fallback, self.s):
1118-
return t
1147+
elif isinstance(self.s, Instance):
1148+
if is_subtype(t.fallback, self.s):
1149+
return t
1150+
if self.s.last_known_value is not None:
1151+
return meet_types(self.s.last_known_value, t)
1152+
return self.default(self.s)
11191153
else:
11201154
return self.default(self.s)
11211155

mypy/subtypes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,8 +628,17 @@ def visit_instance(self, left: Instance) -> bool:
628628
return True
629629
if isinstance(item, Instance):
630630
return is_named_instance(item, "builtins.object")
631-
if isinstance(right, LiteralType) and left.last_known_value is not None:
632-
return self._is_subtype(left.last_known_value, right)
631+
# if isinstance(right, LiteralType) and left.last_known_value is not None:
632+
# return self._is_subtype(left.last_known_value, right)
633+
if isinstance(right, LiteralType):
634+
if self.proper_subtype:
635+
# Instance types like Literal["sum"]? is *assignable* to Literal["sum"],
636+
# but is not a proper subtype of it. (Literal["sum"]? is a gradual type,
637+
# that is a proper subtype of str, and is assignable to Literal["sum"],
638+
# but not a proper subtype of it.)
639+
return False
640+
if left.last_known_value is not None:
641+
return self._is_subtype(left.last_known_value, right)
633642
if isinstance(right, FunctionLike):
634643
# Special case: Instance can be a subtype of Callable / Overloaded.
635644
call = find_member("__call__", left, left, is_operator=True)

test-data/unit/check-inference.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,7 +4173,7 @@ def check_and(maybe: bool) -> None:
41734173
bar = None
41744174
if maybe and (foo := [1])[(bar := 0)]:
41754175
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
4176-
reveal_type(bar) # N: Revealed type is "builtins.int"
4176+
reveal_type(bar) # N: Revealed type is "Literal[0]?"
41774177
else:
41784178
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int] | None"
41794179
reveal_type(bar) # N: Revealed type is "builtins.int | None"
@@ -4199,7 +4199,7 @@ def check_or(maybe: bool) -> None:
41994199
reveal_type(bar) # N: Revealed type is "builtins.int | None"
42004200
else:
42014201
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
4202-
reveal_type(bar) # N: Revealed type is "builtins.int"
4202+
reveal_type(bar) # N: Revealed type is "Literal[0]?"
42034203

42044204
def check_or_nested(maybe: bool) -> None:
42054205
foo = None

test-data/unit/check-python310.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,7 +1520,7 @@ m: str
15201520

15211521
match m:
15221522
case a if a := "test":
1523-
reveal_type(a) # N: Revealed type is "builtins.str"
1523+
reveal_type(a) # N: Revealed type is "Literal['test']?"
15241524

15251525
[case testMatchNarrowingPatternGuard]
15261526
m: object
@@ -2907,7 +2907,7 @@ match m[k]:
29072907

29082908
match 0:
29092909
case 0 as i:
2910-
reveal_type(i) # N: Revealed type is "Literal[0]?"
2910+
reveal_type(i) # N: Revealed type is "Literal[0]"
29112911
case int(i):
29122912
i # E: Statement is unreachable
29132913
case other:

test-data/unit/check-python38.test

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,10 @@ i(arg=0) # E: Unexpected keyword argument "arg"
214214
from typing import Final, NamedTuple, Optional, List
215215

216216
if a := 2:
217-
reveal_type(a) # N: Revealed type is "builtins.int"
217+
reveal_type(a) # N: Revealed type is "Literal[2]?"
218218

219219
while b := "x":
220-
reveal_type(b) # N: Revealed type is "builtins.str"
220+
reveal_type(b) # N: Revealed type is "Literal['x']?"
221221

222222
l = [y2 := 1, y2 + 2, y2 + 3]
223223
reveal_type(y2) # N: Revealed type is "builtins.int"
@@ -242,10 +242,10 @@ reveal_type(new_v) # N: Revealed type is "builtins.int"
242242

243243
def f(x: int = (c := 4)) -> int:
244244
if a := 2:
245-
reveal_type(a) # N: Revealed type is "builtins.int"
245+
reveal_type(a) # N: Revealed type is "Literal[2]?"
246246

247247
while b := "x":
248-
reveal_type(b) # N: Revealed type is "builtins.str"
248+
reveal_type(b) # N: Revealed type is "Literal['x']?"
249249

250250
x = (y := 1) + (z := 2)
251251
reveal_type(x) # N: Revealed type is "builtins.int"
@@ -284,7 +284,7 @@ def f(x: int = (c := 4)) -> int:
284284
f(x=(y7 := 3))
285285
reveal_type(y7) # N: Revealed type is "builtins.int"
286286

287-
reveal_type((lambda: (y8 := 3) and y8)()) # N: Revealed type is "builtins.int"
287+
reveal_type((lambda: (y8 := 3) and y8)()) # N: Revealed type is "Literal[3]?"
288288
y8 # E: Name "y8" is not defined
289289

290290
y7 = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "int")
@@ -325,16 +325,16 @@ def check_binder(x: Optional[int], y: Optional[int], z: Optional[int], a: Option
325325
reveal_type(y) # N: Revealed type is "builtins.int | None"
326326

327327
if x and (y := 1):
328-
reveal_type(y) # N: Revealed type is "builtins.int"
328+
reveal_type(y) # N: Revealed type is "Literal[1]?"
329329

330330
if (a := 1) and x:
331-
reveal_type(a) # N: Revealed type is "builtins.int"
331+
reveal_type(a) # N: Revealed type is "Literal[1]?"
332332

333333
if (b := 1) or x:
334334
reveal_type(b) # N: Revealed type is "builtins.int"
335335

336336
if z := 1:
337-
reveal_type(z) # N: Revealed type is "builtins.int"
337+
reveal_type(z) # N: Revealed type is "Literal[1]?"
338338

339339
def check_partial() -> None:
340340
x = None

0 commit comments

Comments
 (0)