rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.48k stars 12.45k forks source link

"deny" lints are ignored in macro expansion #126499

Open jamesmunns opened 2 months ago

jamesmunns commented 2 months ago

I tried this code:

// In a lib crate
pub trait Val {
    const VAL: [u8; 8];
}

impl Val for u8 {
    const VAL: [u8; 8] = [0u8; 8];
}

impl Val for u16 {
    const VAL: [u8; 8] = [1u8; 8];
}

#[macro_export]
macro_rules! example {
    ($val: ident, $($name:ty),*) => {
        // We want this macro to fire if there are dupes
        #[deny(unreachable_patterns)]
        match $val {
            $(
                <$name as Val>::VAL => println!("$name"),
            )*
            _ => println!("Other"),
        }
    };
}

then:

// in a crate that depends on the above crate's code:
#![deny(unreachable_patterns)]

use macro_source::Val;

fn main() {
    let val = [2u8; 8];
    macro_source::example!(val, u8, u8, u16);
}

I expected to see this happen:

I would expect the deny lint to fire and halt compilation

Instead, this happened:

The crate compiles with no warnings or errors

You can see the issue in the expanded code:

#![feature(prelude_import)]
#![deny(unreachable_patterns)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use macro_source::Val;
fn main() {
    let val = [2u8; 8];
    #[deny(unreachable_patterns)]
    match val {
        <u8 as Val>::VAL => {
            ::std::io::_print(format_args!("$name\n"));
        }
        <u8 as Val>::VAL => {
//      ^^^^^^^^^^^^^^^^ duplicate arm - will never match!
            ::std::io::_print(format_args!("$name\n"));
        }
        <u16 as Val>::VAL => {
            ::std::io::_print(format_args!("$name\n"));
        }
        _ => {
            ::std::io::_print(format_args!("Other\n"));
        }
    };
}

Meta

I asked about this in Zulip, and it was mentioned that only a select number of lints still fire during macro expansion.

However:

But more importantly:

If the compiler could potentially know that a lint will never fire, it might be good to add a warning for that at the macro declaration site, something like:

#[macro_export]
macro_rules! example {
    ($val: ident, $($name:ty),*) => {
        #[deny(unreachable_patterns)]
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: lints in macros don't fire at their expansion point
        match $val {
            $(
                <$name as Val>::VAL => println!("$name"),
            )*
            _ => println!("Other"),
        }
    };
}
jamesmunns commented 2 months ago

CC https://github.com/jamesmunns/postcard-rpc/issues/34 which is where I ran into this specific problem.

I'm not sure if this is really a bug, as it's probably the correct behavior from a couple of other decisions, but it was surprising to me when I ran into it, and might be an opportunity for diagnostics, and maybe revisiting whether deny lints should be ignored.