Rollup merge of #124135 - petrochenkov:deleglob, r=fmease
delegation: Implement glob delegation Support delegating to all trait methods in one go. Overriding globs with explicit definitions is also supported. The implementation is generally based on the design from https://github.com/rust-lang/rfcs/pull/3530#issuecomment-2020869823, but unlike with list delegation in https://github.com/rust-lang/rust/pull/123413 we cannot expand glob delegation eagerly. We have to enqueue it into the queue of unexpanded macros (most other macros are processed this way too), and then a glob delegation waits in that queue until its trait path is resolved, and enough code expands to generate the identifier list produced from the glob. Glob delegation is only allowed in impls, and can only point to traits. Supporting it in other places gives very little practical benefit, but significantly raises the implementation complexity. Part of https://github.com/rust-lang/rust/issues/118212.
This commit is contained in:
commit
f8ce1cfbf5
29 changed files with 878 additions and 123 deletions
|
@ -19,6 +19,7 @@ use rustc_ast::{self as ast, AssocItem, AssocItemKind, MetaItemKind, StmtKind};
|
|||
use rustc_ast::{Block, ForeignItem, ForeignItemKind, Impl, Item, ItemKind, NodeId};
|
||||
use rustc_attr as attr;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_expand::base::ResolverExpand;
|
||||
use rustc_expand::expand::AstFragment;
|
||||
use rustc_hir::def::{self, *};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
|
||||
|
@ -1358,6 +1359,14 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> {
|
|||
self.visit_invoc_in_module(item.id);
|
||||
}
|
||||
AssocCtxt::Impl => {
|
||||
let invoc_id = item.id.placeholder_to_expn_id();
|
||||
if !self.r.glob_delegation_invoc_ids.contains(&invoc_id) {
|
||||
self.r
|
||||
.impl_unexpanded_invocations
|
||||
.entry(self.r.invocation_parent(invoc_id))
|
||||
.or_default()
|
||||
.insert(invoc_id);
|
||||
}
|
||||
self.visit_invoc(item.id);
|
||||
}
|
||||
}
|
||||
|
@ -1379,18 +1388,21 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> {
|
|||
self.r.feed_visibility(feed, vis);
|
||||
}
|
||||
|
||||
let ns = match item.kind {
|
||||
AssocItemKind::Const(..) | AssocItemKind::Delegation(..) | AssocItemKind::Fn(..) => {
|
||||
ValueNS
|
||||
}
|
||||
AssocItemKind::Type(..) => TypeNS,
|
||||
AssocItemKind::MacCall(_) | AssocItemKind::DelegationMac(..) => bug!(), // handled above
|
||||
};
|
||||
if ctxt == AssocCtxt::Trait {
|
||||
let ns = match item.kind {
|
||||
AssocItemKind::Const(..)
|
||||
| AssocItemKind::Delegation(..)
|
||||
| AssocItemKind::Fn(..) => ValueNS,
|
||||
AssocItemKind::Type(..) => TypeNS,
|
||||
AssocItemKind::MacCall(_) | AssocItemKind::DelegationMac(..) => bug!(), // handled above
|
||||
};
|
||||
|
||||
let parent = self.parent_scope.module;
|
||||
let expansion = self.parent_scope.expansion;
|
||||
self.r.define(parent, item.ident, ns, (self.res(def_id), vis, item.span, expansion));
|
||||
} else if !matches!(&item.kind, AssocItemKind::Delegation(deleg) if deleg.from_glob) {
|
||||
let impl_def_id = self.r.tcx.local_parent(local_def_id);
|
||||
let key = BindingKey::new(item.ident.normalize_to_macros_2_0(), ns);
|
||||
self.r.impl_binding_keys.entry(impl_def_id).or_default().insert(key);
|
||||
}
|
||||
|
||||
visit::walk_assoc_item(self, item, ctxt);
|
||||
|
|
|
@ -144,8 +144,9 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> {
|
|||
}
|
||||
ItemKind::GlobalAsm(..) => DefKind::GlobalAsm,
|
||||
ItemKind::Use(..) => return visit::walk_item(self, i),
|
||||
ItemKind::MacCall(..) => return self.visit_macro_invoc(i.id),
|
||||
ItemKind::DelegationMac(..) => unreachable!(),
|
||||
ItemKind::MacCall(..) | ItemKind::DelegationMac(..) => {
|
||||
return self.visit_macro_invoc(i.id);
|
||||
}
|
||||
};
|
||||
let def_id = self.create_def(i.id, i.ident.name, def_kind, i.span);
|
||||
|
||||
|
@ -294,8 +295,9 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> {
|
|||
AssocItemKind::Fn(..) | AssocItemKind::Delegation(..) => DefKind::AssocFn,
|
||||
AssocItemKind::Const(..) => DefKind::AssocConst,
|
||||
AssocItemKind::Type(..) => DefKind::AssocTy,
|
||||
AssocItemKind::MacCall(..) => return self.visit_macro_invoc(i.id),
|
||||
AssocItemKind::DelegationMac(..) => unreachable!(),
|
||||
AssocItemKind::MacCall(..) | AssocItemKind::DelegationMac(..) => {
|
||||
return self.visit_macro_invoc(i.id);
|
||||
}
|
||||
};
|
||||
|
||||
let def = self.create_def(i.id, i.ident.name, def_kind, i.span);
|
||||
|
|
|
@ -1089,7 +1089,7 @@ pub struct Resolver<'a, 'tcx> {
|
|||
single_segment_macro_resolutions:
|
||||
Vec<(Ident, MacroKind, ParentScope<'a>, Option<NameBinding<'a>>)>,
|
||||
multi_segment_macro_resolutions:
|
||||
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>)>,
|
||||
Vec<(Vec<Segment>, Span, MacroKind, ParentScope<'a>, Option<Res>, Namespace)>,
|
||||
builtin_attrs: Vec<(Ident, ParentScope<'a>)>,
|
||||
/// `derive(Copy)` marks items they are applied to so they are treated specially later.
|
||||
/// Derive macros cannot modify the item themselves and have to store the markers in the global
|
||||
|
@ -1163,6 +1163,15 @@ pub struct Resolver<'a, 'tcx> {
|
|||
doc_link_resolutions: FxHashMap<LocalDefId, DocLinkResMap>,
|
||||
doc_link_traits_in_scope: FxHashMap<LocalDefId, Vec<DefId>>,
|
||||
all_macro_rules: FxHashMap<Symbol, Res>,
|
||||
|
||||
/// Invocation ids of all glob delegations.
|
||||
glob_delegation_invoc_ids: FxHashSet<LocalExpnId>,
|
||||
/// Analogue of module `unexpanded_invocations` but in trait impls, excluding glob delegations.
|
||||
/// Needed because glob delegations wait for all other neighboring macros to expand.
|
||||
impl_unexpanded_invocations: FxHashMap<LocalDefId, FxHashSet<LocalExpnId>>,
|
||||
/// Simplified analogue of module `resolutions` but in trait impls, excluding glob delegations.
|
||||
/// Needed because glob delegations exclude explicitly defined names.
|
||||
impl_binding_keys: FxHashMap<LocalDefId, FxHashSet<BindingKey>>,
|
||||
}
|
||||
|
||||
/// Nothing really interesting here; it just provides memory for the rest of the crate.
|
||||
|
@ -1504,6 +1513,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
doc_link_traits_in_scope: Default::default(),
|
||||
all_macro_rules: Default::default(),
|
||||
delegation_fn_sigs: Default::default(),
|
||||
glob_delegation_invoc_ids: Default::default(),
|
||||
impl_unexpanded_invocations: Default::default(),
|
||||
impl_binding_keys: Default::default(),
|
||||
};
|
||||
|
||||
let root_parent_scope = ParentScope::module(graph_root, &resolver);
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::errors::CannotDetermineMacroResolution;
|
|||
use crate::errors::{self, AddAsNonDerive, CannotFindIdentInThisScope};
|
||||
use crate::errors::{MacroExpectedFound, RemoveSurroundingDerive};
|
||||
use crate::Namespace::*;
|
||||
use crate::{BuiltinMacroState, Determinacy, MacroData, Used};
|
||||
use crate::{BindingKey, BuiltinMacroState, Determinacy, MacroData, Used};
|
||||
use crate::{DeriveData, Finalize, ParentScope, ResolutionError, Resolver, ScopeSet};
|
||||
use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment, ToNameBinding};
|
||||
use rustc_ast::expand::StrippedCfgItem;
|
||||
|
@ -198,6 +198,11 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
|
|||
self.output_macro_rules_scopes.insert(expansion, output_macro_rules_scope);
|
||||
|
||||
parent_scope.module.unexpanded_invocations.borrow_mut().remove(&expansion);
|
||||
if let Some(unexpanded_invocations) =
|
||||
self.impl_unexpanded_invocations.get_mut(&self.invocation_parent(expansion))
|
||||
{
|
||||
unexpanded_invocations.remove(&expansion);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_builtin_macro(&mut self, name: Symbol, ext: SyntaxExtensionKind) {
|
||||
|
@ -262,15 +267,21 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
|
|||
}
|
||||
};
|
||||
|
||||
let (path, kind, inner_attr, derives) = match invoc.kind {
|
||||
InvocationKind::Attr { ref attr, ref derives, .. } => (
|
||||
&attr.get_normal_item().path,
|
||||
MacroKind::Attr,
|
||||
attr.style == ast::AttrStyle::Inner,
|
||||
self.arenas.alloc_ast_paths(derives),
|
||||
),
|
||||
InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang, false, &[][..]),
|
||||
InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive, false, &[][..]),
|
||||
let (mut derives, mut inner_attr, mut deleg_impl) = (&[][..], false, None);
|
||||
let (path, kind) = match invoc.kind {
|
||||
InvocationKind::Attr { ref attr, derives: ref attr_derives, .. } => {
|
||||
derives = self.arenas.alloc_ast_paths(attr_derives);
|
||||
inner_attr = attr.style == ast::AttrStyle::Inner;
|
||||
(&attr.get_normal_item().path, MacroKind::Attr)
|
||||
}
|
||||
InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang),
|
||||
InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive),
|
||||
InvocationKind::GlobDelegation { ref item } => {
|
||||
let ast::AssocItemKind::DelegationMac(deleg) = &item.kind else { unreachable!() };
|
||||
deleg_impl = Some(self.invocation_parent(invoc_id));
|
||||
// It is sufficient to consider glob delegation a bang macro for now.
|
||||
(&deleg.prefix, MacroKind::Bang)
|
||||
}
|
||||
};
|
||||
|
||||
// Derives are not included when `invocations` are collected, so we have to add them here.
|
||||
|
@ -286,10 +297,11 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
|
|||
node_id,
|
||||
force,
|
||||
soft_custom_inner_attributes_gate(path, invoc),
|
||||
deleg_impl,
|
||||
)?;
|
||||
|
||||
let span = invoc.span();
|
||||
let def_id = res.opt_def_id();
|
||||
let def_id = if deleg_impl.is_some() { None } else { res.opt_def_id() };
|
||||
invoc_id.set_expn_data(
|
||||
ext.expn_data(
|
||||
parent_scope.expansion,
|
||||
|
@ -452,6 +464,45 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
|
|||
fn registered_tools(&self) -> &RegisteredTools {
|
||||
self.registered_tools
|
||||
}
|
||||
|
||||
fn register_glob_delegation(&mut self, invoc_id: LocalExpnId) {
|
||||
self.glob_delegation_invoc_ids.insert(invoc_id);
|
||||
}
|
||||
|
||||
fn glob_delegation_suffixes(
|
||||
&mut self,
|
||||
trait_def_id: DefId,
|
||||
impl_def_id: LocalDefId,
|
||||
) -> Result<Vec<(Ident, Option<Ident>)>, Indeterminate> {
|
||||
let target_trait = self.expect_module(trait_def_id);
|
||||
if !target_trait.unexpanded_invocations.borrow().is_empty() {
|
||||
return Err(Indeterminate);
|
||||
}
|
||||
// FIXME: Instead of waiting try generating all trait methods, and pruning
|
||||
// the shadowed ones a bit later, e.g. when all macro expansion completes.
|
||||
// Pros: expansion will be stuck less (but only in exotic cases), the implementation may be
|
||||
// less hacky.
|
||||
// Cons: More code is generated just to be deleted later, deleting already created `DefId`s
|
||||
// may be nontrivial.
|
||||
if let Some(unexpanded_invocations) = self.impl_unexpanded_invocations.get(&impl_def_id)
|
||||
&& !unexpanded_invocations.is_empty()
|
||||
{
|
||||
return Err(Indeterminate);
|
||||
}
|
||||
|
||||
let mut idents = Vec::new();
|
||||
target_trait.for_each_child(self, |this, ident, ns, _binding| {
|
||||
// FIXME: Adjust hygiene for idents from globs, like for glob imports.
|
||||
if let Some(overriding_keys) = this.impl_binding_keys.get(&impl_def_id)
|
||||
&& overriding_keys.contains(&BindingKey::new(ident.normalize_to_macros_2_0(), ns))
|
||||
{
|
||||
// The name is overridden, do not produce it from the glob delegation.
|
||||
} else {
|
||||
idents.push((ident, None));
|
||||
}
|
||||
});
|
||||
Ok(idents)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
||||
|
@ -468,15 +519,40 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
node_id: NodeId,
|
||||
force: bool,
|
||||
soft_custom_inner_attributes_gate: bool,
|
||||
deleg_impl: Option<LocalDefId>,
|
||||
) -> Result<(Lrc<SyntaxExtension>, Res), Indeterminate> {
|
||||
let (ext, res) = match self.resolve_macro_path(path, Some(kind), parent_scope, true, force)
|
||||
{
|
||||
let (ext, res) = match self.resolve_macro_or_delegation_path(
|
||||
path,
|
||||
Some(kind),
|
||||
parent_scope,
|
||||
true,
|
||||
force,
|
||||
deleg_impl,
|
||||
) {
|
||||
Ok((Some(ext), res)) => (ext, res),
|
||||
Ok((None, res)) => (self.dummy_ext(kind), res),
|
||||
Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err),
|
||||
Err(Determinacy::Undetermined) => return Err(Indeterminate),
|
||||
};
|
||||
|
||||
// Everything below is irrelevant to glob delegation, take a shortcut.
|
||||
if deleg_impl.is_some() {
|
||||
if !matches!(res, Res::Err | Res::Def(DefKind::Trait, _)) {
|
||||
self.dcx().emit_err(MacroExpectedFound {
|
||||
span: path.span,
|
||||
expected: "trait",
|
||||
article: "a",
|
||||
found: res.descr(),
|
||||
macro_path: &pprust::path_to_string(path),
|
||||
remove_surrounding_derive: None,
|
||||
add_as_non_derive: None,
|
||||
});
|
||||
return Ok((self.dummy_ext(kind), Res::Err));
|
||||
}
|
||||
|
||||
return Ok((ext, res));
|
||||
}
|
||||
|
||||
// Report errors for the resolved macro.
|
||||
for segment in &path.segments {
|
||||
if let Some(args) = &segment.args {
|
||||
|
@ -605,12 +681,25 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
parent_scope: &ParentScope<'a>,
|
||||
trace: bool,
|
||||
force: bool,
|
||||
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
|
||||
self.resolve_macro_or_delegation_path(path, kind, parent_scope, trace, force, None)
|
||||
}
|
||||
|
||||
fn resolve_macro_or_delegation_path(
|
||||
&mut self,
|
||||
path: &ast::Path,
|
||||
kind: Option<MacroKind>,
|
||||
parent_scope: &ParentScope<'a>,
|
||||
trace: bool,
|
||||
force: bool,
|
||||
deleg_impl: Option<LocalDefId>,
|
||||
) -> Result<(Option<Lrc<SyntaxExtension>>, Res), Determinacy> {
|
||||
let path_span = path.span;
|
||||
let mut path = Segment::from_path(path);
|
||||
|
||||
// Possibly apply the macro helper hack
|
||||
if kind == Some(MacroKind::Bang)
|
||||
if deleg_impl.is_none()
|
||||
&& kind == Some(MacroKind::Bang)
|
||||
&& path.len() == 1
|
||||
&& path[0].ident.span.ctxt().outer_expn_data().local_inner_macros
|
||||
{
|
||||
|
@ -618,13 +707,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
path.insert(0, Segment::from_ident(root));
|
||||
}
|
||||
|
||||
let res = if path.len() > 1 {
|
||||
let res = match self.maybe_resolve_path(&path, Some(MacroNS), parent_scope) {
|
||||
let res = if deleg_impl.is_some() || path.len() > 1 {
|
||||
let ns = if deleg_impl.is_some() { TypeNS } else { MacroNS };
|
||||
let res = match self.maybe_resolve_path(&path, Some(ns), parent_scope) {
|
||||
PathResult::NonModule(path_res) if let Some(res) = path_res.full_res() => Ok(res),
|
||||
PathResult::Indeterminate if !force => return Err(Determinacy::Undetermined),
|
||||
PathResult::NonModule(..)
|
||||
| PathResult::Indeterminate
|
||||
| PathResult::Failed { .. } => Err(Determinacy::Determined),
|
||||
PathResult::Module(ModuleOrUniformRoot::Module(module)) => {
|
||||
Ok(module.res().unwrap())
|
||||
}
|
||||
PathResult::Module(..) => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -636,6 +729,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
kind,
|
||||
*parent_scope,
|
||||
res.ok(),
|
||||
ns,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -670,7 +764,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
res
|
||||
};
|
||||
|
||||
res.map(|res| (self.get_macro(res).map(|macro_data| macro_data.ext.clone()), res))
|
||||
let res = res?;
|
||||
let ext = match deleg_impl {
|
||||
Some(impl_def_id) => match res {
|
||||
def::Res::Def(DefKind::Trait, def_id) => {
|
||||
let edition = self.tcx.sess.edition();
|
||||
Some(Lrc::new(SyntaxExtension::glob_delegation(def_id, impl_def_id, edition)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
None => self.get_macro(res).map(|macro_data| macro_data.ext.clone()),
|
||||
};
|
||||
Ok((ext, res))
|
||||
}
|
||||
|
||||
pub(crate) fn finalize_macro_resolutions(&mut self, krate: &Crate) {
|
||||
|
@ -706,14 +811,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
};
|
||||
|
||||
let macro_resolutions = mem::take(&mut self.multi_segment_macro_resolutions);
|
||||
for (mut path, path_span, kind, parent_scope, initial_res) in macro_resolutions {
|
||||
for (mut path, path_span, kind, parent_scope, initial_res, ns) in macro_resolutions {
|
||||
// FIXME: Path resolution will ICE if segment IDs present.
|
||||
for seg in &mut path {
|
||||
seg.id = None;
|
||||
}
|
||||
match self.resolve_path(
|
||||
&path,
|
||||
Some(MacroNS),
|
||||
Some(ns),
|
||||
&parent_scope,
|
||||
Some(Finalize::new(ast::CRATE_NODE_ID, path_span)),
|
||||
None,
|
||||
|
@ -721,6 +826,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
|||
PathResult::NonModule(path_res) if let Some(res) = path_res.full_res() => {
|
||||
check_consistency(self, &path, path_span, kind, initial_res, res)
|
||||
}
|
||||
// This may be a trait for glob delegation expansions.
|
||||
PathResult::Module(ModuleOrUniformRoot::Module(module)) => check_consistency(
|
||||
self,
|
||||
&path,
|
||||
path_span,
|
||||
kind,
|
||||
initial_res,
|
||||
module.res().unwrap(),
|
||||
),
|
||||
path_res @ (PathResult::NonModule(..) | PathResult::Failed { .. }) => {
|
||||
let mut suggestion = None;
|
||||
let (span, label, module) =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue