6

This is an extremely minor issue and I know how to do disable the warning, but reading up on it I suspect it might indicate that I might be doing something incorrect with my macro. Anyway, I have a struct Rational for rational numbers:

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Rational {
    n: i128,
    d: i128,
}

impl Rational {
    pub fn new(n: i128, d: i128) -> Self {
        Rational { n, d }
    }
}

to create these in a readable way I use macros:

macro_rules! rat {
    ($whole: tt $n : tt / $d : tt) => {
        crate::rational::Rational::new($n + $whole * $d, $d)
    };
    ($n : tt / $d : tt) => {
        crate::rational::Rational::new($n, $d)
    };

    ($n : tt) => {
        crate::rational::Rational::new($n, 1)
    };
}

and some code to test and demo this:

    let x = 42;
    println!("{}", rat!(1));
    println!("{}", rat!(2 / 3));
    println!("{}", rat!(x));
    println!("{}", rat!(x / 2));
    println!("{}", rat!(2 / x));
    println!("{}", rat!(x 1/2));
    println!("{}", rat!(1 4/5));
    println!("{}", rat!((5 / 1) / 2)); //this line shows a warning

Hopefully there you can (sort of) see the point of the macros - with a normal function call, things like 1/2 would collapse to 0 before ever being seen by the function. (btw, I know it isn't worth it to do this for single rationals, but I think it might be for quickly parsing larger equations, like equation!(1/2 x + 3 3/4 y + 24 z = 2 3/4 + 4 x). This is just a baby step towards that.)

Anyway, the actual problem: on the last example, it complains that the parens around 5/1 are unnecessary. However, if I remove them (println!("{}", rat!(5 / 1 / 2));) the macro fails with "no rules expected this token in macro call".) (btw, that test is obviously not nice syntax, I just wanted to ensure that it could handle arbitrary expressions.)

So, two questions:

  1. Is this the result of some sort of poor style on my part in how I constructed these macros?
  2. Is there a way to disable this warning that's more specific than just a universal #![allow(unused_parens)] but which doesn't require me to invoke that at every call site either? (I was wondering if I could somehow bake the allow into the macro itself, tho I don't think that makes sense.)

EDIT: I worked out that it was not the call that was the problem, but the generated code - the parens are necessary to the macro and rust recognizes that, but then the macro outputs new((5/1), 2), where the parens are actually unnecessary. I'm not sure of the right way to handle that, though I see a few options.

Edward Peters
  • 3,623
  • 2
  • 16
  • 39
  • Aren't the parens in `crate::rational::Rational::new(($n + $whole * $d), $d)` unnecessary? – Thomas Jan 02 '23 at 15:39
  • @Thomas good catch, but that's not what it's complaining about (somewhat surprising, it seems like it should be, removing those doesn't change anything.) Editing them out of the question for clarity. – Edward Peters Jan 02 '23 at 15:42

1 Answers1

1

Is this the result of some sort of poor style on my part in how I constructed these macros?

No, this looks like a false positive, another case of #73068.

Is there a way to disable this warning that's more specific than just a universal #![allow(unused_parens)] but which doesn't require me to invoke that at every call site either? (I was wondering if I could somehow bake the allow into the macro itself, tho I don't think that makes sense.)

Yes, you can bake that into the macro call. Trying to do that naively won't work as attributes on expressions are experimental. But you can make it a statement:

macro_rules! rat {
    ($whole: tt $n : tt / $d : tt) => {{
        #[allow(unused_parens)]
        let v = crate::rational::Rational::new($n + $whole * $d, $d);
        v
    }};
    ($n : tt / $d : tt) => {{
        #[allow(unused_parens)]
        let v = crate::rational::Rational::new($n, $d);
        v
    }};
    ($n : tt) => {{
        #[allow(unused_parens)]
        let v = crate::rational::Rational::new($n, 1);
        v
    }};
}

Beware that this will disable the lint even for nested expressions, so this may not be what you want.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • I don't think it is a false positive? If you see my edit on the answer, the code I'm outputting does actually include unnecessary parens - they were passed through the macro. If I add an unwrapping step to the output it works (though I don't know if there's an idiomatic way to do that.) – Edward Peters Jan 03 '23 at 00:37
  • @EdwardPeters What edit? It is a false positive because the parentheses are necessary - without them, the macro doesn't match and the code doesn't compile. This is exactly the same as the issue I linked. – Chayim Friedman Jan 03 '23 at 10:12
  • The edit marked "Edit", at the end of the question. And in the macro call the parenthesis are necessary, but they're being passed through to a position where they're unnecessary - i.e., they're unnecessary in the generated code. – Edward Peters Jan 03 '23 at 13:18
  • @EdwardPeters Sure, but they are necessary for the macro, and that should be enough for Rust to not report that. – Chayim Friedman Jan 03 '23 at 13:25