//! Tests macro_metavars_in_unsafe with default configuration
#![feature(decl_macro)]
#![warn(clippy::macro_metavars_in_unsafe)]
#![allow(clippy::no_effect)]

#[macro_export]
macro_rules! allow_works {
    ($v:expr) => {
        #[expect(clippy::macro_metavars_in_unsafe)]
        unsafe {
            $v;
        };
    };
}

#[macro_export]
macro_rules! simple {
    ($v:expr) => {
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            dbg!($v);
        }
    };
}

#[macro_export]
#[rustfmt::skip] // for some reason rustfmt rewrites $r#unsafe to r#u$nsafe, bug?
macro_rules! raw_symbol {
    ($r#mod:expr, $r#unsafe:expr) => {
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $r#mod;
        }
        $r#unsafe;
    };
}

#[macro_export]
macro_rules! multilevel_unsafe {
    ($v:expr) => {
        unsafe {
            unsafe {
                //~^ ERROR: this macro expands metavariables in an unsafe block
                $v;
            }
        }
    };
}

#[macro_export]
macro_rules! in_function {
    ($v:expr) => {
        unsafe {
            fn f() {
                // function introduces a new body, so don't lint.
                $v;
            }
        }
    };
}

#[macro_export]
macro_rules! in_function_with_unsafe {
    ($v:expr) => {
        unsafe {
            fn f() {
                unsafe {
                    //~^ ERROR: this macro expands metavariables in an unsafe block
                    $v;
                }
            }
        }
    };
}

#[macro_export]
macro_rules! const_static {
    ($c:expr, $s:expr) => {
        unsafe {
            // const and static introduces new body, don't lint
            const _X: i32 = $c;
            static _Y: i32 = $s;
        }
    };
}

#[macro_export]
macro_rules! const_generic_in_struct {
    ($inside_unsafe:expr, $outside_unsafe:expr) => {
        unsafe {
            struct Ty<
                const L: i32 = 1,
                const M: i32 = {
                    1;
                    unsafe { $inside_unsafe }
                    //~^ ERROR: this macro expands metavariables in an unsafe block
                },
                const N: i32 = { $outside_unsafe },
            >;
        }
    };
}

#[macro_export]
macro_rules! fn_with_const_generic {
    ($inside_unsafe:expr, $outside_unsafe:expr) => {
        unsafe {
            fn f<const N: usize>() {
                $outside_unsafe;
                unsafe {
                    //~^ ERROR: this macro expands metavariables in an unsafe block
                    $inside_unsafe;
                }
            }
        }
    };
}

#[macro_export]
macro_rules! variables {
    ($inside_unsafe:expr, $outside_unsafe:expr) => {
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $inside_unsafe;
            let inside_unsafe = 1;
            inside_unsafe;
        }
        $outside_unsafe;
        let outside_unsafe = 1;
        outside_unsafe;
    };
}

#[macro_export]
macro_rules! multiple_matchers {
    ($inside_unsafe:expr, $outside_unsafe:expr) => {
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $inside_unsafe;
        }
        $outside_unsafe;
    };
    ($($v:expr, $x:expr),+) => {
        $(
            $v;
            unsafe {
                //~^ ERROR: this macro expands metavariables in an unsafe block
                $x;
            }
        );+
    };
}

#[macro_export]
macro_rules! multiple_unsafe_blocks {
    ($w:expr, $x:expr, $y:expr) => {
        $w;
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $x;
        }
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $x;
            $y;
        }
    };
}

pub macro macro2_0($v:expr) {
    unsafe {
        //~^ ERROR: this macro expands metavariables in an unsafe block
        $v;
    }
}

// don't lint private macros with the default configuration
macro_rules! private_mac {
    ($v:expr) => {
        unsafe {
            $v;
        }
    };
}

// don't lint exported macros that are doc(hidden) because they also aren't part of the public API
#[macro_export]
#[doc(hidden)]
macro_rules! exported_but_hidden {
    ($v:expr) => {
        unsafe {
            $v;
        }
    };
}

// don't lint if the same metavariable is expanded in an unsafe block and then outside of one:
// unsafe {} is still needed at callsite so not problematic
#[macro_export]
macro_rules! does_require_unsafe {
    ($v:expr) => {
        unsafe {
            $v;
        }
        $v;
    };
}

#[macro_export]
macro_rules! unsafe_from_root_ctxt {
    ($v:expr) => {
        // Expands to unsafe { 1 }, but the unsafe block is from the root ctxt and not this macro,
        // so no warning.
        $v;
    };
}

// invoked from another macro, should still generate a warning
#[macro_export]
macro_rules! nested_macro_helper {
    ($v:expr) => {{
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            $v;
        }
    }};
}

#[macro_export]
macro_rules! nested_macros {
    ($v:expr, $v2:expr) => {{
        unsafe {
            //~^ ERROR: this macro expands metavariables in an unsafe block
            nested_macro_helper!($v);
            $v;
        }
    }};
}

fn main() {
    allow_works!(1);
    simple!(1);
    raw_symbol!(1, 1);
    multilevel_unsafe!(1);
    in_function!(1);
    in_function_with_unsafe!(1);
    const_static!(1, 1);
    const_generic_in_struct!(1, 1);
    fn_with_const_generic!(1, 1);
    variables!(1, 1);
    multiple_matchers!(1, 1);
    multiple_matchers!(1, 1, 1, 1);
    macro2_0!(1);
    private_mac!(1);
    exported_but_hidden!(1);
    does_require_unsafe!(1);
    multiple_unsafe_blocks!(1, 1, 1);
    unsafe_from_root_ctxt!(unsafe { 1 });
    nested_macros!(1, 1);
}
