Skip to content

Commit e9b3e83

Browse files
Merge #9128
9128: feat: expand procedural attribute macros r=jonas-schievink a=jonas-schievink This adds experimental support for attribute macros. They can be enabled by setting `rust-analyzer.experimental.procAttrMacros` to `true`. Known issues: * Tokens aren't remapped, presumably because we edit the input syntax tree (this causes IDE features to not work inside items with attribute macros on them) * Macro errors aren't reported correctly Closes #8971 Fixes #8964 / la10736/rstest#120 Fixes #2984 Fixes #5412 Fixes #6029 Fixes #6687 #6740 is still not fixed – we now expand `#[proc_macro_hack]`, but fail to expand the resulting `proc_macro_call!()` macro. Co-authored-by: Jonas Schievink <[email protected]>
2 parents 7f9c4a5 + c45cf47 commit e9b3e83

File tree

15 files changed

+203
-18
lines changed

15 files changed

+203
-18
lines changed

crates/hir/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,18 @@ impl Module {
534534
Some(derive_name.clone()),
535535
)
536536
}
537+
MacroCallKind::Attr { ast_id, invoc_attr_index, attr_name, .. } => {
538+
let node = ast_id.to_node(db.upcast());
539+
let attr =
540+
node.attrs().nth((*invoc_attr_index) as usize).unwrap_or_else(
541+
|| panic!("cannot find attribute #{}", invoc_attr_index),
542+
);
543+
(
544+
ast_id.file_id,
545+
SyntaxNodePtr::from(AstPtr::new(&attr)),
546+
Some(attr_name.clone()),
547+
)
548+
}
537549
};
538550
sink.push(UnresolvedProcMacro {
539551
file,
@@ -558,7 +570,9 @@ impl Module {
558570
let node = ast_id.to_node(db.upcast());
559571
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
560572
}
561-
MacroCallKind::Derive { ast_id, .. } => {
573+
MacroCallKind::Derive { ast_id, .. }
574+
| MacroCallKind::Attr { ast_id, .. } => {
575+
// FIXME: point to the attribute instead, this creates very large diagnostics
562576
let node = ast_id.to_node(db.upcast());
563577
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
564578
}

crates/hir_def/src/db.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub trait InternDatabase: SourceDatabase {
5151

5252
#[salsa::query_group(DefDatabaseStorage)]
5353
pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
54+
#[salsa::input]
55+
fn enable_proc_attr_macros(&self) -> bool;
56+
5457
#[salsa::invoke(ItemTree::file_item_tree_query)]
5558
fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
5659

crates/hir_def/src/lib.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use std::{
5555
sync::Arc,
5656
};
5757

58+
use attr::Attr;
5859
use base_db::{impl_intern_key, salsa, CrateId};
5960
use hir_expand::{
6061
ast_id_map::FileAstId,
@@ -768,3 +769,42 @@ fn derive_macro_as_call_id(
768769
.into();
769770
Ok(res)
770771
}
772+
773+
fn attr_macro_as_call_id(
774+
item_attr: &AstIdWithPath<ast::Item>,
775+
macro_attr: &Attr,
776+
db: &dyn db::DefDatabase,
777+
krate: CrateId,
778+
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
779+
) -> Result<MacroCallId, UnresolvedMacro> {
780+
let def: MacroDefId = resolver(item_attr.path.clone())
781+
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
782+
let last_segment = item_attr
783+
.path
784+
.segments()
785+
.last()
786+
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
787+
let mut arg = match &macro_attr.input {
788+
Some(input) => match &**input {
789+
attr::AttrInput::Literal(_) => tt::Subtree::default(),
790+
attr::AttrInput::TokenTree(tt) => tt.clone(),
791+
},
792+
None => tt::Subtree::default(),
793+
};
794+
// The parentheses are always disposed here.
795+
arg.delimiter = None;
796+
797+
let res = def
798+
.as_lazy_macro(
799+
db.upcast(),
800+
krate,
801+
MacroCallKind::Attr {
802+
ast_id: item_attr.ast_id,
803+
attr_name: last_segment.to_string(),
804+
attr_args: arg,
805+
invoc_attr_index: macro_attr.id.ast_index,
806+
},
807+
)
808+
.into();
809+
Ok(res)
810+
}

crates/hir_def/src/nameres/collector.rs

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use syntax::ast;
2323

2424
use crate::{
2525
attr::{Attr, AttrId, AttrInput, Attrs},
26-
builtin_attr,
26+
attr_macro_as_call_id, builtin_attr,
2727
db::DefDatabase,
2828
derive_macro_as_call_id,
2929
intern::Interned,
@@ -223,7 +223,7 @@ struct MacroDirective {
223223
enum MacroDirectiveKind {
224224
FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
225225
Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
226-
Attr { ast_id: AstIdWithPath<ast::Item>, attr: AttrId, mod_item: ModItem },
226+
Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem },
227227
}
228228

229229
struct DefData<'a> {
@@ -419,7 +419,7 @@ impl DefCollector<'_> {
419419
let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
420420
let pos = unresolved_macros.iter().position(|directive| {
421421
if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
422-
self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), *attr);
422+
self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), attr.id);
423423

424424
let file_id = ast_id.ast_id.file_id;
425425
let item_tree = self.db.file_item_tree(file_id);
@@ -1050,7 +1050,7 @@ impl DefCollector<'_> {
10501050
let file_id = ast_id.ast_id.file_id;
10511051
let item_tree = self.db.file_item_tree(file_id);
10521052
let mod_dir = self.mod_dirs[&directive.module_id].clone();
1053-
self.skip_attrs.insert(InFile::new(file_id, *mod_item), *attr);
1053+
self.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
10541054
ModCollector {
10551055
def_collector: &mut *self,
10561056
macro_depth: directive.depth,
@@ -1067,8 +1067,56 @@ impl DefCollector<'_> {
10671067
}
10681068
}
10691069

1070+
if !self.db.enable_proc_attr_macros() {
1071+
return true;
1072+
}
1073+
10701074
// Not resolved to a derive helper, so try to resolve as a macro.
1071-
// FIXME: not yet :)
1075+
match attr_macro_as_call_id(
1076+
ast_id,
1077+
attr,
1078+
self.db,
1079+
self.def_map.krate,
1080+
&resolver,
1081+
) {
1082+
Ok(call_id) => {
1083+
let loc: MacroCallLoc = self.db.lookup_intern_macro(call_id);
1084+
if let MacroDefKind::ProcMacro(exp, ..) = &loc.def.kind {
1085+
if exp.is_dummy() {
1086+
// Proc macros that cannot be expanded are treated as not
1087+
// resolved, in order to fall back later.
1088+
self.def_map.diagnostics.push(
1089+
DefDiagnostic::unresolved_proc_macro(
1090+
directive.module_id,
1091+
loc.kind,
1092+
),
1093+
);
1094+
1095+
let file_id = ast_id.ast_id.file_id;
1096+
let item_tree = self.db.file_item_tree(file_id);
1097+
let mod_dir = self.mod_dirs[&directive.module_id].clone();
1098+
self.skip_attrs
1099+
.insert(InFile::new(file_id, *mod_item), attr.id);
1100+
ModCollector {
1101+
def_collector: &mut *self,
1102+
macro_depth: directive.depth,
1103+
module_id: directive.module_id,
1104+
file_id,
1105+
item_tree: &item_tree,
1106+
mod_dir,
1107+
}
1108+
.collect(&[*mod_item]);
1109+
1110+
// Remove the macro directive.
1111+
return false;
1112+
}
1113+
}
1114+
resolved.push((directive.module_id, call_id, directive.depth));
1115+
res = ReachedFixedPoint::No;
1116+
return false;
1117+
}
1118+
Err(UnresolvedMacro { .. }) => (),
1119+
}
10721120
}
10731121
}
10741122

@@ -1628,7 +1676,7 @@ impl ModCollector<'_, '_> {
16281676
self.def_collector.unresolved_macros.push(MacroDirective {
16291677
module_id: self.module_id,
16301678
depth: self.macro_depth + 1,
1631-
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item },
1679+
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.clone(), mod_item },
16321680
});
16331681

