internal: macro_arg query always returns a TokenTree

This commit is contained in:
Lukas Wirth 2024-02-12 15:26:17 +01:00
parent e2a985e93f
commit 2fa57d90bc
12 changed files with 139 additions and 131 deletions

View File

@ -1000,10 +1000,6 @@ fn collect_macro_call<T, U>(
krate: *krate, krate: *krate,
}); });
} }
Some(ExpandError::RecursionOverflowPoisoned) => {
// Recursion limit has been reached in the macro expansion tree, but not in
// this very macro call. Don't add diagnostics to avoid duplication.
}
Some(err) => { Some(err) => {
self.source_map.diagnostics.push(BodyDiagnostic::MacroError { self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
node: InFile::new(outer_file, syntax_ptr), node: InFile::new(outer_file, syntax_ptr),

View File

@ -140,13 +140,11 @@ fn within_limit<F, T: ast::AstNode>(
// The overflow error should have been reported when it occurred (see the next branch), // The overflow error should have been reported when it occurred (see the next branch),
// so don't return overflow error here to avoid diagnostics duplication. // so don't return overflow error here to avoid diagnostics duplication.
cov_mark::hit!(overflow_but_not_me); cov_mark::hit!(overflow_but_not_me);
return ExpandResult::only_err(ExpandError::RecursionOverflowPoisoned); return ExpandResult::ok(None);
} else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() { } else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() {
self.recursion_depth = u32::MAX; self.recursion_depth = u32::MAX;
cov_mark::hit!(your_stack_belongs_to_me); cov_mark::hit!(your_stack_belongs_to_me);
return ExpandResult::only_err(ExpandError::other( return ExpandResult::only_err(ExpandError::RecursionOverflow);
"reached recursion limit during macro expansion",
));
} }
let ExpandResult { value, err } = op(self); let ExpandResult { value, err } = op(self);

View File

@ -33,7 +33,7 @@ fn test_expand_bad_literal() {
"#, "#,
expect![[r#" expect![[r#"
macro_rules! m { ($i:literal) => {}; } macro_rules! m { ($i:literal) => {}; }
/* error: invalid token tree */"#]], /* error: mismatched delimiters */"#]],
); );
} }

View File

@ -68,26 +68,26 @@ macro_rules! m2 { () => ( ${invalid()} ) }
"#, "#,
expect![[r#" expect![[r#"
macro_rules! i1 { invalid } macro_rules! i1 { invalid }
/* error: invalid macro definition: expected subtree */ /* error: macro definition has parse errors */
macro_rules! e1 { $i:ident => () } macro_rules! e1 { $i:ident => () }
/* error: invalid macro definition: expected subtree */ /* error: macro definition has parse errors */
macro_rules! e2 { ($i:ident) () } macro_rules! e2 { ($i:ident) () }
/* error: invalid macro definition: expected `=` */ /* error: macro definition has parse errors */
macro_rules! e3 { ($(i:ident)_) => () } macro_rules! e3 { ($(i:ident)_) => () }
/* error: invalid macro definition: invalid repeat */ /* error: macro definition has parse errors */
macro_rules! f1 { ($i) => ($i) } macro_rules! f1 { ($i) => ($i) }
/* error: invalid macro definition: missing fragment specifier */ /* error: macro definition has parse errors */
macro_rules! f2 { ($i:) => ($i) } macro_rules! f2 { ($i:) => ($i) }
/* error: invalid macro definition: missing fragment specifier */ /* error: macro definition has parse errors */
macro_rules! f3 { ($i:_) => () } macro_rules! f3 { ($i:_) => () }
/* error: invalid macro definition: missing fragment specifier */ /* error: macro definition has parse errors */
macro_rules! m1 { ($$i) => () } macro_rules! m1 { ($$i) => () }
/* error: invalid macro definition: `$$` is not allowed on the pattern side */ /* error: macro definition has parse errors */
macro_rules! m2 { () => ( ${invalid()} ) } macro_rules! m2 { () => ( ${invalid()} ) }
/* error: invalid macro definition: invalid metavariable expression */ /* error: macro definition has parse errors */
"#]], "#]],
) )
} }
@ -137,18 +137,18 @@ fn test_rustc_issue_57597() {
macro_rules! mA { ($($($($i:ident)+)?)*) => {}; } macro_rules! mA { ($($($($i:ident)+)?)*) => {}; }
macro_rules! mB { ($($($($i:ident)+)*)?) => {}; } macro_rules! mB { ($($($($i:ident)+)*)?) => {}; }
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
/* error: invalid macro definition: empty token tree in repetition */ /* error: macro definition has parse errors */
"#]], "#]],
); );
} }

View File

@ -275,9 +275,9 @@ macro_rules! depth_too_large {
} }
fn test() { fn test() {
/* error: invalid macro definition: invalid metavariable expression */; /* error: macro definition has parse errors */;
/* error: invalid macro definition: invalid metavariable expression */; /* error: macro definition has parse errors */;
/* error: invalid macro definition: invalid metavariable expression */; /* error: macro definition has parse errors */;
} }
"#]], "#]],
); );

View File

@ -97,8 +97,8 @@ macro_rules! m2 { ($x:ident) => {} }
macro_rules! m1 { ($x:ident) => { ($x } } macro_rules! m1 { ($x:ident) => { ($x } }
macro_rules! m2 { ($x:ident) => {} } macro_rules! m2 { ($x:ident) => {} }
/* error: invalid macro definition: expected subtree */ /* error: macro definition has parse errors */
/* error: invalid token tree */ /* error: mismatched delimiters */
"#]], "#]],
) )
} }

View File

@ -446,7 +446,7 @@ fn compile_error_expand(
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let err = match &*tt.token_trees { let err = match &*tt.token_trees {
[tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) {
Some(unquoted) => ExpandError::other(unquoted), Some(unquoted) => ExpandError::other(unquoted.into_boxed_str()),
None => ExpandError::other("`compile_error!` argument must be a string"), None => ExpandError::other("`compile_error!` argument must be a string"),
}, },
_ => ExpandError::other("`compile_error!` argument must be a string"), _ => ExpandError::other("`compile_error!` argument must be a string"),

View File

@ -108,7 +108,7 @@ fn parse_macro_expansion(
fn macro_arg( fn macro_arg(
&self, &self,
id: MacroCallId, id: MacroCallId,
) -> ValueResult<Option<(Arc<tt::Subtree>, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>>; ) -> ValueResult<(Arc<tt::Subtree>, SyntaxFixupUndoInfo), Arc<Box<[SyntaxError]>>>;
/// Fetches the expander for this macro. /// Fetches the expander for this macro.
#[salsa::transparent] #[salsa::transparent]
#[salsa::invoke(TokenExpander::macro_expander)] #[salsa::invoke(TokenExpander::macro_expander)]
@ -326,58 +326,77 @@ fn macro_arg(
db: &dyn ExpandDatabase, db: &dyn ExpandDatabase,
id: MacroCallId, id: MacroCallId,
// FIXME: consider the following by putting fixup info into eager call info args // FIXME: consider the following by putting fixup info into eager call info args
// ) -> ValueResult<Option<Arc<(tt::Subtree, SyntaxFixupUndoInfo)>>, Arc<Box<[SyntaxError]>>> { // ) -> ValueResult<Arc<(tt::Subtree, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>> {
) -> ValueResult<Option<(Arc<tt::Subtree>, SyntaxFixupUndoInfo)>, Arc<Box<[SyntaxError]>>> { ) -> ValueResult<(Arc<tt::Subtree>, SyntaxFixupUndoInfo), Arc<Box<[SyntaxError]>>> {
let mismatched_delimiters = |arg: &SyntaxNode| {
let first = arg.first_child_or_token().map_or(T![.], |it| it.kind());
let last = arg.last_child_or_token().map_or(T![.], |it| it.kind());
let well_formed_tt =
matches!((first, last), (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']));
if !well_formed_tt {
// Don't expand malformed (unbalanced) macro invocations. This is
// less than ideal, but trying to expand unbalanced macro calls
// sometimes produces pathological, deeply nested code which breaks
// all kinds of things.
//
// Some day, we'll have explicit recursion counters for all
// recursive things, at which point this code might be removed.
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
Some(Arc::new(Box::new([SyntaxError::new(
"unbalanced token tree".to_owned(),
arg.text_range(),
)]) as Box<[_]>))
} else {
None
}
};
let loc = db.lookup_intern_macro_call(id); let loc = db.lookup_intern_macro_call(id);
if let Some(EagerCallInfo { arg, .. }) = matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) if let Some(EagerCallInfo { arg, .. }) = matches!(loc.def.kind, MacroDefKind::BuiltInEager(..))
.then(|| loc.eager.as_deref()) .then(|| loc.eager.as_deref())
.flatten() .flatten()
{ {
ValueResult::ok(Some((arg.clone(), SyntaxFixupUndoInfo::NONE))) ValueResult::ok((arg.clone(), SyntaxFixupUndoInfo::NONE))
} else { } else {
let (parse, map) = parse_with_map(db, loc.kind.file_id()); let (parse, map) = parse_with_map(db, loc.kind.file_id());
let root = parse.syntax_node(); let root = parse.syntax_node();
let syntax = match loc.kind { let syntax = match loc.kind {
MacroCallKind::FnLike { ast_id, .. } => { MacroCallKind::FnLike { ast_id, .. } => {
let dummy_tt = |kind| {
(
Arc::new(tt::Subtree {
delimiter: tt::Delimiter {
open: loc.call_site,
close: loc.call_site,
kind,
},
token_trees: Box::default(),
}),
SyntaxFixupUndoInfo::default(),
)
};
let node = &ast_id.to_ptr(db).to_node(&root); let node = &ast_id.to_ptr(db).to_node(&root);
let offset = node.syntax().text_range().start(); let offset = node.syntax().text_range().start();
match node.token_tree() { let Some(tt) = node.token_tree() else {
Some(tt) => { return ValueResult::new(
let tt = tt.syntax(); dummy_tt(tt::DelimiterKind::Invisible),
if let Some(e) = mismatched_delimiters(tt) { Arc::new(Box::new([SyntaxError::new_at_offset(
return ValueResult::only_err(e); "missing token tree".to_owned(),
} offset,
tt.clone() )])),
} );
None => { };
return ValueResult::only_err(Arc::new(Box::new([ let first = tt.left_delimiter_token().map(|it| it.kind()).unwrap_or(T!['(']);
SyntaxError::new_at_offset("missing token tree".to_owned(), offset), let last = tt.right_delimiter_token().map(|it| it.kind()).unwrap_or(T![.]);
])));
} let mismatched_delimiters = !matches!(
(first, last),
(T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}'])
);
if mismatched_delimiters {
// Don't expand malformed (unbalanced) macro invocations. This is
// less than ideal, but trying to expand unbalanced macro calls
// sometimes produces pathological, deeply nested code which breaks
// all kinds of things.
//
// So instead, we'll return an empty subtree here
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
let kind = match first {
_ if loc.def.is_proc_macro() => tt::DelimiterKind::Invisible,
T!['('] => tt::DelimiterKind::Parenthesis,
T!['['] => tt::DelimiterKind::Bracket,
T!['{'] => tt::DelimiterKind::Brace,
_ => tt::DelimiterKind::Invisible,
};
return ValueResult::new(
dummy_tt(kind),
Arc::new(Box::new([SyntaxError::new_at_offset(
"mismatched delimiters".to_owned(),
offset,
)])),
);
} }
tt.syntax().clone()
} }
MacroCallKind::Derive { ast_id, .. } => { MacroCallKind::Derive { ast_id, .. } => {
ast_id.to_ptr(db).to_node(&root).syntax().clone() ast_id.to_ptr(db).to_node(&root).syntax().clone()
@ -427,15 +446,15 @@ fn macro_arg(
if matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) { if matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) {
match parse.errors() { match parse.errors() {
[] => ValueResult::ok(Some((Arc::new(tt), undo_info))), [] => ValueResult::ok((Arc::new(tt), undo_info)),
errors => ValueResult::new( errors => ValueResult::new(
Some((Arc::new(tt), undo_info)), (Arc::new(tt), undo_info),
// Box::<[_]>::from(res.errors()), not stable yet // Box::<[_]>::from(res.errors()), not stable yet
Arc::new(errors.to_vec().into_boxed_slice()), Arc::new(errors.to_vec().into_boxed_slice()),
), ),
} }
} else { } else {
ValueResult::ok(Some((Arc::new(tt), undo_info))) ValueResult::ok((Arc::new(tt), undo_info))
} }
} }
} }
@ -519,21 +538,20 @@ fn macro_expand(
expander.expand(db, macro_call_id, &node, map.as_ref()) expander.expand(db, macro_call_id, &node, map.as_ref())
} }
_ => { _ => {
let ValueResult { value, err } = db.macro_arg(macro_call_id); let ValueResult { value: (macro_arg, undo_info), err } = db.macro_arg(macro_call_id);
let Some((macro_arg, undo_info)) = value else { let format_parse_err = |err: Arc<Box<[SyntaxError]>>| {
return ExpandResult { let mut buf = String::new();
value: CowArc::Owned(tt::Subtree { for err in &**err {
delimiter: tt::Delimiter::invisible_spanned(loc.call_site), use std::fmt::Write;
token_trees: Box::new([]), _ = write!(buf, "{}, ", err);
}), }
// FIXME: We should make sure to enforce an invariant that invalid macro buf.pop();
// calls do not reach this call path! buf.pop();
err: Some(ExpandError::other("invalid token tree")), ExpandError::other(buf)
};
}; };
let arg = &*macro_arg; let arg = &*macro_arg;
match loc.def.kind { let res = match loc.def.kind {
MacroDefKind::Declarative(id) => { MacroDefKind::Declarative(id) => {
db.decl_macro_expander(loc.def.krate, id).expand(db, arg.clone(), macro_call_id) db.decl_macro_expander(loc.def.krate, id).expand(db, arg.clone(), macro_call_id)
} }
@ -549,16 +567,7 @@ fn macro_expand(
MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => { MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => {
return ExpandResult { return ExpandResult {
value: CowArc::Arc(macro_arg.clone()), value: CowArc::Arc(macro_arg.clone()),
err: err.map(|err| { err: err.map(format_parse_err),
let mut buf = String::new();
for err in &**err {
use std::fmt::Write;
_ = write!(buf, "{}, ", err);
}
buf.pop();
buf.pop();
ExpandError::other(buf)
}),
}; };
} }
MacroDefKind::BuiltInEager(it, _) => { MacroDefKind::BuiltInEager(it, _) => {
@ -570,6 +579,11 @@ fn macro_expand(
res res
} }
_ => unreachable!(), _ => unreachable!(),
};
ExpandResult {
value: res.value,
// if the arg had parse errors, show them instead of the expansion errors
err: err.map(format_parse_err).or(res.err),
} }
} }
}; };
@ -597,17 +611,7 @@ fn macro_expand(
fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> { fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
let loc = db.lookup_intern_macro_call(id); let loc = db.lookup_intern_macro_call(id);
let Some((macro_arg, undo_info)) = db.macro_arg(id).value else { let (macro_arg, undo_info) = db.macro_arg(id).value;
return ExpandResult {
value: Arc::new(tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(loc.call_site),
token_trees: Box::new([]),
}),
// FIXME: We should make sure to enforce an invariant that invalid macro
// calls do not reach this call path!
err: Some(ExpandError::other("invalid token tree")),
};
};
let expander = match loc.def.kind { let expander = match loc.def.kind {
MacroDefKind::ProcMacro(expander, ..) => expander, MacroDefKind::ProcMacro(expander, ..) => expander,

View File

@ -44,9 +44,9 @@ pub fn expand(
) )
}); });
match self.mac.err() { match self.mac.err() {
Some(e) => ExpandResult::new( Some(_) => ExpandResult::new(
tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }), tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }),
ExpandError::other(format!("invalid macro definition: {e}")), ExpandError::MacroDefinition,
), ),
None => self None => self
.mac .mac
@ -80,9 +80,9 @@ pub fn expand_unhygienic(
) )
}); });
match self.mac.err() { match self.mac.err() {
Some(e) => ExpandResult::new( Some(_) => ExpandResult::new(
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
ExpandError::other(format!("invalid macro definition: {e}")), ExpandError::MacroDefinition,
), ),
None => self.mac.expand(&tt, |_| (), new_meta_vars, call_site).map_err(Into::into), None => self.mac.expand(&tt, |_| (), new_meta_vars, call_site).map_err(Into::into),
} }

