Skip to content

Commit 754edc3

Browse files
authored
A proper fix for sentinel problmes (#21919)
This simplifies the implementation and avoids fragile/improper use of `last_known_value`.
1 parent deda499 commit 754edc3

7 files changed

Lines changed: 10 additions & 38 deletions

File tree

mypy/erasetype.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,6 @@ class LastKnownValueEraser(TypeTranslator):
246246
def visit_instance(self, t: Instance) -> Type:
247247
if not t.last_known_value and not t.args:
248248
return t
249-
if t.last_known_value is not None and t.last_known_value.is_sentinel_literal():
250-
# Sentinel values (PEP 661) have no other way to identify themselves than
251-
# via their literal, unlike e.g. enum members, so it must be preserved.
252-
return t
253249
return t.copy_modified(args=[a.accept(self) for a in t.args], last_known_value=None)
254250

255251
def visit_type_alias_type(self, t: TypeAliasType) -> Type:

mypy/expandtype.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,6 @@ def visit_type_var(self, t: TypeVarType) -> Type:
244244
t = t.copy_modified(upper_bound=t.upper_bound.accept(self))
245245
repl = self.variables.get(t.id, t)
246246
if isinstance(repl, ProperType) and isinstance(repl, Instance):
247-
if repl.last_known_value is not None and repl.last_known_value.is_sentinel_literal():
248-
# Sentinel values (PEP 661) have no other way to identify themselves than
249-
# via their literal, unlike e.g. enum members, so it must survive expansion.
250-
return repl
251247
# TODO: do we really need to do this?
252248
# If I try to remove this special-casing ~40 tests fail on reveal_type().
253249
return repl.copy_modified(last_known_value=None)

mypy/messages.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,12 +2712,6 @@ def format_literal_value(typ: LiteralType) -> str:
27122712

27132713
if isinstance(typ, Instance):
27142714
itype = typ
2715-
if itype.last_known_value is not None and itype.last_known_value.is_sentinel_literal():
2716-
# Sentinel values (PEP 661) have no other way to identify themselves
2717-
# than via their literal, so use it instead of the shared fallback
2718-
# class name (unlike other literals, sentinels are always formatted
2719-
# this way, e.g. "MISSING" rather than "Literal[MISSING]").
2720-
return format_literal_value(itype.last_known_value)
27212715
# Get the short name of the type.
27222716
if itype.type.fullname == "types.ModuleType":
27232717
# Make some common error messages simpler and tidier.
@@ -3531,12 +3525,6 @@ def ignore_last_known_values(t: UnionType) -> Type:
35313525
seen_instances = set()
35323526
for item in t.items:
35333527
if isinstance(item, ProperType) and isinstance(item, Instance):
3534-
if item.last_known_value is not None and item.last_known_value.is_sentinel_literal():
3535-
# Sentinel values (PEP 661) have no other way to identify themselves
3536-
# than via their literal, unlike e.g. enum members, so it must be
3537-
# preserved (see mypy/erasetype.py for the same exemption).
3538-
union_items.append(item)
3539-
continue
35403528
erased = item.copy_modified(last_known_value=None)
35413529
if erased in seen_instances:
35423530
continue

mypy/semanal.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3422,27 +3422,23 @@ def setup_sentinel_var(self, s: AssignmentStmt) -> None:
34223422
assert isinstance(lvalue, NameExpr)
34233423
if not isinstance(lvalue.node, Var):
34243424
return
3425+
lvalue.is_special_form = True
34253426
var = lvalue.node
34263427
var.is_sentinel = True
34273428
typ = self.sentinel_type_for_var(var, s.rvalue)
34283429
if typ is not None:
34293430
s.type = typ
34303431

3431-
def sentinel_type_for_var(self, var: Var, rvalue: Expression) -> Instance | None:
3432+
def sentinel_type_for_var(self, var: Var, rvalue: Expression) -> LiteralType | None:
34323433
assert isinstance(rvalue, CallExpr)
34333434
callee = rvalue.callee
34343435
assert isinstance(callee, RefExpr)
34353436
typ = self.named_type_or_none(callee.fullname)
34363437
if typ is None:
34373438
return None
34383439
name = f"{self.type.name}.{var.name}" if self.type is not None else var.name
3439-
return typ.copy_modified(
3440-
last_known_value=LiteralType(
3441-
SentinelValue(var.fullname, name),
3442-
fallback=typ,
3443-
line=rvalue.line,
3444-
column=rvalue.column,
3445-
)
3440+
return LiteralType(
3441+
SentinelValue(var.fullname, name), fallback=typ, line=rvalue.line, column=rvalue.column
34463442
)
34473443

34483444
def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool:

mypy/typeanal.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,13 +1060,10 @@ def analyze_unbound_type_without_type_info(
10601060

10611061
if isinstance(sym.node, Var) and sym.node.is_sentinel:
10621062
typ = get_proper_type(sym.node.type)
1063-
if isinstance(typ, Instance) and typ.last_known_value is not None:
1064-
return LiteralType(
1065-
value=typ.last_known_value.value,
1066-
fallback=typ.last_known_value.fallback,
1067-
line=t.line,
1068-
column=t.column,
1069-
)
1063+
assert isinstance(typ, LiteralType)
1064+
return LiteralType(
1065+
value=typ.value, fallback=typ.fallback, line=t.line, column=t.column
1066+
)
10701067

10711068
# None of the above options worked. We parse the args (if there are any)
10721069
# to make sure there are no remaining semanal-only types, then give up.

test-data/unit/check-sentinels.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,7 @@ X = sentinel("X")
224224

225225
def inspect_sentinel(arg: sentinel) -> None: ...
226226

227-
reveal_type(X) # N: Revealed type is "X?"
227+
reveal_type(X) # N: Revealed type is "X"
228228
reveal_type(inspect_sentinel) # N: Revealed type is "def (arg: typing_extensions.Sentinel)"
229229
inspect_sentinel(arg=X)
230-
231230
[builtins fixtures/tuple.pyi]

test-data/unit/deps-types.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1036,4 +1036,4 @@ def f(x: MISSING) -> None:
10361036
pass
10371037
[builtins fixtures/tuple.pyi]
10381038
[out]
1039-
<m.MISSING> -> <m.MISSING>, <m.f>, m, m.f
1039+
<m.MISSING> -> <m.f>, m, m.f

0 commit comments

Comments
 (0)