Skip to content

PEP 661 sentinel loses type information when reassigned #21971

Description

@edgarrmondragon

Bug Report

Sentinel loses type information when reassigned.

To Reproduce

# mre1.py (fails)
from typing_extensions import Sentinel

MISSING = Sentinel("MISSING")
ALIAS = MISSING

x: ALIAS | int = ALIAS

https://mypy-play.net/?mypy=master&python=3.12&gist=6d4045b697ab57c41bfeecb59233db76

$ mypy mre1.py
mre1.py:6: error: Variable "mre1.ALIAS" is not valid as a type  [valid-type]
mre1.py:6: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)

This call was made in #21647 (comment).

Expected Behavior

mre1.py should type-check the same way mre2.py does. ALIAS is assigned directly from MISSING, so it should be recognized as the same sentinel.

Actual Behavior

$ mypy mre1.py
mre1.py:6: error: Variable "mre1.ALIAS" is not valid as a type  [valid-type]
mre1.py:6: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: mypy 2.4.0+dev (master)
  • Mypy command-line flags: (none)
  • Mypy configuration options from mypy.ini (and other config files): (none)
  • Python version used: 3.15

Additional Context

This came up because Pydantic 2.14.0b2 re-exports its MISSING sentinel from pydantic_core as pydantic.MISSING:

# pydantic/__init__.py
MISSING = pydantic_core.MISSING

https://github.com/pydantic/pydantic/blob/ba830dc07550dd7a11f167cbf8b8b707712cd88e/pydantic/__init__.py#L66

Then this fails:

from pydantic import MISSING

x: MISSING | int = MISSING
$ mypy mre1.py
mre1.py:3: error: Variable "pydantic.MISSING" is not valid as a type  [valid-type]
mre1.py:3: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)

pydantic_core.MISSING doesn't have this problem, since it's where MISSING is originally defined via Sentinel(...):

from pydantic_core import MISSING

x: MISSING | int = MISSING
$ mypy mre2.py
Success: no issues found in 1 source file

FWIW pydantic.MISSING is identified as a valid type if Pydantic is to re-export the sentinel variable via import, i.e.

# pydantic/__init__.py
from pydantic_core import MISSING # instead of MISSING = pydantic_core.MISSING

but I don't think Pydantic is at fault here and other type checkers don't have the same complaint. What do y'all think?

Related

cc @JelleZijlstra

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions