-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Format ExprYield
/ExprYieldFrom
#5921
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice work
PR Check ResultsBenchmarkLinux
Windows
|
The cpython check failed on def a():
with (yield):
pass Seem like yield can appear everywhere, even in e.g. |
Interesting. I've expanded the test cases with a bunch more examples, and inverted the You mention |
if parent.is_stmt_return() || parent.is_expr_await() { | ||
OptionalParentheses::Always | ||
} else { | ||
if parent.is_stmt_assign() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should get a longer explanation about how only assignment right hand side and yield statements don't need parentheses and the special casing in the grammar, but we only need to handle assignment RHS here because StmtExpr doesn't add parentheses anyway (i just had to check that last part myself)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, added a comment describing the situation for both expr_yield
and expr_yield_from
. Thanks for the explanation in #5921 (comment) as well, helps me get a clearer image of how the formatter is structured and what to watch out for.
For def f():
yield 1 you get (simplified for readability) impl FormatNodeRule<StmtExpr> for FormatStmtExpr {
fn fmt_fields(&self, item: &StmtExpr, f: &mut PyFormatter) -> FormatResult<()> {
let StmtExpr { value, .. } = item;
if is_arithmetic_like(value) {
maybe_parenthesize_expression(value, item, Parenthesize::Optional).fmt(f)
} else {
value.format().fmt(f)
}
}
} so this doesn't become def f():
(yield 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome. We may want to consider unifying the implementation in a follow up PR similar to AnyStmtWith
(it could also implement NeedsParentheses
so that we have all the logic in a single place).
ruff/crates/ruff_python_formatter/src/statement/stmt_with.rs
Lines 15 to 114 in 0301889
pub(super) enum AnyStatementWith<'a> { | |
With(&'a StmtWith), | |
AsyncWith(&'a StmtAsyncWith), | |
} | |
impl<'a> AnyStatementWith<'a> { | |
const fn is_async(&self) -> bool { | |
matches!(self, AnyStatementWith::AsyncWith(_)) | |
} | |
fn items(&self) -> &[WithItem] { | |
match self { | |
AnyStatementWith::With(with) => with.items.as_slice(), | |
AnyStatementWith::AsyncWith(with) => with.items.as_slice(), | |
} | |
} | |
fn body(&self) -> &Suite { | |
match self { | |
AnyStatementWith::With(with) => &with.body, | |
AnyStatementWith::AsyncWith(with) => &with.body, | |
} | |
} | |
} | |
impl Ranged for AnyStatementWith<'_> { | |
fn range(&self) -> TextRange { | |
match self { | |
AnyStatementWith::With(with) => with.range(), | |
AnyStatementWith::AsyncWith(with) => with.range(), | |
} | |
} | |
} | |
impl<'a> From<&'a StmtWith> for AnyStatementWith<'a> { | |
fn from(value: &'a StmtWith) -> Self { | |
AnyStatementWith::With(value) | |
} | |
} | |
impl<'a> From<&'a StmtAsyncWith> for AnyStatementWith<'a> { | |
fn from(value: &'a StmtAsyncWith) -> Self { | |
AnyStatementWith::AsyncWith(value) | |
} | |
} | |
impl<'a> From<&AnyStatementWith<'a>> for AnyNodeRef<'a> { | |
fn from(value: &AnyStatementWith<'a>) -> Self { | |
match value { | |
AnyStatementWith::With(with) => AnyNodeRef::StmtWith(with), | |
AnyStatementWith::AsyncWith(with) => AnyNodeRef::StmtAsyncWith(with), | |
} | |
} | |
} | |
impl Format<PyFormatContext<'_>> for AnyStatementWith<'_> { | |
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> { | |
let comments = f.context().comments().clone(); | |
let dangling_comments = comments.dangling_comments(self); | |
write!( | |
f, | |
[ | |
self.is_async() | |
.then_some(format_args![text("async"), space()]), | |
text("with"), | |
space() | |
] | |
)?; | |
if are_with_items_parenthesized(self, f.context())? { | |
optional_parentheses(&format_with(|f| { | |
let mut joiner = f.join_comma_separated(self.body().first().unwrap().start()); | |
for item in self.items() { | |
joiner.entry_with_line_separator( | |
item, | |
&item.format(), | |
in_parentheses_only_soft_line_break_or_space(), | |
); | |
} | |
joiner.finish() | |
})) | |
.fmt(f)?; | |
} else { | |
f.join_with(format_args![text(","), space()]) | |
.entries(self.items().iter().formatted()) | |
.finish()?; | |
} | |
write!( | |
f, | |
[ | |
text(":"), | |
trailing_comments(dangling_comments), | |
block_indent(&self.body().format()) | |
] | |
) | |
} | |
} |
Good idea @MichaReiser. I'll finish up my outstanding PRs, then move to work on that. |
Summary
Formats:
yield x
yield from x
expressions
Test Plan
Added fixtures for both expressions, ran black_comparison.
NOTE: This is my first formatter PR. From what I gather there are many many ways that interesting combinations of comments and parentheses can lead to unexpected results. I will try and run the formatter against some large codebases to see if any unexpected results pop up, but figured I'd open it for review in the meantime
Issue link:
Closes: #5916