✕
Skip to content

Writing stubs: Various updates - #2387

Open
srittau wants to merge 2 commits into
python:mainfrom
srittau:incomplete-none
Open

srittau wants to merge 2 commits into
python:mainfrom
srittau:incomplete-none

Conversation

@srittau

@srittau srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator
  • Don't use deprecated pattern Incomplete | None = None.
  • Use Iterable instead of Sequence as invariant alternative to list. (Keep Mapping as alternative for dict.)
  • Remove "Don't use int | float. See Replace float with float | int typeshed#16059)

- Don't use deprecated pattern `Incomplete | None = None`.
- Use `Iterable` instead of `Sequence` as invariant alternative to
  `list`. (Keep `Mapping` as alternative for `dict`.)
- Remove "Don't use `int | float`.
  See python/typeshed#16059)
@jorenham

jorenham commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Don't use deprecated pattern Incomplete | None = None.

Could you elaborate?

@srittau

srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

x: Incomplete | None = None is functionally equivalent to x=None, which is why we use the latter nowadays in typeshed. This prevents the argument to be flagged as "annotated" when it really isn't and is more readable. See also https://github.com/PyCQA/flake8-pyi/blob/main/ERRORCODES.md#Y067 and python/typeshed#14029.

@srittau

srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Actually not functionally equivalent, but worse, since Incomplete = Any, so x: Incomplete | None = x: Any | None, while x=None = x: Unknown | None.

@jorenham

jorenham commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

since Incomplete = Any, so x: Incomplete | None = x: Any | None, while x=None = x: Unknown | None.

This is what Pyright does, but I'm pretty sure this inference behavior isn't defined the typing spec.

@srittau

srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Sure, other type checkers might handle it differently, but avoiding Incomplete (i.e. an explicit Any) and an unnecessary annotation is still worthwhile, which is why we've been doing it in typeshed for 1.5 years now and added a check to flake8-pyi.

@jorenham

jorenham commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

My point is that we shouldn't advice using def incomplete2(z=None) -> None: ... because that relies on undefined behavior.

@srittau

srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

I disagree. This is the style section of a stub writing guide that mostly serves to reflect typeshed's best practices. Using x: Incomplete | None = None is not a best practice and actively discouraged for the reasons given. It uses the same undefined behavior as other items that are left unannotated, and we explicitly recommend to leave items unannotated.

@jorenham

jorenham commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

I guess I then disagree with typeshed's best practices then. Because in my view, it's always better to explicitly annotate something and follow the typing spec, than to rely on the particular design choice of certain type-checkers that's undefined in the spec.

Comment on lines +650 to +661
*Exception:* If ``X`` is ``Incomplete`` and has a ``None`` default, leave the
argument unannotated.

Yes::

def incomplete1(z: Incomplete | None) -> None: ...
def incomplete2(z=None) -> None: ...

No::

def incomplete(z: Incomplete | None = None): ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with this, see #2387 (comment). So can we leave this out? The other bits I'm fine with, so if you want we can discuss this further in a separate PR for this part.

@AlexWaygood

AlexWaygood commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

I feel like it's basically a de-facto standard at this point to treat an unannotated parameter as semantically distinct from a parameter annotated with Any (or an alias to Any), even if they are ultimately understood by the type checker as signifying that any value can be passed as an argument to the parameter without the call erroring. Mypy/zuban both emit no-untyped-def for the following snippet, pyright emits reportUnknownParameterType, pyrefly emits implicit-any-parameter, and ty will (after astral-sh/ruff#29917 is merged) complain with missing-parameter-annotation. Ruff also has rules that complain about missing parameter annotations. (Note that in all cases, these rules are disabled by default, and must be explicitly selected in configuration or similar.)

In all these cases, the type checker or linter is trying to alert you to the fact that your type hints are incomplete. But, perversely, explicitly annotating something with Incomplete silences all of these type checkers/linters: they no longer detect that your type hints are incomplete, giving you a misleading impression about the state of the type hints in your project.

@jorenham

jorenham commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Standard or not, with the typing spec living next door, I still think we should not advice users to rely on undefined behavior, especially not if there are viable alternatives that are supported by the spec.

@jorenham

jorenham commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

And to be clear: I have no problem with typeshed using this (or anyone else for that matter). I actually agree that it's more honest to not annotate something instead of using Incomplete or something. And it's no secret that I go off-piste in the stubs I maintain myself. It's just that I don't think we should officially recommend doing this (or at least not until it's specified in the spec); that's all.

@srittau

srittau commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

x=None is as much "undefined behavior" as every other unannotated item. Ultimately that means that we should remove the recommendation to leave items unannotated, which does not sound right at all.

@jorenham

jorenham commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Ultimately that means that we should remove the recommendation to leave items unannotated, which does not sound right at all.

I actually think we should indeed not recommend that :p.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants