resolve/expand: Cache intermediate results of #[derive] expansion

This commit is contained in:
Vadim Petrochenkov 2021-03-08 15:05:03 +03:00
parent b1ea2618d3
commit fbf1bec482
5 changed files with 97 additions and 73 deletions

View File

@ -1,6 +1,6 @@
use crate::cfg_eval::cfg_eval;
use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
use rustc_ast::{self as ast, attr, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
use rustc_errors::{struct_span_err, Applicability};
use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
use rustc_feature::AttributeTemplate;
@ -26,32 +26,39 @@ fn expand(
return ExpandResult::Ready(vec![item]);
}
let template =
AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
let attr = ecx.attribute(meta_item.clone());
validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template);
let result =
ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
let template =
AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
let attr = attr::mk_attr_outer(meta_item.clone());
validate_attr::check_builtin_attribute(
&sess.parse_sess,
&attr,
sym::derive,
template,
);
let derives: Vec<_> = attr
.meta_item_list()
.unwrap_or_default()
.into_iter()
.filter_map(|nested_meta| match nested_meta {
NestedMetaItem::MetaItem(meta) => Some(meta),
NestedMetaItem::Literal(lit) => {
// Reject `#[derive("Debug")]`.
report_unexpected_literal(sess, &lit);
None
}
})
.map(|meta| {
// Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
report_path_args(sess, &meta);
meta.path
})
.collect();
attr.meta_item_list()
.unwrap_or_default()
.into_iter()
.filter_map(|nested_meta| match nested_meta {
NestedMetaItem::MetaItem(meta) => Some(meta),
NestedMetaItem::Literal(lit) => {
// Reject `#[derive("Debug")]`.
report_unexpected_literal(sess, &lit);
None
}
})
.map(|meta| {
// Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
report_path_args(sess, &meta);
meta.path
})
.map(|path| (path, None))
.collect()
});
// FIXME: Try to cache intermediate results to avoid collecting same paths multiple times.
match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) {
match result {
Ok(()) => ExpandResult::Ready(cfg_eval(ecx, item)),
Err(Indeterminate) => ExpandResult::Retry(item),
}

View File

@ -868,6 +868,8 @@ pub fn expn_data(
/// Error type that denotes indeterminacy.
pub struct Indeterminate;
pub type DeriveResolutions = Vec<(ast::Path, Option<Lrc<SyntaxExtension>>)>;
pub trait ResolverExpand {
fn next_node_id(&mut self) -> NodeId;
@ -904,15 +906,12 @@ fn resolve_macro_invocation(
fn resolve_derives(
&mut self,
expn_id: ExpnId,
derives: Vec<ast::Path>,
force: bool,
derive_paths: &dyn Fn() -> DeriveResolutions,
) -> Result<(), Indeterminate>;
/// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
/// back from resolver.
fn take_derive_resolutions(
&mut self,
expn_id: ExpnId,
) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>>;
fn take_derive_resolutions(&mut self, expn_id: ExpnId) -> Option<DeriveResolutions>;
/// Path resolution logic for `#[cfg_accessible(path)]`.
fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
}

View File

@ -515,7 +515,7 @@ enum AnnotatableRef<'a> {
invocations.reserve(derives.len());
derives
.into_iter()
.map(|(_exts, path)| {
.map(|(path, _exts)| {
// FIXME: Consider using the derive resolutions (`_exts`)
// instead of enqueuing the derives to be resolved again later.
let expn_id = ExpnId::fresh(None);

View File

@ -37,7 +37,7 @@
use rustc_data_structures::ptr_key::PtrKey;
use rustc_data_structures::sync::Lrc;
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
use rustc_hir::def::Namespace::*;
use rustc_hir::def::{self, CtorOf, DefKind, NonMacroAttrKind, PartialRes};
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX};
@ -851,6 +851,12 @@ enum BuiltinMacroState {
AlreadySeen(Span),
}
struct DeriveData {
resolutions: DeriveResolutions,
helper_attrs: Vec<Ident>,
has_derive_copy: bool,
}
/// The main resolver class.
///
/// This is the visitor that walks the whole crate.
@ -973,8 +979,9 @@ pub struct Resolver<'a> {
output_macro_rules_scopes: FxHashMap<ExpnId, MacroRulesScopeRef<'a>>,
/// Helper attributes that are in scope for the given expansion.
helper_attrs: FxHashMap<ExpnId, Vec<Ident>>,
/// Resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
derive_resolutions: FxHashMap<ExpnId, Vec<(Lrc<SyntaxExtension>, ast::Path)>>,
/// Ready or in-progress results of resolving paths inside the `#[derive(...)]` attribute
/// with the given `ExpnId`.
derive_data: FxHashMap<ExpnId, DeriveData>,
/// Avoid duplicated errors for "name already defined".
name_already_seen: FxHashMap<Symbol, Span>,
@ -1310,7 +1317,7 @@ pub fn new(
invocation_parent_scopes: Default::default(),
output_macro_rules_scopes: Default::default(),
helper_attrs: Default::default(),
derive_resolutions: Default::default(),
derive_data: Default::default(),
local_macro_def_scopes: FxHashMap::default(),
name_already_seen: FxHashMap::default(),
potentially_unused_imports: Vec::new(),

View File

@ -4,7 +4,7 @@
use crate::imports::ImportResolver;
use crate::Namespace::*;
use crate::{AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, BuiltinMacroState, Determinacy};
use crate::{CrateLint, ParentScope, ResolutionError, Resolver, Scope, ScopeSet, Weak};
use crate::{CrateLint, DeriveData, ParentScope, ResolutionError, Resolver, Scope, ScopeSet, Weak};
use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment, ToNameBinding};
use rustc_ast::{self as ast, Inline, ItemKind, ModKind, NodeId};
use rustc_ast_lowering::ResolverAstLowering;
@ -14,8 +14,8 @@
use rustc_data_structures::ptr_key::PtrKey;
use rustc_data_structures::sync::Lrc;
use rustc_errors::struct_span_err;
use rustc_expand::base::Annotatable;
use rustc_expand::base::{Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::base::{Annotatable, DeriveResolutions, Indeterminate, ResolverExpand};
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::compile_declarative_macro;
use rustc_expand::expand::{AstFragment, Invocation, InvocationKind, SupportsMacroExpansion};
use rustc_feature::is_builtin_attr_name;
@ -359,8 +359,8 @@ fn has_derive_copy(&self, expn_id: ExpnId) -> bool {
fn resolve_derives(
&mut self,
expn_id: ExpnId,
derives: Vec<ast::Path>,
force: bool,
derive_paths: &dyn Fn() -> DeriveResolutions,
) -> Result<(), Indeterminate> {
// Block expansion of the container until we resolve all derives in it.
// This is required for two reasons:
@ -368,49 +368,60 @@ fn resolve_derives(
// is applied, so they have to be produced by the container's expansion rather
// than by individual derives.
// - Derives in the container need to know whether one of them is a built-in `Copy`.
// FIXME: Try to cache intermediate results to avoid resolving same derives multiple times.
// Temporarily take the data to avoid borrow checker conflicts.
let mut derive_data = mem::take(&mut self.derive_data);
let entry = derive_data.entry(expn_id).or_insert_with(|| DeriveData {
resolutions: derive_paths(),
helper_attrs: Vec::new(),
has_derive_copy: false,
});
let parent_scope = self.invocation_parent_scopes[&expn_id];
let mut exts = Vec::new();
let mut helper_attrs = Vec::new();
let mut has_derive_copy = false;
for path in derives {
exts.push((
match self.resolve_macro_path(
&path,
Some(MacroKind::Derive),
&parent_scope,
true,
force,
) {
Ok((Some(ext), _)) => {
let span =
path.segments.last().unwrap().ident.span.normalize_to_macros_2_0();
helper_attrs
.extend(ext.helper_attrs.iter().map(|name| Ident::new(*name, span)));
has_derive_copy |= ext.builtin_name == Some(sym::Copy);
ext
}
Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
Err(Determinacy::Undetermined) => return Err(Indeterminate),
},
path,
))
for (path, opt_ext) in &mut entry.resolutions {
if opt_ext.is_none() {
*opt_ext = Some(
match self.resolve_macro_path(
&path,
Some(MacroKind::Derive),
&parent_scope,
true,
force,
) {
Ok((Some(ext), _)) => {
if !ext.helper_attrs.is_empty() {
let last_seg = path.segments.last().unwrap();
let span = last_seg.ident.span.normalize_to_macros_2_0();
entry.helper_attrs.extend(
ext.helper_attrs.iter().map(|name| Ident::new(*name, span)),
);
}
entry.has_derive_copy |= ext.builtin_name == Some(sym::Copy);
ext
}
Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
Err(Determinacy::Undetermined) => {
assert!(self.derive_data.is_empty());
self.derive_data = derive_data;
return Err(Indeterminate);
}
},
);
}
}
self.derive_resolutions.insert(expn_id, exts);
self.helper_attrs.insert(expn_id, helper_attrs);
// If we get to here, then `derive_data` for the given `expn_id` will only be accessed by
// `take_derive_resolutions` later, so we can steal `helper_attrs` instead of cloning them.
self.helper_attrs.insert(expn_id, mem::take(&mut entry.helper_attrs));
// Mark this derive as having `Copy` either if it has `Copy` itself or if its parent derive
// has `Copy`, to support cases like `#[derive(Clone, Copy)] #[derive(Debug)]`.
if has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
if entry.has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
self.containers_deriving_copy.insert(expn_id);
}
assert!(self.derive_data.is_empty());
self.derive_data = derive_data;
Ok(())
}
fn take_derive_resolutions(
&mut self,
expn_id: ExpnId,
) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>> {
self.derive_resolutions.remove(&expn_id)
fn take_derive_resolutions(&mut self, expn_id: ExpnId) -> Option<DeriveResolutions> {
self.derive_data.remove(&expn_id).map(|data| data.resolutions)
}
// The function that implements the resolution logic of `#[cfg_accessible(path)]`.