diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs | 1187 |
1 files changed, 1187 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs new file mode 100644 index 000000000..3ece1379a --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -0,0 +1,1187 @@ +use super::*; +use itertools::Itertools; + +#[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: t + nested: t + + crate::nested + Bar: t + Baz: t + "#]], + ); +} + +#[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: t + n1: t + + crate::m + n3: t + + crate::m::n3 + Y: t v + + crate::n1 + n2: t + + crate::n1::n2 + X: t v + "#]], + ); +} + +#[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: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[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: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[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: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[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: t + bar: m + foo: m + "#]], + ); +} + +#[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: t v + Foo: t v + bar: t + foo: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[test] +fn prelude_is_macro_use() { + cov_mark::check!(prelude_is_macro_use); + check( + r#" +//- /main.rs 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: t v + Foo: t v + Out: t v + bar: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[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: t + + crate::prelude + "#]], + ); +} + +#[test] +fn legacy_macro_use_before_def() { + check( + r#" +m!(); + +macro_rules! m { + () => { + struct S; + } +} +"#, + expect![[r#" + crate + S: t v + "#]], + ); + // 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: t v + Ok: t v + OkAfter: t v + OkShadowStop: t v + m1: t + m2: t + m3: t + m5: t + m7: t + ok_double_macro_use_shadow: v + + crate::m1 + + crate::m2 + + crate::m3 + OkAfterInside: t v + OkMacroUse: t v + OkMacroUseInner: t v + m4: t + m5: t + ok_shadow: v + + crate::m3::m4 + ok_shadow_deep: v + + crate::m3::m5 + + crate::m5 + m6: t + + crate::m5::m6 + + crate::m7 + "#]], + ); + // 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: t m + baz: t v m + foo: t m + "#]], + ); +} + +#[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: t v + Direct: t v + foo: t + "#]], + ); +} + +#[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: t v + OkAliasPlain: t v + OkAliasSuper: t v + OkCrate: t v + OkPlain: t v + bar: m + m: t + + crate::m + alias1: m + alias2: m + alias3: m + not_found: _ + "#]], + ); +} + +#[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: t v + Baz: t v + Foo: t v + FooSelf: t v + foo: t + m: t + + crate::m + "#]], + ); +} + +#[test] +fn macro_dollar_crate_is_correct_in_indirect_deps() { + cov_mark::check!(macro_dollar_crate_other); + // From std + check( + r#" +//- /main.rs 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: t v + "#]], + ); +} + +#[test] +fn expand_derive() { + let map = 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 {} +"#, + ); + assert_eq!(map.modules[map.root].scope.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: t m + "#]], + ); +} + +#[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); + let map = 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 {} +"#, + ); + assert_eq!(map.modules[map.root].scope.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: t v + submod: t + + crate::submod + Bar: t v + "#]], + ); +} + +#[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: v + "#]], + ); +} + +#[test] +fn macros_in_extern_block() { + check( + r#" +macro_rules! m { + () => { static S: u8; }; +} + +extern { + m!(); +} + "#, + expect![[r#" + crate + S: v + "#]], + ); +} + +#[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: t v + derive: m + "#]], + ); +} + +#[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: t v + "#]], + ); +} + +#[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 + "#]], + ); +} + +#[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: t + "#]], + ); +} + +#[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: m + DummyTrait: m + TokenStream: t v + attribute_macro: v m + derive_macro: v + derive_macro_2: v + function_like_macro: v m + "#]], + ); +} + +#[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: m + attribute_macro: m + function_like_macro: m + "#]], + ); +} + +#[test] +fn collects_derive_helpers() { + let def_map = compute_crate_def_map( + r#" +#![crate_type="proc-macro"] +struct TokenStream; + +#[proc_macro_derive(AnotherTrait, attributes(helper_attr))] +pub fn derive_macro_2(_item: TokenStream) -> TokenStream { + TokenStream +} +"#, + ); + + assert_eq!(def_map.exported_derives.len(), 1); + match def_map.exported_derives.values().next() { + Some(helpers) => match &**helpers { + [attr] => assert_eq!(attr.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: t + structs: m + "#]], + ); +} + +#[test] +fn macro_in_prelude() { + check( + r#" +//- /lib.rs 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: t v + "#]], + ) +} + +#[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 + "#]], + ) +} + +#[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: t v + B: t v + inner_a: m + inner_b: m + "#]], + ); +} + +#[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: t v + inner: m + "#]], + ); + // 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: t v + inner: m + "#]], + ); +} + +#[test] +fn macro_use_imports_all_macro_types() { + let def_map = compute_crate_def_map( + 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 root = &def_map[def_map.root()].scope; + let actual = root + .legacy_macros() + .sorted_by(|a, b| std::cmp::Ord::cmp(&a.0, &b.0)) + .map(|(name, _)| format!("{name}\n")) + .collect::<String>(); + + expect![[r#" + legacy + macro20 + proc_attr + "#]] + .assert_eq(&actual); +} |