//! Tests specific to declarative macros, aka macros by example. This covers //! both stable `macro_rules!` macros as well as unstable `macro` macros. // FIXME: Move more of the nameres independent tests from // crates\hir-def\src\macro_expansion_tests\mod.rs to this use expect_test::expect; use span::{ Edition, EditionedFileId, FileId, ROOT_ERASED_FILE_AST_ID, Span, SpanAnchor, SyntaxContext, }; use stdx::format_to; use tt::{TextRange, TextSize}; use crate::DeclarativeMacro; #[expect(deprecated)] fn check_( def_edition: Edition, call_edition: Edition, macro2: bool, decl: &str, arg: &str, render_debug: bool, expect: expect_test::Expect, parse: parser::TopEntryPoint, ) { let db = salsa::DatabaseImpl::default(); let decl_tt = &syntax_bridge::parse_to_token_tree( def_edition, SpanAnchor { file_id: EditionedFileId::new(FileId::from_raw(0), def_edition), ast_id: ROOT_ERASED_FILE_AST_ID, }, SyntaxContext::root(Edition::CURRENT), decl, ) .unwrap(); let mac = if macro2 { DeclarativeMacro::parse_macro2(None, decl_tt, |_| def_edition) } else { DeclarativeMacro::parse_macro_rules(decl_tt, |_| def_edition) }; let call_anchor = SpanAnchor { file_id: EditionedFileId::new(FileId::from_raw(1), call_edition), ast_id: ROOT_ERASED_FILE_AST_ID, }; let arg_tt = syntax_bridge::parse_to_token_tree( call_edition, call_anchor, SyntaxContext::root(Edition::CURRENT), arg, ) .unwrap(); let res = mac.expand( &db, &arg_tt, |_| (), crate::MacroCallStyle::FnLike, Span { range: TextRange::up_to(TextSize::of(arg)), anchor: call_anchor, ctx: SyntaxContext::root(Edition::CURRENT), }, ); let mut expect_res = String::new(); if let Some(err) = res.err { format_to!(expect_res, "{err:#?}\n\n",); } if render_debug { format_to!(expect_res, "{:#?}\n\n", res.value.0); } let (node, _) = syntax_bridge::token_tree_to_syntax_node(&res.value.0, parse, &mut |_| def_edition); format_to!( expect_res, "{}", syntax_bridge::prettify_macro_expansion::prettify_macro_expansion( node.syntax_node(), &mut |_| None, |_| () ) ); expect.assert_eq(&expect_res); } fn check( def_edition: Edition, call_edition: Edition, decl: &str, arg: &str, expect: expect_test::Expect, ) { check_( def_edition, call_edition, false, decl, arg, true, expect, parser::TopEntryPoint::SourceFile, ); } #[test] fn unbalanced_brace() { check( Edition::CURRENT, Edition::CURRENT, r#" () => { { } "#, r#""#, expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..0#ROOT2024 1:Root[0000, 0]@0..0#ROOT2024 SUBTREE {} 0:Root[0000, 0]@9..10#ROOT2024 0:Root[0000, 0]@11..12#ROOT2024 {}"#]], ); } #[test] fn token_mapping_smoke_test() { check( Edition::CURRENT, Edition::CURRENT, r#" ( struct $ident:ident ) => { struct $ident { map: ::std::collections::HashSet<()>, } }; "#, r#" struct MyTraitMap2 "#, expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..20#ROOT2024 1:Root[0000, 0]@0..20#ROOT2024 IDENT struct 0:Root[0000, 0]@34..40#ROOT2024 IDENT MyTraitMap2 1:Root[0000, 0]@8..19#ROOT2024 SUBTREE {} 0:Root[0000, 0]@48..49#ROOT2024 0:Root[0000, 0]@100..101#ROOT2024 IDENT map 0:Root[0000, 0]@58..61#ROOT2024 PUNCH : [alone] 0:Root[0000, 0]@61..62#ROOT2024 PUNCH : [joint] 0:Root[0000, 0]@63..64#ROOT2024 PUNCH : [alone] 0:Root[0000, 0]@64..65#ROOT2024 IDENT std 0:Root[0000, 0]@65..68#ROOT2024 PUNCH : [joint] 0:Root[0000, 0]@68..69#ROOT2024 PUNCH : [alone] 0:Root[0000, 0]@69..70#ROOT2024 IDENT collections 0:Root[0000, 0]@70..81#ROOT2024 PUNCH : [joint] 0:Root[0000, 0]@81..82#ROOT2024 PUNCH : [alone] 0:Root[0000, 0]@82..83#ROOT2024 IDENT HashSet 0:Root[0000, 0]@83..90#ROOT2024 PUNCH < [alone] 0:Root[0000, 0]@90..91#ROOT2024 SUBTREE () 0:Root[0000, 0]@91..92#ROOT2024 0:Root[0000, 0]@92..93#ROOT2024 PUNCH > [joint] 0:Root[0000, 0]@93..94#ROOT2024 PUNCH , [alone] 0:Root[0000, 0]@94..95#ROOT2024 struct MyTraitMap2 { map: ::std::collections::HashSet<()>, }"#]], ); } #[test] fn token_mapping_floats() { // Regression test for https://github.com/rust-lang/rust-analyzer/issues/12216 // (and related issues) check( Edition::CURRENT, Edition::CURRENT, r#" ($($tt:tt)*) => { $($tt)* }; "#, r#" fn main() { 1; 1.0; ((1,),).0.0; let x = 1; } "#, expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..63#ROOT2024 1:Root[0000, 0]@0..63#ROOT2024 IDENT fn 1:Root[0000, 0]@1..3#ROOT2024 IDENT main 1:Root[0000, 0]@4..8#ROOT2024 SUBTREE () 1:Root[0000, 0]@8..9#ROOT2024 1:Root[0000, 0]@9..10#ROOT2024 SUBTREE {} 1:Root[0000, 0]@11..12#ROOT2024 1:Root[0000, 0]@61..62#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@17..18#ROOT2024 PUNCH ; [alone] 1:Root[0000, 0]@18..19#ROOT2024 LITERAL Float 1.0 1:Root[0000, 0]@24..27#ROOT2024 PUNCH ; [alone] 1:Root[0000, 0]@27..28#ROOT2024 SUBTREE () 1:Root[0000, 0]@33..34#ROOT2024 1:Root[0000, 0]@39..40#ROOT2024 SUBTREE () 1:Root[0000, 0]@34..35#ROOT2024 1:Root[0000, 0]@37..38#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@35..36#ROOT2024 PUNCH , [alone] 1:Root[0000, 0]@36..37#ROOT2024 PUNCH , [alone] 1:Root[0000, 0]@38..39#ROOT2024 PUNCH . [alone] 1:Root[0000, 0]@40..41#ROOT2024 LITERAL Float 0.0 1:Root[0000, 0]@41..44#ROOT2024 PUNCH ; [alone] 1:Root[0000, 0]@44..45#ROOT2024 IDENT let 1:Root[0000, 0]@50..53#ROOT2024 IDENT x 1:Root[0000, 0]@54..55#ROOT2024 PUNCH = [alone] 1:Root[0000, 0]@56..57#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@58..59#ROOT2024 PUNCH ; [alone] 1:Root[0000, 0]@59..60#ROOT2024 fn main(){ 1; 1.0; ((1,),).0.0; let x = 1; }"#]], ); } #[test] fn expr_2021() { check( Edition::Edition2024, Edition::Edition2024, r#" ($($e:expr),* $(,)?) => { $($e);* ; }; "#, r#" _, const { 1 }, "#, expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..25#ROOT2024 1:Root[0000, 0]@0..25#ROOT2024 IDENT _ 1:Root[0000, 0]@5..6#ROOT2024 PUNCH ; [joint] 0:Root[0000, 0]@36..37#ROOT2024 SUBTREE () 0:Root[0000, 0]@34..35#ROOT2024 0:Root[0000, 0]@34..35#ROOT2024 IDENT const 1:Root[0000, 0]@12..17#ROOT2024 SUBTREE {} 1:Root[0000, 0]@18..19#ROOT2024 1:Root[0000, 0]@22..23#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@20..21#ROOT2024 PUNCH ; [alone] 0:Root[0000, 0]@39..40#ROOT2024 _; (const { 1 });"#]], ); check( Edition::Edition2021, Edition::Edition2024, r#" ($($e:expr),* $(,)?) => { $($e);* ; }; "#, r#" _, "#, expect![[r#" ExpandError { inner: ( 1:Root[0000, 0]@5..6#ROOT2024, NoMatchingRule, ), } SUBTREE $$ 1:Root[0000, 0]@0..8#ROOT2024 1:Root[0000, 0]@0..8#ROOT2024 PUNCH ; [alone] 0:Root[0000, 0]@39..40#ROOT2024 ;"#]], ); check( Edition::Edition2021, Edition::Edition2024, r#" ($($e:expr),* $(,)?) => { $($e);* ; }; "#, r#" const { 1 }, "#, expect![[r#" ExpandError { inner: ( 1:Root[0000, 0]@5..10#ROOT2024, NoMatchingRule, ), } SUBTREE $$ 1:Root[0000, 0]@0..18#ROOT2024 1:Root[0000, 0]@0..18#ROOT2024 PUNCH ; [alone] 0:Root[0000, 0]@39..40#ROOT2024 ;"#]], ); check( Edition::Edition2024, Edition::Edition2024, r#" ($($e:expr_2021),* $(,)?) => { $($e);* ; }; "#, r#" 4, "literal", funcall(), future.await, break 'foo bar, "#, expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..76#ROOT2024 1:Root[0000, 0]@0..76#ROOT2024 LITERAL Integer 4 1:Root[0000, 0]@5..6#ROOT2024 PUNCH ; [joint] 0:Root[0000, 0]@41..42#ROOT2024 LITERAL Str literal 1:Root[0000, 0]@12..21#ROOT2024 PUNCH ; [joint] 0:Root[0000, 0]@41..42#ROOT2024 SUBTREE () 0:Root[0000, 0]@39..40#ROOT2024 0:Root[0000, 0]@39..40#ROOT2024 IDENT funcall 1:Root[0000, 0]@27..34#ROOT2024 SUBTREE () 1:Root[0000, 0]@34..35#ROOT2024 1:Root[0000, 0]@35..36#ROOT2024 PUNCH ; [joint] 0:Root[0000, 0]@41..42#ROOT2024 SUBTREE () 0:Root[0000, 0]@39..40#ROOT2024 0:Root[0000, 0]@39..40#ROOT2024 IDENT future 1:Root[0000, 0]@42..48#ROOT2024 PUNCH . [alone] 1:Root[0000, 0]@48..49#ROOT2024 IDENT await 1:Root[0000, 0]@49..54#ROOT2024 PUNCH ; [joint] 0:Root[0000, 0]@41..42#ROOT2024 SUBTREE () 0:Root[0000, 0]@39..40#ROOT2024 0:Root[0000, 0]@39..40#ROOT2024 IDENT break 1:Root[0000, 0]@60..65#ROOT2024 PUNCH ' [joint] 1:Root[0000, 0]@66..67#ROOT2024 IDENT foo 1:Root[0000, 0]@67..70#ROOT2024 IDENT bar 1:Root[0000, 0]@71..74#ROOT2024 PUNCH ; [alone] 0:Root[0000, 0]@44..45#ROOT2024 4; "literal"; (funcall()); (future.await); (break 'foo bar);"#]], ); check( Edition::Edition2024, Edition::Edition2024, r#" ($($e:expr_2021),* $(,)?) => { $($e);* ; }; "#, r#" _, "#, expect![[r#" ExpandError { inner: ( 1:Root[0000, 0]@5..6#ROOT2024, NoMatchingRule, ), } SUBTREE $$ 1:Root[0000, 0]@0..8#ROOT2024 1:Root[0000, 0]@0..8#ROOT2024 PUNCH ; [alone] 0:Root[0000, 0]@44..45#ROOT2024 ;"#]], ); } #[test] fn minus_belongs_to_literal() { let decl = r#" (-1) => {-1}; (- 2) => {- 2}; (- 3.0) => {- 3.0}; (@$lit:literal) => {$lit} "#; let check = |args, expect| check(Edition::CURRENT, Edition::CURRENT, decl, args, expect); check( "-1", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..2#ROOT2024 1:Root[0000, 0]@0..2#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@10..11#ROOT2024 LITERAL Integer 1 0:Root[0000, 0]@11..12#ROOT2024 -1"#]], ); check( "- 1", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..3#ROOT2024 1:Root[0000, 0]@0..3#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@10..11#ROOT2024 LITERAL Integer 1 0:Root[0000, 0]@11..12#ROOT2024 -1"#]], ); check( "-2", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..2#ROOT2024 1:Root[0000, 0]@0..2#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@25..26#ROOT2024 LITERAL Integer 2 0:Root[0000, 0]@27..28#ROOT2024 -2"#]], ); check( "- 2", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..3#ROOT2024 1:Root[0000, 0]@0..3#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@25..26#ROOT2024 LITERAL Integer 2 0:Root[0000, 0]@27..28#ROOT2024 -2"#]], ); check( "-3.0", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..4#ROOT2024 1:Root[0000, 0]@0..4#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@43..44#ROOT2024 LITERAL Float 3.0 0:Root[0000, 0]@45..48#ROOT2024 -3.0"#]], ); check( "- 3.0", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..5#ROOT2024 1:Root[0000, 0]@0..5#ROOT2024 PUNCH - [alone] 0:Root[0000, 0]@43..44#ROOT2024 LITERAL Float 3.0 0:Root[0000, 0]@45..48#ROOT2024 -3.0"#]], ); check( "@1", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..2#ROOT2024 1:Root[0000, 0]@0..2#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@1..2#ROOT2024 1"#]], ); check( "@-1", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..3#ROOT2024 1:Root[0000, 0]@0..3#ROOT2024 PUNCH - [alone] 1:Root[0000, 0]@1..2#ROOT2024 LITERAL Integer 1 1:Root[0000, 0]@2..3#ROOT2024 -1"#]], ); check( "@1.0", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..4#ROOT2024 1:Root[0000, 0]@0..4#ROOT2024 LITERAL Float 1.0 1:Root[0000, 0]@1..4#ROOT2024 1.0"#]], ); check( "@-1.0", expect![[r#" SUBTREE $$ 1:Root[0000, 0]@0..5#ROOT2024 1:Root[0000, 0]@0..5#ROOT2024 PUNCH - [alone] 1:Root[0000, 0]@1..2#ROOT2024 LITERAL Float 1.0 1:Root[0000, 0]@2..5#ROOT2024 -1.0"#]], ); check( "@--1.0", expect![[r#" ExpandError { inner: ( 1:Root[0000, 0]@1..2#ROOT2024, BindingError( "expected literal", ), ), } SUBTREE $$ 1:Root[0000, 0]@0..6#ROOT2024 1:Root[0000, 0]@0..6#ROOT2024 PUNCH - [joint] 1:Root[0000, 0]@1..2#ROOT2024 PUNCH - [alone] 1:Root[0000, 0]@2..3#ROOT2024 --"#]], ); }