3671: Add identity expansion checking in ill-form expansion  r=flodiebold a=edwin0cheng

This PR try to add more checking code in error case in macro expansion. The bug in #3642 is introduced by  #3580 , which allow ill-form macro expansion in *all* kind of macro expansions. 

In general we should separate hypothetical macro expansion and the actual macro expansion call. However, currently the `Semantic`  workflow we are using only support single macro expansion type, we might want to review it and make it works in both ways. (Maybe add a field in `MacroCallLoc` for differentiation) 

Fix #3642

Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
This commit is contained in:
bors[bot] 2020-03-21 16:02:07 +00:00 committed by GitHub
commit 50c6a315ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 3 deletions

View File

@ -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.

View File

@ -453,3 +453,34 @@ pub mod str {
// should be Option<char>, 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));
}

View File

@ -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`.