diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index d171d2dfd1d..5a696542f64 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs @@ -6,7 +6,7 @@ use mbe::{ExpandResult, MacroRules}; use ra_db::{salsa, SourceDatabase}; use ra_parser::FragmentKind; use ra_prof::profile; -use ra_syntax::{AstNode, Parse, SyntaxKind::*, SyntaxNode}; +use ra_syntax::{algo::diff, AstNode, Parse, SyntaxKind::*, SyntaxNode}; use crate::{ ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId, @@ -238,7 +238,7 @@ pub fn parse_macro_with_arg( } else { db.macro_expand(macro_call_id) }; - if let Some(err) = err { + if let Some(err) = &err { // Note: // The final goal we would like to make all parse_macro success, // such that the following log will not call anyway. @@ -272,7 +272,25 @@ pub fn parse_macro_with_arg( let fragment_kind = to_fragment_kind(db, macro_call_id); let (parse, rev_token_map) = mbe::token_tree_to_syntax_node(&tt, fragment_kind).ok()?; - Some((parse, Arc::new(rev_token_map))) + + if err.is_none() { + Some((parse, Arc::new(rev_token_map))) + } else { + // FIXME: + // In future, we should propagate the actual error with recovery information + // instead of ignore the error here. + + // Safe check for recurisve identity macro + let node = parse.syntax_node(); + let file: HirFileId = macro_file.into(); + let call_node = file.call_node(db)?; + + if !diff(&node, &call_node.value).is_empty() { + Some((parse, Arc::new(rev_token_map))) + } else { + None + } + } } /// Given a `MacroCallId`, return what `FragmentKind` it belongs to. diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs index 14c8ed3a9b7..a02e3ee0514 100644 --- a/crates/ra_hir_ty/src/tests/regression.rs +++ b/crates/ra_hir_ty/src/tests/regression.rs @@ -453,3 +453,34 @@ pub mod str { // should be Option, but currently not because of Chalk ambiguity problem assert_eq!("(Option<{unknown}>, Option<{unknown}>)", super::type_at_pos(&db, pos)); } + +#[test] +fn issue_3642_bad_macro_stackover() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs +#[macro_export] +macro_rules! match_ast { + (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + + (match ($node:expr) { + $( ast::$ast:ident($it:ident) => $res:expr, )* + _ => $catch_all:expr $(,)? + }) => {{ + $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )* + { $catch_all } + }}; +} + +fn main() { + let anchor<|> = match_ast! { + match parent { + as => {}, + _ => return None + } + }; +}"#, + ); + + assert_eq!("()", super::type_at_pos(&db, pos)); +} diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 344cf0fbedf..ffdbdc76713 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs @@ -95,6 +95,10 @@ impl TreeDiff { builder.replace(from.text_range(), to.to_string()) } } + + pub fn is_empty(&self) -> bool { + self.replacements.is_empty() + } } /// Finds minimal the diff, which, applied to `from`, will result in `to`.