16341682
return Err(());

crates/hir_def/src/test_db.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,19 @@ use crate::{
3030
crate::db::InternDatabaseStorage,
3131
crate::db::DefDatabaseStorage
3232
)]
33-
#[derive(Default)]
3433
pub(crate) struct TestDB {
3534
storage: salsa::Storage<TestDB>,
3635
events: Mutex<Option<Vec<salsa::Event>>>,
3736
}
3837

38+
impl Default for TestDB {
39+
fn default() -> Self {
40+
let mut this = Self { storage: Default::default(), events: Default::default() };
41+
this.set_enable_proc_attr_macros(true);
42+
this
43+
}
44+
}
45+
3946
impl Upcast<dyn AstDatabase> for TestDB {
4047
fn upcast(&self) -> &(dyn AstDatabase + 'static) {
4148
&*self

crates/hir_expand/src/db.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use syntax::{
1313

1414
use crate::{
1515
ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
16-
BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId,
17-
MacroDefKind, MacroFile, ProcMacroExpander,
16+
BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc,
17+
MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
1818
};
1919

2020
/// Total limit on the number of tokens produced by any macro invocation.
@@ -377,7 +377,12 @@ fn expand_proc_macro(
377377
_ => unreachable!(),
378378
};
379379

380-
expander.expand(db, loc.krate, &macro_arg.0)
380+
let attr_arg = match &loc.kind {
381+
MacroCallKind::Attr { attr_args, .. } => Some(attr_args),
382+
_ => None,
383+
};
384+
385+
expander.expand(db, loc.krate, &macro_arg.0, attr_arg)
381386
}
382387

383388
fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {

crates/hir_expand/src/input.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ pub(crate) fn process_macro_input(
2828

2929
remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
3030
}
31+
MacroCallKind::Attr { invoc_attr_index, .. } => {
32+
let item = match ast::Item::cast(node.clone()) {
33+
Some(item) => item,
34+
None => return node,
35+
};
36+
37+
remove_attr_invoc(item, invoc_attr_index as usize).syntax().clone()
38+
}
3139
}
3240
}
3341

@@ -46,6 +54,17 @@ fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
4654
item
4755
}
4856

57+
/// Removes the attribute invoking an attribute macro from `item`.
58+
fn remove_attr_invoc(item: ast::Item, attr_index: usize) -> ast::Item {
59+
let item = item.clone_for_update();
60+
let attr = item
61+
.attrs()
62+
.nth(attr_index)
63+
.unwrap_or_else(|| panic!("cannot find attribute #{}", attr_index));
64+
attr.syntax().detach();
65+
item
66+
}
67+
4968
#[cfg(test)]
5069
mod tests {
5170
use base_db::fixture::WithFixture;

crates/hir_expand/src/lib.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,29 @@ pub enum MacroCallKind {
258258
/// out-of-line modules, which may have attributes spread across 2 files!
259259
derive_attr_index: u32,
260260
},
261+
Attr {
262+
ast_id: AstId<ast::Item>,
263+
attr_name: String,
264+
attr_args: tt::Subtree,
265+
/// Syntactical index of the invoking `#[attribute]`.
266+
///
267+
/// Outer attributes are counted first, then inner attributes. This does not support
268+
/// out-of-line modules, which may have attributes spread across 2 files!
269+
invoc_attr_index: u32,
270+
},
261271
}
262272

273+
// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole
274+
// `cfg_attr` instead of just one of the attributes it expands to
275+
263276
impl MacroCallKind {
264277
/// Returns the file containing the macro invocation.
265278
fn file_id(&self) -> HirFileId {
266279
match self {
267280
MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id,
268-
MacroCallKind::Derive { ast_id, .. } => ast_id.file_id,
281+
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
282+
ast_id.file_id
283+
}
269284
}
270285
}
271286

@@ -274,7 +289,7 @@ impl MacroCallKind {
274289
MacroCallKind::FnLike { ast_id, .. } => {
275290
ast_id.with_value(ast_id.to_node(db).syntax().clone())
276291
}
277-
MacroCallKind::Derive { ast_id, .. } => {
292+
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
278293
ast_id.with_value(ast_id.to_node(db).syntax().clone())
279294
}
280295
}
@@ -285,14 +300,17 @@ impl MacroCallKind {
285300
MacroCallKind::FnLike { ast_id, .. } => {
286301
Some(ast_id.to_node(db).token_tree()?.syntax().clone())
287302
}
288-
MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
303+
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
304+
Some(ast_id.to_node(db).syntax().clone())
305+
}
289306
}
290307
}
291308

292309
fn fragment_kind(&self) -> FragmentKind {
293310
match self {
294311
MacroCallKind::FnLike { fragment, .. } => *fragment,
295312
MacroCallKind::Derive { .. } => FragmentKind::Items,
313+
MacroCallKind::Attr { .. } => FragmentKind::Items, // is this always correct?
296314
}
297315
}
298316
}

crates/hir_expand/src/proc_macro.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,16 @@ impl ProcMacroExpander {
2828
Self { krate, proc_macro_id: None }
2929
}
3030

31+
pub fn is_dummy(&self) -> bool {
32+
self.proc_macro_id.is_none()
33+
}
34+
3135
pub fn expand(
3236
self,
3337
db: &dyn AstDatabase,
3438
calling_crate: CrateId,
3539
tt: &tt::Subtree,
40+
attr_arg: Option<&tt::Subtree>,
3641
) -> Result<tt::Subtree, mbe::ExpandError> {
3742
match self.proc_macro_id {
3843
Some(id) => {
@@ -46,7 +51,7 @@ impl ProcMacroExpander {
4651
// Proc macros have access to the environment variables of the invoking crate.
4752
let env = &krate_graph[calling_crate].env;
4853

49-
proc_macro.expander.expand(&tt, None, &env).map_err(mbe::ExpandError::from)
54+
proc_macro.expander.expand(&tt, attr_arg, &env).map_err(mbe::ExpandError::from)
5055
}
5156
None => Err(mbe::ExpandError::UnresolvedProcMacro),
5257
}

crates/ide_db/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl RootDatabase {
9393
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
9494
db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
9595
db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
96+
db.set_enable_proc_attr_macros(Default::default());
9697
db.update_lru_capacity(lru_capacity);
9798
db
9899
}

crates/rust-analyzer/src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ config_data! {
126126
/// and a blue icon in the `Problems Panel`.
127127
diagnostics_warningsAsInfo: Vec<String> = "[]",
128128

129+
/// Expand attribute macros.
130+
experimental_procAttrMacros: bool = "false",
131+
129132
/// Controls file watching implementation.
130133
files_watcher: String = "\"client\"",
131134
/// These directories will be ignored by rust-analyzer.
@@ -546,6 +549,9 @@ impl Config {
546549
let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?;
547550
Some((path, vec!["proc-macro".into()]))
548551
}
552+
pub fn expand_proc_attr_macros(&self) -> bool {
553+
self.data.experimental_procAttrMacros
554+
}
549555
pub fn files(&self) -> FilesConfig {
550556
FilesConfig {
551557
watcher: match self.data.files_watcher.as_str() {

0 commit comments

Comments
 (0)