From 662e8896d2515998dc35249e39bb5fd4ad25641d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Mon, 21 Sep 2026 12:05:19 -0600 Subject: [PATCH 1/8] Handle arg= in stubgenc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- mypy/stubgenc.py | 16 ++++++++++++++++ mypy/test/teststubgen.py | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index d86818adf2a43..adb8e32fea0b4 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -216,6 +216,18 @@ def __get__(self) -> None: # noqa: PLE0302 _Missing = enum.Enum("_Missing", "VALUE") +def _is_sentinel_object(obj: object) -> bool: + """Is ``obj`` an instance of ``typing_extensions.sentinel()``/``Sentinel()``?""" + typ = type(obj) + return typ.__module__ in ("builtins", "typing_extensions") and typ.__name__ in ( + "sentinel", + # typing_extensions 4.14.0-4.15.x named the class `Sentinel`, with no + # lowercase alias; the rename (and `Sentinel = sentinel` alias) landed + # in 4.16.0. + "Sentinel", + ) + + class InspectionStubGenerator(BaseStubGenerator): """Stub generator that does not parse code. @@ -323,6 +335,10 @@ def add_args( if default_value is not _Missing.VALUE: if arg in annotations: argtype = get_annotation(arg) + elif _is_sentinel_object(default_value): + # The runtime type of a `sentinel()` marker object is not a + # useful annotation for the parameter it defaults. + argtype = self.add_name("_typeshed.Incomplete") else: argtype = self.get_type_annotation(default_value) if argtype == "None": diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index 605409f995232..9b4cf36693f15 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -1040,6 +1040,32 @@ def test(self, arg0=1, arg1=None) -> None: # type: ignore[no-untyped-def] output, ["def test(self, arg0: int = ..., arg1: Incomplete | None = ...) -> None: ..."] ) + def test_generate_c_type_sentinel_default(self) -> None: + from typing_extensions import sentinel + + _MISSING = sentinel("_MISSING") + + class TestClass: + def test(self, arg0=_MISSING) -> None: # type: ignore[no-untyped-def] + pass + + output: list[str] = [] + mod = ModuleType(TestClass.__module__, "") + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.is_c_module = False + gen.generate_function_stub( + "test", + TestClass.test, + output=output, + class_info=ClassInfo( + self_var="self", + cls=TestClass, + name="TestClass", + docstring=getattr(TestClass, "__doc__", None), + ), + ) + assert_equal(output, ["def test(self, arg0: Incomplete = ...) -> None: ..."]) + def test_non_c_generate_signature_with_kw_only_args(self) -> None: class TestClass: def test( From 07cb6bd45b5639386dd7bd8875a6a33f95f8195c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Mon, 21 Sep 2026 22:59:47 -0600 Subject: [PATCH 2/8] Add cases to stubgen.test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- test-data/unit/stubgen.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 0407cf1bc798f..75ec1aa8cf251 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -72,6 +72,22 @@ def f(x=ord): ... [out] def f(x=...) -> None: ... +[case testDefaultArgSentinel] +from typing_extensions import sentinel +_MISSING = sentinel('_MISSING') +def f(x=_MISSING): ... +[out] +def f(x=...) -> None: ... + +[case testDefaultArgSentinel_inspect] +from typing_extensions import sentinel +_MISSING = sentinel('_MISSING') +def f(x=_MISSING): ... +[out] +from _typeshed import Incomplete + +def f(x: Incomplete = ...): ... + [case testPreserveFunctionAnnotation] def f(x: Foo) -> Bar: ... def g(x: Foo = Foo()) -> Bar: ... From 3eebd9b4abb69d0166f5ca017c53ce549d4a97dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Tue, 22 Sep 2026 23:11:52 -0600 Subject: [PATCH 3/8] Annotate with sentinel type in generated stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- mypy/stubgenc.py | 48 ++++++++++++++++++++++++++++++++++--- mypy/test/teststubgen.py | 16 ++++++++++++- test-data/unit/stubgen.test | 22 ++++++++++++++++- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index adb8e32fea0b4..04bf84c995396 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -337,8 +337,13 @@ def add_args( argtype = get_annotation(arg) elif _is_sentinel_object(default_value): # The runtime type of a `sentinel()` marker object is not a - # useful annotation for the parameter it defaults. - argtype = self.add_name("_typeshed.Incomplete") + # useful annotation for the parameter it defaults. Reference + # the sentinel itself by name when possible, e.g. + # `Incomplete | _MISSING`, importing it if it lives in + # another module. + incomplete = self.add_name("_typeshed.Incomplete") + sentinel_ref = self._sentinel_type_ref(default_value) + argtype = f"{incomplete} | {sentinel_ref}" if sentinel_ref else incomplete else: argtype = self.get_type_annotation(default_value) if argtype == "None": @@ -552,9 +557,38 @@ def get_type_annotation(self, obj: object) -> str: return self.add_name("typing.Callable") elif isinstance(obj, ModuleType): return self.add_name("types.ModuleType", require=False) + elif _is_sentinel_object(obj): + # On 3.15+, typing_extensions.sentinel is builtins.sentinel, so + # type(obj).__module__ is "builtins" there and "typing_extensions" + # otherwise. Always use the portable spelling instead of whichever + # one happens to match the interpreter running stubgen. Use the + # dotted form (not add_name) since a module constructing a + # sentinel will itself have already imported the bare `sentinel` + # name, which would otherwise force an alias here. + return "typing_extensions.sentinel" else: return self.get_type_fullname(type(obj)) + def _sentinel_type_ref(self, sentinel_obj: object) -> str | None: + """Return a reference to the name a sentinel object is bound to. + + ``sentinel()``/``Sentinel()`` objects record the module and name they + were assigned to (this is how they support pickling), so we can point + directly at them (importing from another module if needed), rather + than using their unhelpful runtime type. Returns None if we can't (e.g. + the name isn't a valid identifier). + + A leading-underscore sentinel name is treated the same as a public + one: ``generate_variable_stub`` always emits sentinel declarations + regardless of privacy, since a sentinel used as a default is part of + the (typed) signature even when its name looks private. + """ + name = getattr(sentinel_obj, "__name__", None) + module = getattr(sentinel_obj, "__module__", None) + if not isinstance(name, str) or not name.isidentifier() or not module: + return None + return f"{module}.{name}" + def is_function(self, obj: object) -> bool: if self.is_c_module: return inspect.isbuiltin(obj) @@ -929,7 +963,15 @@ def generate_variable_stub(self, name: str, obj: object, output: list[str]) -> N The result lines will be appended to 'output'. If necessary, any required names will be added to 'imports'. """ - if self.is_private_name(name, f"{self.module_name}.{name}") or self.is_not_in_all(name): + if ( + # A sentinel's name is emitted regardless of privacy/`__all__`, since it may + # be referenced from the type of a default value elsewhere in the stub. + not _is_sentinel_object(obj) + and ( + self.is_private_name(name, f"{self.module_name}.{name}") + or self.is_not_in_all(name) + ) + ): return self.record_name(name) type_str = self.strip_or_import(self.get_type_annotation(obj)) diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index 9b4cf36693f15..8b581e1091bcb 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -1064,7 +1064,21 @@ def test(self, arg0=_MISSING) -> None: # type: ignore[no-untyped-def] docstring=getattr(TestClass, "__doc__", None), ), ) - assert_equal(output, ["def test(self, arg0: Incomplete = ...) -> None: ..."]) + assert_equal(output, ["def test(self, arg0: Incomplete | _MISSING = ...) -> None: ..."]) + + def test_generate_variable_stub_sentinel_ignores_privacy(self) -> None: + from typing_extensions import sentinel + + _MISSING = sentinel("_MISSING") + mod = ModuleType(__name__, "") + gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) + gen.is_c_module = False + output: list[str] = [] + gen.generate_variable_stub("_MISSING", _MISSING, output=output) + # A sentinel's declaration is always emitted, even for a private-looking + # name, since it may be referenced from a default value's type elsewhere. + assert len(output) == 1 + assert output[0].startswith("_MISSING: ") def test_non_c_generate_signature_with_kw_only_args(self) -> None: class TestClass: diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 75ec1aa8cf251..acb2227089561 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -74,19 +74,39 @@ def f(x=...) -> None: ... [case testDefaultArgSentinel] from typing_extensions import sentinel + _MISSING = sentinel('_MISSING') + def f(x=_MISSING): ... [out] def f(x=...) -> None: ... [case testDefaultArgSentinel_inspect] from typing_extensions import sentinel + _MISSING = sentinel('_MISSING') + def f(x=_MISSING): ... [out] +import typing_extensions +from _typeshed import Incomplete + +_MISSING: typing_extensions.sentinel +def f(x: Incomplete | _MISSING = ...): ... + +[case testDefaultArgSentinelCrossModule_inspect] +from other import MISSING + +def f(x=MISSING): ... +[file other.py] +from typing_extensions import sentinel + +MISSING = sentinel('MISSING') +[out] +import other from _typeshed import Incomplete -def f(x: Incomplete = ...): ... +def f(x: Incomplete | other.MISSING = ...): ... [case testPreserveFunctionAnnotation] def f(x: Foo) -> Bar: ... From 892c03dbff18bb8c7ac876e1a32997b73a82486d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 25 Sep 2026 10:19:47 -0600 Subject: [PATCH 4/8] Re-declare sentinel in stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- mypy/stubgenc.py | 17 ++++++++--------- mypy/test/teststubgen.py | 2 +- test-data/unit/stubgen.test | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 04bf84c995396..ff7d4e10fa277 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -963,15 +963,14 @@ def generate_variable_stub(self, name: str, obj: object, output: list[str]) -> N The result lines will be appended to 'output'. If necessary, any required names will be added to 'imports'. """ - if ( - # A sentinel's name is emitted regardless of privacy/`__all__`, since it may - # be referenced from the type of a default value elsewhere in the stub. - not _is_sentinel_object(obj) - and ( - self.is_private_name(name, f"{self.module_name}.{name}") - or self.is_not_in_all(name) - ) - ): + # A sentinel's name is emitted regardless of privacy/`__all__`, since it may + # be referenced from the type of a default value elsewhere in the stub. + if _is_sentinel_object(obj): + self.record_name(name) + self.add_import_line("import typing_extensions\n") + output.append(f"{name} = typing_extensions.sentinel('{name}')") + return + if self.is_private_name(name, f"{self.module_name}.{name}") or self.is_not_in_all(name): return self.record_name(name) type_str = self.strip_or_import(self.get_type_annotation(obj)) diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index 8b581e1091bcb..d06edd2264edb 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -1078,7 +1078,7 @@ def test_generate_variable_stub_sentinel_ignores_privacy(self) -> None: # A sentinel's declaration is always emitted, even for a private-looking # name, since it may be referenced from a default value's type elsewhere. assert len(output) == 1 - assert output[0].startswith("_MISSING: ") + assert output[0] == "_MISSING = typing_extensions.sentinel('_MISSING')" def test_non_c_generate_signature_with_kw_only_args(self) -> None: class TestClass: diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index acb2227089561..84201eb136179 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -91,7 +91,7 @@ def f(x=_MISSING): ... import typing_extensions from _typeshed import Incomplete -_MISSING: typing_extensions.sentinel +_MISSING = typing_extensions.sentinel('_MISSING') def f(x: Incomplete | _MISSING = ...): ... [case testDefaultArgSentinelCrossModule_inspect] From 49d5ce8049c86a2ed10b8231315593e2220aa8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 25 Sep 2026 14:01:45 -0600 Subject: [PATCH 5/8] Add sentinel to the stub annotation whenever is has a valid `__name__` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez-Mondragón --- mypy/stubgenc.py | 48 +++++++++++++++---------------------- test-data/unit/stubgen.test | 13 ++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index ff7d4e10fa277..ea2199466363f 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -217,14 +217,11 @@ def __get__(self) -> None: # noqa: PLE0302 def _is_sentinel_object(obj: object) -> bool: - """Is ``obj`` an instance of ``typing_extensions.sentinel()``/``Sentinel()``?""" + """Whether obj is a sentinel object.""" typ = type(obj) return typ.__module__ in ("builtins", "typing_extensions") and typ.__name__ in ( - "sentinel", - # typing_extensions 4.14.0-4.15.x named the class `Sentinel`, with no - # lowercase alias; the rename (and `Sentinel = sentinel` alias) landed - # in 4.16.0. - "Sentinel", + "sentinel", # 4.16+ + "Sentinel", # 4.14 - 4.15 ) @@ -335,17 +332,15 @@ def add_args( if default_value is not _Missing.VALUE: if arg in annotations: argtype = get_annotation(arg) - elif _is_sentinel_object(default_value): - # The runtime type of a `sentinel()` marker object is not a - # useful annotation for the parameter it defaults. Reference - # the sentinel itself by name when possible, e.g. - # `Incomplete | _MISSING`, importing it if it lives in - # another module. - incomplete = self.add_name("_typeshed.Incomplete") - sentinel_ref = self._sentinel_type_ref(default_value) - argtype = f"{incomplete} | {sentinel_ref}" if sentinel_ref else incomplete else: argtype = self.get_type_annotation(default_value) + if _is_sentinel_object(default_value): + # Reference the sentinel itself by name when possible, e.g. + # `Incomplete | _MISSING`. + incomplete = self.add_name("_typeshed.Incomplete") + ref = self._sentinel_type_ref(default_value) + argtype = f"{incomplete} | {ref}" if ref else incomplete + if argtype == "None": # None is not a useful annotation, but we can infer that the arg # is optional @@ -572,22 +567,17 @@ def get_type_annotation(self, obj: object) -> str: def _sentinel_type_ref(self, sentinel_obj: object) -> str | None: """Return a reference to the name a sentinel object is bound to. - ``sentinel()``/``Sentinel()`` objects record the module and name they - were assigned to (this is how they support pickling), so we can point - directly at them (importing from another module if needed), rather - than using their unhelpful runtime type. Returns None if we can't (e.g. - the name isn't a valid identifier). + The sentinel spec says __name__ and __module__ are always present, + typing-extensions 4.16+ implements these, and stubs always use the + latest typing-extensions. - A leading-underscore sentinel name is treated the same as a public - one: ``generate_variable_stub`` always emits sentinel declarations - regardless of privacy, since a sentinel used as a default is part of - the (typed) signature even when its name looks private. + - https://docs.python.org/3.15/builtins/functions.html#sentinel.__name__ + - https://docs.python.org/3.15/builtins/functions.html#sentinel.__module__ """ - name = getattr(sentinel_obj, "__name__", None) - module = getattr(sentinel_obj, "__module__", None) - if not isinstance(name, str) or not name.isidentifier() or not module: - return None - return f"{module}.{name}" + if (name := getattr(sentinel_obj, "__name__", None)) and name.isidentifier(): + return f"{sentinel_obj.__module__}.{name}" + + return None def is_function(self, obj: object) -> bool: if self.is_c_module: diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test index 84201eb136179..b4d8c7ec16977 100644 --- a/test-data/unit/stubgen.test +++ b/test-data/unit/stubgen.test @@ -108,6 +108,19 @@ from _typeshed import Incomplete def f(x: Incomplete | other.MISSING = ...): ... +[case testDefaultArgSentinelNotValidIdentifier_inspect] +from typing_extensions import sentinel + +_MISSING = sentinel('not an identifier') + +def f(x=_MISSING): ... +[out] +import typing_extensions +from _typeshed import Incomplete + +_MISSING = typing_extensions.sentinel('_MISSING') +def f(x: Incomplete = ...): ... + [case testPreserveFunctionAnnotation] def f(x: Foo) -> Bar: ... def g(x: Foo = Foo()) -> Bar: ... From 786dc62a330fa7e129c5ca424e4543a9650a508e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 25 Sep 2026 14:21:48 -0600 Subject: [PATCH 6/8] Trim down comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez-Mondragón --- mypy/stubgenc.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index ea2199466363f..8ea87b8a30001 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -553,13 +553,7 @@ def get_type_annotation(self, obj: object) -> str: elif isinstance(obj, ModuleType): return self.add_name("types.ModuleType", require=False) elif _is_sentinel_object(obj): - # On 3.15+, typing_extensions.sentinel is builtins.sentinel, so - # type(obj).__module__ is "builtins" there and "typing_extensions" - # otherwise. Always use the portable spelling instead of whichever - # one happens to match the interpreter running stubgen. Use the - # dotted form (not add_name) since a module constructing a - # sentinel will itself have already imported the bare `sentinel` - # name, which would otherwise force an alias here. + # Works across all supported Python versions return "typing_extensions.sentinel" else: return self.get_type_fullname(type(obj)) From d87772bda8cd4d0555e92787b97123e97bcb7faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 25 Sep 2026 14:49:01 -0600 Subject: [PATCH 7/8] Remove redundant test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez-Mondragón --- mypy/stubgenc.py | 3 +-- mypy/test/teststubgen.py | 14 -------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 8ea87b8a30001..39516556ef210 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -947,8 +947,7 @@ def generate_variable_stub(self, name: str, obj: object, output: list[str]) -> N The result lines will be appended to 'output'. If necessary, any required names will be added to 'imports'. """ - # A sentinel's name is emitted regardless of privacy/`__all__`, since it may - # be referenced from the type of a default value elsewhere in the stub. + # A sentinel's name is emitted regardless of privacy/`__all__`. if _is_sentinel_object(obj): self.record_name(name) self.add_import_line("import typing_extensions\n") diff --git a/mypy/test/teststubgen.py b/mypy/test/teststubgen.py index d06edd2264edb..b5cdbf479f1c2 100644 --- a/mypy/test/teststubgen.py +++ b/mypy/test/teststubgen.py @@ -1066,20 +1066,6 @@ def test(self, arg0=_MISSING) -> None: # type: ignore[no-untyped-def] ) assert_equal(output, ["def test(self, arg0: Incomplete | _MISSING = ...) -> None: ..."]) - def test_generate_variable_stub_sentinel_ignores_privacy(self) -> None: - from typing_extensions import sentinel - - _MISSING = sentinel("_MISSING") - mod = ModuleType(__name__, "") - gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod) - gen.is_c_module = False - output: list[str] = [] - gen.generate_variable_stub("_MISSING", _MISSING, output=output) - # A sentinel's declaration is always emitted, even for a private-looking - # name, since it may be referenced from a default value's type elsewhere. - assert len(output) == 1 - assert output[0] == "_MISSING = typing_extensions.sentinel('_MISSING')" - def test_non_c_generate_signature_with_kw_only_args(self) -> None: class TestClass: def test( From 59e72ebe84ce565b06c8769502d4b172f0521454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez-Mondrag=C3=B3n?= Date: Fri, 25 Sep 2026 14:51:56 -0600 Subject: [PATCH 8/8] Refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez-Mondragón --- mypy/stubgenc.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 39516556ef210..ba312481c51cc 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -332,15 +332,14 @@ def add_args( if default_value is not _Missing.VALUE: if arg in annotations: argtype = get_annotation(arg) + elif _is_sentinel_object(default_value): + # Reference the sentinel itself by name when possible, e.g. + # `Incomplete | _MISSING`. + incomplete = self.add_name("_typeshed.Incomplete") + ref = self._sentinel_type_ref(default_value) + argtype = f"{incomplete} | {ref}" if ref else incomplete else: argtype = self.get_type_annotation(default_value) - if _is_sentinel_object(default_value): - # Reference the sentinel itself by name when possible, e.g. - # `Incomplete | _MISSING`. - incomplete = self.add_name("_typeshed.Incomplete") - ref = self._sentinel_type_ref(default_value) - argtype = f"{incomplete} | {ref}" if ref else incomplete - if argtype == "None": # None is not a useful annotation, but we can infer that the arg # is optional