-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Call chain formatting in fluent style #6151
Conversation
Current dependencies on/for this PR: This comment was auto-generated by Graphite. |
PR Check ResultsBenchmarkLinux
Windows
|
9bcc65a
to
202c7f0
Compare
Blocked on unstable formatting of y = (
x.a() #
.b()
)
y = x.a().b() #
y = (
x.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa() #
.b()
)
y = x.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa().b() # |
ee69abe
to
ca3ca2a
Compare
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.
Woah, nice improvement on the compatibility!
The fluent style formatting now requires to route through the fluent style in many positions (which I like more than setting in on context).
Have you considered to, instead "unroll" the call chain in the CallExpression
formatting? Meaning, we would have a single formatting that owns the whole call chain formatting without calling into format attribute and format subscript (maybe parts of it). I'm asking because I find it difficult to "unroll" the recursion in my head and wonder if it would be easier if the whole call chain formatting would be in its own file.
|
||
/// Switch call chain and attribute formatting to fluent style. This is otherwise identical to | ||
/// `Never`, fluent style implies a set of outer parentheses | ||
FluentStyle { outermost: bool }, |
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.
FluentStyle
doesn't fit well into the Parentheses
concept, which is intended to be generally applicable to all expressions.
c3801b2
to
58fc27c
Compare
Good point, i made fluent style a bool that gets passed through the call chain formatting. It's still recursive but i think that's better than unrolling |
This took some rotations but now it's just a |
Could you explain your reasoning of why? |
I think consistency, mainly: Every other expression is formatted from outermost to innermost, so this is no difference. The main thing we do differently is to put a newline between the closing parentheses and the dot. |
// Nested expressions that are in some outer expression's parentheses may be formatted in | ||
// fluent style | ||
let fluent_style = f.context().node_level() == NodeLevel::ParenthesizedExpression | ||
&& is_fluent_style_call_chain(expression, f.context().source()); |
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.
Is this still necessary here? It seems that Call
, Attribute
and Subscript
now by-pass the Expr
formatting.
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.
I spent way to much time trying to come up with something better, but i'm afraid the answer is yes. I think the node level is the biggest problem here because e.g. just inlining helpers doesn't seem to work.
Expr::Attribute(expr) => { | ||
return parenthesize_if_expands(&group(&expr.format().with_options(true))) | ||
.fmt(f) | ||
} | ||
Expr::Call(expr) => { | ||
return parenthesize_if_expands(&group(&expr.format().with_options(true))) | ||
.fmt(f) | ||
} | ||
Expr::Subscript(expr) => { | ||
return parenthesize_if_expands(&group(&expr.format().with_options(true))) | ||
.fmt(f) | ||
} |
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.
Would it be possible to remove the code from here if:
- Replace the
boolean
option with an enum that has three variants:Fluent
NonFluent
: Left side of a call chain, but we don't use the fluent styleRoot
: The root of a fluent chain (default)
CallExpression
tests if it is inside of a fluent chain if the variant isRoot
. (or any node that can be the end of a call chain). If so, useFluent
, otherwiseNonFluent
Subscript
,Call
, orAttribute
wrap their content in agroup
if the layout isFluent
.- Change
can_omit_parentheses
to returnfalse
for call chains.
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.
I don't think i really follow; Call chains can appear about everywhere except on statement level. For the MaybeParenthesize it's special because we need to switch parentheses mode, call chain formatting always has parentheses when breaking.
Change can_omit_parentheses to return false for call chains.
This is already the case.
Unfortunately I don't understand why exactly the current solution works but the alternatives i tried don't, the interaction of node level, context, parentheses, MaybeParenthesize and FormatExpr or not is still opaque to me.
} | ||
_ => { | ||
// We to format the following in fluent style: `f2 = (a).w().t(1,)` | ||
if is_expression_parenthesized(AnyNodeRef::from(expr), source) { |
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.
is_expression_parenthesized
returns false positives if it is the first argument in a call expression. I guess this is safe because the expression never has attributes_after_parentheses
that is larger than 1.
Co-authored-by: Micha Reiser <[email protected]>
oh i didn't turn of automerge; it should be fine anyway, i'll do a follow-up PR if anything comes up |
This looks good to me. Does this implementation handle the case you shared with me earlier correctly where it must preserve parentheses around members of the call chain? a = (
b().c(
"asdfasfaefinitive Guidddddde to Django: Web Developfdddddddment Done Rdddight"
)
).d()
a = (
b()
.c("asdfasfaefinitive Guidddddde to Django: Web Developfdddddddment Done Rdddight")
.d()
) |
yep it does, it's and |
Implement fluent style/call chains. See the
call_chains.py
formatting for examples.This isn't fully like black because in
raise A from B
they allowA
breaking can influence the formatting ofB
even if it is already multiline.Similarity index:
Call chain formatting is affected by #627, but i'm cutting scope here.
Closes #5343
Test Plan: