1
Fork 0

delegation: Implement glob delegation

This commit is contained in:
Vadim Petrochenkov 2024-03-15 14:21:03 +03:00
parent 7ac6c2fc68
commit 22d0b1ee18
31 changed files with 892 additions and 135 deletions

View file

@ -35,8 +35,8 @@ expand_duplicate_matcher_binding = duplicate matcher binding
.label = duplicate binding
.label2 = previous binding
expand_empty_delegation_list =
empty list delegation is not supported
expand_empty_delegation_mac =
empty {$kind} delegation is not supported
expand_expected_paren_or_brace =
expected `(` or `{"{"}`, found `{$token}`
@ -58,6 +58,9 @@ expand_feature_removed =
.label = feature has been removed
.reason = {$reason}
expand_glob_delegation_outside_impls =
glob delegation is only supported in impls
expand_helper_attribute_name_invalid =
`{$name}` cannot be a name of derive helper attribute

View file

@ -357,6 +357,10 @@ where
}
}
pub trait GlobDelegationExpander {
fn expand(&self, ecx: &mut ExtCtxt<'_>) -> ExpandResult<Vec<(Ident, Option<Ident>)>, ()>;
}
// Use a macro because forwarding to a simple function has type system issues
macro_rules! make_stmts_default {
($me:expr) => {
@ -714,6 +718,9 @@ pub enum SyntaxExtensionKind {
/// The produced AST fragment is appended to the input AST fragment.
Box<dyn MultiItemModifier + sync::DynSync + sync::DynSend>,
),
/// A glob delegation.
GlobDelegation(Box<dyn GlobDelegationExpander + sync::DynSync + sync::DynSend>),
}
/// A struct representing a macro definition in "lowered" form ready for expansion.
@ -748,7 +755,9 @@ impl SyntaxExtension {
/// Returns which kind of macro calls this syntax extension.
pub fn macro_kind(&self) -> MacroKind {
match self.kind {
SyntaxExtensionKind::Bang(..) | SyntaxExtensionKind::LegacyBang(..) => MacroKind::Bang,
SyntaxExtensionKind::Bang(..)
| SyntaxExtensionKind::LegacyBang(..)
| SyntaxExtensionKind::GlobDelegation(..) => MacroKind::Bang,
SyntaxExtensionKind::Attr(..)
| SyntaxExtensionKind::LegacyAttr(..)
| SyntaxExtensionKind::NonMacroAttr => MacroKind::Attr,
@ -922,6 +931,32 @@ impl SyntaxExtension {
SyntaxExtension::default(SyntaxExtensionKind::NonMacroAttr, edition)
}
pub fn glob_delegation(
trait_def_id: DefId,
impl_def_id: LocalDefId,
edition: Edition,
) -> SyntaxExtension {
struct GlobDelegationExpanderImpl {
trait_def_id: DefId,
impl_def_id: LocalDefId,
}
impl GlobDelegationExpander for GlobDelegationExpanderImpl {
fn expand(
&self,
ecx: &mut ExtCtxt<'_>,
) -> ExpandResult<Vec<(Ident, Option<Ident>)>, ()> {
match ecx.resolver.glob_delegation_suffixes(self.trait_def_id, self.impl_def_id) {
Ok(suffixes) => ExpandResult::Ready(suffixes),
Err(Indeterminate) if ecx.force_mode => ExpandResult::Ready(Vec::new()),
Err(Indeterminate) => ExpandResult::Retry(()),
}
}
}
let expander = GlobDelegationExpanderImpl { trait_def_id, impl_def_id };
SyntaxExtension::default(SyntaxExtensionKind::GlobDelegation(Box::new(expander)), edition)
}
pub fn expn_data(
&self,
parent: LocalExpnId,
@ -1030,6 +1065,16 @@ pub trait ResolverExpand {
/// Tools registered with `#![register_tool]` and used by tool attributes and lints.
fn registered_tools(&self) -> &RegisteredTools;
/// Mark this invocation id as a glob delegation.
fn register_glob_delegation(&mut self, invoc_id: LocalExpnId);
/// Names of specific methods to which glob delegation expands.
fn glob_delegation_suffixes(
&mut self,
trait_def_id: DefId,
impl_def_id: LocalDefId,
) -> Result<Vec<(Ident, Option<Ident>)>, Indeterminate>;
}
pub trait LintStoreExpand {

View file

@ -435,8 +435,16 @@ pub struct ExpectedParenOrBrace<'a> {
}
#[derive(Diagnostic)]
#[diag(expand_empty_delegation_list)]
pub(crate) struct EmptyDelegationList {
#[diag(expand_empty_delegation_mac)]
pub(crate) struct EmptyDelegationMac {
#[primary_span]
pub span: Span,
pub kind: String,
}
#[derive(Diagnostic)]
#[diag(expand_glob_delegation_outside_impls)]
pub(crate) struct GlobDelegationOutsideImpls {
#[primary_span]
pub span: Span,
}

View file

@ -1,8 +1,8 @@
use crate::base::*;
use crate::config::StripUnconfigured;
use crate::errors::{
EmptyDelegationList, IncompleteParse, RecursionLimitReached, RemoveExprNotSupported,
RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind,
EmptyDelegationMac, GlobDelegationOutsideImpls, IncompleteParse, RecursionLimitReached,
RemoveExprNotSupported, RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind,
};
use crate::mbe::diagnostics::annotate_err_with_kind;
use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
@ -343,6 +343,9 @@ pub enum InvocationKind {
is_const: bool,
item: Annotatable,
},
GlobDelegation {
item: P<ast::AssocItem>,
},
}
impl InvocationKind {
@ -370,6 +373,7 @@ impl Invocation {
InvocationKind::Bang { span, .. } => *span,
InvocationKind::Attr { attr, .. } => attr.span,
InvocationKind::Derive { path, .. } => path.span,
InvocationKind::GlobDelegation { item } => item.span,
}
}
@ -378,6 +382,7 @@ impl Invocation {
InvocationKind::Bang { span, .. } => span,
InvocationKind::Attr { attr, .. } => &mut attr.span,
InvocationKind::Derive { path, .. } => &mut path.span,
InvocationKind::GlobDelegation { item } => &mut item.span,
}
}
}
@ -800,6 +805,36 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
_ => unreachable!(),
},
InvocationKind::GlobDelegation { item } => {
let AssocItemKind::DelegationMac(deleg) = &item.kind else { unreachable!() };
let suffixes = match ext {
SyntaxExtensionKind::GlobDelegation(expander) => match expander.expand(self.cx)
{
ExpandResult::Ready(suffixes) => suffixes,
ExpandResult::Retry(()) => {
// Reassemble the original invocation for retrying.
return ExpandResult::Retry(Invocation {
kind: InvocationKind::GlobDelegation { item },
..invoc
});
}
},
SyntaxExtensionKind::LegacyBang(..) => {
let msg = "expanded a dummy glob delegation";
let guar = self.cx.dcx().span_delayed_bug(span, msg);
return ExpandResult::Ready(fragment_kind.dummy(span, guar));
}
_ => unreachable!(),
};
type Node = AstNodeWrapper<P<ast::AssocItem>, ImplItemTag>;
let single_delegations = build_single_delegations::<Node>(
self.cx, deleg, &item, &suffixes, item.span, true,
);
fragment_kind.expect_from_annotatables(
single_delegations.map(|item| Annotatable::ImplItem(P(item))),
)
}
})
}
@ -1067,7 +1102,7 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
fn take_mac_call(self) -> (P<ast::MacCall>, Self::AttrsTy, AddSemicolon) {
unreachable!()
}
fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
None
}
fn delegation_item_kind(_deleg: Box<ast::Delegation>) -> Self::ItemKind {
@ -1126,7 +1161,7 @@ impl InvocationCollectorNode for P<ast::Item> {
_ => unreachable!(),
}
}
fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
match &self.kind {
ItemKind::DelegationMac(deleg) => Some((deleg, self)),
_ => None,
@ -1270,7 +1305,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag>
_ => unreachable!(),
}
}
fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
match &self.wrapped.kind {
AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)),
_ => None,
@ -1311,7 +1346,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag>
_ => unreachable!(),
}
}
fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
match &self.wrapped.kind {
AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)),
_ => None,
@ -1487,7 +1522,7 @@ impl InvocationCollectorNode for ast::Stmt {
};
(mac, attrs, if add_semicolon { AddSemicolon::Yes } else { AddSemicolon::No })
}
fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
match &self.kind {
StmtKind::Item(item) => match &item.kind {
ItemKind::DelegationMac(deleg) => Some((deleg, item)),
@ -1684,6 +1719,44 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::Expr>, MethodReceiverTag>
}
}
fn build_single_delegations<'a, Node: InvocationCollectorNode>(
ecx: &ExtCtxt<'_>,
deleg: &'a ast::DelegationMac,
item: &'a ast::Item<Node::ItemKind>,
suffixes: &'a [(Ident, Option<Ident>)],
item_span: Span,
from_glob: bool,
) -> impl Iterator<Item = ast::Item<Node::ItemKind>> + 'a {
if suffixes.is_empty() {
// Report an error for now, to avoid keeping stem for resolution and
// stability checks.
let kind = String::from(if from_glob { "glob" } else { "list" });
ecx.dcx().emit_err(EmptyDelegationMac { span: item.span, kind });
}
suffixes.iter().map(move |&(ident, rename)| {
let mut path = deleg.prefix.clone();
path.segments.push(ast::PathSegment { ident, id: ast::DUMMY_NODE_ID, args: None });
ast::Item {
attrs: item.attrs.clone(),
id: ast::DUMMY_NODE_ID,
span: if from_glob { item_span } else { ident.span },
vis: item.vis.clone(),
ident: rename.unwrap_or(ident),
kind: Node::delegation_item_kind(Box::new(ast::Delegation {
id: ast::DUMMY_NODE_ID,
qself: deleg.qself.clone(),
path,
rename,
body: deleg.body.clone(),
from_glob,
})),
tokens: None,
}
})
}
struct InvocationCollector<'a, 'b> {
cx: &'a mut ExtCtxt<'b>,
invocations: Vec<(Invocation, Option<Lrc<SyntaxExtension>>)>,
@ -1702,6 +1775,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
fn collect(&mut self, fragment_kind: AstFragmentKind, kind: InvocationKind) -> AstFragment {
let expn_id = LocalExpnId::fresh_empty();
if matches!(kind, InvocationKind::GlobDelegation { .. }) {
// In resolver we need to know which invocation ids are delegations early,
// before their `ExpnData` is filled.
self.cx.resolver.register_glob_delegation(expn_id);
}
let vis = kind.placeholder_visibility();
self.invocations.push((
Invocation {
@ -1734,6 +1812,14 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
self.collect(kind, InvocationKind::Attr { attr, pos, item, derives })
}
fn collect_glob_delegation(
&mut self,
item: P<ast::AssocItem>,
kind: AstFragmentKind,
) -> AstFragment {
self.collect(kind, InvocationKind::GlobDelegation { item })
}
/// If `item` is an attribute invocation, remove the attribute and return it together with
/// its position and derives following it. We have to collect the derives in order to resolve
/// legacy derive helpers (helpers written before derives that introduce them).
@ -1901,37 +1987,27 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
Node::post_flat_map_node_collect_bang(&mut res, add_semicolon);
res
}
None if let Some((deleg, item)) = node.delegation_list() => {
if deleg.suffixes.is_empty() {
// Report an error for now, to avoid keeping stem for resolution and
// stability checks.
self.cx.dcx().emit_err(EmptyDelegationList { span: item.span });
}
Node::flatten_outputs(deleg.suffixes.iter().map(|&(ident, rename)| {
let mut path = deleg.prefix.clone();
path.segments.push(ast::PathSegment {
ident,
id: ast::DUMMY_NODE_ID,
args: None,
});
let mut item = Node::from_item(ast::Item {
attrs: item.attrs.clone(),
id: ast::DUMMY_NODE_ID,
span: ident.span,
vis: item.vis.clone(),
ident: rename.unwrap_or(ident),
kind: Node::delegation_item_kind(Box::new(ast::Delegation {
id: ast::DUMMY_NODE_ID,
qself: deleg.qself.clone(),
path,
rename,
body: deleg.body.clone(),
})),
tokens: None,
});
None if let Some((deleg, item)) = node.delegation() => {
let Some(suffixes) = &deleg.suffixes else {
let item = match node.to_annotatable() {
Annotatable::ImplItem(item) => item,
ann @ (Annotatable::Item(_)
| Annotatable::TraitItem(_)
| Annotatable::Stmt(_)) => {
let span = ann.span();
self.cx.dcx().emit_err(GlobDelegationOutsideImpls { span });
return Default::default();
}
_ => unreachable!(),
};
return self.collect_glob_delegation(item, Node::KIND).make_ast::<Node>();
};
let single_delegations = build_single_delegations::<Node>(
self.cx, deleg, item, suffixes, item.span, false,
);
Node::flatten_outputs(single_delegations.map(|item| {
let mut item = Node::from_item(item);
assign_id!(self, item.node_id_mut(), || item.noop_flat_map(self))
}))
}
@ -1983,7 +2059,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
self.collect_bang(mac, Node::KIND).make_ast::<Node>()
})
}
None if node.delegation_list().is_some() => unreachable!(),
None if node.delegation().is_some() => unreachable!(),
None => {
assign_id!(self, node.node_id_mut(), || node.noop_visit(self))
}