8898: internal: resolve derive helpers r=jonas-schievink a=jonas-schievink

bors r+

Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
bors[bot] 2021-05-20 17:57:06 +00:00 committed by GitHub
commit 79f50bd583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 141 additions and 75 deletions

View File

@ -20,7 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use syntax::ast;
use crate::{
attr::{AttrId, Attrs},
attr::{Attr, AttrId, Attrs},
builtin_attr,
db::DefDatabase,
derive_macro_as_call_id,
@ -94,14 +94,14 @@ pub(super) fn collect_defs(
unresolved_imports: Vec::new(),
resolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
unresolved_macros: Vec::new(),
mod_dirs: FxHashMap::default(),
cfg_options,
proc_macros,
exports_proc_macros: false,
from_glob_import: Default::default(),
ignore_attrs_on: FxHashSet::default(),
derive_helpers_in_scope: FxHashMap::default(),
ignore_attrs_on: Default::default(),
derive_helpers_in_scope: Default::default(),
};
match block {
Some(block) => {
@ -237,7 +237,7 @@ struct DefCollector<'a> {
glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>,
unresolved_imports: Vec<ImportDirective>,
resolved_imports: Vec<ImportDirective>,
unexpanded_macros: Vec<MacroDirective>,
unresolved_macros: Vec<MacroDirective>,
mod_dirs: FxHashMap<LocalModuleId, ModDir>,
cfg_options: &'a CfgOptions,
/// List of procedural macros defined by this crate. This is read from the dynamic library
@ -247,7 +247,13 @@ struct DefCollector<'a> {
proc_macros: Vec<(Name, ProcMacroExpander)>,
exports_proc_macros: bool,
from_glob_import: PerNsGlobImports,
ignore_attrs_on: FxHashSet<InFile<ModItem>>,
/// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute.
/// This map is used to skip all attributes up to and including the one that failed to resolve,
/// in order to not expand them twice.
///
/// This also stores the attributes to skip when we resolve derive helpers and non-macro
/// non-builtin attributes in general.
ignore_attrs_on: FxHashMap<InFile<ModItem>, AttrId>,
/// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
/// attributes.
derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<Name>>,
@ -319,7 +325,7 @@ impl DefCollector<'_> {
}
}
if self.reseed_with_unresolved_attributes() == ReachedFixedPoint::Yes {
if self.reseed_with_unresolved_attribute() == ReachedFixedPoint::Yes {
break;
}
}
@ -362,25 +368,21 @@ impl DefCollector<'_> {
}
/// When the fixed-point loop reaches a stable state, we might still have some unresolved
/// attributes (or unexpanded attribute proc macros) left over. This takes them, and feeds the
/// item they're applied to back into name resolution.
/// attributes (or unexpanded attribute proc macros) left over. This takes one of them, and
/// feeds the item it's applied to back into name resolution.
///
/// This effectively ignores the fact that the macro is there and just treats the items as
/// normal code.
///
/// This improves UX when proc macros are turned off or don't work, and replicates the behavior
/// before we supported proc. attribute macros.
fn reseed_with_unresolved_attributes(&mut self) -> ReachedFixedPoint {
fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint {
cov_mark::hit!(unresolved_attribute_fallback);
let mut added_items = false;
let unexpanded_macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new());
for directive in &unexpanded_macros {
if let MacroDirectiveKind::Attr { ast_id, mod_item, .. } = &directive.kind {
// Make sure to only add such items once.
if !self.ignore_attrs_on.insert(ast_id.ast_id.with_value(*mod_item)) {
continue;
}
let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
let pos = unresolved_macros.iter().position(|directive| {
if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
self.ignore_attrs_on.insert(ast_id.ast_id.with_value(*mod_item), *attr);
let file_id = self.def_map[directive.module_id].definition_source(self.db).file_id;
let item_tree = self.db.file_item_tree(file_id);
@ -394,14 +396,20 @@ impl DefCollector<'_> {
mod_dir,
}
.collect(&[*mod_item]);
added_items = true;
true
} else {
false
}
});
if let Some(pos) = pos {
unresolved_macros.remove(pos);
}
// The collection above might add new unresolved macros (eg. derives), so merge the lists.
self.unexpanded_macros.extend(unexpanded_macros);
self.unresolved_macros.extend(unresolved_macros);
if added_items {
if pos.is_some() {
// Continue name resolution with the new data.
ReachedFixedPoint::No
} else {
@ -873,7 +881,7 @@ impl DefCollector<'_> {
}
fn resolve_macros(&mut self) -> ReachedFixedPoint {
let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new());
let mut macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
let mut resolved = Vec::new();
let mut res = ReachedFixedPoint::Yes;
macros.retain(|directive| {
@ -922,14 +930,45 @@ impl DefCollector<'_> {
Err(UnresolvedMacro { .. }) => (),
}
}
MacroDirectiveKind::Attr { .. } => {
// not yet :)
MacroDirectiveKind::Attr { ast_id, mod_item, attr } => {
if let Some(ident) = ast_id.path.as_ident() {
if let Some(helpers) = self.derive_helpers_in_scope.get(&ast_id.ast_id) {
if helpers.contains(ident) {
cov_mark::hit!(resolved_derive_helper);
// Resolved to derive helper. Collect the item's attributes again,
// starting after the derive helper.
let file_id = self.def_map[directive.module_id]
.definition_source(self.db)
.file_id;
let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.ignore_attrs_on.insert(InFile::new(file_id, *mod_item), *attr);
ModCollector {
def_collector: &mut *self,
macro_depth: directive.depth,
module_id: directive.module_id,
file_id,
item_tree: &item_tree,
mod_dir,
}
.collect(&[*mod_item]);
// Remove the original directive since we resolved it.
return false;
}
}
}
// Not resolved to a derive helper, so try to resolve as a macro.
// FIXME: not yet :)
}
}
true
});
self.unexpanded_macros = macros;
// Attribute resolution can add unresolved macro invocations, so concatenate the lists.
self.unresolved_macros.extend(macros);
for (module_id, macro_call_id, depth) in resolved {
self.collect_macro_expansion(module_id, macro_call_id, depth);
@ -1000,7 +1039,7 @@ impl DefCollector<'_> {
fn finish(mut self) -> DefMap {
// Emit diagnostics for all remaining unexpanded macros.
for directive in &self.unexpanded_macros {
for directive in &self.unresolved_macros {
match &directive.kind {
MacroDirectiveKind::FnLike { ast_id, fragment } => match macro_call_as_call_id(
ast_id,
@ -1102,7 +1141,7 @@ impl ModCollector<'_, '_> {
// Prelude module is always considered to be `#[macro_use]`.
if let Some(prelude_module) = self.def_collector.def_map.prelude {
if prelude_module.krate != self.def_collector.def_map.krate {
if prelude_module.krate != krate {
cov_mark::hit!(prelude_is_macro_use);
self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate);
}
@ -1203,11 +1242,6 @@ impl ModCollector<'_, '_> {
ModItem::Struct(id) => {
let it = &self.item_tree[id];
// FIXME: check attrs to see if this is an attribute macro invocation;
// in which case we don't add the invocation, just a single attribute
// macro invocation
self.collect_derives(&attrs, it.ast_id.upcast());
def = Some(DefData {
id: StructLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
.intern(self.def_collector.db)
@ -1220,11 +1254,6 @@ impl ModCollector<'_, '_> {
ModItem::Union(id) => {
let it = &self.item_tree[id];
// FIXME: check attrs to see if this is an attribute macro invocation;
// in which case we don't add the invocation, just a single attribute
// macro invocation
self.collect_derives(&attrs, it.ast_id.upcast());
def = Some(DefData {
id: UnionLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
.intern(self.def_collector.db)
@ -1237,11 +1266,6 @@ impl ModCollector<'_, '_> {
ModItem::Enum(id) => {
let it = &self.item_tree[id];
// FIXME: check attrs to see if this is an attribute macro invocation;
// in which case we don't add the invocation, just a single attribute
// macro invocation
self.collect_derives(&attrs, it.ast_id.upcast());
def = Some(DefData {
id: EnumLoc { container: module, id: ItemTreeId::new(self.file_id, id) }
.intern(self.def_collector.db)
@ -1453,6 +1477,9 @@ impl ModCollector<'_, '_> {
///
/// Returns `Err` when some attributes could not be resolved to builtins and have been
/// registered as unresolved.
///
/// If `ignore_up_to` is `Some`, attributes precending and including that attribute will be
/// assumed to be resolved already.
fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> {
fn is_builtin_attr(path: &ModPath) -> bool {
if path.kind == PathKind::Plain {
@ -1478,51 +1505,68 @@ impl ModCollector<'_, '_> {
false
}
// We failed to resolve an attribute on this item earlier, and are falling back to treating
// the item as-is.
if self.def_collector.ignore_attrs_on.contains(&InFile::new(self.file_id, mod_item)) {
return Ok(());
}
match attrs.iter().find(|attr| !is_builtin_attr(&attr.path)) {
Some(non_builtin_attr) => {
log::debug!("non-builtin attribute {}", non_builtin_attr.path);
let mut ignore_up_to =
self.def_collector.ignore_attrs_on.get(&InFile::new(self.file_id, mod_item)).copied();
for attr in attrs.iter().skip_while(|attr| match ignore_up_to {
Some(id) if attr.id == id => {
ignore_up_to = None;
true
}
Some(_) => true,
None => false,
}) {
if attr.path.as_ident() == Some(&hir_expand::name![derive]) {
self.collect_derive(attr, mod_item);
} else if is_builtin_attr(&attr.path) {
continue;
} else {
log::debug!("non-builtin attribute {}", attr.path);
let ast_id = AstIdWithPath::new(
self.file_id,
mod_item.ast_id(self.item_tree),
non_builtin_attr.path.as_ref().clone(),
attr.path.as_ref().clone(),
);
self.def_collector.unexpanded_macros.push(MacroDirective {
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Attr { ast_id, attr: non_builtin_attr.id, mod_item },
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item },
});
Err(())
return Err(());
}
None => Ok(()),
}
Ok(())
}
fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) {
for derive in attrs.by_key("derive").attrs() {
match derive.parse_derive() {
Some(derive_macros) => {
for path in derive_macros {
let ast_id = AstIdWithPath::new(self.file_id, ast_id, path);
self.def_collector.unexpanded_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Derive { ast_id, derive_attr: derive.id },
});
}
}
None => {
// FIXME: diagnose
log::debug!("malformed derive: {:?}", derive);
fn collect_derive(&mut self, attr: &Attr, mod_item: ModItem) {
let ast_id: FileAstId<ast::Item> = match mod_item {
ModItem::Struct(it) => self.item_tree[it].ast_id.upcast(),
ModItem::Union(it) => self.item_tree[it].ast_id.upcast(),
ModItem::Enum(it) => self.item_tree[it].ast_id.upcast(),
_ => {
// Cannot use derive on this item.
// FIXME: diagnose
return;
}
};
match attr.parse_derive() {
Some(derive_macros) => {
for path in derive_macros {
let ast_id = AstIdWithPath::new(self.file_id, ast_id, path);
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Derive { ast_id, derive_attr: attr.id },
});
}
}
None => {
// FIXME: diagnose
log::debug!("malformed derive: {:?}", attr);
}
}
}
@ -1686,7 +1730,7 @@ impl ModCollector<'_, '_> {
ast_id.path.kind = PathKind::Super(0);
}
self.def_collector.unexpanded_macros.push(MacroDirective {
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::FnLike { ast_id, fragment: mac.fragment },
@ -1731,13 +1775,13 @@ mod tests {
glob_imports: FxHashMap::default(),
unresolved_imports: Vec::new(),
resolved_imports: Vec::new(),
unexpanded_macros: Vec::new(),
unresolved_macros: Vec::new(),
mod_dirs: FxHashMap::default(),
cfg_options: &CfgOptions::default(),
proc_macros: Default::default(),
exports_proc_macros: false,
from_glob_import: Default::default(),
ignore_attrs_on: FxHashSet::default(),
ignore_attrs_on: Default::default(),
derive_helpers_in_scope: FxHashMap::default(),
};
collector.seed_with_top_level();

View File

@ -735,6 +735,28 @@ fn unresolved_attributes_fall_back_track_per_file_moditems() {
);
}
#[test]
fn resolves_derive_helper() {
cov_mark::check!(resolved_derive_helper);
check(
r#"
//- /main.rs crate:main deps:proc
#[derive(proc::Derive)]
#[helper]
#[unresolved]
struct S;
//- /proc.rs crate:proc
#[proc_macro_derive(Derive, attributes(helper))]
fn derive() {}
"#,
expect![[r#"
crate
S: t v
"#]],
)
}
#[test]
fn macro_expansion_overflow() {
cov_mark::check!(macro_expansion_overflow);