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

Skip to content

Commit b183f2a

Browse files
Recognize plain aliases of an existing sentinel as sentinel declarations
Currently `is_sentinel_declaration` only recognizes the exact `NAME = Sentinel("NAME")` call shape, so a re-export like `ALIAS = NAME` (same module or via `import mod; ALIAS = mod.NAME`) silently loses is_sentinel, and ALIAS becomes invalid in type position. This is exactly the pattern pydantic 2.14 uses to re-export MISSING from pydantic_core: `MISSING = pydantic_core.MISSING`. Extend is_sentinel_declaration/setup_sentinel_var to also accept a `RefExpr` rvalue (`NameExpr` or `MemberExpr`) that resolves to a Var already flagged `is_sentinel`, reusing its already-computed `LiteralType` rather than requiring a fresh `Sentinel(...)` call. Signed-off-by: Edgar Ramírez Mondragón <[email protected]>
1 parent ab3849b commit b183f2a

2 files changed

Lines changed: 42 additions & 6 deletions

File tree

mypy/semanal.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3406,11 +3406,19 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
34063406
self.process__slots__(s)
34073407

34083408
def is_sentinel_declaration(self, s: AssignmentStmt) -> bool:
3409-
"""Does this assignment define a PEP 661 sentinel singleton?"""
3409+
"""Does this assignment define a PEP 661 sentinel singleton?
3410+
3411+
This includes both the original `NAME = Sentinel("NAME")` call and a
3412+
plain alias of an existing sentinel, e.g. `ALIAS = NAME` or
3413+
`ALIAS = mod.NAME`, so that re-exports of a sentinel keep working as
3414+
the same sentinel type.
3415+
"""
34103416
if self.is_nested_within_func_scope() or s.unanalyzed_type is not None:
34113417
return False
34123418
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
34133419
return False
3420+
if isinstance(s.rvalue, RefExpr):
3421+
return isinstance(s.rvalue.node, Var) and s.rvalue.node.is_sentinel
34143422
if not isinstance(s.rvalue, CallExpr):
34153423
return False
34163424
call = s.rvalue
@@ -3430,7 +3438,13 @@ def setup_sentinel_var(self, s: AssignmentStmt) -> None:
34303438
lvalue.is_special_form = True
34313439
var = lvalue.node
34323440
var.is_sentinel = True
3433-
typ = self.sentinel_type_for_var(var, s.rvalue)
3441+
if isinstance(s.rvalue, RefExpr):
3442+
# An alias of an existing sentinel: reuse its already-computed type.
3443+
assert isinstance(s.rvalue.node, Var)
3444+
typ = get_proper_type(s.rvalue.node.type)
3445+
assert isinstance(typ, LiteralType)
3446+
else:
3447+
typ = self.sentinel_type_for_var(var, s.rvalue)
34343448
if typ is not None:
34353449
s.type = typ
34363450

test-data/unit/check-sentinels.test

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def func(x: int | BUILTIN | TYPEXT) -> None:
175175

176176
[builtins fixtures/sentinel.pyi]
177177

178-
[case testSentinelReassignmentIsNotTypeAlias]
178+
[case testSentinelReassignmentIsSameSentinel]
179179
from typing_extensions import sentinel, assert_type
180180

181181
MISSING = sentinel("MISSING")
@@ -190,10 +190,32 @@ def func(x: int | MISSING = MISSING) -> None:
190190
func(MISSING)
191191
func(ALIAS)
192192

193-
# ...but the reassignment does not make ALIAS usable as a type alias.
194-
def uses_alias_as_type(x: ALIAS) -> None: # E: Variable "__main__.ALIAS" is not valid as a type \
195-
# N: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
193+
# ...and the reassignment keeps ALIAS usable as a type expression too.
194+
def uses_alias_as_type(x: ALIAS) -> None:
196195
pass
196+
197+
uses_alias_as_type(MISSING)
198+
uses_alias_as_type(ALIAS)
199+
[builtins fixtures/tuple.pyi]
200+
201+
[case testSentinelReassignmentAcrossModules]
202+
from other import MISSING
203+
204+
def func(x: int | MISSING = MISSING) -> None:
205+
pass
206+
207+
func(MISSING)
208+
209+
[file other.py]
210+
from typing_extensions import sentinel
211+
import lib
212+
213+
MISSING = lib.MISSING
214+
215+
[file lib.py]
216+
from typing_extensions import sentinel
217+
218+
MISSING = sentinel("MISSING")
197219
[builtins fixtures/tuple.pyi]
198220

199221
[case testSentinelPreservedThroughGenericSubstitution]

0 commit comments

Comments
 (0)