mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +00:00
Allow return type annotations to use their own parentheses (#6436)
## Summary This PR modifies our logic for wrapping return type annotations. Previously, we _always_ wrapped the annotation in parentheses if it expanded; however, Black only exhibits this behavior when the function parameters is empty (i.e., it doesn't and can't break). In other cases, it uses the normal parenthesization rules, allowing nodes to bring their own parentheses. For example, given: ```python def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ... def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(x) -> Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ... ``` Black will format as: ```python def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ] ): ... def xxxxxxxxxxxxxxxxxxxxxxxxxxxx( x, ) -> Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ... ``` Whereas, prior to this PR, Ruff would format as: ```python def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ] ): ... def xxxxxxxxxxxxxxxxxxxxxxxxxxxx( x, ) -> ( Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ] ): ... ``` Closes https://github.com/astral-sh/ruff/issues/6431. ## Test Plan Before: - `zulip`: 0.99702 - `django`: 0.99784 - `warehouse`: 0.99585 - `build`: 0.75623 - `transformers`: 0.99470 - `cpython`: 0.75988 - `typeshed`: 0.74853 After: - `zulip`: 0.99724 - `django`: 0.99791 - `warehouse`: 0.99586 - `build`: 0.75623 - `transformers`: 0.99474 - `cpython`: 0.75956 - `typeshed`: 0.74857
This commit is contained in:
parent
d616c9b870
commit
53246b725e
8 changed files with 814 additions and 294 deletions
|
@ -1,9 +1,10 @@
|
|||
use ruff_formatter::write;
|
||||
use ruff_python_ast::{Ranged, StmtFunctionDef};
|
||||
use ruff_python_trivia::lines_after_ignoring_trivia;
|
||||
use ruff_python_ast::{Parameters, Ranged, StmtFunctionDef};
|
||||
use ruff_python_trivia::{lines_after_ignoring_trivia, SimpleTokenKind, SimpleTokenizer};
|
||||
|
||||
use crate::comments::{leading_comments, trailing_comments};
|
||||
use crate::expression::parentheses::{optional_parentheses, Parentheses};
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::{Parentheses, Parenthesize};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::suite::SuiteKind;
|
||||
use crate::FormatNodeRule;
|
||||
|
@ -60,18 +61,71 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
|||
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
write!(f, [item.parameters.format()])?;
|
||||
|
||||
if let Some(return_annotation) = item.returns.as_ref() {
|
||||
write!(f, [space(), text("->"), space()])?;
|
||||
|
||||
if return_annotation.is_tuple_expr() {
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(Parentheses::Never)]
|
||||
)?;
|
||||
} else if comments.has_trailing_comments(return_annotation.as_ref()) {
|
||||
// Intentionally parenthesize any return annotations with trailing comments.
|
||||
// This avoids an instability in cases like:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> (
|
||||
// int # Hello
|
||||
// ):
|
||||
// pass
|
||||
// ```
|
||||
// If we allow this to break, it will be formatted as follows:
|
||||
// ```python
|
||||
// def double(
|
||||
// a: int
|
||||
// ) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// On subsequent formats, the `# Hello` will be interpreted as a dangling
|
||||
// comment on a function, yielding:
|
||||
// ```python
|
||||
// def double(a: int) -> int: # Hello
|
||||
// pass
|
||||
// ```
|
||||
// Ideally, we'd reach that final formatting in a single pass, but doing so
|
||||
// requires that the parent be aware of how the child is formatted, which
|
||||
// is challenging. As a compromise, we break those expressions to avoid an
|
||||
// instability.
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(Parentheses::Always)]
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[optional_parentheses(
|
||||
&return_annotation.format().with_options(Parentheses::Never),
|
||||
[maybe_parenthesize_expression(
|
||||
return_annotation,
|
||||
item,
|
||||
if empty_parameters(&item.parameters, f.context().source()) {
|
||||
// If the parameters are empty, add parentheses if the return annotation
|
||||
// breaks at all.
|
||||
Parenthesize::IfBreaksOrIfRequired
|
||||
} else {
|
||||
// Otherwise, use our normal rules for parentheses, which allows us to break
|
||||
// like:
|
||||
// ```python
|
||||
// def f(
|
||||
// x,
|
||||
// ) -> Tuple[
|
||||
// int,
|
||||
// int,
|
||||
// ]:
|
||||
// ...
|
||||
// ```
|
||||
Parenthesize::IfBreaks
|
||||
},
|
||||
)]
|
||||
)?;
|
||||
}
|
||||
|
@ -100,3 +154,25 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if [`Parameters`] is empty (no parameters, no comments, etc.).
|
||||
fn empty_parameters(parameters: &Parameters, source: &str) -> bool {
|
||||
let mut tokenizer = SimpleTokenizer::new(source, parameters.range())
|
||||
.filter(|token| !matches!(token.kind, SimpleTokenKind::Whitespace));
|
||||
|
||||
let Some(lpar) = tokenizer.next() else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(lpar.kind, SimpleTokenKind::LParen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(rpar) = tokenizer.next() else {
|
||||
return false;
|
||||
};
|
||||
if !matches!(rpar.kind, SimpleTokenKind::RParen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue