internal: resolve attributes in name resolution
This commit is contained in:
parent
312f1fe20a
commit
3e186d4778
@ -4,7 +4,7 @@
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
fmt::{self, Debug, Display},
|
||||
hash::{BuildHasherDefault, Hash, Hasher},
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
@ -171,6 +171,12 @@ impl<T: Debug + Internable + ?Sized> Debug for Interned<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Internable + ?Sized> Display for Interned<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(*self.arc).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InternStorage<T: ?Sized> {
|
||||
map: OnceCell<InternMap<T>>,
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
attr::{AttrId, Attrs},
|
||||
builtin_attr,
|
||||
db::DefDatabase,
|
||||
derive_macro_as_call_id,
|
||||
intern::Interned,
|
||||
@ -99,6 +100,7 @@ pub(super) fn collect_defs(
|
||||
proc_macros,
|
||||
exports_proc_macros: false,
|
||||
from_glob_import: Default::default(),
|
||||
ignore_attrs_on: FxHashSet::default(),
|
||||
};
|
||||
match block {
|
||||
Some(block) => {
|
||||
@ -217,6 +219,7 @@ struct MacroDirective {
|
||||
enum MacroDirectiveKind {
|
||||
FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
|
||||
Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
|
||||
Attr { ast_id: AstIdWithPath<ast::Item>, attr: AttrId, mod_item: ModItem },
|
||||
}
|
||||
|
||||
struct DefData<'a> {
|
||||
@ -243,6 +246,7 @@ struct DefCollector<'a> {
|
||||
proc_macros: Vec<(Name, ProcMacroExpander)>,
|
||||
exports_proc_macros: bool,
|
||||
from_glob_import: PerNsGlobImports,
|
||||
ignore_attrs_on: FxHashSet<ModItem>,
|
||||
}
|
||||
|
||||
impl DefCollector<'_> {
|
||||
@ -297,7 +301,10 @@ impl DefCollector<'_> {
|
||||
self.resolve_imports();
|
||||
|
||||
match self.resolve_macros() {
|
||||
ReachedFixedPoint::Yes => break,
|
||||
ReachedFixedPoint::Yes => match self.reseed_with_unresolved_attributes() {
|
||||
ReachedFixedPoint::Yes => break,
|
||||
ReachedFixedPoint::No => i += 1,
|
||||
},
|
||||
ReachedFixedPoint::No => i += 1,
|
||||
}
|
||||
if i == FIXED_POINT_LIMIT {
|
||||
@ -343,6 +350,50 @@ 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.
|
||||
///
|
||||
/// 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 {
|
||||
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 { mod_item, .. } = &directive.kind {
|
||||
// Make sure to only add such items once.
|
||||
if !self.ignore_attrs_on.insert(*mod_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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();
|
||||
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]);
|
||||
added_items = true;
|
||||
}
|
||||
}
|
||||
self.unexpanded_macros = unexpanded_macros;
|
||||
|
||||
if added_items {
|
||||
// Continue name resolution with the new data.
|
||||
ReachedFixedPoint::No
|
||||
} else {
|
||||
ReachedFixedPoint::Yes
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a definition of procedural macro `name` to the root module.
|
||||
///
|
||||
/// # Notes on procedural macro resolution
|
||||
@ -849,6 +900,9 @@ impl DefCollector<'_> {
|
||||
Err(UnresolvedMacro { .. }) => (),
|
||||
}
|
||||
}
|
||||
MacroDirectiveKind::Attr { .. } => {
|
||||
// not yet :)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
@ -953,7 +1007,7 @@ impl DefCollector<'_> {
|
||||
));
|
||||
}
|
||||
},
|
||||
MacroDirectiveKind::Derive { .. } => {
|
||||
MacroDirectiveKind::Derive { .. } | MacroDirectiveKind::Attr { .. } => {
|
||||
// FIXME: we might want to diagnose this too
|
||||
}
|
||||
}
|
||||
@ -1061,6 +1115,14 @@ impl ModCollector<'_, '_> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(()) = self.resolve_attributes(&attrs, item) {
|
||||
// Do not process the item. It has at least one non-builtin attribute, which *must*
|
||||
// resolve to a proc macro (or fail to resolve), so we'll never see this item during
|
||||
// normal name resolution.
|
||||
continue;
|
||||
}
|
||||
|
||||
let module = self.def_collector.def_map.module_id(self.module_id);
|
||||
|
||||
let mut def = None;
|
||||
@ -1367,6 +1429,62 @@ impl ModCollector<'_, '_> {
|
||||
res
|
||||
}
|
||||
|
||||
/// Resolves attributes on an item.
|
||||
///
|
||||
/// Returns `Err` when some attributes could not be resolved to builtins and have been
|
||||
/// registered as unresolved.
|
||||
fn resolve_attributes(&mut self, attrs: &Attrs, mod_item: ModItem) -> Result<(), ()> {
|
||||
fn is_builtin_attr(path: &ModPath) -> bool {
|
||||
if path.kind == PathKind::Plain {
|
||||
if let Some(tool_module) = path.segments().first() {
|
||||
let tool_module = tool_module.to_string();
|
||||
if builtin_attr::TOOL_MODULES.iter().any(|m| tool_module == *m) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = path.as_ident() {
|
||||
let name = name.to_string();
|
||||
if builtin_attr::INERT_ATTRIBUTES
|
||||
.iter()
|
||||
.chain(builtin_attr::EXTRA_ATTRIBUTES)
|
||||
.any(|attr| name == *attr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(&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 ast_id = AstIdWithPath::new(
|
||||
self.file_id,
|
||||
mod_item.ast_id(self.item_tree),
|
||||
non_builtin_attr.path.as_ref().clone(),
|
||||
);
|
||||
self.def_collector.unexpanded_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 },
|
||||
});
|
||||
|
||||
Err(())
|
||||
}
|
||||
None => 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() {
|
||||
@ -1599,6 +1717,7 @@ mod tests {
|
||||
proc_macros: Default::default(),
|
||||
exports_proc_macros: false,
|
||||
from_glob_import: Default::default(),
|
||||
ignore_attrs_on: FxHashSet::default(),
|
||||
};
|
||||
collector.seed_with_top_level();
|
||||
collector.collect();
|
||||
|
Loading…
x
Reference in New Issue
Block a user