Skip to content

Commit be0091a

Browse files
initial draft
1 parent db67fac commit be0091a

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
@@ -1088,8 +1118,12 @@ def visit_typeddict_type(self, t: TypedDictType) -> ProperType:
10881118
def visit_literal_type(self, t: LiteralType) -> ProperType:
10891119
if isinstance(self.s, LiteralType) and self.s == t:
10901120
return t
1091-
elif isinstance(self.s, Instance) and is_subtype(t.fallback, self.s):
1092-
return t
1121+
elif isinstance(self.s, Instance):
1122+
if is_subtype(t.fallback, self.s):
1123+
return t
1124+
if self.s.last_known_value is not None:
1125+
return meet_types(self.s.last_known_value, t)
1126+
return self.default(self.s)
10931127
else:
10941128
return self.default(self.s)
10951129

mypy/subtypes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,17 @@ def visit_instance(self, left: Instance) -> bool:
629629
return True
630630
if isinstance(item, Instance):
631631
return is_named_instance(item, "builtins.object")
632-
if isinstance(right, LiteralType) and left.last_known_value is not None:
633-
return self._is_subtype(left.last_known_value, right)
632+
# if isinstance(right, LiteralType) and left.last_known_value is not None:
633+
# return self._is_subtype(left.last_known_value, right)
634+
if isinstance(right, LiteralType):
635+
if self.proper_subtype:
636+
# Instance types like Literal["sum"]? is *assignable* to Literal["sum"],
637+
# but is not a proper subtype of it. (Literal["sum"]? is a gradual type,
638+
# that is a proper subtype of str, and is assignable to Literal["sum"],
639+
# but not a proper subtype of it.)
640+
return False
641+
if left.last_known_value is not None:
642+
return self._is_subtype(left.last_known_value, right)
634643
if isinstance(right, FunctionLike):
635644
# Special case: Instance can be a subtype of Callable / Overloaded.
636645
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
@@ -4076,7 +4076,7 @@ def check_and(maybe: bool) -> None:
40764076
bar = None
40774077
if maybe and (foo := [1])[(bar := 0)]:
40784078
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
4079-
reveal_type(bar) # N: Revealed type is "builtins.int"
4079+
reveal_type(bar) # N: Revealed type is "Literal[0]?"
40804080
else:
40814081
reveal_type(foo) # N: Revealed type is "Union[builtins.list[builtins.int], None]"
40824082
reveal_type(bar) # N: Revealed type is "Union[builtins.int, None]"
@@ -4102,7 +4102,7 @@ def check_or(maybe: bool) -> None:
41024102
reveal_type(bar) # N: Revealed type is "Union[builtins.int, None]"
41034103
else:
41044104
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
4105-
reveal_type(bar) # N: Revealed type is "builtins.int"
4105+
reveal_type(bar) # N: Revealed type is "Literal[0]?"
41064106

41074107
def check_or_nested(maybe: bool) -> None:
41084108
foo = None

test-data/unit/check-python310.test

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

13551355
match m:
13561356
case a if a := "test":
1357-
reveal_type(a) # N: Revealed type is "builtins.str"
1357+
reveal_type(a) # N: Revealed type is "Literal['test']?"
13581358

13591359
[case testMatchNarrowingPatternGuard]
13601360
m: object
@@ -2686,7 +2686,7 @@ match m[k]:
26862686

26872687
match 0:
26882688
case 0 as i:
2689-
reveal_type(i) # N: Revealed type is "Literal[0]?"
2689+
reveal_type(i) # N: Revealed type is "Literal[0]"
26902690
case int(i):
26912691
i # E: Statement is unreachable
26922692
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 "Union[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)