//! Tests specific to declarative macros, aka macros by example. This covers //! both stable `macro_rules!` macros as well as unstable `macro` macros. mod matching; mod meta_syntax; mod metavar_expr; mod regression; mod tt_conversion; use expect_test::expect; use crate::macro_expansion_tests::check; #[test] fn token_mapping_smoke_test() { check( r#" macro_rules! f { ( struct $ident:ident ) => { struct $ident { map: ::std::collections::HashSet<()>, } }; } // +spans+syntaxctxt f!(struct MyTraitMap2); "#, expect![[r#" macro_rules! f { ( struct $ident:ident ) => { struct $ident { map: ::std::collections::HashSet<()>, } }; } struct#0:MacroRules[BE8F, 0]@58..64#17408# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#17408# map#0:MacroRules[BE8F, 0]@86..89#17408#:#0:MacroRules[BE8F, 0]@89..90#17408# #0:MacroRules[BE8F, 0]@89..90#17408#::#0:MacroRules[BE8F, 0]@91..93#17408#std#0:MacroRules[BE8F, 0]@93..96#17408#::#0:MacroRules[BE8F, 0]@96..98#17408#collections#0:MacroRules[BE8F, 0]@98..109#17408#::#0:MacroRules[BE8F, 0]@109..111#17408#HashSet#0:MacroRules[BE8F, 0]@111..118#17408#<#0:MacroRules[BE8F, 0]@118..119#17408#(#0:MacroRules[BE8F, 0]@119..120#17408#)#0:MacroRules[BE8F, 0]@120..121#17408#>#0:MacroRules[BE8F, 0]@121..122#17408#,#0:MacroRules[BE8F, 0]@122..123#17408# }#0:MacroRules[BE8F, 0]@132..133#17408# "#]], ); } #[test] fn token_mapping_floats() { // Regression test for https://github.com/rust-lang/rust-analyzer/issues/12216 // (and related issues) check( r#" // +spans+syntaxctxt macro_rules! f { ($($tt:tt)*) => { $($tt)* }; } // +spans+syntaxctxt f! { fn main() { 1; 1.0; ((1,),).0.0; let x = 1; } } "#, expect![[r#" // +spans+syntaxctxt macro_rules! f { ($($tt:tt)*) => { $($tt)* }; } fn#0:MacroCall[BE8F, 0]@30..32#ROOT2024# main#0:MacroCall[BE8F, 0]@33..37#ROOT2024#(#0:MacroCall[BE8F, 0]@37..38#ROOT2024#)#0:MacroCall[BE8F, 0]@38..39#ROOT2024# {#0:MacroCall[BE8F, 0]@40..41#ROOT2024# 1#0:MacroCall[BE8F, 0]@50..51#ROOT2024#;#0:MacroCall[BE8F, 0]@51..52#ROOT2024# 1.0#0:MacroCall[BE8F, 0]@61..64#ROOT2024#;#0:MacroCall[BE8F, 0]@64..65#ROOT2024# (#0:MacroCall[BE8F, 0]@74..75#ROOT2024#(#0:MacroCall[BE8F, 0]@75..76#ROOT2024#1#0:MacroCall[BE8F, 0]@76..77#ROOT2024#,#0:MacroCall[BE8F, 0]@77..78#ROOT2024# )#0:MacroCall[BE8F, 0]@78..79#ROOT2024#,#0:MacroCall[BE8F, 0]@79..80#ROOT2024# )#0:MacroCall[BE8F, 0]@80..81#ROOT2024#.#0:MacroCall[BE8F, 0]@81..82#ROOT2024#0#0:MacroCall[BE8F, 0]@82..85#ROOT2024#.#0:MacroCall[BE8F, 0]@82..85#ROOT2024#0#0:MacroCall[BE8F, 0]@82..85#ROOT2024#;#0:MacroCall[BE8F, 0]@85..86#ROOT2024# let#0:MacroCall[BE8F, 0]@95..98#ROOT2024# x#0:MacroCall[BE8F, 0]@99..100#ROOT2024# =#0:MacroCall[BE8F, 0]@101..102#ROOT2024# 1#0:MacroCall[BE8F, 0]@103..104#ROOT2024#;#0:MacroCall[BE8F, 0]@104..105#ROOT2024# }#0:MacroCall[BE8F, 0]@110..111#ROOT2024# "#]], ); } #[test] fn eager_expands_with_unresolved_within() { check( r#" #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { concat!("hello", identity!("world"), unresolved!(), identity!("!")); } "#, expect![[r##" #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { /* error: unresolved macro unresolved */"helloworld!"; } "##]], ); } #[test] fn concat_spans() { check( r#" #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { concat/*+spans+syntaxctxt*/!("hello", concat!("w", identity!("o")), identity!("rld"), unresolved!(), identity!("!")); } } "#, expect![[r##" #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { #[rustc_builtin_macro] #[macro_export] macro_rules! concat {} macro_rules! identity { ($tt:tt) => { $tt } } fn main(foo: ()) { /* error: unresolved macro unresolved */"helloworld!"#0:Fn[15AE, 0]@236..321#ROOT2024#; } } "##]], ); } #[test] fn token_mapping_across_files() { check( r#" //- /lib.rs #[macro_use] mod foo; mk_struct/*+spans+syntaxctxt*/!(Foo with u32); //- /foo.rs macro_rules! mk_struct { ($foo:ident with $ty:ty) => { struct $foo($ty); } } "#, expect![[r#" #[macro_use] mod foo; struct#1:MacroRules[DB0C, 0]@59..65#17408# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#17408#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#17408#;#1:MacroRules[DB0C, 0]@75..76#17408# "#]], ); } #[test] fn float_field_access_macro_input() { check( r#" macro_rules! foo { ($expr:expr) => { fn foo() { $expr; } }; } foo!(x .0.1); foo!(x .2. 3); foo!(x .4 .5); "#, expect![[r#" macro_rules! foo { ($expr:expr) => { fn foo() { $expr; } }; } fn foo() { (x.0.1); } fn foo() { (x.2.3); } fn foo() { (x.4.5); } "#]], ); } #[test] fn mbe_smoke_test() { check( r#" macro_rules! impl_froms { ($e:ident: $($v:ident),*) => { $( impl From<$v> for $e { fn from(it: $v) -> $e { $e::$v(it) } } )* } } impl_froms!(TokenTree: Leaf, Subtree); "#, expect![[r#" macro_rules! impl_froms { ($e:ident: $($v:ident),*) => { $( impl From<$v> for $e { fn from(it: $v) -> $e { $e::$v(it) } } )* } } impl From for TokenTree { fn from(it: Leaf) -> TokenTree { TokenTree::Leaf(it) } } impl From for TokenTree { fn from(it: Subtree) -> TokenTree { TokenTree::Subtree(it) } } "#]], ); } #[test] fn wrong_nesting_level() { check( r#" macro_rules! m { ($($i:ident);*) => ($i) } m!{a} "#, expect![[r#" macro_rules! m { ($($i:ident);*) => ($i) } /* error: expected simple binding, found nested binding `i` */ "#]], ); } #[test] fn match_by_first_token_literally() { check( r#" macro_rules! m { ($i:ident) => ( enum $i {} ); (= $i:ident) => ( fn $i() {} ); (+ $i:ident) => ( struct $i; ) } m! { Foo } m! { = bar } m! { + Baz } "#, expect![[r#" macro_rules! m { ($i:ident) => ( enum $i {} ); (= $i:ident) => ( fn $i() {} ); (+ $i:ident) => ( struct $i; ) } enum Foo {} fn bar() {} struct Baz; "#]], ); } #[test] fn match_by_last_token_literally() { check( r#" macro_rules! m { ($i:ident) => ( enum $i {} ); ($i:ident =) => ( fn $i() {} ); ($i:ident +) => ( struct $i; ) } m! { Foo } m! { bar = } m! { Baz + } "#, expect![[r#" macro_rules! m { ($i:ident) => ( enum $i {} ); ($i:ident =) => ( fn $i() {} ); ($i:ident +) => ( struct $i; ) } enum Foo {} fn bar() {} struct Baz; "#]], ); } #[test] fn match_by_ident() { check( r#" macro_rules! m { ($i:ident) => ( enum $i {} ); (spam $i:ident) => ( fn $i() {} ); (eggs $i:ident) => ( struct $i; ) } m! { Foo } m! { spam bar } m! { eggs Baz } "#, expect![[r#" macro_rules! m { ($i:ident) => ( enum $i {} ); (spam $i:ident) => ( fn $i() {} ); (eggs $i:ident) => ( struct $i; ) } enum Foo {} fn bar() {} struct Baz; "#]], ); } #[test] fn match_by_separator_token() { check( r#" macro_rules! m { ($($i:ident),*) => ($(enum $i {} )*); ($($i:ident)#*) => ($(fn $i() {} )*); ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) } m! { Baz, Qux } m! { foo# bar } m! { Foo,# Bar } "#, expect![[r#" macro_rules! m { ($($i:ident),*) => ($(enum $i {} )*); ($($i:ident)#*) => ($(fn $i() {} )*); ($i:ident ,# $ j:ident) => ( struct $i; struct $ j; ) } enum Baz {} enum Qux {} fn foo() {} fn bar() {} struct Foo; struct Bar; "#]], ); } #[test] fn test_match_group_pattern_with_multiple_defs() { // FIXME: The pretty printer breaks by leaving whitespace here, +syntaxctxt is used to avoid that check( r#" macro_rules! m { ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); } // +syntaxctxt m! { foo, bar } "#, expect![[r#" macro_rules! m { ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); } impl#\17408# Bar#\17408# {#\17408# fn#\17408# foo#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# fn#\17408# bar#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# }#\17408# "#]], ); } #[test] fn test_match_group_pattern_with_multiple_statement() { check( r#" macro_rules! m { ($($i:ident),*) => ( fn baz() { $($i ();)* } ); } m! { foo, bar } "#, expect![[r#" macro_rules! m { ($($i:ident),*) => ( fn baz() { $($i ();)* } ); } fn baz() { foo(); bar(); } "#]], ) } #[test] fn test_match_group_pattern_with_multiple_statement_without_semi() { check( r#" macro_rules! m { ($($i:ident),*) => ( fn baz() { $($i() );* } ); } m! { foo, bar } "#, expect![[r#" macro_rules! m { ($($i:ident),*) => ( fn baz() { $($i() );* } ); } fn baz() { foo(); bar() } "#]], ) } #[test] fn test_match_group_empty_fixed_token() { check( r#" macro_rules! m { ($($i:ident)* #abc) => ( fn baz() { $($i ();)* } ); } m!{#abc} "#, expect![[r#" macro_rules! m { ($($i:ident)* #abc) => ( fn baz() { $($i ();)* } ); } fn baz() {} "#]], ) } #[test] fn test_match_group_in_subtree() { check( r#" macro_rules! m { (fn $name:ident { $($i:ident)* } ) => ( fn $name() { $($i ();)* } ); } m! { fn baz { a b } } "#, expect![[r#" macro_rules! m { (fn $name:ident { $($i:ident)* } ) => ( fn $name() { $($i ();)* } ); } fn baz() { a(); b(); } "#]], ) } #[test] fn test_expr_order() { check( r#" macro_rules! m { ($ i:expr) => { fn bar() { $ i * 3; } } } // +tree m! { 1 + 2 } "#, expect![[r#" macro_rules! m { ($ i:expr) => { fn bar() { $ i * 3; } } } fn bar() { (1+2)*3; } // MACRO_ITEMS@0..17 // FN@0..17 // FN_KW@0..2 "fn" // NAME@2..5 // IDENT@2..5 "bar" // PARAM_LIST@5..7 // L_PAREN@5..6 "(" // R_PAREN@6..7 ")" // BLOCK_EXPR@7..17 // STMT_LIST@7..17 // L_CURLY@7..8 "{" // EXPR_STMT@8..16 // BIN_EXPR@8..15 // PAREN_EXPR@8..13 // L_PAREN@8..9 "(" // BIN_EXPR@9..12 // LITERAL@9..10 // INT_NUMBER@9..10 "1" // PLUS@10..11 "+" // LITERAL@11..12 // INT_NUMBER@11..12 "2" // R_PAREN@12..13 ")" // STAR@13..14 "*" // LITERAL@14..15 // INT_NUMBER@14..15 "3" // SEMICOLON@15..16 ";" // R_CURLY@16..17 "}" "#]], ) } #[test] fn test_match_group_with_multichar_sep() { check( r#" macro_rules! m { (fn $name:ident { $($i:literal)* }) => ( fn $name() -> bool { $($i)&&* } ); } m! (fn baz { true false } ); "#, expect![[r#" macro_rules! m { (fn $name:ident { $($i:literal)* }) => ( fn $name() -> bool { $($i)&&* } ); } fn baz() -> bool { true && false } "#]], ); check( r#" macro_rules! m { (fn $name:ident { $($i:literal)&&* }) => ( fn $name() -> bool { $($i)&&* } ); } m! (fn baz { true && false } ); "#, expect![[r#" macro_rules! m { (fn $name:ident { $($i:literal)&&* }) => ( fn $name() -> bool { $($i)&&* } ); } fn baz() -> bool { true && false } "#]], ); } #[test] fn test_match_group_zero_match() { check( r#" macro_rules! m { ( $($i:ident)* ) => (); } m!(); "#, expect![[r#" macro_rules! m { ( $($i:ident)* ) => (); } "#]], ); } #[test] fn test_match_group_in_group() { check( r#" macro_rules! m { [ $( ( $($i:ident)* ) )* ] => [ ok![$( ( $($i)* ) )*]; ] } m! ( (a b) ); "#, expect![[r#" macro_rules! m { [ $( ( $($i:ident)* ) )* ] => [ ok![$( ( $($i)* ) )*]; ] } ok![(a b)]; "#]], ) } #[test] fn test_expand_to_item_list() { check( r#" macro_rules! structs { ($($i:ident),*) => { $(struct $i { field: u32 } )* } } // +tree structs!(Foo, Bar); "#, expect![[r#" macro_rules! structs { ($($i:ident),*) => { $(struct $i { field: u32 } )* } } struct Foo { field: u32 } struct Bar { field: u32 } // MACRO_ITEMS@0..40 // STRUCT@0..20 // STRUCT_KW@0..6 "struct" // NAME@6..9 // IDENT@6..9 "Foo" // RECORD_FIELD_LIST@9..20 // L_CURLY@9..10 "{" // RECORD_FIELD@10..19 // NAME@10..15 // IDENT@10..15 "field" // COLON@15..16 ":" // PATH_TYPE@16..19 // PATH@16..19 // PATH_SEGMENT@16..19 // NAME_REF@16..19 // IDENT@16..19 "u32" // R_CURLY@19..20 "}" // STRUCT@20..40 // STRUCT_KW@20..26 "struct" // NAME@26..29 // IDENT@26..29 "Bar" // RECORD_FIELD_LIST@29..40 // L_CURLY@29..30 "{" // RECORD_FIELD@30..39 // NAME@30..35 // IDENT@30..35 "field" // COLON@35..36 ":" // PATH_TYPE@36..39 // PATH@36..39 // PATH_SEGMENT@36..39 // NAME_REF@36..39 // IDENT@36..39 "u32" // R_CURLY@39..40 "}" "#]], ); } #[test] fn test_two_idents() { check( r#" macro_rules! m { ($i:ident, $j:ident) => { fn foo() { let a = $i; let b = $j; } } } m! { foo, bar } "#, expect![[r#" macro_rules! m { ($i:ident, $j:ident) => { fn foo() { let a = $i; let b = $j; } } } fn foo() { let a = foo; let b = bar; } "#]], ); } #[test] fn test_tt_to_stmts() { check( r#" macro_rules! m { () => { let a = 0; a = 10 + 1; a } } fn f() -> i32 { m!/*+tree*/{} } "#, expect![[r#" macro_rules! m { () => { let a = 0; a = 10 + 1; a } } fn f() -> i32 { let a = 0; a = 10+1; a // MACRO_STMTS@0..15 // LET_STMT@0..7 // LET_KW@0..3 "let" // IDENT_PAT@3..4 // NAME@3..4 // IDENT@3..4 "a" // EQ@4..5 "=" // LITERAL@5..6 // INT_NUMBER@5..6 "0" // SEMICOLON@6..7 ";" // EXPR_STMT@7..14 // BIN_EXPR@7..13 // PATH_EXPR@7..8 // PATH@7..8 // PATH_SEGMENT@7..8 // NAME_REF@7..8 // IDENT@7..8 "a" // EQ@8..9 "=" // BIN_EXPR@9..13 // LITERAL@9..11 // INT_NUMBER@9..11 "10" // PLUS@11..12 "+" // LITERAL@12..13 // INT_NUMBER@12..13 "1" // SEMICOLON@13..14 ";" // PATH_EXPR@14..15 // PATH@14..15 // PATH_SEGMENT@14..15 // NAME_REF@14..15 // IDENT@14..15 "a" } "#]], ); } #[test] fn test_match_literal() { check( r#" macro_rules! m { ('(') => { fn l_paren() {} } } m!['(']; "#, expect![[r#" macro_rules! m { ('(') => { fn l_paren() {} } } fn l_paren() {} "#]], ); } #[test] fn test_parse_macro_def_simple() { cov_mark::check!(parse_macro_def_simple); check( r#" macro m($id:ident) { fn $id() {} } m!(bar); "#, expect![[r#" macro m($id:ident) { fn $id() {} } fn bar() {} "#]], ); } #[test] fn test_parse_macro_def_rules() { cov_mark::check!(parse_macro_def_rules); check( r#" macro m { ($id:ident) => { fn $id() {} } } m!(bar); "#, expect![[r#" macro m { ($id:ident) => { fn $id() {} } } fn bar() {} "#]], ); } #[test] fn test_macro_2_0_panic_2015() { check( r#" macro panic_2015 { () => (), (bar) => (), } panic_2015!(bar); "#, expect![[r#" macro panic_2015 { () => (), (bar) => (), } "#]], ); } #[test] fn test_path() { check( r#" macro_rules! m { ($p:path) => { fn foo() { let a = $p; } } } m! { foo } m! { bar::::baz:: } "#, expect![[r#" macro_rules! m { ($p:path) => { fn foo() { let a = $p; } } } fn foo() { let a = foo; } fn foo() { let a = bar::::baz:: ; } "#]], ); } #[test] fn test_two_paths() { check( r#" macro_rules! m { ($i:path, $j:path) => { fn foo() { let a = $ i; let b = $j; } } } m! { foo, bar } "#, expect![[r#" macro_rules! m { ($i:path, $j:path) => { fn foo() { let a = $ i; let b = $j; } } } fn foo() { let a = foo; let b = bar; } "#]], ); } #[test] fn test_path_with_path() { check( r#" macro_rules! m { ($p:path) => { fn foo() { let a = $p::bar; } } } m! { foo } "#, expect![[r#" macro_rules! m { ($p:path) => { fn foo() { let a = $p::bar; } } } fn foo() { let a = foo::bar; } "#]], ); } #[test] fn test_type_path_is_transcribed_as_expr_path() { check( r#" macro_rules! m { ($p:path) => { let $p; } } fn test() { m!(S) m!(S) m!(S>) m!(S<{ module::CONST < 42 }>) } "#, expect![[r#" macro_rules! m { ($p:path) => { let $p; } } fn test() { let S; let S:: ; let S:: > ; let S:: < { module::CONST<42 } > ; } "#]], ); } #[test] fn test_expr() { check( r#" macro_rules! m { ($e:expr) => { fn bar() { $e; } } } m! { 2 + 2 * baz(3).quux() } "#, expect![[r#" macro_rules! m { ($e:expr) => { fn bar() { $e; } } } fn bar() { (2+2*baz(3).quux()); } "#]], ) } #[test] fn test_last_expr() { check( r#" macro_rules! vec { ($($item:expr),*) => {{ let mut v = Vec::new(); $( v.push($item); )* v }}; } fn f() { vec![1,2,3]; } "#, expect![[r#" macro_rules! vec { ($($item:expr),*) => {{ let mut v = Vec::new(); $( v.push($item); )* v }}; } fn f() { { let mut v = Vec::new(); v.push(1); v.push(2); v.push(3); v }; } "#]], ); } #[test] fn test_expr_with_attr() { check( r#" macro_rules! m { ($a:expr) => { ok!(); } } m!(#[allow(a)]()); "#, expect![[r#" macro_rules! m { ($a:expr) => { ok!(); } } ok!(); "#]], ) } #[test] fn test_ty() { check( r#" macro_rules! m { ($t:ty) => ( fn bar() -> $t {} ) } m! { Baz } "#, expect![[r#" macro_rules! m { ($t:ty) => ( fn bar() -> $t {} ) } fn bar() -> Baz {} "#]], ) } #[test] fn test_ty_with_complex_type() { check( r#" macro_rules! m { ($t:ty) => ( fn bar() -> $ t {} ) } m! { &'a Baz } m! { extern "Rust" fn() -> Ret } "#, expect![[r#" macro_rules! m { ($t:ty) => ( fn bar() -> $ t {} ) } fn bar() -> &'a Baz {} fn bar() -> extern "Rust" fn() -> Ret {} "#]], ); } #[test] fn test_pat_() { check( r#" macro_rules! m { ($p:pat) => { fn foo() { let $p; } } } m! { (a, b) } "#, expect![[r#" macro_rules! m { ($p:pat) => { fn foo() { let $p; } } } fn foo() { let (a, b); } "#]], ); } #[test] fn test_stmt() { check( r#" macro_rules! m { ($s:stmt) => ( fn bar() { $s; } ) } m! { 2 } m! { let a = 0 } "#, expect![[r#" macro_rules! m { ($s:stmt) => ( fn bar() { $s; } ) } fn bar() { 2; } fn bar() { let a = 0; } "#]], ) } #[test] fn test_single_item() { check( r#" macro_rules! m { ($i:item) => ( $i ) } m! { struct C {} } "#, expect![[r#" macro_rules! m { ($i:item) => ( $i ) } struct C {} "#]], ) } #[test] fn test_all_items() { check( r#" macro_rules! m { ($($i:item)*) => ($($i )*) } m! { extern crate a; mod b; mod c {} use d; const E: i32 = 0; static F: i32 = 0; impl G {} struct H; enum I { Foo } trait J {} fn h() {} extern {} type T = u8; } "#, // The modules are counted twice, once because of the module and once because of the macro call. expect![[r#" macro_rules! m { ($($i:item)*) => ($($i )*) } extern crate a; mod b; mod c {} use d; const E: i32 = 0; static F: i32 = 0; impl G {} struct H; enum I { Foo } trait J {} fn h() {} extern {} type T = u8; mod b; mod c {}"#]], ); } #[test] fn test_block() { check( r#" macro_rules! m { ($b:block) => { fn foo() $b } } m! { { 1; } } "#, expect![[r#" macro_rules! m { ($b:block) => { fn foo() $b } } fn foo() { 1; } "#]], ); } #[test] fn test_meta() { check( r#" macro_rules! m { ($m:meta) => ( #[$m] fn bar() {} ) } m! { cfg(target_os = "windows") } m! { hello::world } "#, expect![[r#" macro_rules! m { ($m:meta) => ( #[$m] fn bar() {} ) } #[cfg(target_os = "windows")] fn bar() {} #[hello::world] fn bar() {} "#]], ); } #[test] fn test_meta_doc_comments() { check( r#" macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } m! { /// Single Line Doc 1 /** MultiLines Doc */ } "#, expect![[r#" macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } #[doc = r" Single Line Doc 1"] #[doc = r" MultiLines Doc "] fn bar() {} "#]], ); } #[test] fn test_meta_extended_key_value_attributes() { check( r#" macro_rules! m { (#[$m:meta]) => ( #[$m] fn bar() {} ) } m! { #[doc = concat!("The `", "bla", "` lang item.")] } "#, expect![[r#" macro_rules! m { (#[$m:meta]) => ( #[$m] fn bar() {} ) } #[doc = concat!("The `", "bla", "` lang item.")] fn bar() {} "#]], ); } #[test] fn test_meta_doc_comments_non_latin() { check( r#" macro_rules! m { ($(#[$ m:meta])+) => ( $(#[$m])+ fn bar() {} ) } m! { /// 錦瑟無端五十弦,一弦一柱思華年。 /** 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 */ } "#, expect![[r#" macro_rules! m { ($(#[$ m:meta])+) => ( $(#[$m])+ fn bar() {} ) } #[doc = r" 錦瑟無端五十弦,一弦一柱思華年。"] #[doc = r" 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 "] fn bar() {} "#]], ); } #[test] fn test_meta_doc_comments_escaped_characters() { check( r#" macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } m! { /// \ " ' } "#, expect![[r##" macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } #[doc = r#" \ " '"#] fn bar() {} "##]], ); } #[test] fn test_tt_block() { check( r#" macro_rules! m { ($tt:tt) => { fn foo() $tt } } m! { { 1; } } "#, expect![[r#" macro_rules! m { ($tt:tt) => { fn foo() $tt } } fn foo() { 1; } "#]], ); } #[test] fn test_tt_group() { check( r#" macro_rules! m { ($($tt:tt)*) => { $($tt)* } } m! { fn foo() {} }" "#, expect![[r#" macro_rules! m { ($($tt:tt)*) => { $($tt)* } } fn foo() {}" "#]], ); } #[test] fn test_tt_composite() { check( r#" macro_rules! m { ($tt:tt) => { ok!(); } } m! { => } m! { = > } "#, expect![[r#" macro_rules! m { ($tt:tt) => { ok!(); } } ok!(); /* error: leftover tokens */ok!(); "#]], ); } #[test] fn test_tt_composite2() { check( r#" macro_rules! m { ($($tt:tt)*) => { abs!(=> $($tt)*); } } m! {#} "#, expect![[r#" macro_rules! m { ($($tt:tt)*) => { abs!(=> $($tt)*); } } abs!( = > #); "#]], ); } #[test] fn test_tt_with_composite_without_space() { // Test macro input without any spaces // See https://github.com/rust-lang/rust-analyzer/issues/6692 check( r#" macro_rules! m { ($ op:tt, $j:path) => ( ok!(); ) } m!(==,Foo::Bool) "#, expect![[r#" macro_rules! m { ($ op:tt, $j:path) => ( ok!(); ) } ok!(); "#]], ); } #[test] fn test_underscore() { check( r#" macro_rules! m { ($_:tt) => { ok!(); } } m! { => } "#, expect![[r#" macro_rules! m { ($_:tt) => { ok!(); } } ok!(); "#]], ); } #[test] fn test_underscore_not_greedily() { check( r#" // `_` overlaps with `$a:ident` but rustc matches it under the `_` token. macro_rules! m1 { ($($a:ident)* _) => { ok!(); } } m1![a b c d _]; // `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr`. macro_rules! m2 { ($($a:expr => $b:ident)* _ => $c:expr) => { ok!(); } } m2![a => b c => d _ => ou] "#, expect![[r#" // `_` overlaps with `$a:ident` but rustc matches it under the `_` token. macro_rules! m1 { ($($a:ident)* _) => { ok!(); } } ok!(); // `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr`. macro_rules! m2 { ($($a:expr => $b:ident)* _ => $c:expr) => { ok!(); } } /* error: unexpected token in input */ok!(); "#]], ); } #[test] fn test_underscore_flavors() { check( r#" macro_rules! m1 { ($a:ty) => { ok!(); } } m1![_]; macro_rules! m2 { ($a:lifetime) => { ok!(); } } m2!['_]; "#, expect![[r#" macro_rules! m1 { ($a:ty) => { ok!(); } } ok!(); macro_rules! m2 { ($a:lifetime) => { ok!(); } } ok!(); "#]], ); } #[test] fn test_vertical_bar_with_pat_param() { check( r#" macro_rules! m { (|$pat:pat_param| ) => { ok!(); } } m! { |x| } "#, expect![[r#" macro_rules! m { (|$pat:pat_param| ) => { ok!(); } } ok!(); "#]], ); } #[test] fn test_new_std_matches() { check( //- edition:2021 r#" macro_rules! matches { ($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => { match $expression { $pattern $(if $guard)? => true, _ => false } }; } fn main() { matches!(0, 0 | 1 if true); } "#, expect![[r#" macro_rules! matches { ($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => { match $expression { $pattern $(if $guard)? => true, _ => false } }; } fn main() { match 0 { 0|1 if true =>true , _=>false }; } "#]], ); } #[test] fn test_hygienic_pat() { check( r#" //- /new.rs crate:new deps:old edition:2015 old::make!(); fn main() { matches!(0, 0 | 1 if true); } //- /old.rs crate:old edition:2021 #[macro_export] macro_rules! make { () => { macro_rules! matches { ($expression:expr, $pattern:pat if $guard:expr ) => { match $expression { $pattern if $guard => true, _ => false } }; } } } "#, expect![[r#" macro_rules !matches { ($expression: expr, $pattern: pat if $guard: expr) = > { match $expression { $pattern if $guard = > true , _ = > false } } ; } fn main() { match 0 { 0|1 if true =>true , _=>false }; } "#]], ); check( r#" //- /new.rs crate:new deps:old edition:2021 old::make!(); fn main() { matches/*+errors*/!(0, 0 | 1 if true); } //- /old.rs crate:old edition:2015 #[macro_export] macro_rules! make { () => { macro_rules! matches { ($expression:expr, $pattern:pat if $guard:expr ) => { match $expression { $pattern if $guard => true, _ => false } }; } } } "#, expect![[r#" macro_rules !matches { ($expression: expr, $pattern: pat if $guard: expr) = > { match $expression { $pattern if $guard = > true , _ = > false } } ; } fn main() { /* error: unexpected token in input *//* parse error: expected expression */ /* parse error: expected FAT_ARROW */ /* parse error: expected `,` */ /* parse error: expected pattern */ match 0 { 0 if $guard=>true , _=>false }; } "#]], ); } #[test] fn test_dollar_crate_lhs_is_not_meta() { check( r#" macro_rules! m { ($crate) => { err!(); }; () => { ok!(); }; } m!{} "#, expect![[r#" macro_rules! m { ($crate) => { err!(); }; () => { ok!(); }; } ok!(); "#]], ); } #[test] fn test_lifetime() { check( r#" macro_rules! m { ($lt:lifetime) => { struct Ref<$lt>{ s: &$ lt str } } } m! {'a} "#, expect![[r#" macro_rules! m { ($lt:lifetime) => { struct Ref<$lt>{ s: &$ lt str } } } struct Ref<'a> { s: &'a str } "#]], ); } #[test] fn test_literal() { check( r#" macro_rules! m { ($type:ty, $lit:literal) => { const VALUE: $type = $ lit; }; } m!(u8, 0); "#, expect![[r#" macro_rules! m { ($type:ty, $lit:literal) => { const VALUE: $type = $ lit; }; } const VALUE: u8 = 0; "#]], ); check( r#" macro_rules! m { ($type:ty, $lit:literal) => { const VALUE: $ type = $ lit; }; } m!(i32, -1); "#, expect![[r#" macro_rules! m { ($type:ty, $lit:literal) => { const VALUE: $ type = $ lit; }; } const VALUE: i32 = -1; "#]], ); } #[test] fn test_boolean_is_ident() { check( r#" macro_rules! m { ($lit0:literal, $lit1:literal) => { const VALUE: (bool, bool) = ($lit0, $lit1); }; } m!(true, false); "#, expect![[r#" macro_rules! m { ($lit0:literal, $lit1:literal) => { const VALUE: (bool, bool) = ($lit0, $lit1); }; } const VALUE: (bool, bool) = (true , false ); "#]], ); } #[test] fn test_vis() { check( r#" macro_rules! m { ($vis:vis $name:ident) => { $vis fn $name() {} } } m!(pub foo); m!(foo); "#, expect![[r#" macro_rules! m { ($vis:vis $name:ident) => { $vis fn $name() {} } } pub fn foo() {} fn foo() {} "#]], ); } #[test] fn test_inner_macro_rules() { check( r#" macro_rules! m { ($a:ident, $b:ident, $c:tt) => { macro_rules! inner { ($bi:ident) => { fn $bi() -> u8 { $c } } } inner!($a); fn $b() -> u8 { $c } } } m!(x, y, 1); "#, expect![[r#" macro_rules! m { ($a:ident, $b:ident, $c:tt) => { macro_rules! inner { ($bi:ident) => { fn $bi() -> u8 { $c } } } inner!($a); fn $b() -> u8 { $c } } } macro_rules !inner { ($bi: ident) = > { fn $bi()-> u8 { 1 } } } inner!(x); fn y() -> u8 { 1 } "#]], ); } #[test] fn test_expr_after_path_colons() { check( r#" macro_rules! m { ($k:expr) => { fn f() { K::$k; } } } // +tree +errors m!(C("0")); "#, expect![[r#" macro_rules! m { ($k:expr) => { fn f() { K::$k; } } } /* parse error: expected identifier, `self`, `super`, `crate`, or `Self` */ /* parse error: expected SEMICOLON */ /* parse error: expected SEMICOLON */ /* parse error: expected expression, item or let statement */ fn f() { K::(C("0")); } // MACRO_ITEMS@0..19 // FN@0..19 // FN_KW@0..2 "fn" // NAME@2..3 // IDENT@2..3 "f" // PARAM_LIST@3..5 // L_PAREN@3..4 "(" // R_PAREN@4..5 ")" // BLOCK_EXPR@5..19 // STMT_LIST@5..19 // L_CURLY@5..6 "{" // EXPR_STMT@6..10 // PATH_EXPR@6..10 // PATH@6..10 // PATH@6..7 // PATH_SEGMENT@6..7 // NAME_REF@6..7 // IDENT@6..7 "K" // COLON2@7..9 "::" // PATH_SEGMENT@9..10 // ERROR@9..10 // L_PAREN@9..10 "(" // EXPR_STMT@10..16 // CALL_EXPR@10..16 // PATH_EXPR@10..11 // PATH@10..11 // PATH_SEGMENT@10..11 // NAME_REF@10..11 // IDENT@10..11 "C" // ARG_LIST@11..16 // L_PAREN@11..12 "(" // LITERAL@12..15 // STRING@12..15 "\"0\"" // R_PAREN@15..16 ")" // ERROR@16..17 // R_PAREN@16..17 ")" // SEMICOLON@17..18 ";" // R_CURLY@18..19 "}" "#]], ); } #[test] fn test_match_is_not_greedy() { check( r#" macro_rules! foo { ($($i:ident $(,)*),*) => {}; } foo!(a,b); "#, expect![[r#" macro_rules! foo { ($($i:ident $(,)*),*) => {}; } "#]], ); } #[test] fn expr_interpolation() { check( r#" macro_rules! m { ($expr:expr) => { map($expr) } } fn f() { let _ = m!(x + foo); } "#, expect![[r#" macro_rules! m { ($expr:expr) => { map($expr) } } fn f() { let _ = map((x+foo)); } "#]], ) } #[test] fn mbe_are_not_attributes() { check( r#" macro_rules! error { () => {struct Bar} } #[error] struct Foo; "#, expect![[r##" macro_rules! error { () => {struct Bar} } #[error] struct Foo; "##]], ) } #[test] fn test_punct_without_space() { // Puncts are "glued" greedily. check( r#" macro_rules! foo { (: : :) => { "1 1 1" }; (: ::) => { "1 2" }; (:: :) => { "2 1" }; (: : : :) => { "1 1 1 1" }; (:: : :) => { "2 1 1" }; (: :: :) => { "1 2 1" }; (: : ::) => { "1 1 2" }; (:: ::) => { "2 2" }; } fn test() { foo!(:::); foo!(: :::); foo!(::::); } "#, expect![[r#" macro_rules! foo { (: : :) => { "1 1 1" }; (: ::) => { "1 2" }; (:: :) => { "2 1" }; (: : : :) => { "1 1 1 1" }; (:: : :) => { "2 1 1" }; (: :: :) => { "1 2 1" }; (: : ::) => { "1 1 2" }; (:: ::) => { "2 2" }; } fn test() { "2 1"; "1 2 1"; "2 2"; } "#]], ); } #[test] fn test_pat_fragment_eof_17441() { check( r#" macro_rules! matches { ($expression:expr, $pattern:pat $(if $guard:expr)? ) => { match $expression { $pattern $(if $guard)? => true, _ => false } }; } fn f() { matches!(0, 10..); matches!(0, 10.. if true); } "#, expect![[r#" macro_rules! matches { ($expression:expr, $pattern:pat $(if $guard:expr)? ) => { match $expression { $pattern $(if $guard)? => true, _ => false } }; } fn f() { match 0 { 10.. =>true , _=>false }; match 0 { 10..if true =>true , _=>false }; } "#]], ); } #[test] fn test_edition_handling_out() { check( r#" //- /main.rs crate:main deps:old edition:2021 macro_rules! r#try { ($it:expr) => { $it? }; } fn f() { old::invoke_bare_try!(0); } //- /old.rs crate:old edition:2015 #[macro_export] macro_rules! invoke_bare_try { ($it:expr) => { try!($it) }; } "#, expect![[r#" macro_rules! r#try { ($it:expr) => { $it? }; } fn f() { try!(0); } "#]], ); } #[test] fn semicolon_does_not_glue() { check( r#" macro_rules! bug { ($id: expr) => { true }; ($id: expr; $($attr: ident),*) => { true }; ($id: expr; $($attr: ident),*; $norm: expr) => { true }; ($id: expr; $($attr: ident),*;; $print: expr) => { true }; ($id: expr; $($attr: ident),*; $norm: expr; $print: expr) => { true }; } fn f() { let _ = bug!(a;;;test); } "#, expect![[r#" macro_rules! bug { ($id: expr) => { true }; ($id: expr; $($attr: ident),*) => { true }; ($id: expr; $($attr: ident),*; $norm: expr) => { true }; ($id: expr; $($attr: ident),*;; $print: expr) => { true }; ($id: expr; $($attr: ident),*; $norm: expr; $print: expr) => { true }; } fn f() { let _ = true; } "#]], ); } #[test] fn lifetime_repeat() { check( r#" macro_rules! m { ($($x:expr)'a*) => (stringify!($($x)'b*)); } fn f() { let _ = m!(0 'a 1 'a 2); } "#, expect![[r#" macro_rules! m { ($($x:expr)'a*) => (stringify!($($x)'b*)); } fn f() { let _ = stringify!(0 'b 1 'b 2); } "#]], ); } #[test] fn per_token_edition() { check( r#" //- /foo.rs crate:foo edition:2024 #[macro_export] macro_rules! m { ($e:expr) => {}; } //- /bar.rs crate:bar deps:foo edition:2021 fn gen() -> usize { 0 } fn foo() { foo::m!(gen()); } "#, expect![[r#" fn gen() -> usize { 0 } fn foo() { ; } "#]], ); }