Skip to content

Fix stack overflow in ANN401 on quoted annotations with escape sequences#23912

Merged
amyreese merged 1 commit intoastral-sh:mainfrom
VedantMadane:fix/ann401-stack-overflow
Mar 12, 2026
Merged

Fix stack overflow in ANN401 on quoted annotations with escape sequences#23912
amyreese merged 1 commit intoastral-sh:mainfrom
VedantMadane:fix/ann401-stack-overflow

Conversation

@VedantMadane
Copy link
Contributor

Summary

Fixes #14695.

When a quoted annotation like "'in\x74'" is parsed as a complex annotation (because the raw content differs from the parsed content due to escape sequences), parse_complex_type_annotation produces an inner Expr::StringLiteral("int") and relocate_expr gives it the same text range as the outer annotation.

The ParsedAnnotationsCache is keyed by annotation.start(), so when TypingTarget::try_from_expr later encounters this inner StringLiteral and calls parse_type_annotation again, the cache returns the same result. This creates an infinite recursion path:

try_from_expr(StringLiteral) -> parse_type_annotation (cache hit) -> ForwardReference(StringLiteral) -> contains_any / contains_none -> try_from_expr(StringLiteral) -> ...

Fix

In try_from_expr, when a parsed forward reference resolves to another StringLiteral, return None instead of wrapping it as a ForwardReference. A string literal inside a string annotation (e.g. 'int' inside "'int'") is never a valid type -- it's a literal value, not a type name -- so there is no reason to recurse into it.

Test Plan

Added a regression test to annotation_presence.py with the exact reproducer from the issue (def f(x: "'in\x74'"): pass). The test previously caused a stack overflow and now correctly produces ANN401 + ANN201 diagnostics.

All existing flake8_annotations and implicit_optional tests continue to pass.

When a quoted annotation like `"'in\x74'"` is parsed as a complex
annotation, the inner string literal `'int'` gets the same text range
as the outer annotation (via `relocate_expr`). This causes the
`ParsedAnnotationsCache` (keyed by `start()` offset) to return the
same parsed result, creating infinite recursion in
`TypingTarget::try_from_expr` -> `ForwardReference` -> `contains_any`.

Break the cycle by detecting when a parsed forward reference resolves
to another string literal (which is never a valid type annotation) and
returning `None` instead of recursing.

Fixes astral-sh#14695
@astral-sh-bot astral-sh-bot bot requested a review from amyreese March 12, 2026 13:36
@amyreese amyreese added the bug Something isn't working label Mar 12, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 12, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@amyreese amyreese merged commit 3f94c6a into astral-sh:main Mar 12, 2026
44 checks passed
@amyreese
Copy link
Member

Thank you!

@amyreese amyreese changed the title Fix stack overflow in ANN401 on quoted annotations with escape sequences Fix stack overflow in ANN401 on quoted annotations with escape sequences Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ANN401 induces a stack overflow on quoted quoted escape sequences

2 participants