expand: Implement support for retrying macro expansions

This commit is contained in:
Vadim Petrochenkov 2020-03-09 20:50:12 +03:00
parent eafeb9a267
commit 552a8875bd
4 changed files with 124 additions and 54 deletions

View file

@ -2,7 +2,7 @@
use rustc_ast::ast::{self, ItemKind, MetaItem};
use rustc_ast::ptr::P;
use rustc_expand::base::{Annotatable, ExtCtxt, MultiItemModifier};
use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier};
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
@ -48,13 +48,13 @@ impl MultiItemModifier for BuiltinDerive {
span: Span,
meta_item: &MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
// FIXME: Built-in derives often forget to give spans contexts,
// so we are doing it here in a centralized way.
let span = ecx.with_def_site_ctxt(span);
let mut items = Vec::new();
(self.0)(ecx, span, meta_item, &item, &mut |a| items.push(a));
items
ExpandResult::Ready(items)
}
}

View file

@ -258,8 +258,17 @@ impl Annotatable {
}
}
// `meta_item` is the annotation, and `item` is the item being modified.
// FIXME Decorators should follow the same pattern too.
/// Result of an expansion that may need to be retried.
/// Consider using this for non-`MultiItemModifier` expanders as well.
pub enum ExpandResult<T, U> {
/// Expansion produced a result (possibly dummy).
Ready(T),
/// Expansion could not produce a result and needs to be retried.
/// The string is an explanation that will be printed if we are stuck in an infinite retry loop.
Retry(U, String),
}
// `meta_item` is the attribute, and `item` is the item being modified.
pub trait MultiItemModifier {
fn expand(
&self,
@ -267,7 +276,7 @@ pub trait MultiItemModifier {
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable>;
) -> ExpandResult<Vec<Annotatable>, Annotatable>;
}
impl<F> MultiItemModifier for F
@ -280,8 +289,8 @@ where
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
self(ecx, span, meta_item, item)
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
ExpandResult::Ready(self(ecx, span, meta_item, item))
}
}

View file

@ -408,7 +408,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let mut undetermined_invocations = Vec::new();
let (mut progress, mut force) = (false, !self.monotonic);
loop {
let invoc = if let Some(invoc) = invocations.pop() {
let (invoc, res) = if let Some(invoc) = invocations.pop() {
invoc
} else {
self.resolve_imports();
@ -420,30 +420,51 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
continue;
};
let eager_expansion_root =
if self.monotonic { invoc.expansion_data.id } else { orig_expansion_data.id };
let res = match self.cx.resolver.resolve_macro_invocation(
&invoc,
eager_expansion_root,
force,
) {
Ok(res) => res,
Err(Indeterminate) => {
undetermined_invocations.push(invoc);
continue;
let res = match res {
Some(res) => res,
None => {
let eager_expansion_root = if self.monotonic {
invoc.expansion_data.id
} else {
orig_expansion_data.id
};
match self.cx.resolver.resolve_macro_invocation(
&invoc,
eager_expansion_root,
force,
) {
Ok(res) => res,
Err(Indeterminate) => {
// Cannot resolve, will retry this invocation later.
undetermined_invocations.push((invoc, None));
continue;
}
}
}
};
progress = true;
let ExpansionData { depth, id: expn_id, .. } = invoc.expansion_data;
self.cx.current_expansion = invoc.expansion_data.clone();
// FIXME(jseyfried): Refactor out the following logic
let (expanded_fragment, new_invocations) = match res {
InvocationRes::Single(ext) => {
let fragment = self.expand_invoc(invoc, &ext.kind);
self.collect_invocations(fragment, &[])
}
InvocationRes::Single(ext) => match self.expand_invoc(invoc, &ext.kind) {
ExpandResult::Ready(fragment) => self.collect_invocations(fragment, &[]),
ExpandResult::Retry(invoc, explanation) => {
if force {
// We are stuck, stop retrying and produce a dummy fragment.
let span = invoc.span();
self.cx.span_err(span, &explanation);
let fragment = invoc.fragment_kind.dummy(span);
self.collect_invocations(fragment, &[])
} else {
// Cannot expand, will retry this invocation later.
undetermined_invocations
.push((invoc, Some(InvocationRes::Single(ext))));
continue;
}
}
},
InvocationRes::DeriveContainer(_exts) => {
// FIXME: Consider using the derive resolutions (`_exts`) immediately,
// instead of enqueuing the derives to be resolved again later.
@ -463,14 +484,17 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
for path in derives {
let expn_id = ExpnId::fresh(None);
derive_placeholders.push(NodeId::placeholder_from_expn_id(expn_id));
invocations.push(Invocation {
kind: InvocationKind::Derive { path, item: item.clone() },
fragment_kind: invoc.fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
..invoc.expansion_data.clone()
invocations.push((
Invocation {
kind: InvocationKind::Derive { path, item: item.clone() },
fragment_kind: invoc.fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
..invoc.expansion_data.clone()
},
},
});
None,
));
}
let fragment =
invoc.fragment_kind.expect_from_annotatables(::std::iter::once(item));
@ -478,6 +502,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
};
progress = true;
if expanded_fragments.len() < depth {
expanded_fragments.push(Vec::new());
}
@ -535,7 +560,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
&mut self,
mut fragment: AstFragment,
extra_placeholders: &[NodeId],
) -> (AstFragment, Vec<Invocation>) {
) -> (AstFragment, Vec<(Invocation, Option<InvocationRes>)>) {
// Resolve `$crate`s in the fragment for pretty-printing.
self.cx.resolver.resolve_dollar_crates();
@ -635,13 +660,17 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
self.cx.trace_macros_diag();
}
fn expand_invoc(&mut self, invoc: Invocation, ext: &SyntaxExtensionKind) -> AstFragment {
fn expand_invoc(
&mut self,
invoc: Invocation,
ext: &SyntaxExtensionKind,
) -> ExpandResult<AstFragment, Invocation> {
if self.cx.current_expansion.depth > self.cx.ecfg.recursion_limit {
self.error_recursion_limit_reached();
}
let (fragment_kind, span) = (invoc.fragment_kind, invoc.span());
match invoc.kind {
ExpandResult::Ready(match invoc.kind {
InvocationKind::Bang { mac, .. } => match ext {
SyntaxExtensionKind::Bang(expander) => {
self.gate_proc_macro_expansion_kind(span, fragment_kind);
@ -663,7 +692,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
_ => unreachable!(),
},
InvocationKind::Attr { attr, mut item, .. } => match ext {
InvocationKind::Attr { attr, mut item, derives, after_derive } => match ext {
SyntaxExtensionKind::Attr(expander) => {
self.gate_proc_macro_input(&item);
self.gate_proc_macro_attr_item(span, &item);
@ -679,8 +708,25 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
SyntaxExtensionKind::LegacyAttr(expander) => {
match validate_attr::parse_meta(self.cx.parse_sess, &attr) {
Ok(meta) => {
let item = expander.expand(self.cx, span, &meta, item);
fragment_kind.expect_from_annotatables(item)
let items = match expander.expand(self.cx, span, &meta, item) {
ExpandResult::Ready(items) => items,
ExpandResult::Retry(item, explanation) => {
// Reassemble the original invocation for retrying.
return ExpandResult::Retry(
Invocation {
kind: InvocationKind::Attr {
attr,
item,
derives,
after_derive,
},
..invoc
},
explanation,
);
}
};
fragment_kind.expect_from_annotatables(items)
}
Err(mut err) => {
err.emit();
@ -702,19 +748,31 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
SyntaxExtensionKind::Derive(expander)
| SyntaxExtensionKind::LegacyDerive(expander) => {
if !item.derive_allowed() {
return fragment_kind.dummy(span);
return ExpandResult::Ready(fragment_kind.dummy(span));
}
if let SyntaxExtensionKind::Derive(..) = ext {
self.gate_proc_macro_input(&item);
}
let meta = ast::MetaItem { kind: ast::MetaItemKind::Word, span, path };
let items = expander.expand(self.cx, span, &meta, item);
let items = match expander.expand(self.cx, span, &meta, item) {
ExpandResult::Ready(items) => items,
ExpandResult::Retry(item, explanation) => {
// Reassemble the original invocation for retrying.
return ExpandResult::Retry(
Invocation {
kind: InvocationKind::Derive { path: meta.path, item },
..invoc
},
explanation,
);
}
};
fragment_kind.expect_from_annotatables(items)
}
_ => unreachable!(),
},
InvocationKind::DeriveContainer { .. } => unreachable!(),
}
})
}
fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) {
@ -933,7 +991,7 @@ pub fn ensure_complete_parse<'a>(
struct InvocationCollector<'a, 'b> {
cx: &'a mut ExtCtxt<'b>,
cfg: StripUnconfigured<'a>,
invocations: Vec<Invocation>,
invocations: Vec<(Invocation, Option<InvocationRes>)>,
monotonic: bool,
}
@ -955,15 +1013,18 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
};
let expn_id = ExpnId::fresh(expn_data);
let vis = kind.placeholder_visibility();
self.invocations.push(Invocation {
kind,
fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
depth: self.cx.current_expansion.depth + 1,
..self.cx.current_expansion.clone()
self.invocations.push((
Invocation {
kind,
fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
depth: self.cx.current_expansion.depth + 1,
..self.cx.current_expansion.clone()
},
},
});
None,
));
placeholder(fragment_kind, NodeId::placeholder_from_expn_id(expn_id), vis)
}

View file

@ -79,7 +79,7 @@ impl MultiItemModifier for ProcMacroDerive {
span: Span,
_meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
let item = match item {
Annotatable::Arm(..)
| Annotatable::Field(..)
@ -99,7 +99,7 @@ impl MultiItemModifier for ProcMacroDerive {
"proc-macro derives may only be \
applied to a struct, enum, or union",
);
return Vec::new();
return ExpandResult::Ready(Vec::new());
}
};
match item.kind {
@ -110,7 +110,7 @@ impl MultiItemModifier for ProcMacroDerive {
"proc-macro derives may only be \
applied to a struct, enum, or union",
);
return Vec::new();
return ExpandResult::Ready(Vec::new());
}
}
@ -158,7 +158,7 @@ impl MultiItemModifier for ProcMacroDerive {
FatalError.raise();
}
items
ExpandResult::Ready(items)
}
}