expand: Implement support for retrying macro expansions
This commit is contained in:
parent
eafeb9a267
commit
552a8875bd
4 changed files with 124 additions and 54 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue