-
Notifications
You must be signed in to change notification settings - Fork 13.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
Add checking for unnecessary delims in closure body #136906
base: master
Are you sure you want to change the base?
Add checking for unnecessary delims in closure body #136906
Conversation
3b739b5
to
ccdf053
Compare
compiler/rustc_lint/src/unused.rs
Outdated
if matches!(closure.fn_decl.output, FnRetTy::Default(_)) | ||
// skip `#[core::contracts::requires(...)]` and `#[core::contracts::ensures(...)]` which generate closure | ||
&& !cx | ||
.sess() |
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.
any better check for this?
seems the AST generated by contracts are the same with normal closures.
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.
we could generate an #[allow]
for this lint as part of the ast we generate. Alternatively you could check that it's expanded code in general and just ignore all of this. Likely some macros will hit this issue, too (add a test for that, too)
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.
Alternatively you could check that it's expanded code in general and just ignore all of this.
The following check here https://github.com/rust-lang/rust/pull/136906/files#diff-e25b2c6c1fa434ab8078db81003888370f06b9da5503d1590757350be8e31103L945-L946 already checked it?
I also tried to use closure.body.span.can_be_used_for_suggestions()
, seems can not exclude this scenario.
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.
for the solution we could generate an #[allow] for this lint as part of the ast we generate.
I tried to add it here:
428b251
which will break the parser.
seems we only add simple tokentree for the contract and parse it here:
https://github.com/chenyukang/rust/blob/428b251feb3e87e41c9e46656cc96a3b94d88ea5/compiler/rustc_parse/src/parser/generics.rs#L302-L325
we do not expect too much change just to generate an #[allow(unused_parens)]
, where is the proper point to inject it.
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.
already checked it?
ah that means contract expansion doesn't add the expansion info to the spans. I did indeed forget to check for that at all when it was introduced
cc @celinval
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 tried to mark the span with DesugaringKind::Contract
at here: https://github.com/chenyukang/rust/blob/9ec13a62cafd1448b2d43d887034ae6501fca6d0/compiler/rustc_ast_lowering/src/item.rs#L1082-L1089
But found that early lint check is run before this point.
I think this solution with span check maybe enough: 64f1f52
the generated closure span are all the same, which is different from real code.
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.
Ah right this is due to the macro expansion from
fn expand_contract_clause( |
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 tried to follow mark_with_reason
https://github.com/chenyukang/rust/blob/59281e98d0af7ed5071d141235ce5ddff1f60253/compiler/rustc_span/src/hygiene.rs#L906-L919
to create a span with annotation:
59281e9#diff-0843065cebe8379cca975e505c67c393f9933c8d9019fb6a6e3b3d5654157bc1R70
The problem is how can we get a ctx
which meet the trait here, seems we're at a very early stage:
error[E0277]: the trait bound `SyntaxContext: rustc_span::HashStableContext` is not satisfied
--> compiler/rustc_builtin_macros/src/contracts.rs:70:49
|
70 | let expn_id = LocalExpnId::fresh(expn_data, attr_span.ctxt());
| ------------------ ^^^^^^^^^^^^^^^^ the trait `rustc_span::HashStableContext` is not implemented for `SyntaxContext`
| |
| required by a bound introduced by this call
|
note: required by a bound in `LocalExpnId::fresh`
--> /Users/yukang/rust/compiler/rustc_span/src/hygiene.rs:200:53
|
200 | pub fn fresh(mut expn_data: ExpnData, ctx: impl HashStableContext) -> LocalExpnId {
| ^^^^^^^^^^^^^^^^^ required by this bound in `LocalExpnId::fresh`
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 think the span checking here is enough for avoid lint for contract syntax here?
https://github.com/rust-lang/rust/pull/136906/files#diff-e25b2c6c1fa434ab8078db81003888370f06b9da5503d1590757350be8e31103R917-R918
it's a bit hacky, but user written code will always true on : e.span != closure.body.span
.
And also considering the fact that contract syntax is temporary, if we can not find a perfect way to mark the span we should not introduce more change for it. #137134 (comment)
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.
You're absolutely right. Sorry for holding it up
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
c755f78
to
c7ec66c
Compare
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt Some changes occurred in coverage instrumentation. cc @Zalathar |
This comment has been minimized.
This comment has been minimized.
The Miri subtree was changed cc @rust-lang/miri Some changes occurred in src/tools/clippy cc @rust-lang/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.
that's a lot of changes in unrelated tests. Changing tests for such reasons isn't too great and the value of this change in tests isn't really there either. Maybe add the lint to the list of lints that compiletest allows for all tests?
This comment has been minimized.
This comment has been minimized.
IMO we may want to just not warn on this case. Looking at the diffs both in the compiler and in the tests, it doesn't seem like a strict readability improvement to me (at times I find it even a bit harder to read).
I think we should avoid adding blanket allows for all tests where feasible (many tests getting affected is itself a decent indicator, albeit rough, for if a change may impact a lot of cases). |
in the compiler and tools it was always an improvement to me. What specific examples do you dislike? |
This probably comes down to personal preference, but I don't really find these specific examples to be strictly better (I find the -.all(|(node, span_edge)| { span_edge.is_some() <= self.is_supernode(node) }),
+.all(|(node, span_edge)| span_edge.is_some() <= self.is_supernode(node)),
-write!(&mut counter, "{:#}", fmt::from_fn(|f| { self.inner_full_print(None, f, cx) }))
+write!(&mut counter, "{:#}", fmt::from_fn(|f| self.inner_full_print(None, f, cx))) Removing parens on the very short exprs do look more readable to me. So I guess I feel mixed about it? lol |
Since you're already looking at it... r? @oli-obk |
5e56316
to
9ec13a6
Compare
Some changes occurred in src/tools/compiletest cc @jieyouxu |
This comment has been minimized.
This comment has been minimized.
done with commit 3a34ad7 |
Yes, this seems comes down to personal preference, but I just found rustfmt will remove the braces in this closure: let some_predicate = |x: &mut i32| { *x == 2 || *x == 3 || *x == 6 }; |
While this seemed OK to me, by way of double checking, I was still trying to convince myself this wasn't actually too opinionated somehow. Maybe it's OK if people want to disambiguate here? But then I recalled that these two lines _ = || -> _ { 0 }.clone();
_ = || { 0 }.clone(); are quite different. That is, starting a closure body with an opening brace doesn't really serve to disambiguate the scope of that body in the way that one might expect. So since the braces aren't really good for that purpose, it makes sense to treat them as we do other unnecessary braces. This is going to be a meaningful lint expansion (so heads-up to @tmandry), but the fix is automated, so that seems fine too (and I'm glad to not have to come up with another lint name). Therefore, I propose: @rfcbot fcp merge |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
Wait, what?^^ Could you spell this out / link to some place where it is spelled out? |
E.g.: fn main() {
let x = &mut ();
_ = move || { let x = x; }.clone(); //~ OK
let x = &mut ();
_ = move || -> _ { let x = x; }.clone();
//~^ ERROR the trait bound `&mut (): Clone` is not satisfied
} The first one clones unit. The second one tries to clone the closure. That is, if the return type is not annotated, then we parse an expression. If it is, then we require an explicit block. |
Oh wow... That is subtle. But one case does not build so at least it does not lead to semantic ambiguity.
|
Certainly if we try hard enough, we can produce that. E.g.: struct W(u8);
impl Clone for W {
fn clone(&self) -> Self {
W(1)
}
}
fn main() {
let f = move |x: W| { x }.clone();
assert!(matches!(f(W(0)), W(1)));
let f = move |x: W| -> _ { x }.clone();
assert!(matches!(f(W(0)), W(0)));
} There are probably other ways. |
89c1195
to
5ea47e1
Compare
This comment has been minimized.
This comment has been minimized.
5ea47e1
to
c089b1f
Compare
☔ The latest upstream changes (presumably #138459) made this pull request unmergeable. Please resolve the merge conflicts. |
@rfcbot concern ambiguous-block-parsing Thinking more about this, if someone writes an apparently redundant set of braces for the body of a closure without an ascribed return type but where a second block starts immediately inside the first but ends before the first block does, e.g., _ = || { { 0 }.clone() }; then I don't want to suggest removing those outer braces. In fact, I'd like to lint against their absence and suggest adding them, e.g.: _ = || { 0 }.clone(); //~ WARN The reason this case is such a problem is that it'd be pretty reasonable for people to expect that the opening brace at the start of the closure body would force block parsing of that body. If it did, then the closing brace would end the closure body (as it does for |
for this code: _ = || { { 0 }.clone() }; our current |
cc @rust-lang/rustfmt @rust-lang/style |
@traviscross is there a heuristic we can reliably use to force rustfmt not to remove the surrounding blocks? If so, then we could try to modify rust/src/tools/rustfmt/src/closures.rs Lines 456 to 475 in 75530e9
|
This comment was marked as duplicate.
This comment was marked as duplicate.
Probably what we'd want is something along the lines of, "if the expression forming the closure body starts with two opening braces in sequence, and then ends with a closing brace (matching the outer starting brace) without an immediately preceding inner closing brace (matching the inner starting brace), then don't remove the outer braces." That is, we're looking for: _ || { { .. }/* Something here */ }; If we were to add disambiguating braces, the rule would be along the lines of, "if the expression forming the closure body starts with an opening brace and does not end with a closing brace (matching the opening one),then wrap the expression in a set of outer braces. |
I'm pretty sure that rustfmt only removes the outer braces if the closure body contains a single expression, and doesn't contain any comments. The only way I can think to write code that starts with two opening braces and ends with a single closing brace is in this case of a method call receiver being a block expression. If you can think of other ways we might end up with a pattern like For this case, I think something like this could work: fn is_block_closure_forced_inner(expr: &ast::Expr, style_edition: StyleEdition) -> bool {
match expr.kind {
+ ast::ExprKind::MethodCall(ref method_call)
+ if style_edition >= StyleEdition::Edition2027 =>
+ {
+ // Prevent rustfmt from removing outer braces in `_ = || { { 0 }.clone() };`,
+ // which changes the semantics of the code.
+ matches!(method_call.receiver.kind, ast::ExprKind::Block(..))
+ }
ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop { .. } => true,
ast::ExprKind::Loop(..) if style_edition >= StyleEdition::Edition2024 => true,
ast::ExprKind::AddrOf(_, _, ref expr) With that in place the following fn main() {
_ = || { { 0 }.clone() };
} is formatted like this. fn main() {
_ = || {
{ 0 }.clone()
};
} |
Sounds right to me. |
Fixes #136741