-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Current problem
Motivation
As Python type hints become widely adopted, it's increasingly common to see deeply nested or verbose type annotations across codebases, e.g.:
def f() -> tuple[list[dict], dict[str, Any]]:
...While technically valid, such annotations are difficult to read and maintain. A linting rule that flags excessive annotation complexity (e.g., nested generics beyond a certain depth) could help maintain readability and encourage usage of type aliases or TypedDict/helper types.
Desired solution
Proposal
Add an optional checker (e.g. annotation-complexity) to Pylint that:
- Inspects function return and parameter annotations
- Computes a complexity metric (for example, nesting depth of generics, or element count) for each annotation
- Emits a warning when the metric exceeds a configurable threshold (e.g.,
max-annotation-depth = 5) - Allows configuration in
.pylintrc(e.g., disable the check, adjust threshold, treat as error vs warning)
Example behavior
C9001 annotation-too-deep: Type annotation too deep (depth=7, max=5). Consider using a TypeAlias.
C9002 annotation-too-many-elements: Type annotation has too many elements (count=11, max=10). Consider using a TypeAlias.
I9001 annotation-needs-alias: Consider extracting this complex annotation into a TypeAlias for better readability.
On this code:
def process(
data: dict[str, dict[str, list[tuple[int, str]]]]
) -> dict[str, list[dict[str, Any]]]:
...If either annotation's metric exceeds the threshold, the checker warns.
Configuration Example
[MASTER]
load-plugins = pylint_annotation_complexity
[annotation-complexity]
max-annotation-depth = 5
max-annotation-elements = 10
suggest-type-alias = yesComplexity Metrics
Depth Metric - Measures maximum nesting level:
int→ depth 1list[int]→ depth 2dict[str, list[int]]→ depth 3tuple[list[dict], dict[str, Any]]→ depth 4
Element Count Metric - Counts total type references:
int→ 1 elementlist[int]→ 2 elementsdict[str, list[int]]→ 4 elementsUnion[int, str, float]→ 4 elements
I have a working prototype implementation that:
- Supports both depth and element count metrics
- Works with modern astroid (handles removal of
Indexnode) - Provides three message types (error for depth, error for element count, info for suggestions)
- Is fully configurable via
.pylintrc - Handles all annotation contexts (parameters, return types, variable annotations)
I'm happy to open a PR with the implementation if the maintainers are interested in this feature.
Additional context
Related checks in other linters
- flake8-annotations-complexity: Similar plugin for flake8
- mypy: Validates type correctness but not readability
In the issue tracker #3853 seems to be related but that check is related to require type checks, not checking their complexity