Expand procedural attribute macros

This commit is contained in:
Jonas Schievink 2021-05-31 13:37:11 +02:00
parent 7f9c4a59d9
commit e5a2c6596d
7 changed files with 159 additions and 14 deletions

View file

@ -534,6 +534,18 @@ impl Module {
Some(derive_name.clone()), Some(derive_name.clone()),
) )
} }
MacroCallKind::Attr { ast_id, invoc_attr_index, attr_name, .. } => {
let node = ast_id.to_node(db.upcast());
let attr =
node.attrs().nth((*invoc_attr_index) as usize).unwrap_or_else(
|| panic!("cannot find attribute #{}", invoc_attr_index),
);
(
ast_id.file_id,
SyntaxNodePtr::from(AstPtr::new(&attr)),
Some(attr_name.clone()),
)
}
}; };
sink.push(UnresolvedProcMacro { sink.push(UnresolvedProcMacro {
file, file,
@ -558,7 +570,9 @@ impl Module {
let node = ast_id.to_node(db.upcast()); let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
} }
MacroCallKind::Derive { ast_id, .. } => { MacroCallKind::Derive { ast_id, .. }
| MacroCallKind::Attr { ast_id, .. } => {
// FIXME: point to the attribute instead, this creates very large diagnostics
let node = ast_id.to_node(db.upcast()); let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
} }

View file

@ -55,6 +55,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use attr::Attr;
use base_db::{impl_intern_key, salsa, CrateId}; use base_db::{impl_intern_key, salsa, CrateId};
use hir_expand::{ use hir_expand::{
ast_id_map::FileAstId, ast_id_map::FileAstId,
@ -768,3 +769,42 @@ fn derive_macro_as_call_id(
.into(); .into();
Ok(res) Ok(res)
} }
fn attr_macro_as_call_id(
item_attr: &AstIdWithPath<ast::Item>,
macro_attr: &Attr,
db: &dyn db::DefDatabase,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
) -> Result<MacroCallId, UnresolvedMacro> {
let def: MacroDefId = resolver(item_attr.path.clone())
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
let last_segment = item_attr
.path
.segments()
.last()
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
let mut arg = match &macro_attr.input {
Some(input) => match &**input {
attr::AttrInput::Literal(_) => tt::Subtree::default(),
attr::AttrInput::TokenTree(tt) => tt.clone(),
},
None => tt::Subtree::default(),
};
// The parentheses are always disposed here.
arg.delimiter = None;
let res = def
.as_lazy_macro(
db.upcast(),
krate,
MacroCallKind::Attr {
ast_id: item_attr.ast_id,
attr_name: last_segment.to_string(),
attr_args: arg,
invoc_attr_index: macro_attr.id.ast_index,
},
)
.into();
Ok(res)
}

View file

@ -23,7 +23,7 @@ use syntax::ast;
use crate::{ use crate::{
attr::{Attr, AttrId, AttrInput, Attrs}, attr::{Attr, AttrId, AttrInput, Attrs},
builtin_attr, attr_macro_as_call_id, builtin_attr,
db::DefDatabase, db::DefDatabase,
derive_macro_as_call_id, derive_macro_as_call_id,
intern::Interned, intern::Interned,
@ -223,7 +223,7 @@ struct MacroDirective {
enum MacroDirectiveKind { enum MacroDirectiveKind {
FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind }, FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId }, Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
Attr { ast_id: AstIdWithPath<ast::Item>, attr: AttrId, mod_item: ModItem }, Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem },
} }
struct DefData<'a> { struct DefData<'a> {
@ -419,7 +419,7 @@ impl DefCollector<'_> {
let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new()); let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
let pos = unresolved_macros.iter().position(|directive| { let pos = unresolved_macros.iter().position(|directive| {
if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind { if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), *attr); self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), attr.id);
let file_id = ast_id.ast_id.file_id; let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id); let item_tree = self.db.file_item_tree(file_id);
@ -1050,7 +1050,7 @@ impl DefCollector<'_> {
let file_id = ast_id.ast_id.file_id; let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id); let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone(); let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.skip_attrs.insert(InFile::new(file_id, *mod_item), *attr); self.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
ModCollector { ModCollector {
def_collector: &mut *self, def_collector: &mut *self,
macro_depth: directive.depth, macro_depth: directive.depth,
@ -1068,7 +1068,51 @@ impl DefCollector<'_> {
} }
// Not resolved to a derive helper, so try to resolve as a macro. // Not resolved to a derive helper, so try to resolve as a macro.
// FIXME: not yet :) match attr_macro_as_call_id(
ast_id,
attr,
self.db,
self.def_map.krate,
&resolver,
) {
Ok(call_id) => {
let loc: MacroCallLoc = self.db.lookup_intern_macro(call_id);
if let MacroDefKind::ProcMacro(exp, ..) = &loc.def.kind {
if exp.is_dummy() {
// Proc macros that cannot be expanded are treated as not
// resolved, in order to fall back later.
self.def_map.diagnostics.push(
DefDiagnostic::unresolved_proc_macro(
directive.module_id,
loc.kind,
),
);
let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.skip_attrs
.insert(InFile::new(file_id, *mod_item), attr.id);
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 macro directive.
return false;
}
}
resolved.push((directive.module_id, call_id, directive.depth));
res = ReachedFixedPoint::No;
return false;
}
Err(UnresolvedMacro { .. }) => (),
}
} }
} }
@ -1628,7 +1672,7 @@ impl ModCollector<'_, '_> {
self.def_collector.unresolved_macros.push(MacroDirective { self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id, module_id: self.module_id,
depth: self.macro_depth + 1, depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item }, kind: MacroDirectiveKind::Attr { ast_id, attr: attr.clone(), mod_item },
}); });
return Err(()); return Err(());

View file

@ -13,8 +13,8 @@ use syntax::{
use crate::{ use crate::{
ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander, ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc,
MacroDefKind, MacroFile, ProcMacroExpander, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
}; };
/// Total limit on the number of tokens produced by any macro invocation. /// Total limit on the number of tokens produced by any macro invocation.
@ -377,7 +377,12 @@ fn expand_proc_macro(
_ => unreachable!(), _ => unreachable!(),
}; };
expander.expand(db, loc.krate, &macro_arg.0) let attr_arg = match &loc.kind {
MacroCallKind::Attr { attr_args, .. } => Some(attr_args),
_ => None,
};
expander.expand(db, loc.krate, &macro_arg.0, attr_arg)
} }
fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool { fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {

View file

@ -28,6 +28,14 @@ pub(crate) fn process_macro_input(
remove_derives_up_to(item, derive_attr_index as usize).syntax().clone() remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
} }
MacroCallKind::Attr { invoc_attr_index, .. } => {
let item = match ast::Item::cast(node.clone()) {
Some(item) => item,
None => return node,
};
remove_attr_invoc(item, invoc_attr_index as usize).syntax().clone()
}
} }
} }
@ -46,6 +54,17 @@ fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
item item
} }
/// Removes the attribute invoking an attribute macro from `item`.
fn remove_attr_invoc(item: ast::Item, attr_index: usize) -> ast::Item {
let item = item.clone_for_update();
let attr = item
.attrs()
.nth(attr_index)
.unwrap_or_else(|| panic!("cannot find attribute #{}", attr_index));
attr.syntax().detach();
item
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use base_db::fixture::WithFixture; use base_db::fixture::WithFixture;

View file

@ -258,14 +258,29 @@ pub enum MacroCallKind {
/// out-of-line modules, which may have attributes spread across 2 files! /// out-of-line modules, which may have attributes spread across 2 files!
derive_attr_index: u32, derive_attr_index: u32,
}, },
Attr {
ast_id: AstId<ast::Item>,
attr_name: String,
attr_args: tt::Subtree,
/// Syntactical index of the invoking `#[attribute]`.
///
/// Outer attributes are counted first, then inner attributes. This does not support
/// out-of-line modules, which may have attributes spread across 2 files!
invoc_attr_index: u32,
},
} }
// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole
// `cfg_attr` instead of just one of the attributes it expands to
impl MacroCallKind { impl MacroCallKind {
/// Returns the file containing the macro invocation. /// Returns the file containing the macro invocation.
fn file_id(&self) -> HirFileId { fn file_id(&self) -> HirFileId {
match self { match self {
MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id, MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id,
MacroCallKind::Derive { ast_id, .. } => ast_id.file_id, MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
ast_id.file_id
}
} }
} }
@ -274,7 +289,7 @@ impl MacroCallKind {
MacroCallKind::FnLike { ast_id, .. } => { MacroCallKind::FnLike { ast_id, .. } => {
ast_id.with_value(ast_id.to_node(db).syntax().clone()) ast_id.with_value(ast_id.to_node(db).syntax().clone())
} }
MacroCallKind::Derive { ast_id, .. } => { MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
ast_id.with_value(ast_id.to_node(db).syntax().clone()) ast_id.with_value(ast_id.to_node(db).syntax().clone())
} }
} }
@ -285,7 +300,9 @@ impl MacroCallKind {
MacroCallKind::FnLike { ast_id, .. } => { MacroCallKind::FnLike { ast_id, .. } => {
Some(ast_id.to_node(db).token_tree()?.syntax().clone()) Some(ast_id.to_node(db).token_tree()?.syntax().clone())
} }
MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()), MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
Some(ast_id.to_node(db).syntax().clone())
}
} }
} }
@ -293,6 +310,7 @@ impl MacroCallKind {
match self { match self {
MacroCallKind::FnLike { fragment, .. } => *fragment, MacroCallKind::FnLike { fragment, .. } => *fragment,
MacroCallKind::Derive { .. } => FragmentKind::Items, MacroCallKind::Derive { .. } => FragmentKind::Items,
MacroCallKind::Attr { .. } => FragmentKind::Items, // is this always correct?
} }
} }
} }

View file

@ -28,11 +28,16 @@ impl ProcMacroExpander {
Self { krate, proc_macro_id: None } Self { krate, proc_macro_id: None }
} }
pub fn is_dummy(&self) -> bool {
self.proc_macro_id.is_none()
}
pub fn expand( pub fn expand(
self, self,
db: &dyn AstDatabase, db: &dyn AstDatabase,
calling_crate: CrateId, calling_crate: CrateId,
tt: &tt::Subtree, tt: &tt::Subtree,
attr_arg: Option<&tt::Subtree>,
) -> Result<tt::Subtree, mbe::ExpandError> { ) -> Result<tt::Subtree, mbe::ExpandError> {
match self.proc_macro_id { match self.proc_macro_id {
Some(id) => { Some(id) => {
@ -46,7 +51,7 @@ impl ProcMacroExpander {
// Proc macros have access to the environment variables of the invoking crate. // Proc macros have access to the environment variables of the invoking crate.
let env = &krate_graph[calling_crate].env; let env = &krate_graph[calling_crate].env;
proc_macro.expander.expand(&tt, None, &env).map_err(mbe::ExpandError::from) proc_macro.expander.expand(&tt, attr_arg, &env).map_err(mbe::ExpandError::from)
} }
None => Err(mbe::ExpandError::UnresolvedProcMacro), None => Err(mbe::ExpandError::UnresolvedProcMacro),
} }