-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Lint precedence possible ambiguity between closure and method call #14421
base: master
Are you sure you want to change the base?
Conversation
r? @Alexendoo rustbot has assigned @Alexendoo. Use |
tests/ui/precedence.fixed
Outdated
} | ||
|
||
fn closure_method_call() { | ||
let f = move |x: W| { { x }.clone() }; |
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 part of the lint would conflict with the following lint for which lang FCP has been proposed:
Probably what I'd suggest instead is annotating the return type to make the outer block braces required:
let f = move |x: W| { { x }.clone() }; | |
let f = move |x: W| -> _ { { x }.clone() }; |
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.
As rust-lang/rust#136906 (comment) mentions rustfmt
will remove the extra braces from |x: W| { { x }.clone() }
so we definitely can't suggest that
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.
let f = (move |x: W| -> _ { x }).clone(); | ||
assert!(matches!(f(W(0)), W(0))); | ||
//~^^ precedence |
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.
let f = (move |x: W| -> _ { x }).clone(); | |
assert!(matches!(f(W(0)), W(0))); | |
//~^^ precedence |
I'd prefer we not do this one. In my model, the one that's "wrong" is the |x| { .. }.something()
case, because it's reasonable to assume that opening brace triggers the parsing of a block, when it doesn't. This one, by contrast, does the "right" thing, and with the annotated return type there, which then requires those braces, I think it's reasonably clear to the reader that the closure body must terminate with the closing brace.
That is, by linting against the one that's "wrong", as above, and suggesting the return type be annotated to make it unambiguous, probably we've solved the problem.
There's also a practical reason I don't want us pushing the parens here. I'm interested in us later implementing IntoFuture
for thunk async closures. This would then allow writing async || -> _ { .. }.into_future()
, and I think it'd be better ergonomics for those to not require a set of parens around them to please clippy.
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 one, by contrast, does the "right" thing
I find async || -> _ { .. }.into_future()
quite ambiguous and would have a hard time parsing it if I saw it in code, so I am in favor of requiring more explicit parentheses here. Or rather, I'd probably write this as IntoFuture::into_future(async || { ... })
.
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 know, I find that || -> _ { .. }()
, which we accept without linting, feels the same to me, so I'd prefer to lint against both or neither.
And for both, I feel like they look worse in abstract form than they would often in practice, e.g. with the block split over multiple lines or the return type ascribed in other ways.
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.
The broader point is that || -> _ { .. }// anything here
shouldn't feel ambiguous to us because the only thing that can follow the -> _
is a block, so it's completely unambiguous where that block and therefore that closure body ends.
The only reason that it might feel ambiguous to us (I claim) is because of what we did with || { ..
, where the opening brace doesn't force block parsing. I would like us to move away from that. Perhaps we could lint hard against anything that relies on it. Maybe over an edition we could change the rule.
My hope is that in pushing in that direction, || -> _ { .. }// anything here
won't feel ambiguous to us any longer.
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.
|| -> _ { .. }// anything here shouldn't feel ambiguous to us because the only thing that can follow the -> _ is a block
The fact that -> _
affects parsing like that is arcane knowledge most people don't have. I heard about it the first time ~2 weeks ago. The entire point of this PR is to make it so people can parse code correctly regardless of whether they possess this arcane knowledge or not.
The only reason that it might feel ambiguous to us (I claim) is because of what we did with || { .., where the opening brace doesn't force block parsing. I would like us to move away from that. Perhaps we could lint hard against anything that relies on it. Maybe over an edition we could change the rule.
Ah, so you are saying we could make || -> _ {
actually consistent with || {
? Yeah that seems nice, and should definitely be possible with an edition transition.
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.
Exactly.
On the other, all I'm saying is that people know that || -> u64 42
doesn't work, and I don't think people expect it to, and so I think their normal intuitions about blocks kick in. What messes this up and makes it arcane is the other one.
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.
normal intuitions about blocks kick in
My intuition is surprised whenever these end of block cases pop up, for example that the parens are required in
fn f() -> Y {
(unsafe { x } as Y)
}
Similarly, the current parsing of |x: String| {x}.clone()
matches my intuition
A discrepancy between the `cmake` version available on the runners and the one required by the `aws-lc-sys` dependency prevents the crate from building.
`|x: String| {x}.clone()` applies `clone()` to {x}, while `|x: String| -> String {x}.clone()` applies `.clone()` to the closure because a closure with an explicit return type requires a block. This extends the `precedence` lint to suggest using parentheses around the closure definition when the closure would be cloned, as in `(|x: String| -> String {x}).clone()`.
579b5d6
to
c1afad3
Compare
The current state of the proposed change right now is to suggests using parentheses when the method call would apply to the closure itself, and only in this case. That would be, in the top example, |
There's also a proposal on the table here for
|
The original form there is already disambiguated, so I don't think this helps with the problem this lint is trying to address. EDIT: Oh I see this is about now having rustfmt work against disambiguation here. We'd still need the lint, but the lint can now suggest to add braces since rustfmt will not remove them any more. Is that what you mean? |
That's right. |
|
Sorry, closed by mistake and reopened. I'll mark this PR as blocked until the rustfmt one is closed. If it's merged, I'll readd add-ing brackets around the block. @rustbot label +S-blocked +S-waiting-on-author -S-waiting-on-review |
Doesn't need to be considered blocked, we can revisit the braces case at a later date separately |
Sure. |
Did a small survey with some GitHub searches, I managed to find three cases in the wild:
Interestingly the last one has So an in context comparison would be |stream: ParseStream| -> syn::Result<Self> { Self::parse(stream) }.parse2(input) (|stream: ParseStream| -> syn::Result<Self> { Self::parse(stream) }).parse2(input)
|
|x: String| {x}.clone()
appliesclone()
to{x}
, while|x: String| -> String {x}.clone()
applies.clone()
to the closure because a closure with an explicit return type requires a block as its body.This extends the
precedence
lint to suggest using parentheses around the closure definition in the second case, when.clone()
would apply to the whole closure.changelog: [
precedence
]: warn about ambiguity when a closure is used as a method call receiverRelated discussion