-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Description
Story-driven explanation
I was writing a macro that implements ADT <=> int conversions:
TW: macros
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:?}"),
}
}
}
};
}Used like this:
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:
// crate `a`
#[macro_export]
macro_rules! example {
() => {
#[forbid(unused_variables)]
const _: () = { let a = 0; };
}
}
example! {} // <-- invocation in `a`// crate `b`
a::example! {} // <-- invocation in `b`(play)
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.