Skip to content

ruff rule ANN201 missing-return-type-undocumented-public-function - #15296

Open
cclauss wants to merge 1 commit into
TheAlgorithms:masterfrom
cclauss:ruff-rule-ANN201-unsafe-fixes
Open

ruff rule ANN201 missing-return-type-undocumented-public-function#15296
cclauss wants to merge 1 commit into
TheAlgorithms:masterfrom
cclauss:ruff-rule-ANN201-unsafe-fixes

Conversation

@cclauss

@cclauss cclauss commented Sep 12, 2026

Copy link
Copy Markdown
Member

https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function

@dhruvmanila @priya-sundaram-dev, please review and help us understand how to identify which changes are unsafe.

% ruff check --select=ANN201 --statistics

619	ANN201	missing-return-type-undocumented-public-function
Found 619 errors.
No fixes available (260 hidden fixes can be enabled with the `--unsafe-fixes` option).

% ruff check --select=ANN201 --fix --unsafe-fixes --silent

% ruff check --select=ANN201 --statistics

359	ANN201	missing-return-type-undocumented-public-function
Found 359 errors.

% ruff rule ANN201

missing-return-type-undocumented-public-function (ANN201)

Derived from the flake8-annotations linter.

Fix is sometimes available.

What it does

Checks that public functions and methods have return type annotations.

Why is this bad?

Type annotations are a good way to document the return types of functions. They also
help catch bugs when used alongside a type checker by ensuring that the types of
any returned values, and the types expected by callers, match expectations.

Example

def add(a, b):
    return a + b

Use instead:

def add(a: int, b: int) -> int:
    return a + b

Availability

Because this rule relies on the third-party typing_extensions module for some Python versions,
its diagnostic will not be emitted, and no fix will be offered if typing_extensions imports
have been disabled by the [lint.typing-extensions] linter option.

Options

  • lint.typing-extensions

@cclauss
cclauss requested a review from dhruvmanila September 12, 2026 12:57
@cclauss cclauss added the require type hints https://docs.python.org/3/library/typing.html label Sep 12, 2026
@algorithms-keeper algorithms-keeper Bot added awaiting reviews This PR is ready to be reviewed enhancement This PR modified some existing files and removed require type hints https://docs.python.org/3/library/typing.html labels Sep 12, 2026
@cclauss cclauss added the require type hints https://docs.python.org/3/library/typing.html label Sep 12, 2026
@priya-sundaram-dev

Copy link
Copy Markdown
Contributor

Looked into this — the good news is that the ANN201 unsafe autofix is much narrower than "260 unknown changes." It only ever does one thing: add -> None, and only to functions where ruff can statically prove there is no value-returning return/yield. It deliberately leaves alone:

  • functions that return a value (it can't infer the type, so it won't guess), and
  • bodies that raise NotImplementedError / are pure stubs.

Quick repro (isolated, so no repo config interferes):

def implicit_none(x):        # -> gets `-> None`
    if x: print(x)
def bare_return(x):          # -> gets `-> None`
    if not x: return
    print(x)
def base_method(self):       # left ALONE (raises)
    raise NotImplementedError
def returns_value(x):        # left ALONE (returns a value)
    return x + 1
$ ruff check --isolated --select=ANN201 --fix --unsafe-fixes --diff
-def implicit_none(x):
+def implicit_none(x) -> None:
-def bare_return(x):
+def bare_return(x) -> None:
Would fix 2 errors.

So the ~260 fixes that take us 619 → 359 are all -> None additions, and the remaining 359 are genuine "returns a value, ruff won't guess the type" cases that need a human or a type checker.

Why ruff still marks the -> None fix "unsafe" (and where to be careful): it's not that the None inference is wrong — it's that -> None changes the public contract. The one class of real regressions is a base/overridable method: if def load(self): in a base class is currently unannotated and a subclass overrides it to return data, pinning the base to -> None makes the subclass Liskov-inconsistent and a type checker will now flag the subclass. Free functions and main()/CLI entrypoints are safe.

Suggested workflow to land this safely:

  1. ruff check --select=ANN201 --fix --unsafe-fixes to grab all the -> None additions in one branch.
  2. Run the type checker (mypy/ty) on that branch — any newly-added -> None that a checker still accepts on a leaf function is safe to keep.
  3. grep the diff for -> None added to methods (params start with self/cls) and spot-check the ones whose class is subclassed elsewhere — those are the only Liskov risks.
  4. For the remaining 359, split by top-level directory (data_structures/, graphs/, …) into reviewable PRs and annotate the real return types; the checker's inferred types make good starting points.

Happy to take a directory (e.g. data_structures/) as a reference PR if that helps set the pattern.

(Disclosure: I'm Priya Sundaram, an AI software agent; a human reviews my substantive work.)

@cclauss
cclauss requested a review from poyea September 12, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting reviews This PR is ready to be reviewed enhancement This PR modified some existing files require type hints https://docs.python.org/3/library/typing.html

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants