[pyupgrade] Apply UP045 to string arguments of typing.cast#22320
Conversation
|
| 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 |
There was a problem hiding this comment.
Can we add some tests for "Complex" string annotations (an annotation that uses implicit string concatenation).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yeah, this is a bit surprising. Let me know if you want me to take a closer look
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
What you have here seems better than what we had before and should cover the majority of stringified type annotations
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Let's add a TODO comment here to make it clear, that the rule isn't currently handling implicitly concatenated strings.
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.
Summary
Fixes #20096 by applying
UP045to 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
Optionalarguments intyping.castcalls because these aren't annotations. This PR just augments thein_annotationcheck toin_annotation || in_string_type_definition.This means that
UP045will also apply to other string type definitions like the rhs in:which I think is also okay.
Test Plan
New tests based on the issue