diff options
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs | 6 | ||||
| -rw-r--r-- | crates/ide/src/hover/tests.rs | 2 | ||||
| -rw-r--r-- | crates/mbe/src/syntax_bridge.rs | 5 | ||||
| -rw-r--r-- | crates/parser/src/grammar.rs | 26 | ||||
| -rw-r--r-- | crates/parser/src/lib.rs | 112 | ||||
| -rw-r--r-- | crates/parser/src/tests.rs | 2 | ||||
| -rw-r--r-- | crates/parser/src/tests/top_entries.rs | 79 |
7 files changed, 177 insertions, 55 deletions
diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs b/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs index 4c58ea9ba64..5f4b7d6d0bc 100644 --- a/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs +++ b/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs @@ -105,21 +105,21 @@ macro_rules! m2 { ($x:ident) => {} } #[test] fn expansion_does_not_parse_as_expression() { - cov_mark::check!(expansion_does_not_parse_as_expression); check( r#" macro_rules! stmts { () => { let _ = 0; } } -fn f() { let _ = stmts!(); } +fn f() { let _ = stmts!/*+errors*/(); } "#, expect![[r#" macro_rules! stmts { () => { let _ = 0; } } -fn f() { let _ = /* error: could not convert tokens */; } +fn f() { let _ = /* parse error: expected expression */ +let _ = 0;; } "#]], ) } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 82fc385040e..2bfda1aff2e 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -1148,7 +1148,7 @@ fn foo() { let a = id!([0u32, bar($0)] ); } fn test_hover_through_literal_string_in_macro() { check( r#" -macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } } +macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } } fn foo() { let mastered_for_itunes = ""; let _ = arr!("Tr$0acks", &mastered_for_itunes); diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs index 1141365e82c..7d7807206f4 100644 --- a/crates/mbe/src/syntax_bridge.rs +++ b/crates/mbe/src/syntax_bridge.rs @@ -1,7 +1,7 @@ //! Conversions between [`SyntaxNode`] and [`tt::TokenTree`]. use rustc_hash::{FxHashMap, FxHashSet}; -use stdx::non_empty_vec::NonEmptyVec; +use stdx::{never, non_empty_vec::NonEmptyVec}; use syntax::{ ast::{self, make::tokens::doc_comment}, AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, SyntaxKind, @@ -66,8 +66,7 @@ pub fn token_tree_to_syntax_node( parser::Step::Error { msg } => tree_sink.error(msg.to_string()), } } - if tree_sink.roots.len() != 1 { - cov_mark::hit!(expansion_does_not_parse_as_expression); + if never!(tree_sink.roots.len() != 1) { return Err(ExpandError::ConversionError); } //FIXME: would be cool to report errors diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index 6ffb4c191b5..0240a6f14f6 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -135,6 +135,32 @@ pub(crate) mod entry { } m.complete(p, ERROR); } + + pub(crate) fn expr(p: &mut Parser) { + let m = p.start(); + expressions::expr(p); + if p.at(EOF) { + m.abandon(p); + return; + } + while !p.at(EOF) { + p.bump_any(); + } + m.complete(p, ERROR); + } + + pub(crate) fn meta_item(p: &mut Parser) { + let m = p.start(); + attributes::meta(p); + if p.at(EOF) { + m.abandon(p); + return; + } + while !p.at(EOF) { + p.bump_any(); + } + m.complete(p, ERROR); + } } } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 72d529d6cfd..cff4ca4ba29 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -41,48 +41,6 @@ pub use crate::{ syntax_kind::SyntaxKind, }; -/// Parse a prefix of the input as a given syntactic construct. -/// -/// This is used by macro-by-example parser to implement things like `$i:item` -/// and the naming of variants follows the naming of macro fragments. -/// -/// Note that this is generally non-optional -- the result is intentionally not -/// `Option<Output>`. The way MBE work, by the time we *try* to parse `$e:expr` -/// we already commit to expression. In other words, this API by design can't be -/// used to implement "rollback and try another alternative" logic. -#[derive(Debug)] -pub enum PrefixEntryPoint { - Vis, - Block, - Stmt, - Pat, - Ty, - Expr, - Path, - Item, - MetaItem, -} - -impl PrefixEntryPoint { - pub fn parse(&self, input: &Input) -> Output { - let entry_point: fn(&'_ mut parser::Parser) = match self { - PrefixEntryPoint::Vis => grammar::entry::prefix::vis, - PrefixEntryPoint::Block => grammar::entry::prefix::block, - PrefixEntryPoint::Stmt => grammar::entry::prefix::stmt, - PrefixEntryPoint::Pat => grammar::entry::prefix::pat, - PrefixEntryPoint::Ty => grammar::entry::prefix::ty, - PrefixEntryPoint::Expr => grammar::entry::prefix::expr, - PrefixEntryPoint::Path => grammar::entry::prefix::path, - PrefixEntryPoint::Item => grammar::entry::prefix::item, - PrefixEntryPoint::MetaItem => grammar::entry::prefix::meta_item, - }; - let mut p = parser::Parser::new(input); - entry_point(&mut p); - let events = p.finish(); - event::process(events) - } -} - /// Parse the whole of the input as a given syntactic construct. /// /// This covers two main use-cases: @@ -99,9 +57,11 @@ impl PrefixEntryPoint { /// ``` /// /// the input to the macro will be parsed with [`PrefixEntryPoint::Item`], and -/// the result will be [`TopEntryPoint::Items`]. +/// the result will be [`TopEntryPoint::MacroItems`]. /// -/// This *should* (but currently doesn't) guarantee that all input is consumed. +/// [`TopEntryPoint::parse`] makes a guarantee that +/// * all input is consumed +/// * the result is a valid tree (there's one root node) #[derive(Debug)] pub enum TopEntryPoint { SourceFile, @@ -123,9 +83,67 @@ impl TopEntryPoint { TopEntryPoint::MacroItems => grammar::entry::top::macro_items, TopEntryPoint::Pattern => grammar::entry::top::pattern, TopEntryPoint::Type => grammar::entry::top::type_, - // FIXME - TopEntryPoint::Expr => grammar::entry::prefix::expr, - TopEntryPoint::MetaItem => grammar::entry::prefix::meta_item, + TopEntryPoint::Expr => grammar::entry::top::expr, + TopEntryPoint::MetaItem => grammar::entry::top::meta_item, + }; + let mut p = parser::Parser::new(input); + entry_point(&mut p); + let events = p.finish(); + let res = event::process(events); + + if cfg!(debug_assertions) { + let mut depth = 0; + let mut first = true; + for step in res.iter() { + assert!(depth > 0 || first); + first = false; + match step { + Step::Enter { .. } => depth += 1, + Step::Exit => depth -= 1, + Step::Token { .. } | Step::Error { .. } => (), + } + } + assert!(!first, "no tree at all"); + } + + res + } +} + +/// Parse a prefix of the input as a given syntactic construct. +/// +/// This is used by macro-by-example parser to implement things like `$i:item` +/// and the naming of variants follows the naming of macro fragments. +/// +/// Note that this is generally non-optional -- the result is intentionally not +/// `Option<Output>`. The way MBE work, by the time we *try* to parse `$e:expr` +/// we already commit to expression. In other words, this API by design can't be +/// used to implement "rollback and try another alternative" logic. +#[derive(Debug)] +pub enum PrefixEntryPoint { + Vis, + Block, + Stmt, + Pat, + Ty, + Expr, + Path, + Item, + MetaItem, +} + +impl PrefixEntryPoint { + pub fn parse(&self, input: &Input) -> Output { + let entry_point: fn(&'_ mut parser::Parser) = match self { + PrefixEntryPoint::Vis => grammar::entry::prefix::vis, + PrefixEntryPoint::Block => grammar::entry::prefix::block, + PrefixEntryPoint::Stmt => grammar::entry::prefix::stmt, + PrefixEntryPoint::Pat => grammar::entry::prefix::pat, + PrefixEntryPoint::Ty => grammar::entry::prefix::ty, + PrefixEntryPoint::Expr => grammar::entry::prefix::expr, + PrefixEntryPoint::Path => grammar::entry::prefix::path, + PrefixEntryPoint::Item => grammar::entry::prefix::item, + PrefixEntryPoint::MetaItem => grammar::entry::prefix::meta_item, }; let mut p = parser::Parser::new(input); entry_point(&mut p); diff --git a/crates/parser/src/tests.rs b/crates/parser/src/tests.rs index c0437bc3b8a..cb25abdfeae 100644 --- a/crates/parser/src/tests.rs +++ b/crates/parser/src/tests.rs @@ -1,6 +1,6 @@ mod sourcegen_inline_tests; -mod prefix_entries; mod top_entries; +mod prefix_entries; use std::{ fmt::Write, diff --git a/crates/parser/src/tests/top_entries.rs b/crates/parser/src/tests/top_entries.rs index dcf61b6aca5..24e41b46f8e 100644 --- a/crates/parser/src/tests/top_entries.rs +++ b/crates/parser/src/tests/top_entries.rs @@ -53,6 +53,13 @@ fn source_file() { fn macro_stmt() { check( TopEntryPoint::MacroStmts, + "", + expect![[r#" + MACRO_STMTS + "#]], + ); + check( + TopEntryPoint::MacroStmts, "#!/usr/bin/rust", expect![[r##" MACRO_STMTS @@ -96,6 +103,13 @@ fn macro_stmt() { fn macro_items() { check( TopEntryPoint::MacroItems, + "", + expect![[r#" + MACRO_ITEMS + "#]], + ); + check( + TopEntryPoint::MacroItems, "#!/usr/bin/rust", expect![[r##" MACRO_ITEMS @@ -133,6 +147,14 @@ fn macro_items() { fn macro_pattern() { check( TopEntryPoint::Pattern, + "", + expect![[r#" + ERROR + error 0: expected pattern + "#]], + ); + check( + TopEntryPoint::Pattern, "Some(_)", expect![[r#" TUPLE_STRUCT_PAT @@ -179,6 +201,15 @@ fn macro_pattern() { fn type_() { check( TopEntryPoint::Type, + "", + expect![[r#" + ERROR + error 0: expected type + "#]], + ); + + check( + TopEntryPoint::Type, "Option<!>", expect![[r#" PATH_TYPE @@ -224,6 +255,54 @@ fn type_() { ); } +#[test] +fn expr() { + check( + TopEntryPoint::Expr, + "", + expect![[r#" + ERROR + error 0: expected expression + "#]], + ); + check( + TopEntryPoint::Expr, + "2 + 2 == 5", + expect![[r#" + BIN_EXPR + BIN_EXPR + LITERAL + INT_NUMBER "2" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + WHITESPACE " " + EQ2 "==" + WHITESPACE " " + LITERAL + INT_NUMBER "5" + "#]], + ); + check( + TopEntryPoint::Expr, + "let _ = 0;", + expect![[r#" + ERROR + LET_KW "let" + WHITESPACE " " + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + INT_NUMBER "0" + SEMICOLON ";" + error 0: expected expression + "#]], + ); +} + #[track_caller] fn check(entry: TopEntryPoint, input: &str, expect: expect_test::Expect) { let (parsed, _errors) = super::parse(entry, input); |
