From 60342acc9965d78ffe517733b944e9013bcf969f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Mon, 21 Sep 2026 10:03:01 -0600 Subject: [PATCH 1/2] Fix stubtest false positive for sentinel default values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- mypy/stubtest.py | 15 ++++++++++ mypy/test/teststubtest.py | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 4a38939a03907..d4c530ff92354 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -863,6 +863,8 @@ def _verify_arg_default_value( and type(runtime_arg.default) is not object # And ellipsis and runtime_arg.default is not ... + # And sentinels + and not is_sentinel(runtime_type) and not is_subtype_helper(runtime_type, stub_type) ): yield ( @@ -2090,6 +2092,19 @@ def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool: return mypy.subtypes.is_subtype(left, right) +def is_sentinel(runtime: mypy.types.Type) -> bool: + """Checks whether ``runtime`` is the type of a ``sentinel()`` marker object.""" + runtime = mypy.types.get_proper_type(runtime) + return isinstance(runtime, mypy.types.Instance) and runtime.type.fullname in ( + "builtins.sentinel", + "typing_extensions.sentinel", + # typing_extensions 4.14.0-4.15.x defined the class as `Sentinel`, with + # no lowercase alias; the rename (and `Sentinel = sentinel` alias) + # landed in 4.16.0. + "typing_extensions.Sentinel", + ) + + def get_mypy_node_for_name(module: str, type_name: str) -> mypy.nodes.SymbolNode | None: stub = get_stub(module) if stub is None: diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index fb83ff0ef528c..8af55a93e58c1 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -123,6 +123,11 @@ class frozenset(Generic[T]): ... class function: pass +class sentinel: + __name__: str + __module__: str + def __new__(cls, name: str, /, *, repr: str | None = None) -> sentinel: ... + class int: ... class float: ... class bool(int): ... @@ -3075,6 +3080,59 @@ def __init__(self, formatter_class=HelpFormatter): ... ) + @collect_cases + def test_object_marker(self) -> Iterator[Case]: + yield Case( + stub=""" + def f(x: int = ...) -> None: ... + """, + runtime=""" + _MISSING = object() + + + def f(x=_MISSING): + pass + """, + error=None, + ) + + @collect_cases + def test_sentinel_object_like_marker(self) -> Iterator[Case]: + yield Case( + stub=""" + def f(x: int = ...) -> None: ... + """, + runtime=""" + from typing_extensions import sentinel + + _MISSING = sentinel("_MISSING") + + + def f(x=_MISSING): + pass + """, + error=None, + ) + + @collect_cases + def test_legacy_sentinel_object_like_marker(self) -> Iterator[Case]: + yield Case( + stub=""" + def f(x: int = ...) -> None: ... + """, + runtime=""" + from typing_extensions import Sentinel + + _MISSING = Sentinel("_MISSING") + + + def f(x=_MISSING): + pass + """, + error=None, + ) + + def remove_color_code(s: str) -> str: return re.sub("\\x1b.*?m", "", s) # this works! From a788363098130b640f5cc2806b2d7b669c124a38 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:54:27 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/test/teststubtest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 8af55a93e58c1..788f1ed545115 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -3079,7 +3079,6 @@ def __init__(self, formatter_class=HelpFormatter): ... error=None, ) - @collect_cases def test_object_marker(self) -> Iterator[Case]: yield Case(