use base_db::all_crates;
use expect_test::expect;

use itertools::Itertools;
use span::Edition;

use super::*;

#[test]
fn macro_rules_are_globally_visible() {
    check(
        r#"
//- /lib.rs
macro_rules! structs {
    ($($i:ident),*) => {
        $(struct $i { field: u32 } )*
    }
}
structs!(Foo);
mod nested;

//- /nested.rs
structs!(Bar, Baz);
"#,
        expect![[r#"
            crate
            - Foo : type
            - nested : type
            - (legacy) structs : macro!

            crate::nested
            - Bar : type
            - Baz : type
            - (legacy) structs : macro!
        "#]],
    );
}

#[test]
fn macro_rules_can_define_modules() {
    check(
        r#"
//- /lib.rs
macro_rules! m {
    ($name:ident) => { mod $name;  }
}
m!(n1);
mod m { m!(n3) }

//- /n1.rs
m!(n2)
//- /n1/n2.rs
struct X;
//- /m/n3.rs
struct Y;
"#,
        expect![[r#"
            crate
            - m : type
            - n1 : type
            - (legacy) m : macro!

            crate::m
            - n3 : type
            - (legacy) m : macro!

            crate::m::n3
            - Y : type value
            - (legacy) m : macro!

            crate::n1
            - n2 : type
            - (legacy) m : macro!

            crate::n1::n2
            - X : type value
            - (legacy) m : macro!
        "#]],
    );
}

#[test]
fn macro_rules_from_other_crates_are_visible() {
    check(
        r#"
//- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar)
mod bar;

//- /bar.rs
use crate::*;

//- /lib.rs crate:foo
#[macro_export]
macro_rules! structs {
    ($($i:ident),*) => {
        $(struct $i { field: u32 } )*
    }
}
"#,
        expect![[r#"
            crate
            - Bar : type
            - Foo : type
            - bar : type

            crate::bar
            - Bar : type (glob)
            - Foo : type (glob)
            - bar : type (glob)
        "#]],
    );
}

#[test]
fn macro_rules_export_with_local_inner_macros_are_visible() {
    check(
        r#"
//- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar)
mod bar;

//- /bar.rs
use crate::*;

//- /lib.rs crate:foo
#[macro_export(local_inner_macros)]
macro_rules! structs {
    ($($i:ident),*) => {
        $(struct $i { field: u32 } )*
    }
}
"#,
        expect![[r#"
            crate
            - Bar : type
            - Foo : type
            - bar : type

            crate::bar
            - Bar : type (glob)
            - Foo : type (glob)
            - bar : type (glob)
        "#]],
    );
}

#[test]
fn local_inner_macros_makes_local_macros_usable() {
    check(
        r#"
//- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar);
mod bar;

//- /bar.rs
use crate::*;

//- /lib.rs crate:foo
#[macro_export(local_inner_macros)]
macro_rules! structs {
    ($($i:ident),*) => {
        inner!($($i),*);
    }
}
#[macro_export]
macro_rules! inner {
    ($($i:ident),*) => {
        $(struct $i { field: u32 } )*
    }
}
"#,
        expect![[r#"
            crate
            - Bar : type
            - Foo : type
            - bar : type

            crate::bar
            - Bar : type (glob)
            - Foo : type (glob)
            - bar : type (glob)
        "#]],
    );
}

#[test]
fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
    check(
        r#"
//- /main.rs crate:main deps:foo
macro_rules! baz {
    () => {
        use foo::bar;
    }
}
foo!();
bar!();
baz!();

//- /lib.rs crate:foo
#[macro_export]
macro_rules! foo {
    () => {
        struct Foo { field: u32 }
    }
}
#[macro_export]
macro_rules! bar {
    () => {
        use foo::foo;
    }
}
"#,
        expect![[r#"
            crate
            - Foo : type
            - bar : macro! (import)
            - foo : macro! (import)
            - (legacy) baz : macro!
        "#]],
    );
}

#[test]
fn macro_rules_from_other_crates_are_visible_with_macro_use() {
    cov_mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use);
    check(
        r#"
//- /main.rs crate:main deps:foo
structs!(Foo);
structs_priv!(Bar);
structs_not_exported!(MacroNotResolved1);
crate::structs!(MacroNotResolved2);

mod bar;

#[macro_use]
extern crate foo;

//- /bar.rs
structs!(Baz);
crate::structs!(MacroNotResolved3);

//- /lib.rs crate:foo
#[macro_export]
macro_rules! structs {
    ($i:ident) => { struct $i; }
}

macro_rules! structs_not_exported {
    ($i:ident) => { struct $i; }
}

mod priv_mod {
    #[macro_export]
    macro_rules! structs_priv {
        ($i:ident) => { struct $i; }
    }
}
"#,
        expect![[r#"
            crate
            - Bar : type value
            - Foo : type value
            - bar : type
            - foo : type (extern)

            crate::bar
            - Baz : type value
        "#]],
    );
}

#[test]
fn macro_use_filter() {
    check(
        r#"
//- /main.rs crate:main deps:empty,multiple,all
#[macro_use()]
extern crate empty;

foo_not_imported!();

#[macro_use(bar1)]
#[macro_use()]
#[macro_use(bar2, bar3)]
extern crate multiple;

bar1!();
bar2!();
bar3!();
bar_not_imported!();

#[macro_use(baz1)]
#[macro_use]
#[macro_use(baz2)]
extern crate all;

baz1!();
baz2!();
baz3!();

//- /empty.rs crate:empty
#[macro_export]
macro_rules! foo_not_imported { () => { struct NotOkFoo; } }

//- /multiple.rs crate:multiple
#[macro_export]
macro_rules! bar1 { () => { struct OkBar1; } }
#[macro_export]
macro_rules! bar2 { () => { struct OkBar2; } }
#[macro_export]
macro_rules! bar3 { () => { struct OkBar3; } }
#[macro_export]
macro_rules! bar_not_imported { () => { struct NotOkBar; } }

//- /all.rs crate:all
#[macro_export]
macro_rules! baz1 { () => { struct OkBaz1; } }
#[macro_export]
macro_rules! baz2 { () => { struct OkBaz2; } }
#[macro_export]
macro_rules! baz3 { () => { struct OkBaz3; } }
"#,
        expect![[r#"
            crate
            - OkBar1 : type value
            - OkBar2 : type value
            - OkBar3 : type value
            - OkBaz1 : type value
            - OkBaz2 : type value
            - OkBaz3 : type value
            - all : type (extern)
            - empty : type (extern)
            - multiple : type (extern)
        "#]],
    );
}

#[test]
fn prelude_is_macro_use() {
    cov_mark::check!(prelude_is_macro_use);
    check(
        r#"
//- /main.rs edition:2018 crate:main deps:std
structs!(Foo);
structs_priv!(Bar);
structs_outside!(Out);
crate::structs!(MacroNotResolved2);

mod bar;

//- /bar.rs
structs!(Baz);
crate::structs!(MacroNotResolved3);

//- /lib.rs crate:std
pub mod prelude {
    pub mod rust_2018 {
        #[macro_export]
        macro_rules! structs {
            ($i:ident) => { struct $i; }
        }

        mod priv_mod {
            #[macro_export]
            macro_rules! structs_priv {
                ($i:ident) => { struct $i; }
            }
        }
    }
}

#[macro_export]
macro_rules! structs_outside {
    ($i:ident) => { struct $i; }
}
"#,
        expect![[r#"
            crate
            - Bar : type value
            - Foo : type value
            - Out : type value
            - bar : type

            crate::bar
            - Baz : type value
        "#]],
    );
}

#[test]
fn prelude_cycle() {
    check(
        r#"
#[prelude_import]
use self::prelude::*;

declare_mod!();

mod prelude {
    macro_rules! declare_mod {
        () => (mod foo {})
    }
}
"#,
        expect![[r#"
            crate
            - prelude : type

            crate::prelude
            - (legacy) declare_mod : macro!
        "#]],
    );
}

#[test]
fn legacy_macro_use_before_def() {
    check(
        r#"
m!();

macro_rules! m {
    () => {
        struct S;
    }
}
"#,
        expect![[r#"
            crate
            - S : type value
            - (legacy) m : macro!
        "#]],
    );
    // FIXME: should not expand. legacy macro scoping is not implemented.
}

#[test]
fn plain_macros_are_legacy_textual_scoped() {
    check(
        r#"
//- /main.rs
mod m1;
bar!(NotFoundNotMacroUse);

mod m2 { foo!(NotFoundBeforeInside2); }

macro_rules! foo {
    ($x:ident) => { struct $x; }
}
foo!(Ok);

mod m3;
foo!(OkShadowStop);
bar!(NotFoundMacroUseStop);

#[macro_use]
mod m5 {
    #[macro_use]
    mod m6 {
        macro_rules! foo {
            ($x:ident) => { fn $x() {} }
        }
    }
}
foo!(ok_double_macro_use_shadow);

baz!(NotFoundBefore);
#[macro_use]
mod m7 {
    macro_rules! baz {
        ($x:ident) => { struct $x; }
    }
}
baz!(OkAfter);

//- /m1.rs
foo!(NotFoundBeforeInside1);
macro_rules! bar {
    ($x:ident) => { struct $x; }
}

//- /m3/mod.rs
foo!(OkAfterInside);
macro_rules! foo {
    ($x:ident) => { fn $x() {} }
}
foo!(ok_shadow);

#[macro_use]
mod m4;
bar!(OkMacroUse);

mod m5;
baz!(OkMacroUseInner);

//- /m3/m4.rs
foo!(ok_shadow_deep);
macro_rules! bar {
    ($x:ident) => { struct $x; }
}
//- /m3/m5.rs
#![macro_use]
macro_rules! baz {
    ($x:ident) => { struct $x; }
}


"#,
        expect![[r#"
            crate
            - NotFoundBefore : type value
            - Ok : type value
            - OkAfter : type value
            - OkShadowStop : type value
            - m1 : type
            - m2 : type
            - m3 : type
            - m5 : type
            - m7 : type
            - ok_double_macro_use_shadow : value
            - (legacy) baz : macro!
            - (legacy) foo : macro! macro! macro!

            crate::m1
            - (legacy) bar : macro!

            crate::m2

            crate::m3
            - OkAfterInside : type value
            - OkMacroUse : type value
            - OkMacroUseInner : type value
            - m4 : type
            - m5 : type
            - ok_shadow : value
            - (legacy) bar : macro! macro!
            - (legacy) baz : macro!
            - (legacy) foo : macro! macro! macro! macro!

            crate::m3::m4
            - ok_shadow_deep : value
            - (legacy) bar : macro!
            - (legacy) foo : macro! macro!

            crate::m3::m5
            - (legacy) bar : macro!
            - (legacy) baz : macro!
            - (legacy) foo : macro! macro! macro!

            crate::m5
            - m6 : type
            - (legacy) foo : macro! macro!

            crate::m5::m6
            - (legacy) foo : macro! macro!

            crate::m7
            - (legacy) baz : macro!
            - (legacy) foo : macro! macro!
        "#]],
    );
    // FIXME: should not see `NotFoundBefore`
}

#[test]
fn type_value_macro_live_in_different_scopes() {
    check(
        r#"
#[macro_export]
macro_rules! foo {
    ($x:ident) => { type $x = (); }
}

foo!(foo);
use foo as bar;

use self::foo as baz;
fn baz() {}
"#,
        expect![[r#"
            crate
            - bar : type (import) macro! (import)
            - baz : type (import) value macro! (import)
            - foo : type macro!
            - (legacy) foo : macro!
        "#]],
    );
}

#[test]
fn macro_use_can_be_aliased() {
    check(
        r#"
//- /main.rs crate:main deps:foo
#[macro_use]
extern crate foo;

foo!(Direct);
bar!(Alias);

//- /lib.rs crate:foo
use crate::foo as bar;

mod m {
    #[macro_export]
    macro_rules! foo {
        ($x:ident) => { struct $x; }
    }
}
"#,
        expect![[r#"
            crate
            - Alias : type value
            - Direct : type value
            - foo : type (extern)
        "#]],
    );
}

#[test]
fn path_qualified_macros() {
    check(
        r#"
macro_rules! foo {
    ($x:ident) => { struct $x; }
}

crate::foo!(NotResolved);

crate::bar!(OkCrate);
bar!(OkPlain);
alias1!(NotHere);
m::alias1!(OkAliasPlain);
m::alias2!(OkAliasSuper);
m::alias3!(OkAliasCrate);
not_found!(NotFound);

mod m {
    #[macro_export]
    macro_rules! bar {
        ($x:ident) => { struct $x; }
    }
    pub use bar as alias1;
    pub use super::bar as alias2;
    pub use crate::bar as alias3;
    pub use self::bar as not_found;
}
"#,
        expect![[r#"
            crate
            - OkAliasCrate : type value
            - OkAliasPlain : type value
            - OkAliasSuper : type value
            - OkCrate : type value
            - OkPlain : type value
            - bar : macro!
            - m : type
            - (legacy) foo : macro!

            crate::m
            - alias1 : macro! (import)
            - alias2 : macro! (import)
            - alias3 : macro! (import)
            - not_found : _
            - (legacy) bar : macro!
            - (legacy) foo : macro!
        "#]],
    );
}

#[test]
fn macro_dollar_crate_is_correct_in_item() {
    cov_mark::check!(macro_dollar_crate_self);
    check(
        r#"
//- /main.rs crate:main deps:foo
#[macro_use]
extern crate foo;

#[macro_use]
mod m {
    macro_rules! current {
        () => {
            use $crate::Foo as FooSelf;
        }
    }
}

struct Foo;

current!();
not_current1!();
foo::not_current2!();

//- /lib.rs crate:foo
mod m {
    #[macro_export]
    macro_rules! not_current1 {
        () => {
            use $crate::Bar;
        }
    }
}

#[macro_export]
macro_rules! not_current2 {
    () => {
        use $crate::Baz;
    }
}

pub struct Bar;
pub struct Baz;
"#,
        expect![[r#"
            crate
            - Bar : type (import) value (import)
            - Baz : type (import) value (import)
            - Foo : type value
            - FooSelf : type (import) value (import)
            - foo : type (extern)
            - m : type
            - (legacy) current : macro!

            crate::m
            - (legacy) current : macro!
        "#]],
    );
}

#[test]
fn macro_dollar_crate_is_correct_in_indirect_deps() {
    cov_mark::check!(macro_dollar_crate_other);
    // From std
    check(
        r#"
//- /main.rs edition:2018 crate:main deps:std
foo!();

//- /std.rs crate:std deps:core
pub use core::foo;

pub mod prelude {
    pub mod rust_2018 {}
}

#[macro_use]
mod std_macros;

//- /core.rs crate:core
#[macro_export]
macro_rules! foo {
    () => {
        use $crate::bar;
    }
}

pub struct bar;
"#,
        expect![[r#"
            crate
            - bar : type (import) value (import)
        "#]],
    );
}

#[test]
fn macro_dollar_crate_is_correct_in_derive_meta() {
    compute_crate_def_map(
        r#"
//- minicore: derive, clone
//- /main.rs crate:main deps:lib
lib::foo!();

//- /lib.rs crate:lib
#[macro_export]
macro_rules! foo {
    () => {
        #[derive($crate::Clone)]
        struct S;
    }
}

pub use core::clone::Clone;
"#,
        |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1),
    );
}

#[test]
fn expand_derive() {
    compute_crate_def_map(
        r#"
//- /main.rs crate:main deps:core
use core::Copy;

#[core::derive(Copy, core::Clone)]
struct Foo;

//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro derive($item:item) {}
#[rustc_builtin_macro]
pub macro Copy {}
#[rustc_builtin_macro]
pub macro Clone {}
"#,
        |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 2),
    );
}

#[test]
fn resolve_builtin_derive() {
    check(
        r#"
//- /main.rs crate:main deps:core
use core::*;

//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro Clone {}

pub trait Clone {}
"#,
        expect![[r#"
            crate
            - Clone : type (glob) macro# (glob)
        "#]],
    );
}

#[test]
fn builtin_derive_with_unresolved_attributes_fall_back() {
    // Tests that we still resolve derives after ignoring an unresolved attribute.
    cov_mark::check!(unresolved_attribute_fallback);
    compute_crate_def_map(
        r#"
//- /main.rs crate:main deps:core
use core::{Clone, derive};

#[derive(Clone)]
#[unresolved]
struct Foo;

//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro derive($item:item) {}
#[rustc_builtin_macro]
pub macro Clone {}
"#,
        |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1),
    );
}

#[test]
fn unresolved_attributes_fall_back_track_per_file_moditems() {
    // Tests that we track per-file ModItems when ignoring an unresolved attribute.
    // Just tracking the `ModItem` leads to `Foo` getting ignored.

    check(
        r#"
        //- /main.rs crate:main

        mod submod;

        #[unresolved]
        struct Foo;

        //- /submod.rs
        #[unresolved]
        struct Bar;
        "#,
        expect![[r#"
            crate
            - Foo : type value
            - submod : type

            crate::submod
            - Bar : type value
        "#]],
    );
}

#[test]
fn unresolved_attrs_extern_block_hang() {
    // Regression test for https://github.com/rust-lang/rust-analyzer/issues/8905
    check(
        r#"
#[unresolved]
extern "C" {
    #[unresolved]
    fn f();
}
    "#,
        expect![[r#"
            crate
            - f : value
        "#]],
    );
}

#[test]
fn macros_in_extern_block() {
    check(
        r#"
macro_rules! m {
    () => { static S: u8; };
}

extern {
    m!();
}
    "#,
        expect![[r#"
            crate
            - S : value
            - (legacy) m : macro!
        "#]],
    );
}

#[test]
fn resolves_derive_helper() {
    cov_mark::check!(resolved_derive_helper);
    check(
        r#"
//- /main.rs crate:main deps:proc
#[rustc_builtin_macro]
pub macro derive($item:item) {}

#[derive(proc::Derive)]
#[helper]
#[unresolved]
struct S;

//- /proc.rs crate:proc
#![crate_type="proc-macro"]
#[proc_macro_derive(Derive, attributes(helper))]
fn derive() {}
        "#,
        expect![[r#"
            crate
            - S : type value
            - derive : macro#
        "#]],
    );
}

#[test]
fn resolves_derive_helper_rustc_builtin_macro() {
    cov_mark::check!(resolved_derive_helper);
    // This is NOT the correct usage of `default` helper attribute, but we don't resolve helper
    // attributes on non mod items in hir nameres.
    check(
        r#"
//- minicore: derive, default
#[derive(Default)]
#[default]
enum E {
    A,
    B,
}
"#,
        expect![[r#"
            crate
            - E : type
        "#]],
    );
}

#[test]
fn unresolved_attr_with_cfg_attr_hang() {
    // Another regression test for https://github.com/rust-lang/rust-analyzer/issues/8905
    check(
        r#"
#[cfg_attr(not(off), unresolved, unresolved)]
struct S;
        "#,
        expect![[r#"
            crate
            - S : type value
        "#]],
    );
}

#[test]
fn macro_expansion_overflow() {
    cov_mark::check!(macro_expansion_overflow);
    check(
        r#"
macro_rules! a {
    ($e:expr; $($t:tt)*) => {
        b!(static = (); $($t)*);
    };
    () => {};
}

macro_rules! b {
    (static = $e:expr; $($t:tt)*) => {
        a!($e; $($t)*);
    };
    () => {};
}

b! { static = #[] ();}
"#,
        expect![[r#"
            crate
            - (legacy) a : macro!
            - (legacy) b : macro!
        "#]],
    );
}

#[test]
fn macros_defining_macros() {
    check(
        r#"
macro_rules! item {
    ($item:item) => { $item }
}

item! {
    macro_rules! indirect_macro { () => { struct S {} } }
}

indirect_macro!();
    "#,
        expect![[r#"
            crate
            - S : type
            - (legacy) indirect_macro : macro!
            - (legacy) item : macro!
        "#]],
    );
}

#[test]
fn resolves_proc_macros() {
    check(
        r#"
#![crate_type="proc-macro"]
struct TokenStream;

#[proc_macro]
pub fn function_like_macro(args: TokenStream) -> TokenStream {
    args
}

#[proc_macro_attribute]
pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream {
    item
}

#[proc_macro_derive(DummyTrait)]
pub fn derive_macro(_item: TokenStream) -> TokenStream {
    TokenStream
}

#[proc_macro_derive(AnotherTrait, attributes(helper_attr))]
pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
    TokenStream
}
"#,
        expect![[r#"
            crate
            - AnotherTrait : macro#
            - DummyTrait : macro#
            - TokenStream : type value
            - attribute_macro : macro#
            - function_like_macro : macro!
        "#]],
    );
}

#[test]
fn proc_macro_censoring() {
    // Make sure that only proc macros are publicly exported from proc-macro crates.

    check(
        r#"
//- /main.rs crate:main deps:macros
pub use macros::*;

//- /macros.rs crate:macros
#![crate_type="proc-macro"]
pub struct TokenStream;

#[proc_macro]
pub fn function_like_macro(args: TokenStream) -> TokenStream {
    args
}

#[proc_macro_attribute]
pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream {
    item
}

#[proc_macro_derive(DummyTrait)]
pub fn derive_macro(_item: TokenStream) -> TokenStream {
    TokenStream
}

#[macro_export]
macro_rules! mbe {
    () => {};
}
"#,
        expect![[r#"
            crate
            - DummyTrait : macro# (glob)
            - attribute_macro : macro# (glob)
            - function_like_macro : macro! (glob)
        "#]],
    );
}

#[test]
fn collects_derive_helpers() {
    let db = TestDB::with_files(
        r#"
#![crate_type="proc-macro"]
struct TokenStream;

#[proc_macro_derive(AnotherTrait, attributes(helper_attr))]
pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
    TokenStream
}
"#,
    );
    let krate = *all_crates(&db).last().expect("no crate graph present");
    let def_map = crate_def_map(&db, krate);

    assert_eq!(def_map.data.exported_derives.len(), 1);
    match def_map.data.exported_derives.values().next() {
        Some(helpers) => match &**helpers {
            [attr] => assert_eq!(attr.display(&db, Edition::CURRENT).to_string(), "helper_attr"),
            _ => unreachable!(),
        },
        _ => unreachable!(),
    }
}

#[test]
fn resolve_macro_def() {
    check(
        r#"
pub macro structs($($i:ident),*) {
    $(struct $i { field: u32 } )*
}
structs!(Foo);
"#,
        expect![[r#"
            crate
            - Foo : type
            - structs : macro!
        "#]],
    );
}

#[test]
fn macro_in_prelude() {
    check(
        r#"
//- /lib.rs edition:2018 crate:lib deps:std
global_asm!();

//- /std.rs crate:std
pub mod prelude {
    pub mod rust_2018 {
        pub macro global_asm() {
            pub struct S;
        }
    }
}
        "#,
        expect![[r#"
            crate
            - S : type value
        "#]],
    )
}

#[test]
fn issue9358_bad_macro_stack_overflow() {
    cov_mark::check!(issue9358_bad_macro_stack_overflow);
    check(
        r#"
macro_rules! m {
  ($cond:expr) => { m!($cond, stringify!($cond)) };
  ($cond:expr, $($arg:tt)*) => { $cond };
}
m!(
"#,
        expect![[r#"
            crate
            - (legacy) m : macro!
        "#]],
    )
}

#[test]
fn eager_macro_correctly_resolves_contents() {
    // Eager macros resolve any contained macros when expanded. This should work correctly with the
    // usual name resolution rules, so both of these `include!`s should include the right file.

    check(
        r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }

include!(inner_a!());
include!(crate::inner_b!());

#[macro_export]
macro_rules! inner_a {
    () => { "inc_a.rs" };
}
#[macro_export]
macro_rules! inner_b {
    () => { "inc_b.rs" };
}
//- /inc_a.rs
struct A;
//- /inc_b.rs
struct B;
"#,
        expect![[r#"
            crate
            - A : type value
            - B : type value
            - inner_a : macro!
            - inner_b : macro!
            - (legacy) include : macro!
            - (legacy) inner_a : macro!
            - (legacy) inner_b : macro!
        "#]],
    );
}

#[test]
fn eager_macro_correctly_resolves_dollar_crate() {
    // MBE -> eager -> $crate::mbe
    check(
        r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }

#[macro_export]
macro_rules! inner {
    () => { "inc.rs" };
}

macro_rules! m {
    () => { include!($crate::inner!()); };
}

m!();

//- /inc.rs
struct A;
"#,
        expect![[r#"
            crate
            - A : type value
            - inner : macro!
            - (legacy) include : macro!
            - (legacy) inner : macro!
            - (legacy) m : macro!
        "#]],
    );
    // eager -> MBE -> $crate::mbe
    check(
        r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }

#[macro_export]
macro_rules! inner {
    () => { "inc.rs" };
}

macro_rules! n {
    () => {
        $crate::inner!()
    };
}

include!(n!());

//- /inc.rs
struct A;
"#,
        expect![[r#"
            crate
            - A : type value
            - inner : macro!
            - (legacy) include : macro!
            - (legacy) inner : macro!
            - (legacy) n : macro!
        "#]],
    );
}

#[test]
fn nested_include() {
    check(
        r#"
//- minicore: include
//- /lib.rs
include!("out_dir/includes.rs");

//- /out_dir/includes.rs
pub mod company_name {
    pub mod network {
        pub mod v1 {
            include!("company_name.network.v1.rs");
        }
    }
}
//- /out_dir/company_name.network.v1.rs
pub struct IpAddress {
    pub ip_type: &'static str,
}
/// Nested message and enum types in `IpAddress`.
pub mod ip_address {
    pub enum IpType {
        IpV4(u32),
    }
}

"#,
        expect![[r#"
            crate
            - company_name : type

            crate::company_name
            - network : type

            crate::company_name::network
            - v1 : type

            crate::company_name::network::v1
            - IpAddress : type
            - ip_address : type

            crate::company_name::network::v1::ip_address
            - IpType : type
        "#]],
    );
}

#[test]
fn include_with_submod_file() {
    check(
        r#"
//- minicore: include
//- /lib.rs
include!("out_dir/includes.rs");

//- /out_dir/includes.rs
pub mod company_name {
    pub mod network {
        pub mod v1;
    }
}
//- /out_dir/company_name/network/v1.rs
pub struct IpAddress {
    pub ip_type: &'static str,
}
/// Nested message and enum types in `IpAddress`.
pub mod ip_address {
    pub enum IpType {
        IpV4(u32),
    }
}

"#,
        expect![[r#"
            crate
            - company_name : type

            crate::company_name
            - network : type

            crate::company_name::network
            - v1 : type

            crate::company_name::network::v1
            - IpAddress : type
            - ip_address : type

            crate::company_name::network::v1::ip_address
            - IpType : type
        "#]],
    );
}

#[test]
fn include_many_mods() {
    check(
        r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }

mod nested {
    include!("out_dir/includes.rs");

    mod different_company {
        include!("out_dir/different_company/mod.rs");
    }

    mod util;
}

//- /nested/util.rs
pub struct Helper {}
//- /out_dir/includes.rs
pub mod company_name {
    pub mod network {
        pub mod v1;
    }
}
//- /out_dir/company_name/network/v1.rs
pub struct IpAddress {}
//- /out_dir/different_company/mod.rs
pub mod network;
//- /out_dir/different_company/network.rs
pub struct Url {}

"#,
        expect![[r#"
            crate
            - nested : type
            - (legacy) include : macro!

            crate::nested
            - company_name : type
            - different_company : type
            - util : type
            - (legacy) include : macro!

            crate::nested::company_name
            - network : type
            - (legacy) include : macro!

            crate::nested::company_name::network
            - v1 : type
            - (legacy) include : macro!

            crate::nested::company_name::network::v1
            - IpAddress : type
            - (legacy) include : macro!

            crate::nested::different_company
            - network : type
            - (legacy) include : macro!

            crate::nested::different_company::network
            - Url : type
            - (legacy) include : macro!

            crate::nested::util
            - Helper : type
            - (legacy) include : macro!
        "#]],
    );
}

#[test]
fn macro_use_imports_all_macro_types() {
    let db = TestDB::with_files(
        r#"
//- /main.rs crate:main deps:lib
#[macro_use]
extern crate lib;

//- /lib.rs crate:lib deps:proc
pub use proc::*;

#[macro_export]
macro_rules! legacy { () => () }

pub macro macro20 {}

//- /proc.rs crate:proc
#![crate_type="proc-macro"]

struct TokenStream;

#[proc_macro_attribute]
fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a }
    "#,
    );
    let krate = *all_crates(&db).last().expect("no crate graph present");
    let def_map = crate_def_map(&db, krate);

    let root_module = &def_map[def_map.root].scope;
    assert!(
        root_module.legacy_macros().count() == 0,
        "`#[macro_use]` shouldn't bring macros into textual macro scope",
    );

    let actual = def_map
        .macro_use_prelude
        .keys()
        .map(|name| name.display(&db, Edition::CURRENT).to_string())
        .sorted()
        .join("\n");

    expect![[r#"
        legacy
        macro20
        proc_attr"#]]
    .assert_eq(&actual);
}

#[test]
fn non_prelude_macros_take_precedence_over_macro_use_prelude() {
    check(
        r#"
//- /lib.rs edition:2021 crate:lib deps:dep,core
#[macro_use]
extern crate dep;

macro foo() { struct Ok; }
macro bar() { fn ok() {} }

foo!();
bar!();

//- /dep.rs crate:dep
#[macro_export]
macro_rules! foo {
    () => { struct NotOk; }
}

//- /core.rs crate:core
pub mod prelude {
    pub mod rust_2021 {
        #[macro_export]
        macro_rules! bar {
            () => { fn not_ok() {} }
        }
    }
}
        "#,
        expect![[r#"
            crate
            - Ok : type value
            - bar : macro!
            - dep : type (extern)
            - foo : macro!
            - ok : value
        "#]],
    );
}

#[test]
fn macro_use_prelude_is_eagerly_expanded() {
    // See FIXME in `ModCollector::collect_macro_call()`.
    check(
        r#"
//- /main.rs crate:main deps:lib
#[macro_use]
extern crate lib;
mk_foo!();
mod a {
    foo!();
}
//- /lib.rs crate:lib
#[macro_export]
macro_rules! mk_foo {
    () => {
        macro_rules! foo {
            () => { struct Ok; }
        }
    }
}
    "#,
        expect![[r#"
            crate
            - a : type
            - lib : type (extern)
            - (legacy) foo : macro!

            crate::a
            - Ok : type value
            - (legacy) foo : macro!
        "#]],
    );
}

#[test]
fn macro_sub_namespace() {
    compute_crate_def_map(
        r#"
//- minicore: derive, clone
macro_rules! Clone { () => {} }
macro_rules! derive { () => {} }

#[derive(Clone)]
struct S;
    "#,
        |map| assert_eq!(map.modules[map.root].scope.builtin_derive_impls().len(), 1),
    );
}

#[test]
fn macro_sub_namespace2() {
    check(
        r#"
//- /main.rs edition:2021 crate:main deps:proc,core
use proc::{foo, bar};

foo!();
bar!();

//- /proc.rs crate:proc
#![crate_type="proc-macro"]
#[proc_macro_derive(foo)]
pub fn foo() {}
#[proc_macro_attribute]
pub fn bar() {}

//- /core.rs crate:core
pub mod prelude {
    pub mod rust_2021 {
        pub macro foo() {
            struct Ok;
        }
        pub macro bar() {
            fn ok() {}
        }
    }
}
    "#,
        expect![[r#"
            crate
            - Ok : type value
            - bar : macro# (import)
            - foo : macro# (import)
            - ok : value
        "#]],
    );
}

#[test]
fn macro_rules_mixed_style() {
    check(
        r#"

macro_rules! foo {
             () => {};
      attr() () => {};
    derive() () => {};
}

use foo;
"#,
        expect![[r#"
    crate
    - foo : macro!# (import)
    - (legacy) foo : macro!#
"#]],
    );
}

#[test]
fn macro_2_mixed_style() {
    check(
        r#"

macro foo {
             () => {};
      attr() () => {};
    derive() () => {};
}

use foo;
"#,
        expect![[r#"
            crate
            - foo : macro!#
        "#]],
    );
}

#[test]
fn macro_rules_attr() {
    check(
        r#"

macro_rules! my_attr {
    attr() ($($tt:tt)*) => { fn attr_fn() {} }
}

#[my_attr]
enum MyEnum {}

"#,
        expect![[r#"
    crate
    - attr_fn : value
    - (legacy) my_attr : macro#
"#]],
    );
}

#[test]
fn macro_2_attr() {
    check(
        r#"

macro my_attr {
    attr() ($($tt:tt)*) => { fn attr_fn() {} }
}

#[my_attr]
enum MyEnum {}

"#,
        expect![[r#"
    crate
    - attr_fn : value
    - my_attr : macro#
"#]],
    );
}

#[test]
fn macro_rules_derive() {
    check(
        r#"
//- minicore: derive

macro_rules! MyDerive {
    derive() ($($tt:tt)*) => { fn derived_fn() {} }
}

#[derive(MyDerive)]
enum MyEnum {}

"#,
        expect![[r#"
            crate
            - MyEnum : type
            - derived_fn : value
            - (legacy) MyDerive : macro#
        "#]],
    );
}

#[test]
fn macro_2_derive() {
    check(
        r#"
//- minicore: derive

macro MyDerive {
    derive() ($($tt:tt)*) => { fn derived_fn() {} }
}

#[derive(MyDerive)]
enum MyEnum {}

"#,
        expect![[r#"
            crate
            - MyDerive : macro#
            - MyEnum : type
            - derived_fn : value
        "#]],
    );
}
