Skip to content

Commit 1415367

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 + 17565f4 commit 1415367

File tree

17 files changed

+234
-35
lines changed

17 files changed

+234
-35
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/builtin_attr.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
44
//!
5-
//! It was last synchronized with upstream commit 2225ee1b62ff089917434aefd9b2bf509cfa087f.
5+
//! It was last synchronized with upstream commit 835150e70288535bc57bb624792229b9dc94991d.
66
//!
77
//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
88
//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
@@ -58,7 +58,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
5858
ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
5959

6060
// Macros:
61-
ungated!(derive, Normal, template!(List: "Trait1, Trait2, ...")),
6261
ungated!(automatically_derived, Normal, template!(Word)),
6362
// FIXME(#14407)
6463
ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),
@@ -98,8 +97,8 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
9897
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#),
9998
),
10099
ungated!(link_name, AssumedUsed, template!(NameValueStr: "name")),
101-
ungated!(no_link, Normal, template!(Word)),
102-
ungated!(repr, Normal, template!(List: "C")),
100+
ungated!(no_link, AssumedUsed, template!(Word)),
101+
ungated!(repr, AssumedUsed, template!(List: "C")),
103102
ungated!(export_name, AssumedUsed, template!(NameValueStr: "name")),
104103
ungated!(link_section, AssumedUsed, template!(NameValueStr: "name")),
105104
ungated!(no_mangle, AssumedUsed, template!(Word)),
@@ -112,6 +111,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
112111
const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit,
113112
experimental!(const_eval_limit)
114113
),
114+
gated!(
115+
move_size_limit, CrateLevel, template!(NameValueStr: "N"), large_assignments,
116+
experimental!(move_size_limit)
117+
),
115118

116119
// Entry point:
117120
ungated!(main, Normal, template!(Word)),
@@ -140,6 +143,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
140143
template!(List: "address, memory, thread"),
141144
experimental!(no_sanitize)
142145
),
146+
gated!(no_coverage, AssumedUsed, template!(Word), experimental!(no_coverage)),
143147

144148
// FIXME: #14408 assume docs are used since rustdoc looks at them.
145149
ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),
@@ -150,11 +154,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
150154

151155
// Linking:
152156
gated!(naked, AssumedUsed, template!(Word), naked_functions, experimental!(naked)),
153-
gated!(
154-
link_args, Normal, template!(NameValueStr: "args"),
155-
"the `link_args` attribute is experimental and not portable across platforms, \
156-
it is recommended to use `#[link(name = \"foo\")] instead",
157-
),
158157
gated!(
159158
link_ordinal, AssumedUsed, template!(List: "ordinal"), raw_dylib,
160159
experimental!(link_ordinal)
@@ -172,7 +171,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
172171
"custom test frameworks are an unstable feature",
173172
),
174173
// RFC #1268
175-
gated!(marker, Normal, template!(Word), marker_trait_attr, experimental!(marker)),
174+
gated!(marker, AssumedUsed, template!(Word), marker_trait_attr, experimental!(marker)),
176175
gated!(
177176
thread_local, AssumedUsed, template!(Word),
178177
"`#[thread_local]` is an experimental feature, and does not currently handle destructors",
@@ -291,7 +290,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
291290
// Internal attributes, Macro related:
292291
// ==========================================================================
293292

294-
rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word), IMPL_DETAIL),
293+
rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word, NameValueStr: "name"), IMPL_DETAIL),
295294
rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
296295
rustc_attr!(
297296
rustc_macro_transparency, AssumedUsed,
@@ -319,7 +318,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
319318
// ==========================================================================
320319

321320
rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL),
322-
rustc_attr!(rustc_args_required_const, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
321+
rustc_attr!(rustc_legacy_const_generics, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
323322

324323
// ==========================================================================
325324
// Internal attributes, Layout related:
@@ -380,13 +379,23 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
380379
rustc_specialization_trait, Normal, template!(Word),
381380
"the `#[rustc_specialization_trait]` attribute is used to check specializations"
382381
),
382+
rustc_attr!(
383+
rustc_main, Normal, template!(Word),
384+
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
385+
),
386+
rustc_attr!(
387+
rustc_skip_array_during_method_dispatch, Normal, template!(Word),
388+
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
389+
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
390+
),
383391

384392
// ==========================================================================
385393
// Internal attributes, Testing:
386394
// ==========================================================================
387395

388396
rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)),
389397
rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)),
398+
rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word)),
390399
rustc_attr!(TEST, rustc_variance, Normal, template!(Word)),
391400
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")),
392401
rustc_attr!(TEST, rustc_regions, Normal, template!(Word)),
@@ -395,12 +404,9 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
395404
template!(Word, List: "delay_span_bug_from_inside_query")
396405
),
397406
rustc_attr!(TEST, rustc_dump_user_substs, AssumedUsed, template!(Word)),
407+
rustc_attr!(TEST, rustc_evaluate_where_clauses, AssumedUsed, template!(Word)),
398408
rustc_attr!(TEST, rustc_if_this_changed, AssumedUsed, template!(Word, List: "DepNode")),
399409
rustc_attr!(TEST, rustc_then_this_would_need, AssumedUsed, template!(List: "DepNode")),
400-
rustc_attr!(
401-
TEST, rustc_dirty, AssumedUsed,
402-
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
403-
),
404410
rustc_attr!(
405411
TEST, rustc_clean, AssumedUsed,
406412
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),

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;

0 commit comments

Comments
 (0)