Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[pyupgrade] Apply UP045 to string arguments of typing.cast#22320

Merged
ntBre merged 7 commits intomainfrom
brent/up045
Jan 22, 2026
Merged

[pyupgrade] Apply UP045 to string arguments of typing.cast#22320
ntBre merged 7 commits intomainfrom
brent/up045

Conversation

@ntBre
Copy link
Contributor

@ntBre ntBre commented Dec 31, 2025

Summary

Fixes #20096 by applying UP045 to string type definitions as well as annotations.

This is kind of a niche issue, especially now that Python 3.9 has reached end-of-life, but UP045 was not applying to string Optional arguments in typing.cast calls because these aren't annotations. This PR just augments the in_annotation check to in_annotation || in_string_type_definition.

This means that UP045 will also apply to other string type definitions like the rhs in:

from __future__ import annotations

from typing_extensions import TypeAlias

x: TypeAlias = "Optional[str]"

which I think is also okay.

Test Plan

New tests based on the issue

@ntBre ntBre added the bug Something isn't working label Dec 31, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Dec 31, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@ntBre ntBre marked this pull request as ready for review December 31, 2025 22:56
Comment on lines +10 to +15
x: Optional[str] # UP045
x: "Optional[str]" # UP045
cast("Optional[str]", None) # UP045
cast(Optional[str], None) # okay, str | None is a runtime error
x: TypeAlias = "Optional[str]" # UP045
x: TypeAlias = Optional[str] # okay
Copy link
Member

Choose a reason for hiding this comment

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

Can we add some tests for "Complex" string annotations (an annotation that uses implicit string concatenation).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interestingly, the complex cases don't emit diagnostics even with my change. Claude thinks this is a bug, but I'll have to dig into it a bit more.

ty also emits an implicit-concatenated-string-type-annotation diagnostic on the new test cases, so I could possibly just replace in_string_type_definition with in_simple_string_type_definition if we want to filter out the complex cases intentionally.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is a bit surprising. Let me know if you want me to take a closer look

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'll just close this for now, unless you think it's worth landing without looking into the (arguably separate) issue with concatenated annotations. This is a pretty niche issue in any case.

Copy link
Member

Choose a reason for hiding this comment

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

What you have here seems better than what we had before and should cover the majority of stringified type annotations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I only just realized that my comment probably made it sound like I had left a bug in the PR. When Claude and I looked into it, the bug appears to be upstream of the rule itself in our complex string annotation parsing, or at least in how that parsing interacts with our semantic model. I think the rule itself is behaving correctly with the annotations it's asked to check.

Thanks for taking another look!

x: TypeAlias = "Optional[str]" # UP045
x: TypeAlias = Optional[str] # okay

# complex (implicitly concatenated) annotations
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a TODO comment here to make it clear, that the rule isn't currently handling implicitly concatenated strings.

ntBre added 2 commits January 22, 2026 09:09
I'm not sure how accurate this is, but it sounds fairly plausible and
potentially worth recording. This is what Claude said when asked to diagnose the
issue:

>  Simple strings use parse_string_annotation → parse_expression_range which:
>  - Takes the full source code
>  - Properly offsets each parsed node to be at the correct position within the source file
>  - Results in unique, accurate ranges for each node (e.g., 200..208 for Optional, 209..212 for str)
>
>  Complex strings use parse_complex_type_annotation which:
>  - Parses just the concatenated string content: string_expr.value.to_str() (just "Optional[str]" without source context)
>  - Calls relocate_expr(parsed.expr_mut(), string_expr.range())
>  - relocate_expr sets ALL nodes to the SAME range (the entire concatenated expression range)
>  - Results in every node having identical ranges: 472..508 for everything
>
>  This is indeed a bug. The relocate_expr approach breaks symbol resolution because:
>
>  1. The semantic model likely uses ranges to look up bindings
>  2. When all nodes have the same range, it can't differentiate between Optional and str
>  3. This causes resolve_qualified_name to fail, returning ["", "Optional"] instead of ["typing", "Optional"]
>
>  The comment in the code says complex strings "cannot be accurately located" - but that should only prevent fixes from being applied, not prevent diagnostics from being emitted. Both simple and complex annotations have access to the same imports.

so I think it's pointing to the `relocate_expr` call in
`parse_complex_type_annotation` in `crates/ruff_python_parser/src/typing.rs`.
I'm also not sure that we can do much better when parsing the annotation, but we
may be able to work around this later in the process anyway.
@ntBre ntBre merged commit b912dfc into main Jan 22, 2026
103 of 106 checks passed
@ntBre ntBre deleted the brent/up045 branch January 22, 2026 15:15
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.

UP045 doesn't trigger inside of cast

2 participants