Unnamed repository; edit this file 'description' to name the repository.
Merge #10494
10494: Macro expansion tests r=matklad a=matklad
bors r+
Co-authored-by: Aleksey Kladov <[email protected]>
| -rw-r--r-- | crates/hir_def/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests.rs | 117 | ||||
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests/mbe.rs | 48 | ||||
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests/mbe/matching.rs | 25 | ||||
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs | 79 | ||||
| -rw-r--r-- | crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs | 84 | ||||
| -rw-r--r-- | crates/hir_expand/src/db.rs | 59 | ||||
| -rw-r--r-- | crates/hir_expand/src/hygiene.rs | 2 | ||||
| -rw-r--r-- | crates/hir_expand/src/lib.rs | 4 | ||||
| -rw-r--r-- | crates/mbe/src/expander.rs | 61 | ||||
| -rw-r--r-- | crates/mbe/src/lib.rs | 16 | ||||
| -rw-r--r-- | crates/mbe/src/subtree_source.rs | 32 | ||||
| -rw-r--r-- | crates/mbe/src/syntax_bridge.rs | 113 | ||||
| -rw-r--r-- | crates/mbe/src/tests.rs | 1 | ||||
| -rw-r--r-- | crates/mbe/src/tests/rule.rs | 48 |
15 files changed, 401 insertions, 290 deletions
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index afcf1bc707..7d5f63afd0 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs @@ -49,6 +49,8 @@ pub mod import_map; #[cfg(test)] mod test_db; +#[cfg(test)] +mod macro_expansion_tests; use std::{ hash::{Hash, Hasher}, diff --git a/crates/hir_def/src/macro_expansion_tests.rs b/crates/hir_def/src/macro_expansion_tests.rs new file mode 100644 index 0000000000..76bd0299fb --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests.rs @@ -0,0 +1,117 @@ +//! This module contains tests for macro expansion. Effectively, it covers `tt`, +//! `mbe`, `proc_macro_api` and `hir_expand` crates. This might seem like a +//! wrong architecture at the first glance, but is intentional. +//! +//! Physically, macro expansion process is intertwined with name resolution. You +//! can not expand *just* the syntax. So, to be able to write integration tests +//! of the "expand this code please" form, we have to do it after name +//! resolution. That is, in this crate. We *could* fake some dependencies and +//! write unit-tests (in fact, we used to do that), but that makes tests brittle +//! and harder to understand. + +mod mbe; + +use std::{iter, ops::Range}; + +use base_db::{fixture::WithFixture, SourceDatabase}; +use expect_test::Expect; +use hir_expand::{db::AstDatabase, InFile, MacroFile}; +use stdx::format_to; +use syntax::{ + ast::{self, edit::IndentLevel}, + AstNode, + SyntaxKind::{self, IDENT}, + SyntaxNode, T, +}; + +use crate::{ + db::DefDatabase, nameres::ModuleSource, resolver::HasResolver, test_db::TestDB, AsMacroCall, +}; + +fn check(ra_fixture: &str, expect: Expect) { + let db = TestDB::with_files(ra_fixture); + let krate = db.crate_graph().iter().next().unwrap(); + let def_map = db.crate_def_map(krate); + let local_id = def_map.root(); + let module = def_map.module_id(local_id); + let resolver = module.resolver(&db); + let source = def_map[local_id].definition_source(&db); + let source_file = match source.value { + ModuleSource::SourceFile(it) => it, + ModuleSource::Module(_) | ModuleSource::BlockExpr(_) => panic!(), + }; + + 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 macro_call_id = macro_call + .as_call_id_with_errors( + &db, + krate, + |path| resolver.resolve_path_as_macro(&db, &path), + &mut |err| panic!("{}", err), + ) + .unwrap() + .unwrap(); + let macro_file = MacroFile { macro_call_id }; + let expansion_result = db.parse_macro_expansion(macro_file); + 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 expn_text = String::new(); + if let Some(err) = exp.err { + format_to!(expn_text, "/* error: {} */", err); + } + if let Some((parse, _token_map)) = exp.value { + 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); + } + let range = call.syntax().text_range(); + let range: Range<usize> = range.into(); + expanded_text.replace_range(range, &expn_text) + } + + expect.assert_eq(&expanded_text); +} + +fn reindent(indent: IndentLevel, pp: String) -> String { + if !pp.contains('\n') { + return pp; + } + let mut lines = pp.split_inclusive('\n'); + let mut res = lines.next().unwrap().to_string(); + for line in lines { + if line.trim().is_empty() { + res.push_str(&line) + } else { + format_to!(res, "{}{}", indent, line) + } + } + res +} + +fn pretty_print_macro_expansion(expn: SyntaxNode) -> String { + let mut res = String::new(); + let mut prev_kind = SyntaxKind::EOF; + for token in iter::successors(expn.first_token(), |t| t.next_token()) { + let curr_kind = token.kind(); + let space = match (prev_kind, curr_kind) { + _ if prev_kind.is_trivia() || curr_kind.is_trivia() => "", + (T![=], _) | (_, T![=]) => " ", + (T![;], _) => "\n", + (IDENT, IDENT) => " ", + (IDENT, _) if curr_kind.is_keyword() => " ", + (_, IDENT) if prev_kind.is_keyword() => " ", + _ => "", + }; + + res.push_str(space); + prev_kind = curr_kind; + format_to!(res, "{}", token) + } + res +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe.rs b/crates/hir_def/src/macro_expansion_tests/mbe.rs new file mode 100644 index 0000000000..cc6551f8aa --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/mbe.rs @@ -0,0 +1,48 @@ +//! Tests specific to declarative macros, aka macros by example. This covers +//! both stable `macro_rules!` macros as well as unstable `macro` macros. + +mod tt_conversion; +mod matching; +mod meta_syntax; + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn expansion_does_not_parse_as_expression() { + check( + r#" +macro_rules! stmts { + () => { let _ = 0; } +} + +fn f() { let _ = stmts!(); } +"#, + expect![[r#" +macro_rules! stmts { + () => { let _ = 0; } +} + +fn f() { let _ = /* error: could not convert tokens */; } +"#]], + ) +} + +#[test] +fn wrong_nesting_level() { + check( + r#" +macro_rules! m { + ($($i:ident);*) => ($i) +} +m!{a} +"#, + expect![[r#" +macro_rules! m { + ($($i:ident);*) => ($i) +} +/* error: expected simple binding, found nested binding `i` */ +"#]], + ); +} diff --git a/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs new file mode 100644 index 0000000000..11317cfa9d --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/mbe/matching.rs @@ -0,0 +1,25 @@ +//! Test that `$var:expr` captures function correctly. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn unary_minus_is_a_literal() { + check( + r#" +macro_rules! m { ($x:literal) => (literal!()); ($x:tt) => (not_a_literal!()); } +m!(92); +m!(-92); +m!(-9.2); +m!(--92); +"#, + expect![[r#" +macro_rules! m { ($x:literal) => (literal!()); ($x:tt) => (not_a_literal!()); } +literal!() +literal!() +literal!() +/* error: leftover tokens */not_a_literal!() +"#]], + ) +} 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 new file mode 100644 index 0000000000..4249c2507e --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -0,0 +1,79 @@ +//! Test for the syntax of macros themselves. + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn well_formed_macro_rules() { + check( + r#" +macro_rules! m { + ($i:ident) => (); + ($(x),*) => (); + ($(x)_*) => (); + ($(x)i*) => (); + ($($i:ident)*) => ($_); + ($($true:ident)*) => ($true); + ($($false:ident)*) => ($false); + ($) => ($); +} +m!($); +"#, + expect![[r#" +macro_rules! m { + ($i:ident) => (); + ($(x),*) => (); + ($(x)_*) => (); + ($(x)i*) => (); + ($($i:ident)*) => ($_); + ($($true:ident)*) => ($true); + ($($false:ident)*) => ($false); + ($) => ($); +} +$ +"#]], + ) +} + +#[test] +fn malformed_macro_rules() { + check( + r#" +macro_rules! i1 { invalid } +i1!(); + +macro_rules! e1 { $i:ident => () } +e1!(); +macro_rules! e2 { ($i:ident) () } +e2!(); +macro_rules! e3 { ($(i:ident)_) => () } +e3!(); + +macro_rules! f1 { ($i) => ($i) } +f1!(); +macro_rules! f2 { ($i:) => ($i) } +f2!(); +macro_rules! f3 { ($i:_) => () } +f3!(); +"#, + expect![[r#" +macro_rules! i1 { invalid } +/* error: invalid macro definition: expected subtree */ + +macro_rules! e1 { $i:ident => () } +/* error: invalid macro definition: expected subtree */ +macro_rules! e2 { ($i:ident) () } +/* error: invalid macro definition: expected `=` */ +macro_rules! e3 { ($(i:ident)_) => () } +/* error: invalid macro definition: invalid repeat */ + +macro_rules! f1 { ($i) => ($i) } +/* error: invalid macro definition: bad fragment specifier 1 */ +macro_rules! f2 { ($i:) => ($i) } +/* error: invalid macro definition: bad fragment specifier 1 */ +macro_rules! f3 { ($i:_) => () } +/* error: invalid macro definition: bad fragment specifier 1 */ +"#]], + ) +} 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 new file mode 100644 index 0000000000..a24fe9bee2 --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/mbe/tt_conversion.rs @@ -0,0 +1,84 @@ +//! Unlike rustc, rust-analyzer's syntax tree are not "made of" token trees. +//! Rather, token trees are an explicit bridge between the parser and +//! (procedural or declarative) macros. +//! +//! This module tests tt <-> syntax tree conversion specifically +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn round_trips_compound_tokens() { + check( + r#" +macro_rules! m { + () => { type qual: ::T = qual::T; } +} +m!(); +"#, + expect![[r#" +macro_rules! m { + () => { type qual: ::T = qual::T; } +} +type qual: ::T = qual::T; +"#]], + ) +} + +#[test] +fn round_trips_literals() { + check( + r#" +macro_rules! m { + () => { + let _ = 'c'; + let _ = 1000; + let _ = 12E+99_f64; + let _ = "rust1"; + let _ = -92; + } +} +fn f() { + m!() +} +"#, + expect![[r#" +macro_rules! m { + () => { + let _ = 'c'; + let _ = 1000; + let _ = 12E+99_f64; + let _ = "rust1"; + let _ = -92; + } +} +fn f() { + let_ = 'c'; + let_ = 1000; + let_ = 12E+99_f64; + let_ = "rust1"; + let_ = -92; +} +"#]], + ); +} + +#[test] +fn broken_parenthesis_sequence() { + check( + r#" +macro_rules! m1 { ($x:ident) => { ($x } } +macro_rules! m2 { ($x:ident) => {} } + +m1!(); +m2!(x +"#, + expect![[r#" +macro_rules! m1 { ($x:ident) => { ($x } } +macro_rules! m2 { ($x:ident) => {} } + +/* error: invalid macro definition: expected subtree */ +/* error: Failed to lower macro args to token tree */ +"#]], + ) +} diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index fd1d8d2e60..08ad9ffd94 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -8,7 +8,7 @@ use mbe::{syntax_node_to_token_tree, ExpandError, ExpandResult}; use rustc_hash::FxHashSet; use syntax::{ algo::diff, - ast::{self, HasAttrs, HasName}, + ast::{self, HasAttrs}, AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken, T, }; @@ -119,7 +119,7 @@ pub trait AstDatabase: SourceDatabase { fn macro_arg_text(&self, id: MacroCallId) -> Option<GreenNode>; /// Gets the expander for this macro. This compiles declarative macros, and /// just fetches procedural ones. - fn macro_def(&self, id: MacroDefId) -> Option<Arc<TokenExpander>>; + fn macro_def(&self, id: MacroDefId) -> Result<Arc<TokenExpander>, mbe::ParseError>; /// Expand macro call to a token tree. This query is LRUed (we keep 128 or so results in memory) fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult<Option<Arc<tt::Subtree>>>; @@ -145,7 +145,7 @@ pub fn expand_speculative( token_to_map: SyntaxToken, ) -> Option<(SyntaxNode, SyntaxToken)> { let loc = db.lookup_intern_macro(actual_macro_call); - let macro_def = db.macro_def(loc.def)?; + let macro_def = db.macro_def(loc.def).ok()?; let token_range = token_to_map.text_range(); // Build the subtree and token mapping for the speculative args @@ -360,45 +360,39 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { Some(arg.green().into()) } -fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<TokenExpander>> { +fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Result<Arc<TokenExpander>, mbe::ParseError> { match id.kind { MacroDefKind::Declarative(ast_id) => match ast_id.to_node(db) { ast::Macro::MacroRules(macro_rules) => { - let arg = macro_rules.token_tree()?; + let arg = macro_rules + .token_tree() + .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?; let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax()); - let mac = match mbe::MacroRules::parse(&tt) { - Ok(it) => it, - Err(err) => { - let name = macro_rules.name().map(|n| n.to_string()).unwrap_or_default(); - tracing::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt); - return None; - } - }; - Some(Arc::new(TokenExpander::MacroRules { mac, def_site_token_map })) + let mac = mbe::MacroRules::parse(&tt)?; + Ok(Arc::new(TokenExpander::MacroRules { mac, def_site_token_map })) } ast::Macro::MacroDef(macro_def) => { - let arg = macro_def.body()?; + let arg = macro_def + .body() + .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?; let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax()); - let mac = match mbe::MacroDef::parse(&tt) { - Ok(it) => it, - Err(err) => { - let name = macro_def.name().map(|n| n.to_string()).unwrap_or_default(); - tracing::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt); - return None; - } - }; - Some(Arc::new(TokenExpander::MacroDef { mac, def_site_token_map })) + let mac = mbe::MacroDef::parse(&tt)?; + Ok(Arc::new(TokenExpander::MacroDef { mac, def_site_token_map })) } }, - MacroDefKind::BuiltIn(expander, _) => Some(Arc::new(TokenExpander::Builtin(expander))), + MacroDefKind::BuiltIn(expander, _) => Ok(Arc::new(TokenExpander::Builtin(expander))), MacroDefKind::BuiltInAttr(expander, _) => { - Some(Arc::new(TokenExpander::BuiltinAttr(expander))) + Ok(Arc::new(TokenExpander::BuiltinAttr(expander))) } MacroDefKind::BuiltInDerive(expander, _) => { - Some(Arc::new(TokenExpander::BuiltinDerive(expander))) + Ok(Arc::new(TokenExpander::BuiltinDerive(expander))) } - MacroDefKind::BuiltInEager(..) => None, - MacroDefKind::ProcMacro(expander, ..) => Some(Arc::new(TokenExpander::ProcMacro(expander))), + MacroDefKind::BuiltInEager(..) => { + // FIXME: Return a random error here just to make the types align. + // This obviously should do something real instead. + Err(mbe::ParseError::UnexpectedToken("unexpected eager macro".to_string())) + } + MacroDefKind::ProcMacro(expander, ..) => Ok(Arc::new(TokenExpander::ProcMacro(expander))), } } @@ -419,8 +413,11 @@ fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<Option<Ar }; let expander = match db.macro_def(loc.def) { - Some(it) => it, - None => return ExpandResult::str_err("Failed to find macro definition".into()), + Ok(it) => it, + // FIXME: This is weird -- we effectively report macro *definition* + // errors lazily, when we try to expand the macro. Instead, they should + // be reported at the definition site (when we construct a def map). + Err(err) => return ExpandResult::str_err(format!("invalid macro definition: {}", err)), }; let ExpandResult { value: tt, err } = expander.expand(db, id, ¯o_arg.0); // Set a hard limit for the expanded tt diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs index 5b3ccdeb60..b2879e37c3 100644 --- a/crates/hir_expand/src/hygiene.rs +++ b/crates/hir_expand/src/hygiene.rs @@ -195,7 +195,7 @@ fn make_hygiene_info( _ => None, }); - let macro_def = db.macro_def(loc.def)?; + let macro_def = db.macro_def(loc.def).ok()?; let (_, exp_map) = db.parse_macro_expansion(macro_file).value?; let macro_arg = db.macro_arg(macro_file.macro_call_id)?; diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index d903725d88..ad6b84dd17 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -143,7 +143,7 @@ impl HirFileId { _ => None, }); - let macro_def = db.macro_def(loc.def)?; + let macro_def = db.macro_def(loc.def).ok()?; let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?; let macro_arg = db.macro_arg(macro_file.macro_call_id)?; @@ -204,7 +204,7 @@ impl HirFileId { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroFile { - macro_call_id: MacroCallId, + pub macro_call_id: MacroCallId, } /// `MacroCallId` identifies a particular macro invocation, like diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs index 230d33e538..7244d21161 100644 --- a/crates/mbe/src/expander.rs +++ b/crates/mbe/src/expander.rs @@ -114,64 +114,3 @@ enum Fragment { /// like `$i * 2` where `$i = 1 + 1` work as expectd. Ast(tt::TokenTree), } - -#[cfg(test)] -mod tests { - use syntax::{ast, AstNode}; - - use super::*; - use crate::syntax_node_to_token_tree; - - #[test] - fn test_expand_rule() { - assert_err( - "($($i:ident);*) => ($i)", - "foo!{a}", - ExpandError::BindingError(String::from( - "expected simple binding, found nested binding `i`", - )), - ); - - // FIXME: - // Add an err test case for ($($i:ident)) => ($()) - } - - fn assert_err(macro_body: &str, invocation: &str, err: ExpandError) { - assert_eq!( - expand_first(&create_rules(&format_macro(macro_body)), invocation).err, - Some(err) - ); - } - - fn format_macro(macro_body: &str) -> String { - format!( - " - macro_rules! foo {{ - {} - }} -", - macro_body - ) - } - - fn create_rules(macro_definition: &str) -> crate::MacroRules { - let source_file = ast::SourceFile::parse(macro_definition).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()); - crate::MacroRules::parse(&definition_tt).unwrap() - } - - fn expand_first(rules: &crate::MacroRules, invocation: &str) -> ExpandResult<tt::Subtree> { - let source_file = ast::SourceFile::parse(invocation).ok().unwrap(); - 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()); - - expand_rules(&rules.rules, &invocation_tt) - } -} diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 8470ea0aaf..61e032e0af 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -2,6 +2,9 @@ //! `macro_rules` macros. It uses `TokenTree` (from `tt` package) as the //! interface, although it contains some code to bridge `SyntaxNode`s and //! `TokenTree`s as well! +//! +//! The tes for this functionality live in another crate: +//! `hir_def::macro_expansion_tests::mbe`. mod parser; mod expander; @@ -27,7 +30,7 @@ use crate::{ pub use ::parser::ParserEntryPoint; pub use tt::{Delimiter, DelimiterKind, Punct}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum ParseError { UnexpectedToken(String), Expected(String), @@ -35,6 +38,17 @@ pub enum ParseError { RepetitionEmptyTokenTree, } +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseError::UnexpectedToken(it) => f.write_str(it), + ParseError::Expected(it) => f.write_str(it), + ParseError::InvalidRepeat => f.write_str("invalid repeat"), + ParseError::RepetitionEmptyTokenTree => f.write_str("empty token tree in repetition"), + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum ExpandError { NoMatchingRule, diff --git a/crates/mbe/src/subtree_source.rs b/crates/mbe/src/subtree_source.rs index ffc2a60172..247616aa1b 100644 --- a/crates/mbe/src/subtree_source.rs +++ b/crates/mbe/src/subtree_source.rs @@ -18,17 +18,6 @@ pub(crate) struct SubtreeTokenSource { } impl<'a> SubtreeTokenSource { - // Helper function used in test - #[cfg(test)] - pub(crate) fn text(&self) -> SmolStr { - match self.cached.get(self.curr.1) { - Some(tt) => tt.text.clone(), - _ => SmolStr::new(""), - } - } -} - -impl<'a> SubtreeTokenSource { pub(crate) fn new(buffer: &TokenBuffer) -> SubtreeTokenSource { let mut current = buffer.begin(); let mut cached = Vec::with_capacity(100); @@ -181,24 +170,3 @@ fn convert_leaf(leaf: &tt::Leaf) -> TtToken { tt::Leaf::Punct(punct) => convert_punct(*punct), } } - -#[cfg(test)] -mod tests { - use super::{convert_literal, TtToken}; - use parser::Token; - use syntax::{SmolStr, SyntaxKind}; - - #[test] - fn test_negative_literal() { - assert_eq!( - convert_literal(&tt::Literal { - id: tt::TokenId::unspecified(), - text: SmolStr::new("-42.0") - }), - TtToken { - tt: Token { kind: SyntaxKind::FLOAT_NUMBER, is_jointed_to_next: false }, - text: SmolStr::new("-42.0") - } - ); - } -} diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs index 7f68543310..f3c9af1ccb 100644 --- a/crates/mbe/src/syntax_bridge.rs +++ b/crates/mbe/src/syntax_bridge.rs @@ -743,116 +743,3 @@ impl<'a> TreeSink for TtTreeSink<'a> { self.inner.error(error, self.text_pos) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::parse_macro; - use parser::TokenSource; - use syntax::{ - ast::{make, AstNode}, - ted, - }; - use test_utils::assert_eq_text; - - #[test] - fn convert_tt_token_source() { - let expansion = parse_macro( - r#" - macro_rules! literals { - ($i:ident) => { - { - let a = 'c'; - let c = 1000; - let f = 12E+99_f64; - let s = "rust1"; - } - } - } - "#, - ) - .expand_tt("literals!(foo);"); - let tts = &[expansion.into()]; - let buffer = tt::buffer::TokenBuffer::from_tokens(tts); - let mut tt_src = SubtreeTokenSource::new(&buffer); - let mut tokens = vec![]; - while tt_src.current().kind != EOF { - tokens.push((tt_src.current().kind, tt_src.text())); - tt_src.bump(); - } - - // [${] - // [let] [a] [=] ['c'] [;] - assert_eq!(tokens[2 + 3].1, "'c'"); - assert_eq!(tokens[2 + 3].0, CHAR); - // [let] [c] [=] [1000] [;] - assert_eq!(tokens[2 + 5 + 3].1, "1000"); - assert_eq!(tokens[2 + 5 + 3].0, INT_NUMBER); - // [let] [f] [=] [12E+99_f64] [;] - assert_eq!(tokens[2 + 10 + 3].1, "12E+99_f64"); - assert_eq!(tokens[2 + 10 + 3].0, FLOAT_NUMBER); - - // [let] [s] [=] ["rust1"] [;] - assert_eq!(tokens[2 + 15 + 3].1, "\"rust1\""); - assert_eq!(tokens[2 + 15 + 3].0, STRING); - } - - #[test] - fn stmts_token_trees_to_expr_is_err() { - let expansion = parse_macro( - r#" - macro_rules! stmts { - () => { - let a = 0; - let b = 0; - let c = 0; - let d = 0; - } - } - "#, - ) - .expand_tt("stmts!();"); - assert!(token_tree_to_syntax_node(&expansion, ParserEntryPoint::Expr).is_err()); - } - - #[test] - fn test_token_tree_last_child_is_white_space() { - let source_file = ast::SourceFile::parse("f!{}").ok().unwrap(); - let macro_call = source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); - let token_tree = macro_call.token_tree().unwrap(); - - // Token Tree now is : - // TokenTree - // - TokenTree - // - T!['{'] - // - T!['}'] - - let token_tree = token_tree.clone_for_update(); - ted::append_child(token_tree.syntax(), make::tokens::single_space()); - let token_tree = token_tree.clone_subtree(); - // Token Tree now is : - // TokenTree - // - T!['{'] - // - T!['}'] - // - WHITE_SPACE - - let tt = syntax_node_to_token_tree(token_tree.syntax()).0; - assert_eq!(tt.delimiter_kind(), Some(tt::DelimiterKind::Brace)); - } - - #[test] - fn test_token_tree_multi_char_punct() { - let source_file = ast::SourceFile::parse("struct Foo { a: x::Y }").ok().unwrap(); - let struct_def = source_file.syntax().descendants().find_map(ast::Struct::cast).unwrap(); - let tt = syntax_node_to_token_tree(struct_def.syntax()).0; - token_tree_to_syntax_node(&tt, ParserEntryPoint::Item).unwrap(); - } - - #[test] - fn test_missing_closing_delim() { - let source_file = ast::SourceFile::parse("m!(x").tree(); - let node = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(node.syntax()).0.to_string(); - assert_eq_text!(&*tt, "( x"); - } -} diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs index 26924fa8f3..ad5ae3a32c 100644 --- a/crates/mbe/src/tests.rs +++ b/crates/mbe/src/tests.rs @@ -1,5 +1,4 @@ mod expand; -mod rule; use std::{fmt::Write, iter}; diff --git a/crates/mbe/src/tests/rule.rs b/crates/mbe/src/tests/rule.rs deleted file mode 100644 index 691e359e4d..0000000000 --- a/crates/mbe/src/tests/rule.rs +++ /dev/null @@ -1,48 +0,0 @@ -use syntax::{ast, AstNode}; - -use super::*; - -#[test] -fn test_valid_arms() { - fn check(macro_body: &str) { - let m = parse_macro_arm(macro_body); - m.unwrap(); - } - - check("($i:ident) => ()"); - check("($(x),*) => ()"); - check("($(x)_*) => ()"); - check("($(x)i*) => ()"); - check("($($i:ident)*) => ($_)"); - check("($($true:ident)*) => ($true)"); - check("($($false:ident)*) => ($false)"); - check("($) => ($)"); -} - -#[test] -fn test_invalid_arms() { - fn check(macro_body: &str, err: ParseError) { - let m = parse_macro_arm(macro_body); - assert_eq!(m, Err(err)); - } - check("invalid", ParseError::Expected("expected subtree".into())); - - check("$i:ident => ()", ParseError::Expected("expected subtree".into())); - check("($i:ident) ()", ParseError::Expected("expected `=`".into())); - check("($($i:ident)_) => ()", ParseError::InvalidRepeat); - - check("($i) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); - check("($i:) => ($i)", ParseError::UnexpectedToken("bad fragment specifier 1".into())); - check("($i:_) => ()", ParseError::UnexpectedToken("bad fragment specifier 1".into())); -} - -fn parse_macro_arm(arm_definition: &str) -> Result<crate::MacroRules, ParseError> { - let macro_definition = format!(" macro_rules! m {{ {} }} ", arm_definition); - let source_file = ast::SourceFile::parse(¯o_definition).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()); - crate::MacroRules::parse(&definition_tt) -} |