Fix stack overflow in ANN401 on quoted annotations with escape sequences#23912
Merged
amyreese merged 1 commit intoastral-sh:mainfrom Mar 12, 2026
Merged
Conversation
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
amyreese
approved these changes
Mar 12, 2026
|
Member
|
Thank you! |
ANN401 on quoted annotations with escape sequences
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_annotationproduces an innerExpr::StringLiteral("int")andrelocate_exprgives it the same text range as the outer annotation.The
ParsedAnnotationsCacheis keyed byannotation.start(), so whenTypingTarget::try_from_exprlater encounters this innerStringLiteraland callsparse_type_annotationagain, 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 anotherStringLiteral, returnNoneinstead of wrapping it as aForwardReference. 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.pywith the exact reproducer from the issue (def f(x: "'in\x74'"): pass). The test previously caused a stack overflow and now correctly producesANN401+ANN201diagnostics.All existing
flake8_annotationsandimplicit_optionaltests continue to pass.