Skip to content

Commit 27e1417

Browse files
committed
Wrap UP007 target for multiline fixes
1 parent e6dc572 commit 27e1417

3 files changed

Lines changed: 55 additions & 6 deletions

File tree

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP007.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,17 @@ def myfunc(param: "tuple[Union[int, 'AClass', None], str]"):
139139
b_string_te_2: "UnionTE[NamedTupleTE, None]" = None
140140
b_string_typing_1: "typing.Union[typing.NamedTuple]" = None
141141
b_string_typing_2: "typing.Union[typing.NamedTuple, None]" = None
142+
143+
144+
# Regression test for https://github.com/astral-sh/ruff/issues/23207
145+
# Multi-line single-argument Union should be wrapped in parentheses
146+
from __future__ import annotations
147+
148+
if TYPE_CHECKING:
149+
from typing import Literal, TypeAlias
150+
151+
LongLiterals: TypeAlias = Union[
152+
Literal["LongLiteralNumberOne"]
153+
| Literal["LongLiteralNumberTwo"]
154+
| Literal["LongLiteralNumberThree"]
155+
]

crates/ruff_linter/src/rules/pyupgrade/rules/use_pep604_annotation.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use ruff_python_ast::PythonVersion;
33
use ruff_python_ast::helpers::{pep_604_optional, pep_604_union};
44
use ruff_python_ast::{self as ast, Expr};
55
use ruff_python_semantic::analyze::typing::{Pep604Operator, to_pep604_operator};
6+
use ruff_source_file::LineRanges;
67
use ruff_text_size::Ranged;
78

89
use crate::checkers::ast::Checker;
@@ -230,13 +231,22 @@ pub(crate) fn non_pep604_annotation(
230231
}
231232
_ => {
232233
// Single argument.
234+
let inner = checker.locator().slice(slice);
235+
let content = if checker
236+
.locator()
237+
.contains_line_break(slice.range())
238+
{
239+
// If the inner expression spans multiple lines, wrap in
240+
// parentheses since the `Union[...]` brackets that
241+
// previously provided implicit line continuation are being
242+
// removed.
243+
format!("({inner})")
244+
} else {
245+
inner.to_string()
246+
};
233247
diagnostic.set_fix(Fix::applicable_edit(
234248
Edit::range_replacement(
235-
pad(
236-
checker.locator().slice(slice).to_string(),
237-
expr.range(),
238-
checker.locator(),
239-
),
249+
pad(content, expr.range(), checker.locator()),
240250
expr.range(),
241251
),
242252
applicability,

crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP007.py.snap

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,29 @@ help: Convert to `X | Y`
342342
91 + def myfunc(param: "tuple[int | 'AClass' | None, str]"):
343343
92 | print(param)
344344
93 |
345-
94 |
345+
94 |
346+
347+
UP007 [*] Use `X | Y` for type annotations
348+
--> UP007.py:151:31
349+
|
350+
149 | from typing import Literal, TypeAlias
351+
150 |
352+
151 | LongLiterals: TypeAlias = Union[
353+
| _______________________________^
354+
152 | | Literal["LongLiteralNumberOne"]
355+
153 | | | Literal["LongLiteralNumberTwo"]
356+
154 | | | Literal["LongLiteralNumberThree"]
357+
155 | | ]
358+
| |_____^
359+
|
360+
help: Convert to `X | Y`
361+
148 | if TYPE_CHECKING:
362+
149 | from typing import Literal, TypeAlias
363+
150 |
364+
- LongLiterals: TypeAlias = Union[
365+
- Literal["LongLiteralNumberOne"]
366+
151 + LongLiterals: TypeAlias = (Literal["LongLiteralNumberOne"]
367+
152 | | Literal["LongLiteralNumberTwo"]
368+
- | Literal["LongLiteralNumberThree"]
369+
- ]
370+
153 + | Literal["LongLiteralNumberThree"])

0 commit comments

Comments
 (0)