Skip to content

Commit

Permalink
wherein the parens lint keeps its own counsel re args in nested macros
Browse files Browse the repository at this point in the history
In rust-lang#46980 ("in which the unused-parens lint..." (14982db)), the
unused-parens lint was made to check function and method arguments,
which it previously did not (seemingly due to oversight rather than
willful design). However, in rust-lang#47775 and discussion thereon,
user–developers of Geal/nom and graphql-rust/juniper reported that the
lint was seemingly erroneously triggering on certain complex macros in
those projects. While this doesn't seem like a bug in the lint in the
particular strict sense that the expanded code would, in fact, contain
unncecessary parentheses, it also doesn't seem like the sort of thing
macro authors should have to think about: the spirit of the
unused-parens lint is to prevent needless clutter in code, not to give
macro authors extra heartache in the handling of token trees.

We propose the expediency of declining to lint unused parentheses in
function or method args inside of nested expansions: we believe that
this should eliminate the petty, troublesome lint warnings reported
in the issue, without forgoing the benefits of the lint in simpler
macros.

It seemed like too much duplicated code for the `Call` and `MethodCall`
match arms to duplicate the nested-macro check in addition to each
having their own `for` loop, so this occasioned a slight refactor so
that the function and method cases could share code—hopefully the
overall intent is at least no less clear to the gentle reader.

This is concerning rust-lang#47775.
  • Loading branch information
zackmdavis committed Jan 31, 2018
1 parent def3269 commit 5985b0b
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 9 deletions.
37 changes: 28 additions & 9 deletions src/librustc_lint/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,19 +302,38 @@ impl EarlyLintPass for UnusedParens {
Assign(_, ref value) => (value, "assigned value", false),
AssignOp(.., ref value) => (value, "assigned value", false),
InPlace(_, ref value) => (value, "emplacement value", false),
Call(_, ref args) => {
for arg in args {
self.check_unused_parens_core(cx, arg, "function argument", false)
// either function/method call, or something this lint doesn't care about
ref call_or_other => {
let args_to_check;
let call_kind;
match *call_or_other {
Call(_, ref args) => {
call_kind = "function";
args_to_check = &args[..];
},
MethodCall(_, ref args) => {
call_kind = "method";
// first "argument" is self (which sometimes needs parens)
args_to_check = &args[1..];
}
// actual catch-all arm
_ => { return; }
}
return;
},
MethodCall(_, ref args) => {
for arg in &args[1..] { // first "argument" is self (which sometimes needs parens)
self.check_unused_parens_core(cx, arg, "method argument", false)
// Don't lint if this is a nested macro expansion: otherwise, the lint could
// trigger in situations that macro authors shouldn't have to care about, e.g.,
// when a parenthesized token tree matched in one macro expansion is matched as
// an expression in another and used as a fn/method argument (Issue #47775)
if e.span.ctxt().outer().expn_info()
.map_or(false, |info| info.call_site.ctxt().outer()
.expn_info().is_some()) {
return;
}
let msg = format!("{} argument", call_kind);
for arg in args_to_check {
self.check_unused_parens_core(cx, arg, &msg, false);
}
return;
}
_ => return,
};
self.check_unused_parens_core(cx, &value, msg, struct_lit_needs_parens);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// must-compile-successfully

#![warn(unused_parens)]

macro_rules! the_worship_the_heart_lifts_above {
( @as_expr, $e:expr) => { $e };
( @generate_fn, $name:tt) => {
#[allow(dead_code)] fn the_moth_for_the_star<'a>() -> Option<&'a str> {
Some(the_worship_the_heart_lifts_above!( @as_expr, $name ))
}
};
( $name:ident ) => { the_worship_the_heart_lifts_above!( @generate_fn, (stringify!($name))); }
// ↑ Notably, this does 𝘯𝘰𝘵 warn: we're declining to lint unused parens in
// function/method arguments inside of nested macros because of situations
// like those reported in Issue #47775
}

macro_rules! and_the_heavens_reject_not {
() => {
// ↓ But let's test that we still lint for unused parens around
// function args inside of simple, one-deep macros.
#[allow(dead_code)] fn the_night_for_the_morrow() -> Option<isize> { Some((2)) }
//~^ WARN unnecessary parentheses around function argument
}
}

the_worship_the_heart_lifts_above!(rah);
and_the_heavens_reject_not!();

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
warning: unnecessary parentheses around function argument
--> $DIR/issue-47775-nested-macro-unnecessary-parens-arg.rs:32:83
|
32 | #[allow(dead_code)] fn the_night_for_the_morrow() -> Option<isize> { Some((2)) }
| ^^^ help: remove these parentheses
...
38 | and_the_heavens_reject_not!();
| ------------------------------ in this macro invocation
|
note: lint level defined here
--> $DIR/issue-47775-nested-macro-unnecessary-parens-arg.rs:13:9
|
13 | #![warn(unused_parens)]
| ^^^^^^^^^^^^^

0 comments on commit 5985b0b

Please sign in to comment.