Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/nameres/collector.rs')
-rw-r--r--crates/hir-def/src/nameres/collector.rs229
1 files changed, 133 insertions, 96 deletions
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index a030ed1e0d..9aa7febdfd 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -3,14 +3,14 @@
//! `DefCollector::collect` contains the fixed-point iteration loop which
//! resolves imports and expands macros.
-use std::{cmp::Ordering, iter, mem, ops::Not};
+use std::{cmp::Ordering, iter, mem};
use base_db::{BuiltDependency, Crate, CrateOrigin, LangCrateOrigin};
use cfg::{CfgAtom, CfgExpr, CfgOptions};
use either::Either;
use hir_expand::{
- EditionedFileId, ErasedAstId, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind,
- MacroDefId, MacroDefKind,
+ AttrMacroAttrIds, EditionedFileId, ErasedAstId, ExpandTo, HirFileId, InFile, MacroCallId,
+ MacroCallKind, MacroDefId, MacroDefKind,
attrs::{Attr, AttrId},
builtin::{find_builtin_attr, find_builtin_derive, find_builtin_macro},
mod_path::{ModPath, PathKind},
@@ -18,9 +18,10 @@ use hir_expand::{
proc_macro::CustomProcMacroExpander,
};
use intern::{Interned, sym};
-use itertools::{Itertools, izip};
+use itertools::izip;
use la_arena::Idx;
use rustc_hash::{FxHashMap, FxHashSet};
+use smallvec::SmallVec;
use span::{Edition, FileAstId, SyntaxContext};
use syntax::ast;
use triomphe::Arc;
@@ -32,12 +33,11 @@ use crate::{
MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ModuleDefId, ModuleId, ProcMacroId,
ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseId,
UseLoc,
- attr::Attrs,
db::DefDatabase,
item_scope::{GlobId, ImportId, ImportOrExternCrate, PerNsGlobImports},
item_tree::{
- self, FieldsShape, ImportAlias, ImportKind, ItemTree, ItemTreeAstId, Macro2, MacroCall,
- MacroRules, Mod, ModItemId, ModKind, TreeId,
+ self, Attrs, AttrsOrCfg, FieldsShape, ImportAlias, ImportKind, ItemTree, ItemTreeAstId,
+ Macro2, MacroCall, MacroRules, Mod, ModItemId, ModKind, TreeId,
},
macro_call_as_call_id,
nameres::{
@@ -102,6 +102,7 @@ pub(super) fn collect_defs(
proc_macros,
from_glob_import: Default::default(),
skip_attrs: Default::default(),
+ prev_active_attrs: Default::default(),
unresolved_extern_crates: Default::default(),
is_proc_macro: krate.is_proc_macro,
};
@@ -206,6 +207,7 @@ enum MacroDirectiveKind<'db> {
},
Attr {
ast_id: AstIdWithPath<ast::Item>,
+ attr_id: AttrId,
attr: Attr,
mod_item: ModItemId,
/* is this needed? */ tree: TreeId,
@@ -246,28 +248,27 @@ struct DefCollector<'db> {
/// This also stores the attributes to skip when we resolve derive helpers and non-macro
/// non-builtin attributes in general.
// FIXME: There has to be a better way to do this
- skip_attrs: FxHashMap<InFile<FileAstId<ast::Item>>, AttrId>,
+ skip_attrs: FxHashMap<AstId<ast::Item>, AttrId>,
+ /// When we expand attributes, we need to censor all previous active attributes
+ /// on the same item. Therefore, this holds all active attributes that we already
+ /// expanded.
+ prev_active_attrs: FxHashMap<AstId<ast::Item>, SmallVec<[AttrId; 1]>>,
}
impl<'db> DefCollector<'db> {
fn seed_with_top_level(&mut self) {
let _p = tracing::info_span!("seed_with_top_level").entered();
- let file_id = self.def_map.krate.data(self.db).root_file_id(self.db);
+ let file_id = self.def_map.krate.root_file_id(self.db);
let item_tree = self.db.file_item_tree(file_id.into());
- let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate);
+ let attrs = match item_tree.top_level_attrs() {
+ AttrsOrCfg::Enabled { attrs } => attrs.as_ref(),
+ AttrsOrCfg::CfgDisabled(it) => it.1.as_ref(),
+ };
let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap();
- let mut process = true;
-
// Process other crate-level attributes.
for attr in &*attrs {
- if let Some(cfg) = attr.cfg()
- && self.cfg_options.check(&cfg) == Some(false)
- {
- process = false;
- break;
- }
let Some(attr_name) = attr.path.as_ident() else { continue };
match () {
@@ -291,7 +292,7 @@ impl<'db> DefCollector<'db> {
() if *attr_name == sym::feature => {
let features =
attr.parse_path_comma_token_tree(self.db).into_iter().flatten().filter_map(
- |(feat, _)| match feat.segments() {
+ |(feat, _, _)| match feat.segments() {
[name] => Some(name.symbol().clone()),
_ => None,
},
@@ -344,7 +345,7 @@ impl<'db> DefCollector<'db> {
self.inject_prelude();
- if !process {
+ if matches!(item_tree.top_level_attrs(), AttrsOrCfg::CfgDisabled(_)) {
return;
}
@@ -362,10 +363,7 @@ impl<'db> DefCollector<'db> {
fn seed_with_inner(&mut self, tree_id: TreeId) {
let item_tree = tree_id.item_tree(self.db);
- let is_cfg_enabled = item_tree
- .top_level_attrs(self.db, self.def_map.krate)
- .cfg()
- .is_none_or(|cfg| self.cfg_options.check(&cfg) != Some(false));
+ let is_cfg_enabled = matches!(item_tree.top_level_attrs(), AttrsOrCfg::Enabled { .. });
if is_cfg_enabled {
self.inject_prelude();
@@ -456,18 +454,18 @@ impl<'db> DefCollector<'db> {
self.unresolved_macros.iter().enumerate().find_map(|(idx, directive)| match &directive
.kind
{
- MacroDirectiveKind::Attr { ast_id, mod_item, attr, tree, item_tree } => {
+ MacroDirectiveKind::Attr { ast_id, mod_item, attr_id, attr, tree, item_tree } => {
self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
directive.module_id,
MacroCallKind::Attr {
ast_id: ast_id.ast_id,
attr_args: None,
- invoc_attr_index: attr.id,
+ censored_attr_ids: AttrMacroAttrIds::from_one(*attr_id),
},
- attr.path().clone(),
+ (*attr.path).clone(),
));
- self.skip_attrs.insert(ast_id.ast_id.with_value(mod_item.ast_id()), attr.id);
+ self.skip_attrs.insert(ast_id.ast_id.with_value(mod_item.ast_id()), *attr_id);
Some((idx, directive, *mod_item, *tree, *item_tree))
}
@@ -1240,7 +1238,17 @@ impl<'db> DefCollector<'db> {
let mut macros = mem::take(&mut self.unresolved_macros);
let mut resolved = Vec::new();
let mut push_resolved = |directive: &MacroDirective<'_>, call_id| {
- resolved.push((directive.module_id, directive.depth, directive.container, call_id));
+ let attr_macro_item = match &directive.kind {
+ MacroDirectiveKind::Attr { ast_id, .. } => Some(ast_id.ast_id),
+ MacroDirectiveKind::FnLike { .. } | MacroDirectiveKind::Derive { .. } => None,
+ };
+ resolved.push((
+ directive.module_id,
+ directive.depth,
+ directive.container,
+ call_id,
+ attr_macro_item,
+ ));
};
#[derive(PartialEq, Eq)]
@@ -1350,6 +1358,7 @@ impl<'db> DefCollector<'db> {
MacroDirectiveKind::Attr {
ast_id: file_ast_id,
mod_item,
+ attr_id,
attr,
tree,
item_tree,
@@ -1362,7 +1371,7 @@ impl<'db> DefCollector<'db> {
let mod_dir = collector.mod_dirs[&directive.module_id].clone();
collector
.skip_attrs
- .insert(InFile::new(file_id, mod_item.ast_id()), attr.id);
+ .insert(InFile::new(file_id, mod_item.ast_id()), *attr_id);
ModCollector {
def_collector: collector,
@@ -1398,7 +1407,6 @@ impl<'db> DefCollector<'db> {
// being cfg'ed out).
// Ideally we will just expand them to nothing here. But we are only collecting macro calls,
// not expanding them, so we have no way to do that.
- // If you add an ignored attribute here, also add it to `Semantics::might_be_inside_macro_call()`.
if matches!(
def.kind,
MacroDefKind::BuiltInAttr(_, expander)
@@ -1410,8 +1418,18 @@ impl<'db> DefCollector<'db> {
}
}
- let call_id = || {
- attr_macro_as_call_id(self.db, file_ast_id, attr, self.def_map.krate, def)
+ let mut call_id = || {
+ let active_attrs = self.prev_active_attrs.entry(ast_id).or_default();
+ active_attrs.push(*attr_id);
+
+ attr_macro_as_call_id(
+ self.db,
+ file_ast_id,
+ attr,
+ AttrMacroAttrIds::from_many(active_attrs),
+ self.def_map.krate,
+ def,
+ )
};
if matches!(def,
MacroDefId { kind: MacroDefKind::BuiltInAttr(_, exp), .. }
@@ -1429,7 +1447,7 @@ impl<'db> DefCollector<'db> {
let diag = DefDiagnostic::invalid_derive_target(
directive.module_id,
ast_id,
- attr.id,
+ *attr_id,
);
self.def_map.diagnostics.push(diag);
return recollect_without(self);
@@ -1442,7 +1460,7 @@ impl<'db> DefCollector<'db> {
Some(derive_macros) => {
let call_id = call_id();
let mut len = 0;
- for (idx, (path, call_site)) in derive_macros.enumerate() {
+ for (idx, (path, call_site, _)) in derive_macros.enumerate() {
let ast_id = AstIdWithPath::new(
file_id,
ast_id.value,
@@ -1453,7 +1471,7 @@ impl<'db> DefCollector<'db> {
depth: directive.depth + 1,
kind: MacroDirectiveKind::Derive {
ast_id,
- derive_attr: attr.id,
+ derive_attr: *attr_id,
derive_pos: idx,
ctxt: call_site.ctx,
derive_macro_id: call_id,
@@ -1469,13 +1487,13 @@ impl<'db> DefCollector<'db> {
// Check the comment in [`builtin_attr_macro`].
self.def_map.modules[directive.module_id]
.scope
- .init_derive_attribute(ast_id, attr.id, call_id, len + 1);
+ .init_derive_attribute(ast_id, *attr_id, call_id, len + 1);
}
None => {
let diag = DefDiagnostic::malformed_derive(
directive.module_id,
ast_id,
- attr.id,
+ *attr_id,
);
self.def_map.diagnostics.push(diag);
}
@@ -1522,8 +1540,14 @@ impl<'db> DefCollector<'db> {
self.def_map.modules[module_id].scope.add_macro_invoc(ptr.map(|(_, it)| it), call_id);
}
- for (module_id, depth, container, macro_call_id) in resolved {
- self.collect_macro_expansion(module_id, macro_call_id, depth, container);
+ for (module_id, depth, container, macro_call_id, attr_macro_item) in resolved {
+ self.collect_macro_expansion(
+ module_id,
+ macro_call_id,
+ depth,
+ container,
+ attr_macro_item,
+ );
}
res
@@ -1535,6 +1559,7 @@ impl<'db> DefCollector<'db> {
macro_call_id: MacroCallId,
depth: usize,
container: ItemContainerId,
+ attr_macro_item: Option<AstId<ast::Item>>,
) {
if depth > self.def_map.recursion_limit() as usize {
cov_mark::hit!(macro_expansion_overflow);
@@ -1545,6 +1570,34 @@ impl<'db> DefCollector<'db> {
let item_tree = self.db.file_item_tree(file_id);
+ // Derive helpers that are in scope for an item are also in scope for attribute macro expansions
+ // of that item (but not derive or fn like macros).
+ // FIXME: This is a hack. The proper way to do this is by having a chain of derive helpers scope,
+ // where the next scope in the chain is the parent hygiene context of the span. Unfortunately
+ // it's difficult to implement with our current name resolution and hygiene system.
+ // This hack is also incorrect since it ignores item in blocks. But the main reason to bring derive
+ // helpers into scope in this case is to help with:
+ // ```
+ // #[derive(DeriveWithHelper)]
+ // #[helper]
+ // #[attr_macro]
+ // struct Foo;
+ // ```
+ // Where `attr_macro`'s input will include `#[helper]` but not the derive, and it will likely therefore
+ // also include it in its output. Therefore I hope not supporting blocks is fine at least for now.
+ if let Some(attr_macro_item) = attr_macro_item
+ && let Some(derive_helpers) = self.def_map.derive_helpers_in_scope.get(&attr_macro_item)
+ {
+ let derive_helpers = derive_helpers.clone();
+ for item in item_tree.top_level_items() {
+ self.def_map
+ .derive_helpers_in_scope
+ .entry(InFile::new(file_id, item.ast_id()))
+ .or_default()
+ .extend(derive_helpers.iter().cloned());
+ }
+ }
+
let mod_dir = if macro_call_id.is_include_macro(self.db) {
ModDir::root()
} else {
@@ -1712,16 +1765,17 @@ impl ModCollector<'_, '_> {
};
let mut process_mod_item = |item: ModItemId| {
- let attrs = self.item_tree.attrs(db, krate, item.ast_id());
- if let Some(cfg) = attrs.cfg()
- && !self.is_cfg_enabled(&cfg)
- {
- let ast_id = item.ast_id().erase();
- self.emit_unconfigured_diagnostic(InFile::new(self.file_id(), ast_id), &cfg);
- return;
- }
+ let attrs = match self.item_tree.attrs(item.ast_id()) {
+ Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(),
+ None => Attrs::EMPTY,
+ Some(AttrsOrCfg::CfgDisabled(cfg)) => {
+ let ast_id = item.ast_id().erase();
+ self.emit_unconfigured_diagnostic(InFile::new(self.file_id(), ast_id), &cfg.0);
+ return;
+ }
+ };
- if let Err(()) = self.resolve_attributes(&attrs, item, container) {
+ if let Err(()) = self.resolve_attributes(attrs, item, container) {
// Do not process the item. It has at least one non-builtin attribute, so the
// fixed-point algorithm is required to resolve the rest of them.
return;
@@ -1733,7 +1787,7 @@ impl ModCollector<'_, '_> {
self.def_collector.crate_local_def_map.unwrap_or(&self.def_collector.local_def_map);
match item {
- ModItemId::Mod(m) => self.collect_module(m, &attrs),
+ ModItemId::Mod(m) => self.collect_module(m, attrs),
ModItemId::Use(item_tree_id) => {
let id =
UseLoc { container: module, id: InFile::new(self.file_id(), item_tree_id) }
@@ -2006,7 +2060,7 @@ impl ModCollector<'_, '_> {
);
return;
};
- for (path, _) in paths {
+ for (path, _, _) in paths {
if let Some(name) = path.as_ident() {
single_imports.push(name.clone());
}
@@ -2020,7 +2074,7 @@ impl ModCollector<'_, '_> {
);
}
- fn collect_module(&mut self, module_ast_id: ItemTreeAstId<Mod>, attrs: &Attrs) {
+ fn collect_module(&mut self, module_ast_id: ItemTreeAstId<Mod>, attrs: Attrs<'_>) {
let path_attr = attrs.by_key(sym::path).string_value_unescape();
let is_macro_use = attrs.by_key(sym::macro_use).exists();
let module = &self.item_tree[module_ast_id];
@@ -2061,23 +2115,18 @@ impl ModCollector<'_, '_> {
self.file_id(),
&module.name,
path_attr.as_deref(),
+ self.def_collector.def_map.krate,
) {
Ok((file_id, is_mod_rs, mod_dir)) => {
let item_tree = db.file_item_tree(file_id.into());
- let krate = self.def_collector.def_map.krate;
- let is_enabled = item_tree
- .top_level_attrs(db, krate)
- .cfg()
- .and_then(|cfg| self.is_cfg_enabled(&cfg).not().then_some(cfg))
- .map_or(Ok(()), Err);
- match is_enabled {
- Err(cfg) => {
+ match item_tree.top_level_attrs() {
+ AttrsOrCfg::CfgDisabled(cfg) => {
self.emit_unconfigured_diagnostic(
InFile::new(self.file_id(), module_ast_id.erase()),
- &cfg,
+ &cfg.0,
);
}
- Ok(()) => {
+ AttrsOrCfg::Enabled { attrs } => {
let module_id = self.push_child_module(
module.name.clone(),
ast_id.value,
@@ -2093,11 +2142,8 @@ impl ModCollector<'_, '_> {
mod_dir,
}
.collect_in_top_module(item_tree.top_level_items());
- let is_macro_use = is_macro_use
- || item_tree
- .top_level_attrs(db, krate)
- .by_key(sym::macro_use)
- .exists();
+ let is_macro_use =
+ is_macro_use || attrs.as_ref().by_key(sym::macro_use).exists();
if is_macro_use {
self.import_all_legacy_macros(module_id);
}
@@ -2185,36 +2231,16 @@ impl ModCollector<'_, '_> {
/// assumed to be resolved already.
fn resolve_attributes(
&mut self,
- attrs: &Attrs,
+ attrs: Attrs<'_>,
mod_item: ModItemId,
container: ItemContainerId,
) -> Result<(), ()> {
- let mut ignore_up_to = self
+ let ignore_up_to = self
.def_collector
.skip_attrs
.get(&InFile::new(self.file_id(), mod_item.ast_id()))
.copied();
- let iter = attrs
- .iter()
- .dedup_by(|a, b| {
- // FIXME: this should not be required, all attributes on an item should have a
- // unique ID!
- // Still, this occurs because `#[cfg_attr]` can "expand" to multiple attributes:
- // #[cfg_attr(not(off), unresolved, unresolved)]
- // struct S;
- // We should come up with a different way to ID attributes.
- a.id == b.id
- })
- .skip_while(|attr| match ignore_up_to {
- Some(id) if attr.id == id => {
- ignore_up_to = None;
- true
- }
- Some(_) => true,
- None => false,
- });
-
- for attr in iter {
+ for (attr_id, attr) in attrs.iter_after(ignore_up_to) {
if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) {
continue;
}
@@ -2229,6 +2255,7 @@ impl ModCollector<'_, '_> {
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Attr {
ast_id,
+ attr_id,
attr: attr.clone(),
mod_item,
tree: self.tree_id,
@@ -2246,7 +2273,13 @@ impl ModCollector<'_, '_> {
fn collect_macro_rules(&mut self, ast_id: ItemTreeAstId<MacroRules>, module: ModuleId) {
let krate = self.def_collector.def_map.krate;
let mac = &self.item_tree[ast_id];
- let attrs = self.item_tree.attrs(self.def_collector.db, krate, ast_id.upcast());
+ let attrs = match self.item_tree.attrs(ast_id.upcast()) {
+ Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(),
+ None => Attrs::EMPTY,
+ Some(AttrsOrCfg::CfgDisabled(_)) => {
+ unreachable!("we only get here if the macro is not cfg'ed out")
+ }
+ };
let f_ast_id = InFile::new(self.file_id(), ast_id.upcast());
let export_attr = || attrs.by_key(sym::macro_export);
@@ -2331,7 +2364,13 @@ impl ModCollector<'_, '_> {
fn collect_macro_def(&mut self, ast_id: ItemTreeAstId<Macro2>, module: ModuleId) {
let krate = self.def_collector.def_map.krate;
let mac = &self.item_tree[ast_id];
- let attrs = self.item_tree.attrs(self.def_collector.db, krate, ast_id.upcast());
+ let attrs = match self.item_tree.attrs(ast_id.upcast()) {
+ Some(AttrsOrCfg::Enabled { attrs }) => attrs.as_ref(),
+ None => Attrs::EMPTY,
+ Some(AttrsOrCfg::CfgDisabled(_)) => {
+ unreachable!("we only get here if the macro is not cfg'ed out")
+ }
+ };
let f_ast_id = InFile::new(self.file_id(), ast_id.upcast());
// Case 1: builtin macros
@@ -2460,6 +2499,7 @@ impl ModCollector<'_, '_> {
call_id,
self.macro_depth + 1,
container,
+ None,
);
}
@@ -2515,10 +2555,6 @@ impl ModCollector<'_, '_> {
Some((a, b))
}
- fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool {
- self.def_collector.cfg_options.check(cfg) != Some(false)
- }
-
fn emit_unconfigured_diagnostic(&mut self, ast_id: ErasedAstId, cfg: &CfgExpr) {
self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
self.module_id,
@@ -2558,6 +2594,7 @@ mod tests {
proc_macros: Default::default(),
from_glob_import: Default::default(),
skip_attrs: Default::default(),
+ prev_active_attrs: Default::default(),
is_proc_macro: false,
unresolved_extern_crates: Default::default(),
};