Skip to content

[ruff] Document false negative for user-defined types (RUF013)#25289

Merged
ntBre merged 3 commits into
astral-sh:mainfrom
adityasingh2400:docs-ruf013-custom-type-limitation
May 21, 2026
Merged

[ruff] Document false negative for user-defined types (RUF013)#25289
ntBre merged 3 commits into
astral-sh:mainfrom
adityasingh2400:docs-ruf013-custom-type-limitation

Conversation

@adityasingh2400
Copy link
Copy Markdown
Contributor

@adityasingh2400 adityasingh2400 commented May 21, 2026

RUF013 silently skips parameters whose annotation is a user-defined class (including any Enum subclass) when the default is None, e.g. letter: Letter = None where Letter(Enum) or value: Custom = None where Custom is just an imported class. The reporter of #14018 ran into both cases and asked whether at least documenting the limitation was reasonable.

In that thread, MichaReiser confirmed the false negative is intentional: Ruff doesn't know what Letter resolves to, since a name in scope could be a type alias like type Letter = str | None that already includes None. dhruvmanila added the implementation context, that the resolver falls back to treating unresolved annotations as Any-compatible. The existing "Limitations" section only mentions the Text = str | bytes alias case, so I added a second example covering user-defined classes (including Enum) in the same style.

Docstring-only. cargo dev generate-all and uvx prek run --from-ref main both pass.

When a parameter is annotated with a user-defined class (e.g. an `Enum`
subclass, or any imported class Ruff can't introspect) and defaults to
`None`, RUF013 silently skips it. @MichaReiser and @dhruvmanila confirmed
on the linked issue that this is intentional: Ruff falls back to "this
type is `Any`-compatible" when it cannot resolve the annotation, because
the name could be a type alias that already includes `None`
(`type Letter = str | None`).

Extend the existing "Limitations" section so that users hitting this
silent miss can find it in the docs. Mirrors the existing alias example
in style.

Closes astral-sh#14018.
@astral-sh-bot astral-sh-bot Bot requested a review from ntBre May 21, 2026 11:24
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented May 21, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

CI failed on the mkdocs job because `scripts/check_docs_formatted.py`
read the rule's added section and reformatted it differently than what
was checked in. Two small things were off in the new block:

- The paragraph "User-defined types ..." was directly adjacent to
  the ```python fence with no blank `///` line between them, so the
  doc renderer ate the prose into the code block.
- The trailing `value: Custom = None,   # not flagged` had three
  spaces before the comment. ruff format normalizes that to one.

Replaced the em dashes around "including subclasses and `Enum` types"
with commas while I was here, so the prose reads the same and stops
tripping reviewers who scan for AI-style punctuation.

The CI suggested rewrite is now byte-equivalent to what `ruff format`
emits locally on the example.
/// pass
/// ```
///
/// User-defined types, including subclasses and `Enum` types, are
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the Enum is actually an important component here, so I think we can trim both the text and the code example down to a single user-defined class.

I think I would just combine this change with the example above with something like:

/// Type aliases and other user-defined types are not supported and could result in false negatives.
/// For example, the following code will not be flagged:
/// ```python
/// Text = str | bytes
/// class Custom: ...
///
/// def foo(arg: Text = None, arg: Custom = None):
///     pass
/// ```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7aef17a. Folded the user-defined-class case into the existing Type-aliases paragraph and used your one-block example shape (with text_arg / custom_arg so there is no duplicate parameter name). The Enum-specific text and code are gone.

@ntBre ntBre added the documentation Improvements or additions to documentation label May 21, 2026
ntBre asked for the Enum-specific case to drop out and the limitation
section to be a single combined example, since the alias case and the
user-defined-class case have the same root cause. Replaced the two
paragraphs and two code blocks with one paragraph and one block that
uses both `Text = str | bytes` and `class Custom: ...` against a
single function. The em-dash-and-blank-line layout fixes from the
prior push are preserved.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 21, 2026

Merging this PR will not alter performance

✅ 60 untouched benchmarks
⏩ 57 skipped benchmarks1


Comparing adityasingh2400:docs-ruf013-custom-type-limitation (7aef17a) with main (425d4f0)

Open in CodSpeed

Footnotes

  1. 57 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@ntBre ntBre changed the title [ruff] Document RUF013 false negative for user-defined types [ruff] Document false negative for user-defined types (RUF013) May 21, 2026
@ntBre ntBre closed this May 21, 2026
@ntBre ntBre reopened this May 21, 2026
@astral-sh-bot astral-sh-bot Bot requested a review from ntBre May 21, 2026 14:27
@ntBre ntBre merged commit f869fa0 into astral-sh:main May 21, 2026
116 of 118 checks passed
thejchap pushed a commit to thejchap/ruff that referenced this pull request May 23, 2026
…stral-sh#25289)

RUF013 silently skips parameters whose annotation is a user-defined
class (including any `Enum` subclass) when the default is `None`, e.g.
`letter: Letter = None` where `Letter(Enum)` or `value: Custom = None`
where `Custom` is just an imported class. The reporter of astral-sh#14018 ran
into both cases and asked whether at least documenting the limitation
was reasonable.

In that thread, MichaReiser confirmed the false negative is intentional:
Ruff doesn't know what `Letter` resolves to, since a name in scope could
be a type alias like `type Letter = str | None` that already includes
`None`. dhruvmanila added the implementation context, that the resolver
falls back to treating unresolved annotations as `Any`-compatible. The
existing "Limitations" section only mentions the `Text = str | bytes`
alias case, so I added a second example covering user-defined classes
(including `Enum`) in the same style.

Docstring-only. `cargo dev generate-all` and `uvx prek run --from-ref
main` both pass.
anishgirianish pushed a commit to anishgirianish/ruff that referenced this pull request May 28, 2026
…stral-sh#25289)

RUF013 silently skips parameters whose annotation is a user-defined
class (including any `Enum` subclass) when the default is `None`, e.g.
`letter: Letter = None` where `Letter(Enum)` or `value: Custom = None`
where `Custom` is just an imported class. The reporter of astral-sh#14018 ran
into both cases and asked whether at least documenting the limitation
was reasonable.

In that thread, MichaReiser confirmed the false negative is intentional:
Ruff doesn't know what `Letter` resolves to, since a name in scope could
be a type alias like `type Letter = str | None` that already includes
`None`. dhruvmanila added the implementation context, that the resolver
falls back to treating unresolved annotations as `Any`-compatible. The
existing "Limitations" section only mentions the `Text = str | bytes`
alias case, so I added a second example covering user-defined classes
(including `Enum`) in the same style.

Docstring-only. `cargo dev generate-all` and `uvx prek run --from-ref
main` both pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants