diff --git a/crates/hir_def/src/macro_expansion_tests.rs b/crates/hir_def/src/macro_expansion_tests.rs index 66874a674d82..c317dc27a583 100644 --- a/crates/hir_def/src/macro_expansion_tests.rs +++ b/crates/hir_def/src/macro_expansion_tests.rs @@ -10,6 +10,7 @@ //! and harder to understand. mod mbe; +mod builtin; use std::{iter, ops::Range}; @@ -20,7 +21,7 @@ use stdx::format_to; use syntax::{ ast::{self, edit::IndentLevel}, AstNode, - SyntaxKind::{EOF, IDENT, LIFETIME_IDENT}, + SyntaxKind::{COMMENT, EOF, IDENT, LIFETIME_IDENT}, SyntaxNode, T, }; @@ -28,6 +29,7 @@ use crate::{ db::DefDatabase, nameres::ModuleSource, resolver::HasResolver, test_db::TestDB, AsMacroCall, }; +#[track_caller] fn check(ra_fixture: &str, mut expect: Expect) { let db = TestDB::with_files(ra_fixture); let krate = db.crate_graph().iter().next().unwrap(); @@ -44,37 +46,54 @@ fn check(ra_fixture: &str, mut expect: Expect) { let mut expansions = Vec::new(); for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) { let macro_call = InFile::new(source.file_id, ¯o_call); + let mut error = None; let macro_call_id = macro_call .as_call_id_with_errors( &db, krate, |path| resolver.resolve_path_as_macro(&db, &path), - &mut |err| panic!("{}", err), + &mut |err| error = Some(err), ) .unwrap() .unwrap(); let macro_file = MacroFile { macro_call_id }; - let expansion_result = db.parse_macro_expansion(macro_file); + let mut expansion_result = db.parse_macro_expansion(macro_file); + expansion_result.err = expansion_result.err.or(error); expansions.push((macro_call.value.clone(), expansion_result)); } let mut expanded_text = source_file.to_string(); for (call, exp) in expansions.into_iter().rev() { + let mut tree = false; + let mut expect_errors = false; + for comment in call.syntax().children_with_tokens().filter(|it| it.kind() == COMMENT) { + tree |= comment.to_string().contains("+tree"); + expect_errors |= comment.to_string().contains("+errors"); + } + let mut expn_text = String::new(); if let Some(err) = exp.err { format_to!(expn_text, "/* error: {} */", err); } if let Some((parse, _token_map)) = exp.value { - assert!( - parse.errors().is_empty(), - "parse errors in expansion: \n{:#?}", - parse.errors() - ); + if expect_errors { + assert!(!parse.errors().is_empty(), "no parse errors in expansion"); + for e in parse.errors() { + format_to!(expn_text, "/* parse error: {} */\n", e); + } + } else { + assert!( + parse.errors().is_empty(), + "parse errors in expansion: \n{:#?}", + parse.errors() + ); + } let pp = pretty_print_macro_expansion(parse.syntax_node()); let indent = IndentLevel::from_node(call.syntax()); let pp = reindent(indent, pp); format_to!(expn_text, "{}", pp); - if call.to_string().contains("// +tree") { + + if tree { let tree = format!("{:#?}", parse.syntax_node()) .split_inclusive("\n") .map(|line| format!("// {}", line)) @@ -129,6 +148,8 @@ fn pretty_print_macro_expansion(expn: SyntaxNode) -> String { (T![->], _) | (_, T![->]) => " ", (T![&&], _) | (_, T![&&]) => " ", (T![,], _) => " ", + (T![:], IDENT | T!['(']) => " ", + (T![:], _) if curr_kind.is_keyword() => " ", (T![fn], T!['(']) => "", (T![']'], _) if curr_kind.is_keyword() => " ", (T![']'], T![#]) => "\n", diff --git a/crates/hir_def/src/macro_expansion_tests/builtin.rs b/crates/hir_def/src/macro_expansion_tests/builtin.rs new file mode 100644 index 000000000000..6982116522c6 --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/builtin.rs @@ -0,0 +1,332 @@ +//! Tests for builtin macros (see `builtin_macro.rs` in `hir_expand`). + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_column_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { column!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { 0; } +"##]], + ); +} + +#[test] +fn test_line_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { line!() } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { 0 } +"##]], + ); +} + +#[test] +fn test_stringify_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + stringify!( + a + b + c + ); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + "a b c"; +} +"##]], + ); +} + +#[test] +fn test_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { "__RA_UNIMPLEMENTED__"; } +"##]], + ); +} + +#[test] +fn test_option_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { option_env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { std::option::Option::None:: < &str>; } +"##]], + ); +} + +#[test] +fn test_file_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { file!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { ""; } +"##]], + ); +} + +#[test] +fn test_assert_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + assert!(true, "{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + { + if !true { + $crate::panic!("{} {:?}", arg1(a, b, c), arg2); + } + }; +} +"##]], + ); +} + +#[test] +fn test_compile_error_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +// This expands to nothing (since it's in item position), but emits an error. +compile_error!("error!"); +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +/* error: error! */ +"##]], + ); +} + +#[test] +fn test_format_args_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + unsafe { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a, b, c)), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(arg2), std::fmt::Display::fmt), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_comma_exprs() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", a::(), b); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + unsafe { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::()), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(b), std::fmt::Display::fmt), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_broken_member_access() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + // +errors + format_args!("{} {:?}", a.); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + /* parse error: expected field name or number */ +unsafe { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.), std::fmt::Display::fmt), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_include_bytes_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"##]], + ); +} + +#[test] +fn test_concat_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { concat!("foo", "r", 0, r#"bar"#, "\n", false); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { "foor0bar\nfalse"; } +"##]], + ); +} + +#[test] +fn test_concat_idents_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { concat_idents!(foo, bar); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { foobar; } +"##]], + ); +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe.rs b/crates/hir_def/src/macro_expansion_tests/mbe.rs index df93298a3561..1da0110fe261 100644 --- a/crates/hir_def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir_def/src/macro_expansion_tests/mbe.rs @@ -4,6 +4,7 @@ mod tt_conversion; mod matching; mod meta_syntax; +mod regression; use expect_test::expect; @@ -35,12 +36,12 @@ macro_rules! impl_froms { } } impl From for TokenTree { - fn from(it:Leaf) -> TokenTree { + fn from(it: Leaf) -> TokenTree { TokenTree::Leaf(it) } } impl From for TokenTree { - fn from(it:Subtree) -> TokenTree { + fn from(it: Subtree) -> TokenTree { TokenTree::Subtree(it) } } @@ -433,10 +434,10 @@ macro_rules! structs { } struct Foo { - field:u32 + field: u32 } struct Bar { - field:u32 + field: u32 } // MACRO_ITEMS@0..40 // STRUCT@0..20 @@ -906,8 +907,8 @@ extern crate a; mod b; mod c {} use d; -const E:i32 = 0; -static F:i32 = 0; +const E: i32 = 0; +static F: i32 = 0; impl G {} struct H; enum I { @@ -1119,3 +1120,329 @@ 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!(); } +} +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() { + check( + r#" +macro_rules! m { (|$pat:pat| ) => { ok!(); } } +m! { |x| } + "#, + expect![[r#" +macro_rules! m { (|$pat:pat| ) => { ok!(); } } +ok!(); + "#]], + ); +} + +#[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 */ +/* parse error: expected SEMICOLON */ +fn f() { + K::C("0"); +} +// MACRO_ITEMS@0..17 +// FN@0..17 +// 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..17 +// STMT_LIST@5..17 +// L_CURLY@5..6 "{" +// EXPR_STMT@6..9 +// PATH_EXPR@6..9 +// PATH@6..9 +// PATH@6..7 +// PATH_SEGMENT@6..7 +// NAME_REF@6..7 +// IDENT@6..7 "K" +// COLON2@7..9 "::" +// EXPR_STMT@9..16 +// CALL_EXPR@9..15 +// PATH_EXPR@9..10 +// PATH@9..10 +// PATH_SEGMENT@9..10 +// NAME_REF@9..10 +// IDENT@9..10 "C" +// ARG_LIST@10..15 +// L_PAREN@10..11 "(" +// LITERAL@11..14 +// STRING@11..14 "\"0\"" +// R_PAREN@14..15 ")" +// SEMICOLON@15..16 ";" +// R_CURLY@16..17 "}" + +"#]], + ); +} + +#[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); +} +"#]], + ) +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs index 5bdabfc598ba..9fb6d96b7254 100644 --- a/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs +++ b/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs @@ -23,3 +23,30 @@ literal!(); "#]], ) } + +#[test] +fn test_expand_bad_literal() { + check( + r#" +macro_rules! m { ($i:literal) => {}; } +m!(&k"); +"#, + expect![[r#" +macro_rules! m { ($i:literal) => {}; } +/* error: Failed to lower macro args to token tree */"#]], + ); +} + +#[test] +fn test_empty_comments() { + check( + r#" +macro_rules! m{ ($fmt:expr) => (); } +m!(/**/); +"#, + expect![[r#" +macro_rules! m{ ($fmt:expr) => (); } +/* error: expected Expr */ +"#]], + ); +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs b/crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs index d434c8824492..dd5effa3683a 100644 --- a/crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs +++ b/crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -77,3 +77,64 @@ macro_rules! f3 { ($i:_) => () } "#]], ) } + +#[test] +fn test_rustc_issue_57597() { + // + check( + r#" +macro_rules! m0 { ($($($i:ident)?)+) => {}; } +macro_rules! m1 { ($($($i:ident)?)*) => {}; } +macro_rules! m2 { ($($($i:ident)?)?) => {}; } +macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; } +macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; } +macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; } +macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; } +macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; } +macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; } +macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; } +macro_rules! mA { ($($($($i:ident)+)?)*) => {}; } +macro_rules! mB { ($($($($i:ident)+)*)?) => {}; } + +m0!(); +m1!(); +m2!(); +m3!(); +m4!(); +m5!(); +m6!(); +m7!(); +m8!(); +m9!(); +mA!(); +mB!(); + "#, + expect![[r#" +macro_rules! m0 { ($($($i:ident)?)+) => {}; } +macro_rules! m1 { ($($($i:ident)?)*) => {}; } +macro_rules! m2 { ($($($i:ident)?)?) => {}; } +macro_rules! m3 { ($($($($i:ident)?)?)?) => {}; } +macro_rules! m4 { ($($($($i:ident)*)?)?) => {}; } +macro_rules! m5 { ($($($($i:ident)?)*)?) => {}; } +macro_rules! m6 { ($($($($i:ident)?)?)*) => {}; } +macro_rules! m7 { ($($($($i:ident)*)*)?) => {}; } +macro_rules! m8 { ($($($($i:ident)?)*)*) => {}; } +macro_rules! m9 { ($($($($i:ident)?)*)+) => {}; } +macro_rules! mA { ($($($($i:ident)+)?)*) => {}; } +macro_rules! mB { ($($($($i:ident)+)*)?) => {}; } + +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ +/* error: invalid macro definition: empty token tree in repetition */ + "#]], + ); +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir_def/src/macro_expansion_tests/mbe/regression.rs new file mode 100644 index 000000000000..563fe5058850 --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/mbe/regression.rs @@ -0,0 +1,902 @@ +//! Real world regressions and issues, not particularly minimized. +//! +//! While it's OK to just dump large macros here, it's preferable to come up +//! with a minimal example for the program and put a specific test to the parent +//! directory. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_vec() { + check( + r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} +fn main() { + vec!(); + vec![1u32,2]; +} +"#, + expect![[r#" +macro_rules! vec { + ($($item:expr),*) => {{ + let mut v = Vec::new(); + $( v.push($item); )* + v + }}; +} +fn main() { + { + let mut v = Vec::new(); + v + }; + { + let mut v = Vec::new(); + v.push(1u32); + v.push(2); + v + }; +} +"#]], + ); +} + +#[test] +fn test_winapi_struct() { + // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/macros.rs#L366 + + check( + r#" +macro_rules! STRUCT { + ($(#[$attrs:meta])* struct $name:ident { + $($field:ident: $ftype:ty,)+ + }) => ( + #[repr(C)] #[derive(Copy)] $(#[$attrs])* + pub struct $name { + $(pub $field: $ftype,)+ + } + impl Clone for $name { + #[inline] + fn clone(&self) -> $name { *self } + } + #[cfg(feature = "impl-default")] + impl Default for $name { + #[inline] + fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } + } + ); +} + +// from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/shared/d3d9caps.rs +STRUCT!{struct D3DVSHADERCAPS2_0 {Caps: u8,}} + +STRUCT!{#[cfg_attr(target_arch = "x86", repr(packed))] struct D3DCONTENTPROTECTIONCAPS {Caps : u8 ,}} +"#, + expect![[r##" +macro_rules! STRUCT { + ($(#[$attrs:meta])* struct $name:ident { + $($field:ident: $ftype:ty,)+ + }) => ( + #[repr(C)] #[derive(Copy)] $(#[$attrs])* + pub struct $name { + $(pub $field: $ftype,)+ + } + impl Clone for $name { + #[inline] + fn clone(&self) -> $name { *self } + } + #[cfg(feature = "impl-default")] + impl Default for $name { + #[inline] + fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } + } + ); +} + +#[repr(C)] +#[derive(Copy)] pub struct D3DVSHADERCAPS2_0 { + pub Caps: u8, +} +impl Clone for D3DVSHADERCAPS2_0 { + #[inline] fn clone(&self ) -> D3DVSHADERCAPS2_0 { + *self + } +} +#[cfg(feature = "impl-default")] impl Default for D3DVSHADERCAPS2_0 { + #[inline] fn default() -> D3DVSHADERCAPS2_0 { + unsafe { + $crate::_core::mem::zeroed() + } + } +} + +#[repr(C)] +#[derive(Copy)] +#[cfg_attr(target_arch = "x86", repr(packed))] pub struct D3DCONTENTPROTECTIONCAPS { + pub Caps: u8, +} +impl Clone for D3DCONTENTPROTECTIONCAPS { + #[inline] fn clone(&self ) -> D3DCONTENTPROTECTIONCAPS { + *self + } +} +#[cfg(feature = "impl-default")] impl Default for D3DCONTENTPROTECTIONCAPS { + #[inline] fn default() -> D3DCONTENTPROTECTIONCAPS { + unsafe { + $crate::_core::mem::zeroed() + } + } +} +"##]], + ); +} + +#[test] +fn test_int_base() { + check( + r#" +macro_rules! int_base { + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::$Trait for $T { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $Radix.fmt_int(*self as $U, f) + } + } + } +} +int_base!{Binary for isize as usize -> Binary} +"#, + expect![[r##" +macro_rules! int_base { + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::$Trait for $T { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $Radix.fmt_int(*self as $U, f) + } + } + } +} +#[stable(feature = "rust1", since = "1.0.0")] impl fmt::Binary for isize { + fn fmt(&self , f: &mut fmt::Formatter< '_>) -> fmt::Result { + Binary.fmt_int(*self as usize, f) + } +} +"##]], + ); +} + +#[test] +fn test_generate_pattern_iterators() { + // From . + check( + r#" +macro_rules! generate_pattern_iterators { + { double ended; with $(#[$common_stability_attribute:meta])*, + $forward_iterator:ident, + $reverse_iterator:ident, $iterty:ty + } => { ok!(); } +} +generate_pattern_iterators ! ( double ended ; with # [ stable ( feature = "rust1" , since = "1.0.0" ) ] , Split , RSplit , & 'a str ); +"#, + expect![[r##" +macro_rules! generate_pattern_iterators { + { double ended; with $(#[$common_stability_attribute:meta])*, + $forward_iterator:ident, + $reverse_iterator:ident, $iterty:ty + } => { ok!(); } +} +ok!(); +"##]], + ); +} + +#[test] +fn test_impl_fn_for_zst() { + // From . + check( + r#" +macro_rules! impl_fn_for_zst { + {$( $( #[$attr: meta] )* + struct $Name: ident impl$( <$( $lifetime : lifetime ),+> )? Fn = + |$( $arg: ident: $ArgTy: ty ),*| -> $ReturnTy: ty $body: block; + )+} => {$( + $( #[$attr] )* + struct $Name; + + impl $( <$( $lifetime ),+> )? Fn<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call(&self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + $body + } + } + + impl $( <$( $lifetime ),+> )? FnMut<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call_mut( + &mut self, + ($( $arg, )*): ($( $ArgTy, )*) + ) -> $ReturnTy { + Fn::call(&*self, ($( $arg, )*)) + } + } + + impl $( <$( $lifetime ),+> )? FnOnce<($( $ArgTy, )*)> for $Name { + type Output = $ReturnTy; + + #[inline] + extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + Fn::call(&self, ($( $arg, )*)) + } + } + )+} +} + +impl_fn_for_zst ! { + #[derive(Clone)] + struct CharEscapeDebugContinue impl Fn = |c: char| -> char::EscapeDebug { + c.escape_debug_ext(false) + }; + + #[derive(Clone)] + struct CharEscapeUnicode impl Fn = |c: char| -> char::EscapeUnicode { + c.escape_unicode() + }; + + #[derive(Clone)] + struct CharEscapeDefault impl Fn = |c: char| -> char::EscapeDefault { + c.escape_default() + }; +} + +"#, + expect![[r##" +macro_rules! impl_fn_for_zst { + {$( $( #[$attr: meta] )* + struct $Name: ident impl$( <$( $lifetime : lifetime ),+> )? Fn = + |$( $arg: ident: $ArgTy: ty ),*| -> $ReturnTy: ty $body: block; + )+} => {$( + $( #[$attr] )* + struct $Name; + + impl $( <$( $lifetime ),+> )? Fn<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call(&self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + $body + } + } + + impl $( <$( $lifetime ),+> )? FnMut<($( $ArgTy, )*)> for $Name { + #[inline] + extern "rust-call" fn call_mut( + &mut self, + ($( $arg, )*): ($( $ArgTy, )*) + ) -> $ReturnTy { + Fn::call(&*self, ($( $arg, )*)) + } + } + + impl $( <$( $lifetime ),+> )? FnOnce<($( $ArgTy, )*)> for $Name { + type Output = $ReturnTy; + + #[inline] + extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { + Fn::call(&self, ($( $arg, )*)) + } + } + )+} +} + +#[derive(Clone)] struct CharEscapeDebugContinue; +impl Fn<(char, )> for CharEscapeDebugContinue { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDebug { { + c.escape_debug_ext(false ) + } + } +} +impl FnMut<(char, )> for CharEscapeDebugContinue { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDebug { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeDebugContinue { + type Output = char::EscapeDebug; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDebug { + Fn::call(&self , (c, )) + } +} +#[derive(Clone)] struct CharEscapeUnicode; +impl Fn<(char, )> for CharEscapeUnicode { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeUnicode { { + c.escape_unicode() + } + } +} +impl FnMut<(char, )> for CharEscapeUnicode { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeUnicode { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeUnicode { + type Output = char::EscapeUnicode; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeUnicode { + Fn::call(&self , (c, )) + } +} +#[derive(Clone)] struct CharEscapeDefault; +impl Fn<(char, )> for CharEscapeDefault { + #[inline] extern "rust-call"fn call(&self , (c, ): (char, )) -> char::EscapeDefault { { + c.escape_default() + } + } +} +impl FnMut<(char, )> for CharEscapeDefault { + #[inline] extern "rust-call"fn call_mut(&mut self , (c, ): (char, )) -> char::EscapeDefault { + Fn::call(&*self , (c, )) + } +} +impl FnOnce<(char, )> for CharEscapeDefault { + type Output = char::EscapeDefault; + #[inline] extern "rust-call"fn call_once(self , (c, ): (char, )) -> char::EscapeDefault { + Fn::call(&self , (c, )) + } +} + +"##]], + ); +} + +#[test] +fn test_impl_nonzero_fmt() { + // From . + check( + r#" +macro_rules! impl_nonzero_fmt { + ( #[$stability: meta] ( $( $Trait: ident ),+ ) for $Ty: ident ) => { ok!(); } +} +impl_nonzero_fmt! { + #[stable(feature= "nonzero",since="1.28.0")] + (Debug, Display, Binary, Octal, LowerHex, UpperHex) for NonZeroU8 +} +"#, + expect![[r##" +macro_rules! impl_nonzero_fmt { + ( #[$stability: meta] ( $( $Trait: ident ),+ ) for $Ty: ident ) => { ok!(); } +} +ok!(); +"##]], + ); +} + +#[test] +fn test_cfg_if_items() { + // From . + check( + r#" +macro_rules! __cfg_if_items { + (($($not:meta,)*) ; ) => {}; + (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { + __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } + } +} +__cfg_if_items! { + (rustdoc,); + ( () ( + #[ cfg(any(target_os = "redox", unix))] + #[ stable(feature = "rust1", since = "1.0.0")] + pub use sys::ext as unix; + + #[cfg(windows)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use sys::ext as windows; + + #[cfg(any(target_os = "linux", target_os = "l4re"))] + pub mod linux; + )), +} +"#, + expect![[r#" +macro_rules! __cfg_if_items { + (($($not:meta,)*) ; ) => {}; + (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { + __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } + } +} +__cfg_if_items! { + (rustdoc, ); +} +"#]], + ); +} + +#[test] +fn test_cfg_if_main() { + // From . + check( + r#" +macro_rules! cfg_if { + ($(if #[cfg($($meta:meta),*)] { $($it:item)* } )else* else { $($it2:item)* }) + => { + __cfg_if_items! { + () ; + $( ( ($($meta),*) ($($it)*) ), )* + ( () ($($it2)*) ), + } + }; + + // Internal macro to Apply a cfg attribute to a list of items + (@__apply $m:meta, $($it:item)*) => { $(#[$m] $it)* }; +} + +cfg_if! { + if #[cfg(target_env = "msvc")] { + // no extra unwinder support needed + } else if #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] { + // no unwinder on the system! + } else { + mod libunwind; + pub use libunwind::*; + } +} + +cfg_if! { + @__apply cfg(all(not(any(not(any(target_os = "solaris", target_os = "illumos")))))), +} +"#, + expect![[r##" +macro_rules! cfg_if { + ($(if #[cfg($($meta:meta),*)] { $($it:item)* } )else* else { $($it2:item)* }) + => { + __cfg_if_items! { + () ; + $( ( ($($meta),*) ($($it)*) ), )* + ( () ($($it2)*) ), + } + }; + + // Internal macro to Apply a cfg attribute to a list of items + (@__apply $m:meta, $($it:item)*) => { $(#[$m] $it)* }; +} + +__cfg_if_items! { + (); + ((target_env = "msvc")()), ((all(target_arch = "wasm32", not(target_os = "emscripten")))()), (()(mod libunwind; + pub use libunwind::*; + )), +} + + +"##]], + ); +} + +#[test] +fn test_proptest_arbitrary() { + // From . + check( + r#" +macro_rules! arbitrary { + ([$($bounds : tt)*] $typ: ty, $strat: ty, $params: ty; + $args: ident => $logic: expr) => { + impl<$($bounds)*> $crate::arbitrary::Arbitrary for $typ { + type Parameters = $params; + type Strategy = $strat; + fn arbitrary_with($args: Self::Parameters) -> Self::Strategy { + $logic + } + } + }; +} + +arbitrary!( + [A:Arbitrary] + Vec , + VecStrategy, + RangedParams1; + args => { + let product_unpack![range, a] = args; + vec(any_with::(a), range) + } +); +"#, + expect![[r#" +macro_rules! arbitrary { + ([$($bounds : tt)*] $typ: ty, $strat: ty, $params: ty; + $args: ident => $logic: expr) => { + impl<$($bounds)*> $crate::arbitrary::Arbitrary for $typ { + type Parameters = $params; + type Strategy = $strat; + fn arbitrary_with($args: Self::Parameters) -> Self::Strategy { + $logic + } + } + }; +} + +impl $crate::arbitrary::Arbitrary for Vec { + type Parameters = RangedParams1; + type Strategy = VecStrategy; + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { { + let product_unpack![range, a] = args; + vec(any_with::(a), range) + } + } +} +"#]], + ); +} + +#[test] +fn test_old_ridl() { + // This is from winapi 2.8, which do not have a link from github. + check( + r#" +#[macro_export] +macro_rules! RIDL { + (interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) + {$( + fn $method:ident(&mut self $(,$p:ident : $t:ty)*) -> $rtr:ty + ),+} + ) => { + impl $interface { + $(pub unsafe fn $method(&mut self) -> $rtr { + ((*self.lpVtbl).$method)(self $(,$p)*) + })+ + } + }; +} + +RIDL!{interface ID3D11Asynchronous(ID3D11AsynchronousVtbl): ID3D11DeviceChild(ID3D11DeviceChildVtbl) { + fn GetDataSize(&mut self) -> UINT +}} +"#, + expect![[r##" +#[macro_export] +macro_rules! RIDL { + (interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) + {$( + fn $method:ident(&mut self $(,$p:ident : $t:ty)*) -> $rtr:ty + ),+} + ) => { + impl $interface { + $(pub unsafe fn $method(&mut self) -> $rtr { + ((*self.lpVtbl).$method)(self $(,$p)*) + })+ + } + }; +} + +impl ID3D11Asynchronous { + pub unsafe fn GetDataSize(&mut self ) -> UINT { + ((*self .lpVtbl).GetDataSize)(self ) + } +} +"##]], + ); +} + +#[test] +fn test_quick_error() { + check( + r#" +macro_rules! quick_error { + (SORT [enum $name:ident $( #[$meta:meta] )*] + items [$($( #[$imeta:meta] )* + => $iitem:ident: $imode:tt [$( $ivar:ident: $ityp:ty ),*] + {$( $ifuncs:tt )*} )* ] + buf [ ] + queue [ ] + ) => { + quick_error!(ENUMINITION [enum $name $( #[$meta] )*] + body [] + queue [$( + $( #[$imeta] )* + => + $iitem: $imode [$( $ivar: $ityp ),*] + )*] + ); + }; +} +quick_error ! ( + SORT + [enum Wrapped #[derive(Debug)]] + items [ + => One: UNIT [] {} + => Two: TUPLE [s :String] {display ("two: {}" , s) from ()} ] + buf [ ] + queue [ ] +); + +"#, + expect![[r##" +macro_rules! quick_error { + (SORT [enum $name:ident $( #[$meta:meta] )*] + items [$($( #[$imeta:meta] )* + => $iitem:ident: $imode:tt [$( $ivar:ident: $ityp:ty ),*] + {$( $ifuncs:tt )*} )* ] + buf [ ] + queue [ ] + ) => { + quick_error!(ENUMINITION [enum $name $( #[$meta] )*] + body [] + queue [$( + $( #[$imeta] )* + => + $iitem: $imode [$( $ivar: $ityp ),*] + )*] + ); + }; +} +quick_error!(ENUMINITION[enum Wrapped#[derive(Debug)]]body[]queue[ = > One: UNIT[] = > Two: TUPLE[s: String]]); + +"##]], + ) +} + +#[test] +fn test_empty_repeat_vars_in_empty_repeat_vars() { + check( + r#" +macro_rules! delegate_impl { + ([$self_type:ident, $self_wrap:ty, $self_map:ident] + pub trait $name:ident $(: $sup:ident)* $(+ $more_sup:ident)* { + + $( + @escape [type $assoc_name_ext:ident] + )* + $( + @section type + $( + $(#[$_assoc_attr:meta])* + type $assoc_name:ident $(: $assoc_bound:ty)*; + )+ + )* + $( + @section self + $( + $(#[$_method_attr:meta])* + fn $method_name:ident(self $(: $self_selftype:ty)* $(,$marg:ident : $marg_ty:ty)*) -> $mret:ty; + )+ + )* + $( + @section nodelegate + $($tail:tt)* + )* + }) => { + impl<> $name for $self_wrap where $self_type: $name { + $( + $( + fn $method_name(self $(: $self_selftype)* $(,$marg: $marg_ty)*) -> $mret { + $self_map!(self).$method_name($($marg),*) + } + )* + )* + } + } +} +delegate_impl ! { + [G, &'a mut G, deref] pub trait Data: GraphBase {@section type type NodeWeight;} +} +"#, + expect![[r##" +macro_rules! delegate_impl { + ([$self_type:ident, $self_wrap:ty, $self_map:ident] + pub trait $name:ident $(: $sup:ident)* $(+ $more_sup:ident)* { + + $( + @escape [type $assoc_name_ext:ident] + )* + $( + @section type + $( + $(#[$_assoc_attr:meta])* + type $assoc_name:ident $(: $assoc_bound:ty)*; + )+ + )* + $( + @section self + $( + $(#[$_method_attr:meta])* + fn $method_name:ident(self $(: $self_selftype:ty)* $(,$marg:ident : $marg_ty:ty)*) -> $mret:ty; + )+ + )* + $( + @section nodelegate + $($tail:tt)* + )* + }) => { + impl<> $name for $self_wrap where $self_type: $name { + $( + $( + fn $method_name(self $(: $self_selftype)* $(,$marg: $marg_ty)*) -> $mret { + $self_map!(self).$method_name($($marg),*) + } + )* + )* + } + } +} +impl <> Data for & 'amut G where G: Data {} +"##]], + ); +} + +#[test] +fn test_issue_2520() { + check( + r#" +macro_rules! my_macro { + { + ( $( + $( [] $sname:ident : $stype:ty )? + $( [$expr:expr] $nname:ident : $ntype:ty )? + ),* ) + } => {ok!( + Test { + $( + $( $sname, )? + )* + } + );}; +} + +my_macro! { + ([] p1: u32, [|_| S0K0] s: S0K0, [] k0: i32) +} + "#, + expect![[r#" +macro_rules! my_macro { + { + ( $( + $( [] $sname:ident : $stype:ty )? + $( [$expr:expr] $nname:ident : $ntype:ty )? + ),* ) + } => {ok!( + Test { + $( + $( $sname, )? + )* + } + );}; +} + +ok!(Test { + p1, k0, +} +); + "#]], + ); +} + +#[test] +fn test_repeat_bad_var() { + // FIXME: the second rule of the macro should be removed and an error about + // `$( $c )+` raised + check( + r#" +macro_rules! foo { + ($( $b:ident )+) => { ok!($( $c )+); }; + ($( $b:ident )+) => { ok!($( $b )+); } +} + +foo!(b0 b1); +"#, + expect![[r#" +macro_rules! foo { + ($( $b:ident )+) => { ok!($( $c )+); }; + ($( $b:ident )+) => { ok!($( $b )+); } +} + +ok!(b0 b1); +"#]], + ); +} + +#[test] +fn test_issue_3861() { + // This is should (and does) produce a parse error. It used to infinite loop + // instead. + check( + r#" +macro_rules! rgb_color { + ($p:expr, $t:ty) => { + pub fn new() { + let _ = 0 as $t << $p; + } + }; +} +// +tree +errors +rgb_color!(8 + 8, u32); +"#, + expect![[r#" +macro_rules! rgb_color { + ($p:expr, $t:ty) => { + pub fn new() { + let _ = 0 as $t << $p; + } + }; +} +/* parse error: expected type */ +/* parse error: expected R_ANGLE */ +/* parse error: expected COMMA */ +/* parse error: expected R_ANGLE */ +/* parse error: expected SEMICOLON */ +pub fn new() { + let _ = 0as u32<<8+8; +} +// MACRO_ITEMS@0..29 +// FN@0..29 +// VISIBILITY@0..3 +// PUB_KW@0..3 "pub" +// FN_KW@3..5 "fn" +// NAME@5..8 +// IDENT@5..8 "new" +// PARAM_LIST@8..10 +// L_PAREN@8..9 "(" +// R_PAREN@9..10 ")" +// BLOCK_EXPR@10..29 +// STMT_LIST@10..29 +// L_CURLY@10..11 "{" +// LET_STMT@11..24 +// LET_KW@11..14 "let" +// WILDCARD_PAT@14..15 +// UNDERSCORE@14..15 "_" +// EQ@15..16 "=" +// CAST_EXPR@16..24 +// LITERAL@16..17 +// INT_NUMBER@16..17 "0" +// AS_KW@17..19 "as" +// PATH_TYPE@19..24 +// PATH@19..24 +// PATH_SEGMENT@19..24 +// NAME_REF@19..22 +// IDENT@19..22 "u32" +// GENERIC_ARG_LIST@22..24 +// L_ANGLE@22..23 "<" +// TYPE_ARG@23..24 +// PATH_TYPE@23..24 +// PATH@23..24 +// PATH_SEGMENT@23..24 +// L_ANGLE@23..24 "<" +// EXPR_STMT@24..28 +// BIN_EXPR@24..27 +// LITERAL@24..25 +// INT_NUMBER@24..25 "8" +// PLUS@25..26 "+" +// LITERAL@26..27 +// INT_NUMBER@26..27 "8" +// SEMICOLON@27..28 ";" +// R_CURLY@28..29 "}" + +"#]], + ); +} + +#[test] +fn test_no_space_after_semi_colon() { + check( + r#" +macro_rules! with_std { + ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) +} + +with_std! {mod m;mod f;} +"#, + expect![[r##" +macro_rules! with_std { + ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) +} + +#[cfg(feature = "std")] mod m; +#[cfg(feature = "std")] mod f; +"##]], + ) +} diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs index a4ddafbf7af4..4b801eb2a154 100644 --- a/crates/hir_expand/src/builtin_macro.rs +++ b/crates/hir_expand/src/builtin_macro.rs @@ -565,304 +565,3 @@ fn option_env_expand( ExpandResult::ok(Some(ExpandedEager::new(expanded))) } - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use base_db::{fixture::WithFixture, SourceDatabase}; - use expect_test::{expect, Expect}; - use syntax::ast::HasName; - - use crate::{ - name::AsName, test_db::TestDB, AstNode, EagerCallInfo, ExpandTo, MacroCallId, - MacroCallKind, MacroCallLoc, - }; - - use super::*; - - fn expand_builtin_macro(ra_fixture: &str) -> String { - let (db, file_id) = TestDB::with_single_file(ra_fixture); - let parsed = db.parse(file_id); - let mut macro_rules: Vec<_> = - parsed.syntax_node().descendants().filter_map(ast::MacroRules::cast).collect(); - let mut macro_calls: Vec<_> = - parsed.syntax_node().descendants().filter_map(ast::MacroCall::cast).collect(); - - let ast_id_map = db.ast_id_map(file_id.into()); - - assert_eq!(macro_rules.len(), 1, "test must contain exactly 1 `macro_rules!`"); - assert_eq!(macro_calls.len(), 1, "test must contain exactly 1 macro call"); - let macro_rules = ast::Macro::from(macro_rules.pop().unwrap()); - let macro_call = macro_calls.pop().unwrap(); - - let expander = find_by_name(¯o_rules.name().unwrap().as_name()).unwrap(); - let ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(¯o_rules)); - - let krate = CrateId(0); - let file_id = match expander { - Either::Left(expander) => { - // the first one should be a macro_rules - let def = MacroDefId { - krate: CrateId(0), - kind: MacroDefKind::BuiltIn(expander, ast_id), - local_inner: false, - }; - - let loc = MacroCallLoc { - def, - krate, - eager: None, - kind: MacroCallKind::FnLike { - ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_call)), - expand_to: ExpandTo::Expr, - }, - }; - - let id: MacroCallId = db.intern_macro(loc); - id.as_file() - } - Either::Right(expander) => { - // the first one should be a macro_rules - let def = MacroDefId { - krate, - kind: MacroDefKind::BuiltInEager(expander, ast_id), - local_inner: false, - }; - - let args = macro_call.token_tree().unwrap(); - let parsed_args = mbe::syntax_node_to_token_tree(args.syntax()).0; - let call_id = AstId::new(file_id.into(), ast_id_map.ast_id(¯o_call)); - - let arg_id = db.intern_macro(MacroCallLoc { - def, - krate, - eager: Some(EagerCallInfo { - arg_or_expansion: Arc::new(parsed_args.clone()), - included_file: None, - }), - kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr }, - }); - - let expanded = expander.expand(&db, arg_id, &parsed_args).value.unwrap(); - let expand_to = crate::ExpandTo::from_call_site(¯o_call); - let loc = MacroCallLoc { - def, - krate, - eager: Some(EagerCallInfo { - arg_or_expansion: Arc::new(expanded.subtree), - included_file: expanded.included_file, - }), - kind: MacroCallKind::FnLike { ast_id: call_id, expand_to }, - }; - - let id: MacroCallId = db.intern_macro(loc); - id.as_file() - } - }; - - db.parse_or_expand(file_id).unwrap().to_string() - } - - fn check_expansion(ra_fixture: &str, expect: Expect) { - let expansion = expand_builtin_macro(ra_fixture); - expect.assert_eq(&expansion); - } - - #[test] - fn test_column_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! column {() => {}} - column!() - "#, - expect![["0"]], - ); - } - - #[test] - fn test_line_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! line {() => {}} - line!() - "#, - expect![["0"]], - ); - } - - #[test] - fn test_stringify_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! stringify {() => {}} - stringify!( - a - b - c - ) - "#, - expect![["\"a b c\""]], - ); - } - - #[test] - fn test_env_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! env {() => {}} - env!("TEST_ENV_VAR") - "#, - expect![["\"__RA_UNIMPLEMENTED__\""]], - ); - } - - #[test] - fn test_option_env_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! option_env {() => {}} - option_env!("TEST_ENV_VAR") - "#, - expect![["std::option::Option::None:: < &str>"]], - ); - } - - #[test] - fn test_file_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! file {() => {}} - file!() - "#, - expect![[r#""""#]], - ); - } - - #[test] - fn test_assert_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! assert { - ($cond:expr) => ({ /* compiler built-in */ }); - ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) - } - assert!(true, "{} {:?}", arg1(a, b, c), arg2); - "#, - expect![[r#"{if!true{$crate::panic!("{} {:?}",arg1(a,b,c),arg2);}}"#]], - ); - } - - #[test] - fn test_compile_error_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! compile_error { - ($msg:expr) => ({ /* compiler built-in */ }); - ($msg:expr,) => ({ /* compiler built-in */ }) - } - compile_error!("error!"); - "#, - // This expands to nothing (since it's in item position), but emits an error. - expect![[""]], - ); - } - - #[test] - fn test_format_args_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) - } - format_args!("{} {:?}", arg1(a, b, c), arg2); - "#, - expect![[ - r#"unsafe{std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a,b,c)),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(arg2),std::fmt::Display::fmt),])}"# - ]], - ); - } - - #[test] - fn test_format_args_expand_with_comma_exprs() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) - } - format_args!("{} {:?}", a::(), b); - "#, - expect![[ - r#"unsafe{std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::()),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(b),std::fmt::Display::fmt),])}"# - ]], - ); - } - - #[test] - fn test_format_args_expand_with_broken_member_access() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) - } - format_args!("{} {:?}", a.); - "#, - expect![[ - r#"unsafe{std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.),std::fmt::Display::fmt),])}"# - ]], - ); - } - - #[test] - fn test_include_bytes_expand() { - check_expansion( - r#" - #[rustc_builtin_macro] - macro_rules! include_bytes { - ($file:expr) => {{ /* compiler built-in */ }}; - ($file:expr,) => {{ /* compiler built-in */ }}; - } - include_bytes("foo"); - "#, - expect![[r#"b"""#]], - ); - } - - #[test] - fn test_concat_expand() { - check_expansion( - r##" - #[rustc_builtin_macro] - macro_rules! concat {} - concat!("foo", "r", 0, r#"bar"#, "\n", false); - "##, - expect![[r#""foor0bar\nfalse""#]], - ); - } - - #[test] - fn test_concat_idents_expand() { - check_expansion( - r##" - #[rustc_builtin_macro] - macro_rules! concat_idents {} - concat_idents!(foo, bar); - "##, - expect![[r#"foobar"#]], - ); - } -} diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs index 64d80baa3ed1..f4d7e6059593 100644 --- a/crates/mbe/src/tests.rs +++ b/crates/mbe/src/tests.rs @@ -1,219 +1,9 @@ -mod expand; +use std::iter; -use std::{fmt::Write, iter}; - -use syntax::{ast, AstNode, NodeOrToken, SyntaxNode, WalkEvent}; -use test_utils::assert_eq_text; - -use crate::ParserEntryPoint; +use syntax::{ast, AstNode}; use super::*; -pub(crate) struct MacroFixture { - rules: MacroRules, -} - -pub(crate) struct MacroFixture2 { - rules: MacroDef, -} - -macro_rules! impl_fixture { - ($name:ident) => { - impl $name { - pub(crate) fn expand_tt(&self, invocation: &str) -> tt::Subtree { - self.try_expand_tt(invocation).unwrap() - } - - fn try_expand_tt(&self, invocation: &str) -> Result { - let source_file = ast::SourceFile::parse(invocation).tree(); - let macro_invocation = - source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); - - let (invocation_tt, _) = - syntax_node_to_token_tree(macro_invocation.token_tree().unwrap().syntax()); - - self.rules.expand(&invocation_tt).result() - } - - #[allow(unused)] - fn assert_expand_err(&self, invocation: &str, err: &ExpandError) { - assert_eq!(self.try_expand_tt(invocation).as_ref(), Err(err)); - } - - #[allow(unused)] - fn expand_items(&self, invocation: &str) -> SyntaxNode { - let expanded = self.expand_tt(invocation); - token_tree_to_syntax_node(&expanded, ParserEntryPoint::Items) - .unwrap() - .0 - .syntax_node() - } - - #[allow(unused)] - fn expand_statements(&self, invocation: &str) -> SyntaxNode { - let expanded = self.expand_tt(invocation); - token_tree_to_syntax_node(&expanded, ParserEntryPoint::Statements) - .unwrap() - .0 - .syntax_node() - } - - #[allow(unused)] - fn expand_expr(&self, invocation: &str) -> SyntaxNode { - let expanded = self.expand_tt(invocation); - token_tree_to_syntax_node(&expanded, ParserEntryPoint::Expr) - .unwrap() - .0 - .syntax_node() - } - - #[allow(unused)] - fn assert_expand_tt(&self, invocation: &str, expected: &str) { - let expansion = self.expand_tt(invocation); - assert_eq!(expansion.to_string(), expected); - } - - #[allow(unused)] - fn assert_expand(&self, invocation: &str, expected: &str) { - let expansion = self.expand_tt(invocation); - let actual = format!("{:?}", expansion); - test_utils::assert_eq_text!(&expected.trim(), &actual.trim()); - } - - #[allow(unused)] - fn assert_expand_items(&self, invocation: &str, expected: &str) -> &$name { - self.assert_expansion(ParserEntryPoint::Items, invocation, expected); - self - } - - #[allow(unused)] - fn assert_expand_statements(&self, invocation: &str, expected: &str) -> &$name { - self.assert_expansion(ParserEntryPoint::Statements, invocation, expected); - self - } - - fn assert_expansion(&self, kind: ParserEntryPoint, invocation: &str, expected: &str) { - let expanded = self.expand_tt(invocation); - assert_eq!(expanded.to_string(), expected); - - let expected = expected.replace("$crate", "C_C__C"); - - // wrap the given text to a macro call - let expected = { - let wrapped = format!("wrap_macro!( {} )", expected); - let wrapped = ast::SourceFile::parse(&wrapped); - let wrapped = wrapped - .tree() - .syntax() - .descendants() - .find_map(ast::TokenTree::cast) - .unwrap(); - let mut wrapped = syntax_node_to_token_tree(wrapped.syntax()).0; - wrapped.delimiter = None; - wrapped - }; - - let expanded_tree = - token_tree_to_syntax_node(&expanded, kind).unwrap().0.syntax_node(); - let expanded_tree = debug_dump_ignore_spaces(&expanded_tree).trim().to_string(); - - let expected_tree = - token_tree_to_syntax_node(&expected, kind).unwrap().0.syntax_node(); - let expected_tree = debug_dump_ignore_spaces(&expected_tree).trim().to_string(); - - let expected_tree = expected_tree.replace("C_C__C", "$crate"); - assert_eq!( - expanded_tree, expected_tree, - "\nleft:\n{}\nright:\n{}", - expanded_tree, expected_tree, - ); - } - } - }; -} - -impl_fixture!(MacroFixture); -impl_fixture!(MacroFixture2); - -pub(crate) fn parse_macro(ra_fixture: &str) -> MacroFixture { - let definition_tt = parse_macro_rules_to_tt(ra_fixture); - let rules = MacroRules::parse(&definition_tt).unwrap(); - MacroFixture { rules } -} - -pub(crate) fn parse_macro_error(ra_fixture: &str) -> ParseError { - let definition_tt = parse_macro_rules_to_tt(ra_fixture); - - match MacroRules::parse(&definition_tt) { - Ok(_) => panic!("Expect error"), - Err(err) => err, - } -} - -pub(crate) fn parse_to_token_tree_by_syntax(ra_fixture: &str) -> tt::Subtree { - let source_file = ast::SourceFile::parse(ra_fixture).ok().unwrap(); - let tt = syntax_node_to_token_tree(source_file.syntax()).0; - - let parsed = parse_to_token_tree(ra_fixture).unwrap().0; - assert_eq!(tt, parsed); - - parsed -} - -fn parse_macro_rules_to_tt(ra_fixture: &str) -> tt::Subtree { - let source_file = ast::SourceFile::parse(ra_fixture).ok().unwrap(); - let macro_definition = - source_file.syntax().descendants().find_map(ast::MacroRules::cast).unwrap(); - - let (definition_tt, _) = - syntax_node_to_token_tree(macro_definition.token_tree().unwrap().syntax()); - - let parsed = parse_to_token_tree( - &ra_fixture[macro_definition.token_tree().unwrap().syntax().text_range()], - ) - .unwrap() - .0; - assert_eq!(definition_tt, parsed); - - definition_tt -} - -fn debug_dump_ignore_spaces(node: &syntax::SyntaxNode) -> String { - let mut level = 0; - let mut buf = String::new(); - macro_rules! indent { - () => { - for _ in 0..level { - buf.push_str(" "); - } - }; - } - - for event in node.preorder_with_tokens() { - match event { - WalkEvent::Enter(element) => { - match element { - NodeOrToken::Node(node) => { - indent!(); - writeln!(buf, "{:?}", node.kind()).unwrap(); - } - NodeOrToken::Token(token) => match token.kind() { - syntax::SyntaxKind::WHITESPACE => {} - _ => { - indent!(); - writeln!(buf, "{:?}", token.kind()).unwrap(); - } - }, - } - level += 1; - } - WalkEvent::Leave(_) => level -= 1, - } - } - - buf -} - #[test] fn test_node_to_tt_censor() { use syntax::ast::{HasAttrs, HasModuleItem}; diff --git a/crates/mbe/src/tests/expand.rs b/crates/mbe/src/tests/expand.rs deleted file mode 100644 index 15a718402911..000000000000 --- a/crates/mbe/src/tests/expand.rs +++ /dev/null @@ -1,951 +0,0 @@ -use ::parser::ParserEntryPoint; -use syntax::{ - SyntaxKind::{ERROR, IDENT}, - T, -}; -use test_utils::assert_eq_text; - -use super::*; - -// Good first issue (although a slightly challenging one): -// -// * Pick a random test from here -// https://github.com/intellij-rust/intellij-rust/blob/c4e9feee4ad46e7953b1948c112533360b6087bb/src/test/kotlin/org/rust/lang/core/macros/RsMacroExpansionTest.kt -// * Port the test to rust and add it to this module -// * Make it pass :-) - -#[test] -fn test_token_id_shift() { - let expansion = parse_macro( - r#" -macro_rules! foobar { - ($e:ident) => { foo bar $e } -} -"#, - ) - .expand_tt("foobar!(baz);"); - - fn get_id(t: &tt::TokenTree) -> Option { - if let tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) = t { - return Some(ident.id.0); - } - None - } - - assert_eq!(expansion.token_trees.len(), 3); - // {($e:ident) => { foo bar $e }} - // 012345 67 8 9 T 12 - assert_eq!(get_id(&expansion.token_trees[0]), Some(9)); - assert_eq!(get_id(&expansion.token_trees[1]), Some(10)); - - // The input args of macro call include parentheses: - // (baz) - // So baz should be 12+1+1 - assert_eq!(get_id(&expansion.token_trees[2]), Some(14)); -} - -#[test] -fn test_token_map() { - let expanded = parse_macro( - r#" -macro_rules! foobar { - ($e:ident) => { fn $e() {} } -} -"#, - ) - .expand_tt("foobar!(baz);"); - - let (node, token_map) = token_tree_to_syntax_node(&expanded, ParserEntryPoint::Items).unwrap(); - let content = node.syntax_node().to_string(); - - let get_text = |id, kind| -> String { - content[token_map.first_range_by_token(id, kind).unwrap()].to_string() - }; - - assert_eq!(expanded.token_trees.len(), 4); - // {($e:ident) => { fn $e() {} }} - // 012345 67 8 9 T12 3 - - assert_eq!(get_text(tt::TokenId(9), IDENT), "fn"); - assert_eq!(get_text(tt::TokenId(12), T!['(']), "("); - assert_eq!(get_text(tt::TokenId(13), T!['{']), "{"); -} - -fn to_subtree(tt: &tt::TokenTree) -> &tt::Subtree { - if let tt::TokenTree::Subtree(subtree) = tt { - return subtree; - } - unreachable!("It is not a subtree"); -} - -fn to_punct(tt: &tt::TokenTree) -> &tt::Punct { - if let tt::TokenTree::Leaf(tt::Leaf::Punct(lit)) = tt { - return lit; - } - unreachable!("It is not a Punct"); -} - -#[test] -fn test_attr_to_token_tree() { - let expansion = parse_to_token_tree_by_syntax( - r#" - #[derive(Copy)] - struct Foo; - "#, - ); - - assert_eq!(to_punct(&expansion.token_trees[0]).char, '#'); - assert_eq!( - to_subtree(&expansion.token_trees[1]).delimiter_kind(), - Some(tt::DelimiterKind::Bracket) - ); -} - -#[test] -fn test_underscore() { - parse_macro( - r#" - macro_rules! foo { - ($_:tt) => { 0 } - } - "#, - ) - .assert_expand_items(r#"foo! { => }"#, r#"0"#); -} - -#[test] -fn test_underscore_not_greedily() { - parse_macro( - r#" -macro_rules! q { - ($($a:ident)* _) => {0}; -} -"#, - ) - // `_` overlaps with `$a:ident` but rustc matches it under the `_` token - .assert_expand_items(r#"q![a b c d _]"#, r#"0"#); - - parse_macro( - r#" -macro_rules! q { - ($($a:expr => $b:ident)* _ => $c:expr) => {0}; -} -"#, - ) - // `_ => ou` overlaps with `$a:expr => $b:ident` but rustc matches it under `_ => $c:expr` - .assert_expand_items(r#"q![a => b c => d _ => ou]"#, r#"0"#); -} - -#[test] -fn test_underscore_as_type() { - parse_macro( - r#" -macro_rules! q { - ($a:ty) => {0}; -} -"#, - ) - // Underscore is a type - .assert_expand_items(r#"q![_]"#, r#"0"#); -} - -#[test] -fn test_underscore_lifetime() { - parse_macro(r#"macro_rules! q { ($a:lifetime) => {0}; }"#) - .assert_expand_items(r#"q!['_]"#, r#"0"#); -} - -#[test] -fn test_vertical_bar_with_pat() { - parse_macro( - r#" - macro_rules! foo { - (| $pat:pat | ) => { 0 } - } - "#, - ) - .assert_expand_items(r#"foo! { | x | }"#, r#"0"#); -} - -#[test] -fn test_dollar_crate_lhs_is_not_meta() { - parse_macro( - r#" -macro_rules! foo { - ($crate) => {}; - () => {0}; -} - "#, - ) - .assert_expand_items(r#"foo!{}"#, r#"0"#); -} - -#[test] -fn test_lifetime() { - parse_macro( - r#" - macro_rules! foo { - ($ lt:lifetime) => { struct Ref<$ lt>{ s: &$ lt str } } - } -"#, - ) - .assert_expand_items(r#"foo!{'a}"#, r#"struct Ref <'a > {s : &'a str}"#); -} - -#[test] -fn test_literal() { - parse_macro( - r#" - macro_rules! foo { - ($ type:ty , $ lit:literal) => { const VALUE: $ type = $ lit;}; - } -"#, - ) - .assert_expand_items(r#"foo!(u8,0);"#, r#"const VALUE : u8 = 0 ;"#); - - parse_macro( - r#" - macro_rules! foo { - ($ type:ty , $ lit:literal) => { const VALUE: $ type = $ lit;}; - } -"#, - ) - .assert_expand_items(r#"foo!(i32,-1);"#, r#"const VALUE : i32 = - 1 ;"#); -} - -#[test] -fn test_boolean_is_ident() { - parse_macro( - r#" - macro_rules! foo { - ($lit0:literal, $lit1:literal) => { const VALUE: (bool,bool) = ($lit0,$lit1); }; - } -"#, - ) - .assert_expand( - r#"foo!(true,false);"#, - r#" -SUBTREE $ - IDENT const 14 - IDENT VALUE 15 - PUNCH : [alone] 16 - SUBTREE () 17 - IDENT bool 18 - PUNCH , [alone] 19 - IDENT bool 20 - PUNCH = [alone] 21 - SUBTREE () 22 - IDENT true 29 - PUNCH , [joint] 25 - IDENT false 31 - PUNCH ; [alone] 28 -"#, - ); -} - -#[test] -fn test_vis() { - parse_macro( - r#" - macro_rules! foo { - ($ vis:vis $ name:ident) => { $ vis fn $ name() {}}; - } -"#, - ) - .assert_expand_items(r#"foo!(pub foo);"#, r#"pub fn foo () {}"#) - // test optional cases - .assert_expand_items(r#"foo!(foo);"#, r#"fn foo () {}"#); -} - -#[test] -fn test_inner_macro_rules() { - parse_macro( - r#" -macro_rules! foo { - ($a:ident, $b:ident, $c:tt) => { - - macro_rules! bar { - ($bi:ident) => { - fn $bi() -> u8 {$c} - } - } - - bar!($a); - fn $b() -> u8 {$c} - } -} -"#, - ). - assert_expand_items( - r#"foo!(x,y, 1);"#, - r#"macro_rules ! bar {($ bi : ident) => {fn $ bi () -> u8 {1}}} bar ! (x) ; fn y () -> u8 {1}"#, - ); -} - -#[test] -fn test_expr_after_path_colons() { - assert!(parse_macro( - r#" -macro_rules! m { - ($k:expr) => { - f(K::$k); - } -} -"#, - ) - .expand_statements(r#"m!(C("0"))"#) - .descendants() - .any(|token| token.kind() == ERROR)); -} - -#[test] -fn test_match_is_not_greedy() { - parse_macro( - r#" -macro_rules! foo { - ($($i:ident $(,)*),*) => {}; -} -"#, - ) - .assert_expand_items(r#"foo!(a,b);"#, r#""#); -} - -// The following tests are based on real world situations -#[test] -fn test_vec() { - let fixture = parse_macro( - r#" - macro_rules! vec { - ($($item:expr),*) => { - { - let mut v = Vec::new(); - $( - v.push($item); - )* - v - } - }; -} -"#, - ); - fixture - .assert_expand_items(r#"vec!();"#, r#"{let mut v = Vec :: new () ; v}"#) - .assert_expand_items( - r#"vec![1u32,2];"#, - r#"{let mut v = Vec :: new () ; v . push (1u32) ; v . push (2) ; v}"#, - ); - - let tree = fixture.expand_expr(r#"vec![1u32,2];"#); - - assert_eq_text!( - &format!("{:#?}", tree), - r#"BLOCK_EXPR@0..45 - STMT_LIST@0..45 - L_CURLY@0..1 "{" - LET_STMT@1..20 - LET_KW@1..4 "let" - IDENT_PAT@4..8 - MUT_KW@4..7 "mut" - NAME@7..8 - IDENT@7..8 "v" - EQ@8..9 "=" - CALL_EXPR@9..19 - PATH_EXPR@9..17 - PATH@9..17 - PATH@9..12 - PATH_SEGMENT@9..12 - NAME_REF@9..12 - IDENT@9..12 "Vec" - COLON2@12..14 "::" - PATH_SEGMENT@14..17 - NAME_REF@14..17 - IDENT@14..17 "new" - ARG_LIST@17..19 - L_PAREN@17..18 "(" - R_PAREN@18..19 ")" - SEMICOLON@19..20 ";" - EXPR_STMT@20..33 - METHOD_CALL_EXPR@20..32 - PATH_EXPR@20..21 - PATH@20..21 - PATH_SEGMENT@20..21 - NAME_REF@20..21 - IDENT@20..21 "v" - DOT@21..22 "." - NAME_REF@22..26 - IDENT@22..26 "push" - ARG_LIST@26..32 - L_PAREN@26..27 "(" - LITERAL@27..31 - INT_NUMBER@27..31 "1u32" - R_PAREN@31..32 ")" - SEMICOLON@32..33 ";" - EXPR_STMT@33..43 - METHOD_CALL_EXPR@33..42 - PATH_EXPR@33..34 - PATH@33..34 - PATH_SEGMENT@33..34 - NAME_REF@33..34 - IDENT@33..34 "v" - DOT@34..35 "." - NAME_REF@35..39 - IDENT@35..39 "push" - ARG_LIST@39..42 - L_PAREN@39..40 "(" - LITERAL@40..41 - INT_NUMBER@40..41 "2" - R_PAREN@41..42 ")" - SEMICOLON@42..43 ";" - PATH_EXPR@43..44 - PATH@43..44 - PATH_SEGMENT@43..44 - NAME_REF@43..44 - IDENT@43..44 "v" - R_CURLY@44..45 "}" -"# - ); -} - -#[test] -fn test_winapi_struct() { - // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/macros.rs#L366 - - parse_macro( - r#" -macro_rules! STRUCT { - ($(#[$attrs:meta])* struct $name:ident { - $($field:ident: $ftype:ty,)+ - }) => ( - #[repr(C)] #[derive(Copy)] $(#[$attrs])* - pub struct $name { - $(pub $field: $ftype,)+ - } - impl Clone for $name { - #[inline] - fn clone(&self) -> $name { *self } - } - #[cfg(feature = "impl-default")] - impl Default for $name { - #[inline] - fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } - } - ); -} -"#, - ). - // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/shared/d3d9caps.rs - assert_expand_items(r#"STRUCT!{struct D3DVSHADERCAPS2_0 {Caps: u8,}}"#, - "# [repr (C)] # [derive (Copy)] pub struct D3DVSHADERCAPS2_0 {pub Caps : u8 ,} impl Clone for D3DVSHADERCAPS2_0 {# [inline] fn clone (& self) -> D3DVSHADERCAPS2_0 {* self}} # [cfg (feature = \"impl-default\")] impl Default for D3DVSHADERCAPS2_0 {# [inline] fn default () -> D3DVSHADERCAPS2_0 {unsafe {$crate :: _core :: mem :: zeroed ()}}}" - ) - .assert_expand_items(r#"STRUCT!{#[cfg_attr(target_arch = "x86", repr(packed))] struct D3DCONTENTPROTECTIONCAPS {Caps : u8 ,}}"#, - "# [repr (C)] # [derive (Copy)] # [cfg_attr (target_arch = \"x86\" , repr (packed))] pub struct D3DCONTENTPROTECTIONCAPS {pub Caps : u8 ,} impl Clone for D3DCONTENTPROTECTIONCAPS {# [inline] fn clone (& self) -> D3DCONTENTPROTECTIONCAPS {* self}} # [cfg (feature = \"impl-default\")] impl Default for D3DCONTENTPROTECTIONCAPS {# [inline] fn default () -> D3DCONTENTPROTECTIONCAPS {unsafe {$crate :: _core :: mem :: zeroed ()}}}" - ); -} - -#[test] -fn test_int_base() { - parse_macro( - r#" -macro_rules! int_base { - ($Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { - #[stable(feature = "rust1", since = "1.0.0")] - impl fmt::$Trait for $T { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - $Radix.fmt_int(*self as $U, f) - } - } - } -} -"#, - ).assert_expand_items(r#" int_base!{Binary for isize as usize -> Binary}"#, - "# [stable (feature = \"rust1\" , since = \"1.0.0\")] impl fmt ::Binary for isize {fn fmt (& self , f : & mut fmt :: Formatter < \'_ >) -> fmt :: Result {Binary . fmt_int (* self as usize , f)}}" - ); -} - -#[test] -fn test_generate_pattern_iterators() { - // from https://github.com/rust-lang/rust/blob/316a391dcb7d66dc25f1f9a4ec9d368ef7615005/src/libcore/str/mod.rs - parse_macro( - r#" -macro_rules! generate_pattern_iterators { - { double ended; with $(#[$common_stability_attribute:meta])*, - $forward_iterator:ident, - $reverse_iterator:ident, $iterty:ty - } => { - fn foo(){} - } -} -"#, - ).assert_expand_items( - r#"generate_pattern_iterators ! ( double ended ; with # [ stable ( feature = "rust1" , since = "1.0.0" ) ] , Split , RSplit , & 'a str );"#, - "fn foo () {}", - ); -} - -#[test] -fn test_impl_fn_for_zst() { - // from https://github.com/rust-lang/rust/blob/5d20ff4d2718c820632b38c1e49d4de648a9810b/src/libcore/internal_macros.rs - parse_macro( - r#" -macro_rules! impl_fn_for_zst { - { $( $( #[$attr: meta] )* - struct $Name: ident impl$( <$( $lifetime : lifetime ),+> )? Fn = - |$( $arg: ident: $ArgTy: ty ),*| -> $ReturnTy: ty -$body: block; )+ - } => { - $( - $( #[$attr] )* - struct $Name; - - impl $( <$( $lifetime ),+> )? Fn<($( $ArgTy, )*)> for $Name { - #[inline] - extern "rust-call" fn call(&self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { - $body - } - } - - impl $( <$( $lifetime ),+> )? FnMut<($( $ArgTy, )*)> for $Name { - #[inline] - extern "rust-call" fn call_mut( - &mut self, - ($( $arg, )*): ($( $ArgTy, )*) - ) -> $ReturnTy { - Fn::call(&*self, ($( $arg, )*)) - } - } - - impl $( <$( $lifetime ),+> )? FnOnce<($( $ArgTy, )*)> for $Name { - type Output = $ReturnTy; - - #[inline] - extern "rust-call" fn call_once(self, ($( $arg, )*): ($( $ArgTy, )*)) -> $ReturnTy { - Fn::call(&self, ($( $arg, )*)) - } - } - )+ -} - } -"#, - ).assert_expand_items(r#" -impl_fn_for_zst ! { - # [ derive ( Clone ) ] - struct CharEscapeDebugContinue impl Fn = | c : char | -> char :: EscapeDebug { - c . escape_debug_ext ( false ) - } ; - - # [ derive ( Clone ) ] - struct CharEscapeUnicode impl Fn = | c : char | -> char :: EscapeUnicode { - c . escape_unicode ( ) - } ; - # [ derive ( Clone ) ] - struct CharEscapeDefault impl Fn = | c : char | -> char :: EscapeDefault { - c . escape_default ( ) - } ; - } -"#, - "# [derive (Clone)] struct CharEscapeDebugContinue ; impl Fn < (char ,) > for CharEscapeDebugContinue {# [inline] extern \"rust-call\" fn call (& self , (c ,) : (char ,)) -> char :: EscapeDebug {{c . escape_debug_ext (false)}}} impl FnMut < (char ,) > for CharEscapeDebugContinue {# [inline] extern \"rust-call\" fn call_mut (& mut self , (c ,) : (char ,)) -> char :: EscapeDebug {Fn :: call (&* self , (c ,))}} impl FnOnce < (char ,) > for CharEscapeDebugContinue {type Output = char :: EscapeDebug ; # [inline] extern \"rust-call\" fn call_once (self , (c ,) : (char ,)) -> char :: EscapeDebug {Fn :: call (& self , (c ,))}} # [derive (Clone)] struct CharEscapeUnicode ; impl Fn < (char ,) > for CharEscapeUnicode {# [inline] extern \"rust-call\" fn call (& self , (c ,) : (char ,)) -> char :: EscapeUnicode {{c . escape_unicode ()}}} impl FnMut < (char ,) > for CharEscapeUnicode {# [inline] extern \"rust-call\" fn call_mut (& mut self , (c ,) : (char ,)) -> char :: EscapeUnicode {Fn :: call (&* self , (c ,))}} impl FnOnce < (char ,) > for CharEscapeUnicode {type Output = char :: EscapeUnicode ; # [inline] extern \"rust-call\" fn call_once (self , (c ,) : (char ,)) -> char :: EscapeUnicode {Fn :: call (& self , (c ,))}} # [derive (Clone)] struct CharEscapeDefault ; impl Fn < (char ,) > for CharEscapeDefault {# [inline] extern \"rust-call\" fn call (& self , (c ,) : (char ,)) -> char :: EscapeDefault {{c . escape_default ()}}} impl FnMut < (char ,) > for CharEscapeDefault {# [inline] extern \"rust-call\" fn call_mut (& mut self , (c ,) : (char ,)) -> char :: EscapeDefault {Fn :: call (&* self , (c ,))}} impl FnOnce < (char ,) > for CharEscapeDefault {type Output = char :: EscapeDefault ; # [inline] extern \"rust-call\" fn call_once (self , (c ,) : (char ,)) -> char :: EscapeDefault {Fn :: call (& self , (c ,))}}" - ); -} - -#[test] -fn test_impl_nonzero_fmt() { - // from https://github.com/rust-lang/rust/blob/316a391dcb7d66dc25f1f9a4ec9d368ef7615005/src/libcore/num/mod.rs#L12 - parse_macro( - r#" - macro_rules! impl_nonzero_fmt { - ( #[$stability: meta] ( $( $Trait: ident ),+ ) for $Ty: ident ) => { - fn foo () {} - } - } -"#, - ).assert_expand_items( - r#"impl_nonzero_fmt! { # [stable(feature= "nonzero",since="1.28.0")] (Debug,Display,Binary,Octal,LowerHex,UpperHex) for NonZeroU8}"#, - "fn foo () {}", - ); -} - -#[test] -fn test_cfg_if_items() { - // from https://github.com/rust-lang/rust/blob/33fe1131cadba69d317156847be9a402b89f11bb/src/libstd/macros.rs#L986 - parse_macro( - r#" - macro_rules! __cfg_if_items { - (($($not:meta,)*) ; ) => {}; - (($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => { - __cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* } - } - } -"#, - ).assert_expand_items( - r#"__cfg_if_items ! { ( rustdoc , ) ; ( ( ) ( # [ cfg ( any ( target_os = "redox" , unix ) ) ] # [ stable ( feature = "rust1" , since = "1.0.0" ) ] pub use sys :: ext as unix ; # [ cfg ( windows ) ] # [ stable ( feature = "rust1" , since = "1.0.0" ) ] pub use sys :: ext as windows ; # [ cfg ( any ( target_os = "linux" , target_os = "l4re" ) ) ] pub mod linux ; ) ) , }"#, - "__cfg_if_items ! {(rustdoc ,) ;}", - ); -} - -#[test] -fn test_cfg_if_main() { - // from https://github.com/rust-lang/rust/blob/3d211248393686e0f73851fc7548f6605220fbe1/src/libpanic_unwind/macros.rs#L9 - parse_macro( - r#" - macro_rules! cfg_if { - ($( - if #[cfg($($meta:meta),*)] { $($it:item)* } - ) else * else { - $($it2:item)* - }) => { - __cfg_if_items! { - () ; - $( ( ($($meta),*) ($($it)*) ), )* - ( () ($($it2)*) ), - } - }; - - // Internal macro to Apply a cfg attribute to a list of items - (@__apply $m:meta, $($it:item)*) => { - $(#[$m] $it)* - }; - } -"#, - ).assert_expand_items(r#" -cfg_if ! { - if # [ cfg ( target_env = "msvc" ) ] { - // no extra unwinder support needed - } else if # [ cfg ( all ( target_arch = "wasm32" , not ( target_os = "emscripten" ) ) ) ] { - // no unwinder on the system! - } else { - mod libunwind ; - pub use libunwind :: * ; - } - } -"#, - "__cfg_if_items ! {() ; ((target_env = \"msvc\") ()) , ((all (target_arch = \"wasm32\" , not (target_os = \"emscripten\"))) ()) , (() (mod libunwind ; pub use libunwind :: * ;)) ,}" - ).assert_expand_items( - r#" -cfg_if ! { @ __apply cfg ( all ( not ( any ( not ( any ( target_os = "solaris" , target_os = "illumos" ) ) ) ) ) ) , } -"#, - "", - ); -} - -#[test] -fn test_proptest_arbitrary() { - // from https://github.com/AltSysrq/proptest/blob/d1c4b049337d2f75dd6f49a095115f7c532e5129/proptest/src/arbitrary/macros.rs#L16 - parse_macro( - r#" -macro_rules! arbitrary { - ([$($bounds : tt)*] $typ: ty, $strat: ty, $params: ty; - $args: ident => $logic: expr) => { - impl<$($bounds)*> $crate::arbitrary::Arbitrary for $typ { - type Parameters = $params; - type Strategy = $strat; - fn arbitrary_with($args: Self::Parameters) -> Self::Strategy { - $logic - } - } - }; - -}"#, - ).assert_expand_items(r#"arbitrary ! ( [ A : Arbitrary ] - Vec < A > , - VecStrategy < A :: Strategy > , - RangedParams1 < A :: Parameters > ; - args => { let product_unpack ! [ range , a ] = args ; vec ( any_with :: < A > ( a ) , range ) } - ) ;"#, - "impl $crate :: arbitrary :: Arbitrary for Vec < A > {type Parameters = RangedParams1 < A :: Parameters > ; type Strategy = VecStrategy < A :: Strategy > ; fn arbitrary_with (args : Self :: Parameters) -> Self :: Strategy {{let product_unpack ! [range , a] = args ; vec (any_with :: < A > (a) , range)}}}" - ); -} - -#[test] -fn test_old_ridl() { - // This is from winapi 2.8, which do not have a link from github - // - let expanded = parse_macro( - r#" -#[macro_export] -macro_rules! RIDL { - (interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) - {$( - fn $method:ident(&mut self $(,$p:ident : $t:ty)*) -> $rtr:ty - ),+} - ) => { - impl $interface { - $(pub unsafe fn $method(&mut self) -> $rtr { - ((*self.lpVtbl).$method)(self $(,$p)*) - })+ - } - }; -}"#, - ).expand_tt(r#" - RIDL!{interface ID3D11Asynchronous(ID3D11AsynchronousVtbl): ID3D11DeviceChild(ID3D11DeviceChildVtbl) { - fn GetDataSize(&mut self) -> UINT - }}"#); - - assert_eq!(expanded.to_string(), "impl ID3D11Asynchronous {pub unsafe fn GetDataSize (& mut self) -> UINT {((* self . lpVtbl) .GetDataSize) (self)}}"); -} - -#[test] -fn test_quick_error() { - let expanded = parse_macro( - r#" -macro_rules! quick_error { - - (SORT [enum $name:ident $( #[$meta:meta] )*] - items [$($( #[$imeta:meta] )* - => $iitem:ident: $imode:tt [$( $ivar:ident: $ityp:ty ),*] - {$( $ifuncs:tt )*} )* ] - buf [ ] - queue [ ] - ) => { - quick_error!(ENUMINITION [enum $name $( #[$meta] )*] - body [] - queue [$( - $( #[$imeta] )* - => - $iitem: $imode [$( $ivar: $ityp ),*] - )*] - ); -}; - -} -"#, - ) - .expand_tt( - r#" -quick_error ! (SORT [enum Wrapped # [derive (Debug)]] items [ - => One : UNIT [] {} - => Two : TUPLE [s :String] {display ("two: {}" , s) from ()} - ] buf [] queue []) ; -"#, - ); - - assert_eq!(expanded.to_string(), "quick_error ! (ENUMINITION [enum Wrapped # [derive (Debug)]] body [] queue [=> One : UNIT [] => Two : TUPLE [s : String]]) ;"); -} - -#[test] -fn test_empty_repeat_vars_in_empty_repeat_vars() { - parse_macro( - r#" -macro_rules! delegate_impl { - ([$self_type:ident, $self_wrap:ty, $self_map:ident] - pub trait $name:ident $(: $sup:ident)* $(+ $more_sup:ident)* { - - $( - @escape [type $assoc_name_ext:ident] - )* - $( - @section type - $( - $(#[$_assoc_attr:meta])* - type $assoc_name:ident $(: $assoc_bound:ty)*; - )+ - )* - $( - @section self - $( - $(#[$_method_attr:meta])* - fn $method_name:ident(self $(: $self_selftype:ty)* $(,$marg:ident : $marg_ty:ty)*) -> $mret:ty; - )+ - )* - $( - @section nodelegate - $($tail:tt)* - )* - }) => { - impl<> $name for $self_wrap where $self_type: $name { - $( - $( - fn $method_name(self $(: $self_selftype)* $(,$marg: $marg_ty)*) -> $mret { - $self_map!(self).$method_name($($marg),*) - } - )* - )* - } - } -} -"#, - ).assert_expand_items( - r#"delegate_impl ! {[G , & 'a mut G , deref] pub trait Data : GraphBase {@ section type type NodeWeight ;}}"#, - "impl <> Data for & \'a mut G where G : Data {}", - ); -} - -#[test] -fn expr_interpolation() { - let expanded = parse_macro( - r#" - macro_rules! id { - ($expr:expr) => { - map($expr) - } - } - "#, - ) - .expand_expr("id!(x + foo);"); - - assert_eq!(expanded.to_string(), "map(x+foo)"); -} - -#[test] -fn test_issue_2520() { - let macro_fixture = parse_macro( - r#" - macro_rules! my_macro { - { - ( $( - $( [] $sname:ident : $stype:ty )? - $( [$expr:expr] $nname:ident : $ntype:ty )? - ),* ) - } => { - Test { - $( - $( $sname, )? - )* - } - }; - } - "#, - ); - - macro_fixture.assert_expand_items( - r#"my_macro ! { - ([] p1 : u32 , [|_| S0K0] s : S0K0 , [] k0 : i32) - }"#, - "Test {p1 , k0 ,}", - ); -} - -#[test] -fn test_issue_3861() { - let macro_fixture = parse_macro( - r#" - macro_rules! rgb_color { - ($p:expr, $t: ty) => { - pub fn new() { - let _ = 0 as $t << $p; - } - }; - } - "#, - ); - - macro_fixture.expand_items(r#"rgb_color!(8 + 8, u32);"#); -} - -#[test] -fn test_repeat_bad_var() { - // FIXME: the second rule of the macro should be removed and an error about - // `$( $c )+` raised - parse_macro( - r#" - macro_rules! foo { - ($( $b:ident )+) => { - $( $c )+ - }; - ($( $b:ident )+) => { - $( $b )+ - } - } - "#, - ) - .assert_expand_items("foo!(b0 b1);", "b0 b1"); -} - -#[test] -fn test_no_space_after_semi_colon() { - let expanded = parse_macro( - r#" - macro_rules! with_std { ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) } - "#, - ) - .expand_items(r#"with_std! {mod m;mod f;}"#); - - let dump = format!("{:#?}", expanded); - assert_eq_text!( - r###"MACRO_ITEMS@0..52 - MODULE@0..26 - ATTR@0..21 - POUND@0..1 "#" - L_BRACK@1..2 "[" - META@2..20 - PATH@2..5 - PATH_SEGMENT@2..5 - NAME_REF@2..5 - IDENT@2..5 "cfg" - TOKEN_TREE@5..20 - L_PAREN@5..6 "(" - IDENT@6..13 "feature" - EQ@13..14 "=" - STRING@14..19 "\"std\"" - R_PAREN@19..20 ")" - R_BRACK@20..21 "]" - MOD_KW@21..24 "mod" - NAME@24..25 - IDENT@24..25 "m" - SEMICOLON@25..26 ";" - MODULE@26..52 - ATTR@26..47 - POUND@26..27 "#" - L_BRACK@27..28 "[" - META@28..46 - PATH@28..31 - PATH_SEGMENT@28..31 - NAME_REF@28..31 - IDENT@28..31 "cfg" - TOKEN_TREE@31..46 - L_PAREN@31..32 "(" - IDENT@32..39 "feature" - EQ@39..40 "=" - STRING@40..45 "\"std\"" - R_PAREN@45..46 ")" - R_BRACK@46..47 "]" - MOD_KW@47..50 "mod" - NAME@50..51 - IDENT@50..51 "f" - SEMICOLON@51..52 ";""###, - dump.trim() - ); -} - -// https://github.com/rust-lang/rust/blob/master/src/test/ui/issues/issue-57597.rs -#[test] -fn test_rustc_issue_57597() { - fn test_error(fixture: &str) { - assert_eq!(parse_macro_error(fixture), ParseError::RepetitionEmptyTokenTree); - } - - test_error("macro_rules! foo { ($($($i:ident)?)+) => {}; }"); - test_error("macro_rules! foo { ($($($i:ident)?)*) => {}; }"); - test_error("macro_rules! foo { ($($($i:ident)?)?) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)?)?)?) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)*)?)?) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)?)*)?) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)?)?)*) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)*)*)?) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)?)*)*) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)?)*)+) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)+)?)*) => {}; }"); - test_error("macro_rules! foo { ($($($($i:ident)+)*)?) => {}; }"); -} - -#[test] -fn test_expand_bad_literal() { - parse_macro( - r#" - macro_rules! foo { ($i:literal) => {}; } - "#, - ) - .assert_expand_err(r#"foo!(&k");"#, &ExpandError::BindingError("".into())); -} - -#[test] -fn test_empty_comments() { - parse_macro( - r#" - macro_rules! one_arg_macro { ($fmt:expr) => (); } - "#, - ) - .assert_expand_err( - r#"one_arg_macro!(/**/)"#, - &ExpandError::BindingError("expected Expr".into()), - ); -} diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs index 5ea7a994b4fc..3d303a1237d0 100644 --- a/crates/test_utils/src/fixture.rs +++ b/crates/test_utils/src/fixture.rs @@ -147,6 +147,7 @@ impl Fixture { if line.starts_with("// ") && line.contains(':') && !line.contains("::") + && !line.contains(".") && line.chars().all(|it| !it.is_uppercase()) { panic!("looks like invalid metadata line: {:?}", line);