View File

@ -44,7 +44,6 @@
builtin_derive_macro::BuiltinDeriveExpander, builtin_derive_macro::BuiltinDeriveExpander,
builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
db::{ExpandDatabase, TokenExpander}, db::{ExpandDatabase, TokenExpander},
fixup::SyntaxFixupUndoInfo,
hygiene::SyntaxContextData, hygiene::SyntaxContextData,
mod_path::ModPath, mod_path::ModPath,
proc_macro::{CustomProcMacroExpander, ProcMacroKind}, proc_macro::{CustomProcMacroExpander, ProcMacroKind},
@ -131,8 +130,9 @@ pub enum ExpandError {
UnresolvedProcMacro(CrateId), UnresolvedProcMacro(CrateId),
/// The macro expansion is disabled. /// The macro expansion is disabled.
MacroDisabled, MacroDisabled,
MacroDefinition,
Mbe(mbe::ExpandError), Mbe(mbe::ExpandError),
RecursionOverflowPoisoned, RecursionOverflow,
Other(Box<Box<str>>), Other(Box<Box<str>>),
ProcMacroPanic(Box<Box<str>>), ProcMacroPanic(Box<Box<str>>),
} }
@ -154,15 +154,14 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
ExpandError::Mbe(it) => it.fmt(f), ExpandError::Mbe(it) => it.fmt(f),
ExpandError::RecursionOverflowPoisoned => { ExpandError::RecursionOverflow => f.write_str("overflow expanding the original macro"),
f.write_str("overflow expanding the original macro")
}
ExpandError::ProcMacroPanic(it) => { ExpandError::ProcMacroPanic(it) => {
f.write_str("proc-macro panicked: ")?; f.write_str("proc-macro panicked: ")?;
f.write_str(it) f.write_str(it)
} }
ExpandError::Other(it) => f.write_str(it), ExpandError::Other(it) => f.write_str(it),
ExpandError::MacroDisabled => f.write_str("macro disabled"), ExpandError::MacroDisabled => f.write_str("macro disabled"),
ExpandError::MacroDefinition => f.write_str("macro definition has parse errors"),
} }
} }
} }
@ -761,15 +760,7 @@ pub fn new(db: &dyn ExpandDatabase, macro_file: MacroFileId) -> ExpansionInfo {
let (parse, exp_map) = db.parse_macro_expansion(macro_file).value; let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() }; let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() };
let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| { let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value;
(
Arc::new(tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(loc.call_site),
token_trees: Box::new([]),
}),
SyntaxFixupUndoInfo::NONE,
)
});
let def = loc.def.ast_id().left().and_then(|id| { let def = loc.def.ast_id().left().and_then(|id| {
let def_tt = match id.to_node(db) { let def_tt = match id.to_node(db) {

View File

@ -542,7 +542,26 @@ fn quux(x: i32) {
m!(x$0 m!(x$0
} }
"#, "#,
expect![[r#""#]], expect![[r#"
fn quux() fn(i32)
lc x i32
lc y i32
ma m!() macro_rules! m
bt u32 u32
kw crate::
kw false
kw for
kw if
kw if let
kw loop
kw match
kw return
kw self::
kw true
kw unsafe
kw while
kw while let
"#]],
); );
} }

View File

@ -242,7 +242,7 @@ macro_rules! foo {
fn f() { fn f() {
foo!(); foo!();
//^^^ error: invalid macro definition: expected subtree //^^^ error: macro definition has parse errors
} }
"#, "#,