Repository: rust
Issue: Forbidding lints doesn't really work in macros
Labels: A-lints,A-macros,T-compiler

## Story-driven explanation

I was writing a macro that implements ADT <=> int conversions:

<details>
<summary>TW: macros</summary>

```rust
trait Convert {
    fn into_u32(self) -> u32;

    fn from_u32(v: u32) -> Self;
}

macro_rules! impl_convert {
    (
        for $Self:ty;

        // This basically matches `Type::Variant { fields... } <=> 0,`
        $( $($path:ident)::* $( { $( $fields:tt )* })? <=> $int:literal, )*
    ) => {
        impl $crate::Convert for $Self {
            fn into_u32(self) -> u32 {
                #[forbid(unreachable_patterns)]
                match self {
                    $( $($path)::* $( { $( $fields )* } )? => $int, )*
                }
            }

            fn from_u32(v: u32) -> Self {
                #[forbid(unreachable_patterns)]
                match v {
                    $( $int => $($path)::* $( { $( $fields )* } )?, )*
                    _ => panic!("invalid value: {v:?}"),
                }
            }
        }
    };
}
```

</details>

Used like this:

```rust
enum Type {
    A,
    B { v: bool },
}

impl_convert! {
    for Type;
    Type::A <=> 0,
    Type::B { v: false } <=> 1,
    Type::B { v: true  } <=> 2,
}
```

I tested it, it worked perfectly. `#[forbid(unreachable_patterns)]` even ensures that the mapping is 1 to 1 (duplicate patterns would make the lint go off).

However, when I've made a `compile_fail` doc test to ensure that duplicate patterns are always rejected, it failed (meaning the compilation passed). At first I though that the problem is in rustdoc, but latter testing showed that this is actually the rustc's behavior: if a lint is forbidden inside a macro and is issued inside the macro, then it is not shown and does not abort compilation in case of `#[forbid]`.

This is a shame! I would expect `#[forbid(unreachable_patterns)]` actually work and catch wrong macro uses. This behavior seems hard to anticipate, in other words "footgun-y". This is especially annoying given that the calls in the same crate, where you are most likely to test your macros, **do** produce lints.

## More precise explanation

Given these two crates:

```rust
// crate `a`

#[macro_export]
macro_rules! example {
    () => {
        #[forbid(unused_variables)]
        const _: () = { let a = 0; };
    }
}

example! {} // <-- invocation in `a`
```
```rust
// crate `b`

a::example! {} // <-- invocation in `b`
```
([play](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=7fe6fe1589419cf56c01675b5e5167c9))

Invocation in `a` fails the compilation because of the `forbid`. However, if you comment it out, the invocation in `b` compiles successfully, the lint and `forbid` are ignored. This is inconsistent, hard to test and error prone (especially if the lint is actually caused by the user input, as in my example in the story above).

## Proposal?

I'd like to "fix" this, however, it's not clear what exact rules here should be, how much of a breaking change this is, etc. I'm opening this issue to hear the feedback.