Skip to content

Rustc says necessary parentheses are unnecessary #120642

Closed
@mysteriouslyseeing

Description

@mysteriouslyseeing

This code:

trait Foo {
    fn bar();
}

macro_rules! problem {
    ($ty:ident) => {
        impl<$ty: Foo> Foo for ($ty,) {
            fn bar() { <$ty>::bar() }
        }
    };
    ($ty:ident $(, $rest:ident)*) => {
        impl<$ty: Foo, $($rest: Foo),*> Foo for ($ty, $($rest),*) {
            fn bar() {
                <$ty>::bar();
                <($($rest),*)>::bar()
            }
        }

        problem!($($rest),*);
    }
}

problem!(T1, T2);

Playground

Creates this diagnostic:

   Compiling playground v0.0.1 (/home/wschroeder/Projects/playground)
warning: unnecessary parentheses around type
  --> src/lib.rs:15:18
   |
15 |                 <($($rest),*)>::bar()
   |                  ^^^     ^^^^
...
23 | problem!(T1, T2);
   | ---------------- in this macro invocation
   |
   = note: `#[warn(unused_parens)]` on by default
   = note: this warning originates in the macro `problem` (in Nightly builds, run with -Z macro-backtrace for more info)
help: remove these parentheses
   |
15 -                 <($($rest),*)>::bar()
15 +                 <$rest>::bar()
   |

warning: `playground` (lib) generated 1 warning (run `cargo fix --lib -p playground` to apply 1 suggestion)
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s

I ran cargo fix --lib -p playground --allow-dirty (note that I created a local project called playground) which created this output:

    Checking playground v0.0.1 (/home/wschroeder/Projects/playground)
warning: failed to automatically apply fixes suggested by rustc to crate `playground`

after fixes were automatically applied the compiler reported errors within these files:

  * src/lib.rs

This likely indicates a bug in either rustc or cargo itself,
and we would appreciate a bug report! You're likely to see
a number of compiler warnings after this message which cargo
attempted to fix but failed. If you could open an issue at
https://github.com/rust-lang/rust/issues
quoting the full output of this command we'd be very appreciative!
Note that you may be able to make some more progress in the near-term
fixing code with the `--broken-code` flag

The following errors were reported:
error: variable 'rest' is still repeating at this depth
  --> src/lib.rs:15:18
   |
15 |                 <$rest>::bar()
   |                  ^^^^^

error: aborting due to 1 previous error

Original diagnostics will follow.

warning: unnecessary parentheses around type
  --> src/lib.rs:15:18
   |
15 |                 <($($rest),*)>::bar()
   |                  ^^^     ^^^^
...
23 | problem!(T1, T2);
   | ---------------- in this macro invocation
   |
   = note: `#[warn(unused_parens)]` on by default
   = note: this warning originates in the macro `problem` (in Nightly builds, run with -Z macro-backtrace for more info)
help: remove these parentheses
   |
15 -                 <($($rest),*)>::bar()
15 +                 <$rest>::bar()
   |

warning: `playground` (lib) generated 1 warning (run `cargo fix --lib -p playground` to apply 1 suggestion)
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s

This told me to create an issue here.

Going into more depth regarding the issue - the macro expands after one step like so:

impl<T1: Foo, T2: Foo> Foo for (T1, T2) {
    fn bar() {
        <T1>::bar();
        <(T2)>::bar();
    }
}

problem!(T2);

and we can see where the issue is:

<(T2)>::bar()

These parentheses are indeed fully removable, and it points to an issue in the macro definition. By changing

<($($rest),*)>

to

<($($rest,)*)>

the compiler no longer emits the warning, because now it expands to

<(T2,)>::bar()

which is properly recognized as a tuple with one element, rather than a type surrounded by redundant parentheses.

Another way to fix it is to remove the outer parentheses in the macro call:

<$($rest,)*>

which does compile without warnings, but breaks usage of the macro with tuples with more than two elements:

problem!(T1, T2, T3);

Playground
as while its fine to remove the parentheses when there is only one element between the angle brackets (<(T2)> to <T2>), it is not fine when there are more than one element in the tuple (<(T2, T3)> to <T2, T3>).

Importantly, the compiler still emits the unused parentheses warning and suggests their removal when the macro is called with three types when you haven't removed the parentheses (Playground).

Therefore, part of the issue is that rustc detects the problem, and assumes it can fix it by going straight to the macro and correcting it, neglecting previous expansions of the macro which require the currently redundant parentheses.

I say part of the issue, because going back to the original diagnostic:

15 |                 <($($rest),*)>::bar()
   |                  ^^^     ^^^^

rustc marks ($( and ),*) as removable, and it suggests you do so:

15 -                 <($($rest),*)>::bar()
15 +                 <$rest>::bar()

when the diagnostic should really only be suggesting the removal of the outer brackets.

I'm not too sure why this one happens. Using Syn's terminology, my best guess is that it has something to do with the span of the outer parentheses including the inner $( and ),* after expansion.

Meta

rustc --version --verbose:

rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-unknown-linux-gnu
release: 1.75.0
LLVM version: 17.0.6

Also tried with nightly (had identical results):

rustc 1.78.0-nightly (b11fbfbf3 2024-02-03)
binary: rustc
commit-hash: b11fbfbf351b94c7eecf9e6749a4544a6d4717fa
commit-date: 2024-02-03
host: x86_64-unknown-linux-gnu
release: 1.78.0-nightly
LLVM version: 17.0.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.A-suggestion-diagnosticsArea: Suggestions generated by the compiler applied by `cargo fix`C-bugCategory: This is a bug.D-invalid-suggestionDiagnostics: A structured suggestion resulting in incorrect code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions