| //! 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 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( |
| &arg_tt, |
| |_| (), |
| Span { |
| range: TextRange::up_to(TextSize::of(arg)), |
| anchor: call_anchor, |
| ctx: SyntaxContext::root(Edition::CURRENT), |
| }, |
| def_edition, |
| ); |
| 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, |
| 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 |
| |
| --"#]], |
| ); |
| } |