diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 968e88700067..1e2e8da60cde 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -10,16 +10,44 @@ fn test_derive_empty() { assert_expand( "DeriveEmpty", - r#"struct S;"#, + r#"struct S { field: &'r#lt fn(u32) -> &'a r#u32 }"#, expect![[r#" IDENT 1 struct IDENT 1 S - PUNCT 1 ; [alone] + GROUP {} 1 1 1 + IDENT 1 field + PUNCT 1 : [alone] + PUNCT 1 & [joint] + PUNCT 1 ' [joint] + IDENT 1 r#lt + IDENT 1 fn + GROUP () 1 1 1 + IDENT 1 u32 + PUNCT 1 - [joint] + PUNCT 1 > [alone] + PUNCT 1 & [joint] + PUNCT 1 ' [joint] + IDENT 1 a + IDENT 1 r#u32 "#]], expect![[r#" IDENT 42:Root[0000, 0]@0..6#ROOT2024 struct IDENT 42:Root[0000, 0]@7..8#ROOT2024 S - PUNCT 42:Root[0000, 0]@8..9#ROOT2024 ; [alone] + GROUP {} 42:Root[0000, 0]@9..10#ROOT2024 42:Root[0000, 0]@46..47#ROOT2024 42:Root[0000, 0]@9..47#ROOT2024 + IDENT 42:Root[0000, 0]@11..16#ROOT2024 field + PUNCT 42:Root[0000, 0]@16..17#ROOT2024 : [alone] + PUNCT 42:Root[0000, 0]@18..19#ROOT2024 & [joint] + PUNCT 42:Root[0000, 0]@22..23#ROOT2024 ' [joint] + IDENT 42:Root[0000, 0]@22..24#ROOT2024 r#lt + IDENT 42:Root[0000, 0]@25..27#ROOT2024 fn + GROUP () 42:Root[0000, 0]@27..28#ROOT2024 42:Root[0000, 0]@31..32#ROOT2024 42:Root[0000, 0]@27..32#ROOT2024 + IDENT 42:Root[0000, 0]@28..31#ROOT2024 u32 + PUNCT 42:Root[0000, 0]@33..34#ROOT2024 - [joint] + PUNCT 42:Root[0000, 0]@34..35#ROOT2024 > [alone] + PUNCT 42:Root[0000, 0]@36..37#ROOT2024 & [joint] + PUNCT 42:Root[0000, 0]@38..39#ROOT2024 ' [joint] + IDENT 42:Root[0000, 0]@38..39#ROOT2024 a + IDENT 42:Root[0000, 0]@42..45#ROOT2024 r#u32 "#]], ); } @@ -466,6 +494,30 @@ fn test_attr_macro() { ); } +#[test] +#[should_panic = "called `Result::unwrap()` on an `Err` value: \"Mismatched token groups\""] +fn test_broken_input_unclosed_delim() { + assert_expand("fn_like_clone_tokens", r###"{"###, expect![[]], expect![[]]); +} + +#[test] +#[should_panic = "called `Result::unwrap()` on an `Err` value: \"Unexpected '}'\""] +fn test_broken_input_unopened_delim() { + assert_expand("fn_like_clone_tokens", r###"}"###, expect![[]], expect![[]]); +} + +#[test] +#[should_panic = "called `Result::unwrap()` on an `Err` value: \"Expected '}'\""] +fn test_broken_input_mismatched_delim() { + assert_expand("fn_like_clone_tokens", r###"(}"###, expect![[]], expect![[]]); +} + +#[test] +#[should_panic = "called `Result::unwrap()` on an `Err` value: \"Invalid identifier: `🪟`\""] +fn test_broken_input_unknowm_token() { + assert_expand("fn_like_clone_tokens", r###"🪟"###, expect![[]], expect![[]]); +} + /// Tests that we find and classify all proc macros correctly. #[test] fn list_test_macros() { diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs index cfdf0da903f9..628d6942392c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs @@ -98,7 +98,11 @@ pub(crate) fn from_str(s: &str, span: S) -> Result groups.push((proc_macro::Delimiter::Parenthesis, range, vec![])) } rustc_lexer::TokenKind::CloseParen if *open_delim != Delimiter::Parenthesis => { - return Err("Expected ')'".to_owned()); + return if *open_delim == Delimiter::None { + Err("Unexpected ')'".to_owned()) + } else { + Err("Expected ')'".to_owned()) + }; } rustc_lexer::TokenKind::CloseParen => { let (delimiter, open_range, stream) = groups.pop().unwrap(); @@ -122,7 +126,11 @@ pub(crate) fn from_str(s: &str, span: S) -> Result groups.push((proc_macro::Delimiter::Brace, range, vec![])) } rustc_lexer::TokenKind::CloseBrace if *open_delim != Delimiter::Brace => { - return Err("Expected '}'".to_owned()); + return if *open_delim == Delimiter::None { + Err("Unexpected '}'".to_owned()) + } else { + Err("Expected '}'".to_owned()) + }; } rustc_lexer::TokenKind::CloseBrace => { let (delimiter, open_range, stream) = groups.pop().unwrap(); @@ -146,7 +154,11 @@ pub(crate) fn from_str(s: &str, span: S) -> Result groups.push((proc_macro::Delimiter::Bracket, range, vec![])) } rustc_lexer::TokenKind::CloseBracket if *open_delim != Delimiter::Bracket => { - return Err("Expected ']'".to_owned()); + return if *open_delim == Delimiter::None { + Err("Unexpected ']'".to_owned()) + } else { + Err("Expected ']'".to_owned()) + }; } rustc_lexer::TokenKind::CloseBracket => { let (delimiter, open_range, stream) = groups.pop().unwrap(); @@ -223,10 +235,14 @@ pub(crate) fn from_str(s: &str, span: S) -> Result } rustc_lexer::TokenKind::Whitespace => continue, rustc_lexer::TokenKind::Frontmatter { .. } => unreachable!(), - rustc_lexer::TokenKind::Unknown => return Err("Unknown token".to_owned()), - rustc_lexer::TokenKind::UnknownPrefix => return Err("Unknown prefix".to_owned()), + rustc_lexer::TokenKind::Unknown => { + return Err(format!("Unknown token: `{}`", &s[range])); + } + rustc_lexer::TokenKind::UnknownPrefix => { + return Err(format!("Unknown prefix: `{}`", &s[range])); + } rustc_lexer::TokenKind::UnknownPrefixLifetime => { - return Err("Unknown lifetime prefix".to_owned()); + return Err(format!("Unknown lifetime prefix: `{}`", &s[range])); } // FIXME: Error on edition >= 2024 ... I dont think the proc-macro server can fetch editions currently // and whose edition is this? @@ -247,7 +263,9 @@ pub(crate) fn from_str(s: &str, span: S) -> Result is_raw: false, span: span.derive_ranged(range), })), - rustc_lexer::TokenKind::InvalidIdent => return Err("Invalid identifier".to_owned()), + rustc_lexer::TokenKind::InvalidIdent => { + return Err(format!("Invalid identifier: `{}`", &s[range])); + } rustc_lexer::TokenKind::RawIdent => { let range = range.start + 2..range.end; tokenstream.push(TokenTree::Ident(Ident { diff --git a/src/tools/rust-analyzer/xtask/src/tidy.rs b/src/tools/rust-analyzer/xtask/src/tidy.rs index ebfc7d0a9430..05528505f280 100644 --- a/src/tools/rust-analyzer/xtask/src/tidy.rs +++ b/src/tools/rust-analyzer/xtask/src/tidy.rs @@ -194,6 +194,7 @@ fn check_test_attrs(path: &Path, text: &str) { "test-utils/src/fixture.rs", // Generated code from lints contains doc tests in string literals. "ide-db/src/generated/lints.rs", + "proc-macro-srv/src/tests/mod.rs", ]; if need_panic.iter().any(|p| path.ends_with(p)) { return;