aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-06-03 17:17:25 +0100
committerGitHub <[email protected]>2021-06-03 17:17:25 +0100
commit14153671caaca852c94bd1d0d7f279acb8eb1913 (patch)
treeeb377999a5e8677c210791440b18593f909198e6
parent7f9c4a59d9a84cd8c734286937439b5cd215be27 (diff)
parent17565f4deafab800d8d87208cff1e27d028e9b0e (diff)
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 https://github.com/rust-analyzer/rust-analyzer/issues/8971 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/8964 / https://github.com/la10736/rstest/issues/120 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/2984 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/5412 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6029 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6687 https://github.com/rust-analyzer/rust-analyzer/issues/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]>
-rw-r--r--crates/hir/src/lib.rs16
-rw-r--r--crates/hir_def/src/builtin_attr.rs38
-rw-r--r--crates/hir_def/src/db.rs3
-rw-r--r--crates/hir_def/src/lib.rs40
-rw-r--r--crates/hir_def/src/nameres/collector.rs60
-rw-r--r--crates/hir_def/src/test_db.rs9
-rw-r--r--crates/hir_expand/src/db.rs11
-rw-r--r--crates/hir_expand/src/input.rs19
-rw-r--r--crates/hir_expand/src/lib.rs24
-rw-r--r--crates/hir_expand/src/proc_macro.rs7
-rw-r--r--crates/hir_ty/src/test_db.rs10
-rw-r--r--crates/ide_db/src/lib.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs6
-rw-r--r--crates/rust-analyzer/src/global_state.rs9
-rw-r--r--crates/rust-analyzer/src/reload.rs6
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
17 files changed, 234 insertions, 35 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index d3ef29db4..b43d61d0e 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -534,6 +534,18 @@ impl Module {
534 Some(derive_name.clone()), 534 Some(derive_name.clone()),
535 ) 535 )
536 } 536 }
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 }
537 }; 549 };
538 sink.push(UnresolvedProcMacro { 550 sink.push(UnresolvedProcMacro {
539 file, 551 file,
@@ -558,7 +570,9 @@ impl Module {
558 let node = ast_id.to_node(db.upcast()); 570 let node = ast_id.to_node(db.upcast());
559 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) 571 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
560 } 572 }
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
562 let node = ast_id.to_node(db.upcast()); 576 let node = ast_id.to_node(db.upcast());
563 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) 577 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
564 } 578 }
diff --git a/crates/hir_def/src/builtin_attr.rs b/crates/hir_def/src/builtin_attr.rs
index d5d7f0f47..39c7f84f7 100644
--- a/crates/hir_def/src/builtin_attr.rs
+++ b/crates/hir_def/src/builtin_attr.rs
@@ -2,7 +2,7 @@
2//! 2//!
3//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`. 3//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
4//! 4//!
5//! It was last synchronized with upstream commit 2225ee1b62ff089917434aefd9b2bf509cfa087f. 5//! It was last synchronized with upstream commit 835150e70288535bc57bb624792229b9dc94991d.
6//! 6//!
7//! The macros were adjusted to only expand to the attribute name, since that is all we need to do 7//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
8//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to 8//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
@@ -58,7 +58,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
58 ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")), 58 ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
59 59
60 // Macros: 60 // Macros:
61 ungated!(derive, Normal, template!(List: "Trait1, Trait2, ...")),
62 ungated!(automatically_derived, Normal, template!(Word)), 61 ungated!(automatically_derived, Normal, template!(Word)),
63 // FIXME(#14407) 62 // FIXME(#14407)
64 ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")), 63 ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),
@@ -98,8 +97,8 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
98 template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#), 97 template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#),
99 ), 98 ),
100 ungated!(link_name, AssumedUsed, template!(NameValueStr: "name")), 99 ungated!(link_name, AssumedUsed, template!(NameValueStr: "name")),
101 ungated!(no_link, Normal, template!(Word)), 100 ungated!(no_link, AssumedUsed, template!(Word)),
102 ungated!(repr, Normal, template!(List: "C")), 101 ungated!(repr, AssumedUsed, template!(List: "C")),
103 ungated!(export_name, AssumedUsed, template!(NameValueStr: "name")), 102 ungated!(export_name, AssumedUsed, template!(NameValueStr: "name")),
104 ungated!(link_section, AssumedUsed, template!(NameValueStr: "name")), 103 ungated!(link_section, AssumedUsed, template!(NameValueStr: "name")),
105 ungated!(no_mangle, AssumedUsed, template!(Word)), 104 ungated!(no_mangle, AssumedUsed, template!(Word)),
@@ -112,6 +111,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
112 const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit, 111 const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit,
113 experimental!(const_eval_limit) 112 experimental!(const_eval_limit)
114 ), 113 ),
114 gated!(
115 move_size_limit, CrateLevel, template!(NameValueStr: "N"), large_assignments,
116 experimental!(move_size_limit)
117 ),
115 118
116 // Entry point: 119 // Entry point:
117 ungated!(main, Normal, template!(Word)), 120 ungated!(main, Normal, template!(Word)),
@@ -140,6 +143,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
140 template!(List: "address, memory, thread"), 143 template!(List: "address, memory, thread"),
141 experimental!(no_sanitize) 144 experimental!(no_sanitize)
142 ), 145 ),
146 gated!(no_coverage, AssumedUsed, template!(Word), experimental!(no_coverage)),
143 147
144 // FIXME: #14408 assume docs are used since rustdoc looks at them. 148 // FIXME: #14408 assume docs are used since rustdoc looks at them.
145 ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")), 149 ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),
@@ -151,11 +155,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
151 // Linking: 155 // Linking:
152 gated!(naked, AssumedUsed, template!(Word), naked_functions, experimental!(naked)), 156 gated!(naked, AssumedUsed, template!(Word), naked_functions, experimental!(naked)),
153 gated!( 157 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 ),
158 gated!(
159 link_ordinal, AssumedUsed, template!(List: "ordinal"), raw_dylib, 158 link_ordinal, AssumedUsed, template!(List: "ordinal"), raw_dylib,
160 experimental!(link_ordinal) 159 experimental!(link_ordinal)
161 ), 160 ),
@@ -172,7 +171,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
172 "custom test frameworks are an unstable feature", 171 "custom test frameworks are an unstable feature",
173 ), 172 ),
174 // RFC #1268 173 // RFC #1268
175 gated!(marker, Normal, template!(Word), marker_trait_attr, experimental!(marker)), 174 gated!(marker, AssumedUsed, template!(Word), marker_trait_attr, experimental!(marker)),
176 gated!( 175 gated!(
177 thread_local, AssumedUsed, template!(Word), 176 thread_local, AssumedUsed, template!(Word),
178 "`#[thread_local]` is an experimental feature, and does not currently handle destructors", 177 "`#[thread_local]` is an experimental feature, and does not currently handle destructors",
@@ -291,7 +290,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
291 // Internal attributes, Macro related: 290 // Internal attributes, Macro related:
292 // ========================================================================== 291 // ==========================================================================
293 292
294 rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word), IMPL_DETAIL), 293 rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word, NameValueStr: "name"), IMPL_DETAIL),
295 rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE), 294 rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
296 rustc_attr!( 295 rustc_attr!(
297 rustc_macro_transparency, AssumedUsed, 296 rustc_macro_transparency, AssumedUsed,
@@ -319,7 +318,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
319 // ========================================================================== 318 // ==========================================================================
320 319
321 rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL), 320 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),
323 322
324 // ========================================================================== 323 // ==========================================================================
325 // Internal attributes, Layout related: 324 // Internal attributes, Layout related:
@@ -380,6 +379,15 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
380 rustc_specialization_trait, Normal, template!(Word), 379 rustc_specialization_trait, Normal, template!(Word),
381 "the `#[rustc_specialization_trait]` attribute is used to check specializations" 380 "the `#[rustc_specialization_trait]` attribute is used to check specializations"
382 ), 381 ),
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 ),
383 391
384 // ========================================================================== 392 // ==========================================================================
385 // Internal attributes, Testing: 393 // Internal attributes, Testing:
@@ -387,6 +395,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
387 395
388 rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)), 396 rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)),
389 rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)), 397 rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)),
398 rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word)),
390 rustc_attr!(TEST, rustc_variance, Normal, template!(Word)), 399 rustc_attr!(TEST, rustc_variance, Normal, template!(Word)),
391 rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")), 400 rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")),
392 rustc_attr!(TEST, rustc_regions, Normal, template!(Word)), 401 rustc_attr!(TEST, rustc_regions, Normal, template!(Word)),
@@ -395,13 +404,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
395 template!(Word, List: "delay_span_bug_from_inside_query") 404 template!(Word, List: "delay_span_bug_from_inside_query")
396 ), 405 ),
397 rustc_attr!(TEST, rustc_dump_user_substs, AssumedUsed, template!(Word)), 406 rustc_attr!(TEST, rustc_dump_user_substs, AssumedUsed, template!(Word)),
407 rustc_attr!(TEST, rustc_evaluate_where_clauses, AssumedUsed, template!(Word)),
398 rustc_attr!(TEST, rustc_if_this_changed, AssumedUsed, template!(Word, List: "DepNode")), 408 rustc_attr!(TEST, rustc_if_this_changed, AssumedUsed, template!(Word, List: "DepNode")),
399 rustc_attr!(TEST, rustc_then_this_would_need, AssumedUsed, template!(List: "DepNode")), 409 rustc_attr!(TEST, rustc_then_this_would_need, AssumedUsed, template!(List: "DepNode")),
400 rustc_attr!( 410 rustc_attr!(
401 TEST, rustc_dirty, AssumedUsed,
402 template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
403 ),
404 rustc_attr!(
405 TEST, rustc_clean, AssumedUsed, 411 TEST, rustc_clean, AssumedUsed,
406 template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#), 412 template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
407 ), 413 ),
diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs
index 7eadc8e0d..c977971cd 100644
--- a/crates/hir_def/src/db.rs
+++ b/crates/hir_def/src/db.rs
@@ -51,6 +51,9 @@ pub trait InternDatabase: SourceDatabase {
51 51
52#[salsa::query_group(DefDatabaseStorage)] 52#[salsa::query_group(DefDatabaseStorage)]
53pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { 53pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
54 #[salsa::input]
55 fn enable_proc_attr_macros(&self) -> bool;
56
54 #[salsa::invoke(ItemTree::file_item_tree_query)] 57 #[salsa::invoke(ItemTree::file_item_tree_query)]
55 fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>; 58 fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
56 59
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 9aa95720a..987485acc 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -55,6 +55,7 @@ use std::{
55 sync::Arc, 55 sync::Arc,
56}; 56};
57 57
58use attr::Attr;
58use base_db::{impl_intern_key, salsa, CrateId}; 59use base_db::{impl_intern_key, salsa, CrateId};
59use hir_expand::{ 60use hir_expand::{
60 ast_id_map::FileAstId, 61 ast_id_map::FileAstId,
@@ -768,3 +769,42 @@ fn derive_macro_as_call_id(
768 .into(); 769 .into();
769 Ok(res) 770 Ok(res)
770} 771}
772
773fn 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}
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 6b41921ae..b2ce739bd 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -23,7 +23,7 @@ use syntax::ast;
23 23
24use crate::{ 24use crate::{
25 attr::{Attr, AttrId, AttrInput, Attrs}, 25 attr::{Attr, AttrId, AttrInput, Attrs},
26 builtin_attr, 26 attr_macro_as_call_id, builtin_attr,
27 db::DefDatabase, 27 db::DefDatabase,
28 derive_macro_as_call_id, 28 derive_macro_as_call_id,
29 intern::Interned, 29 intern::Interned,
@@ -223,7 +223,7 @@ struct MacroDirective {
223enum MacroDirectiveKind { 223enum MacroDirectiveKind {
224 FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind }, 224 FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
225 Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId }, 225 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 },
227} 227}
228 228
229struct DefData<'a> { 229struct DefData<'a> {
@@ -419,7 +419,7 @@ impl DefCollector<'_> {
419 let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new()); 419 let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
420 let pos = unresolved_macros.iter().position(|directive| { 420 let pos = unresolved_macros.iter().position(|directive| {
421 if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind { 421 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);
423 423
424 let file_id = ast_id.ast_id.file_id; 424 let file_id = ast_id.ast_id.file_id;
425 let item_tree = self.db.file_item_tree(file_id); 425 let item_tree = self.db.file_item_tree(file_id);
@@ -1050,7 +1050,7 @@ impl DefCollector<'_> {
1050 let file_id = ast_id.ast_id.file_id; 1050 let file_id = ast_id.ast_id.file_id;
1051 let item_tree = self.db.file_item_tree(file_id); 1051 let item_tree = self.db.file_item_tree(file_id);
1052 let mod_dir = self.mod_dirs[&directive.module_id].clone(); 1052 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);
1054 ModCollector { 1054 ModCollector {
1055 def_collector: &mut *self, 1055 def_collector: &mut *self,
1056 macro_depth: directive.depth, 1056 macro_depth: directive.depth,
@@ -1067,8 +1067,56 @@ impl DefCollector<'_> {
1067 } 1067 }
1068 } 1068 }
1069 1069
1070 if !self.db.enable_proc_attr_macros() {
1071 return true;
1072 }
1073
1070 // Not resolved to a derive helper, so try to resolve as a macro. 1074 // 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 }
1072 } 1120 }
1073 } 1121 }
1074 1122
@@ -1628,7 +1676,7 @@ impl ModCollector<'_, '_> {
1628 self.def_collector.unresolved_macros.push(MacroDirective { 1676 self.def_collector.unresolved_macros.push(MacroDirective {
1629 module_id: self.module_id, 1677 module_id: self.module_id,
1630 depth: self.macro_depth + 1, 1678 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 },
1632 }); 1680 });
1633 1681
1634 return Err(()); 1682 return Err(());
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index e840fe5e8..b20b066e2 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -30,12 +30,19 @@ use crate::{
30 crate::db::InternDatabaseStorage, 30 crate::db::InternDatabaseStorage,
31 crate::db::DefDatabaseStorage 31 crate::db::DefDatabaseStorage
32)] 32)]
33#[derive(Default)]
34pub(crate) struct TestDB { 33pub(crate) struct TestDB {
35 storage: salsa::Storage<TestDB>, 34 storage: salsa::Storage<TestDB>,
36 events: Mutex<Option<Vec<salsa::Event>>>, 35 events: Mutex<Option<Vec<salsa::Event>>>,
37} 36}
38 37
38impl 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
39impl Upcast<dyn AstDatabase> for TestDB { 46impl Upcast<dyn AstDatabase> for TestDB {
40 fn upcast(&self) -> &(dyn AstDatabase + 'static) { 47 fn upcast(&self) -> &(dyn AstDatabase + 'static) {
41 &*self 48 &*self
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index e8f4af309..3ebe194e4 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -13,8 +13,8 @@ use syntax::{
13 13
14use crate::{ 14use crate::{
15 ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander, 15 ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
16 BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId, 16 BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc,
17 MacroDefKind, MacroFile, ProcMacroExpander, 17 MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
18}; 18};
19 19
20/// Total limit on the number of tokens produced by any macro invocation. 20/// Total limit on the number of tokens produced by any macro invocation.
@@ -377,7 +377,12 @@ fn expand_proc_macro(
377 _ => unreachable!(), 377 _ => unreachable!(),
378 }; 378 };
379 379
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)
381} 386}
382 387
383fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool { 388fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
diff --git a/crates/hir_expand/src/input.rs b/crates/hir_expand/src/input.rs
index fe4790e7b..40116a479 100644
--- a/crates/hir_expand/src/input.rs
+++ b/crates/hir_expand/src/input.rs
@@ -28,6 +28,14 @@ pub(crate) fn process_macro_input(
28 28
29 remove_derives_up_to(item, derive_attr_index as usize).syntax().clone() 29 remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
30 } 30 }
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 }
31 } 39 }
32} 40}
33 41
@@ -46,6 +54,17 @@ fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
46 item 54 item
47} 55}
48 56
57/// Removes the attribute invoking an attribute macro from `item`.
58fn 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
49#[cfg(test)] 68#[cfg(test)]
50mod tests { 69mod tests {
51 use base_db::fixture::WithFixture; 70 use base_db::fixture::WithFixture;
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 90d8ae240..618f26b95 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -258,14 +258,29 @@ pub enum MacroCallKind {
258 /// out-of-line modules, which may have attributes spread across 2 files! 258 /// out-of-line modules, which may have attributes spread across 2 files!
259 derive_attr_index: u32, 259 derive_attr_index: u32,
260 }, 260 },
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 },
261} 271}
262 272
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
263impl MacroCallKind { 276impl MacroCallKind {
264 /// Returns the file containing the macro invocation. 277 /// Returns the file containing the macro invocation.
265 fn file_id(&self) -> HirFileId { 278 fn file_id(&self) -> HirFileId {
266 match self { 279 match self {
267 MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id, 280 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 }
269 } 284 }
270 } 285 }
271 286
@@ -274,7 +289,7 @@ impl MacroCallKind {
274 MacroCallKind::FnLike { ast_id, .. } => { 289 MacroCallKind::FnLike { ast_id, .. } => {
275 ast_id.with_value(ast_id.to_node(db).syntax().clone()) 290 ast_id.with_value(ast_id.to_node(db).syntax().clone())
276 } 291 }
277 MacroCallKind::Derive { ast_id, .. } => { 292 MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
278 ast_id.with_value(ast_id.to_node(db).syntax().clone()) 293 ast_id.with_value(ast_id.to_node(db).syntax().clone())
279 } 294 }
280 } 295 }
@@ -285,7 +300,9 @@ impl MacroCallKind {
285 MacroCallKind::FnLike { ast_id, .. } => { 300 MacroCallKind::FnLike { ast_id, .. } => {
286 Some(ast_id.to_node(db).token_tree()?.syntax().clone()) 301 Some(ast_id.to_node(db).token_tree()?.syntax().clone())
287 } 302 }
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 }
289 } 306 }
290 } 307 }
291 308
@@ -293,6 +310,7 @@ impl MacroCallKind {
293 match self { 310 match self {
294 MacroCallKind::FnLike { fragment, .. } => *fragment, 311 MacroCallKind::FnLike { fragment, .. } => *fragment,
295 MacroCallKind::Derive { .. } => FragmentKind::Items, 312 MacroCallKind::Derive { .. } => FragmentKind::Items,
313 MacroCallKind::Attr { .. } => FragmentKind::Items, // is this always correct?
296 } 314 }
297 } 315 }
298} 316}
diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs
index d5643393a..dbe1b446e 100644
--- a/crates/hir_expand/src/proc_macro.rs
+++ b/crates/hir_expand/src/proc_macro.rs
@@ -28,11 +28,16 @@ impl ProcMacroExpander {
28 Self { krate, proc_macro_id: None } 28 Self { krate, proc_macro_id: None }
29 } 29 }
30 30
31 pub fn is_dummy(&self) -> bool {
32 self.proc_macro_id.is_none()
33 }
34
31 pub fn expand( 35 pub fn expand(
32 self, 36 self,
33 db: &dyn AstDatabase, 37 db: &dyn AstDatabase,
34 calling_crate: CrateId, 38 calling_crate: CrateId,
35 tt: &tt::Subtree, 39 tt: &tt::Subtree,
40 attr_arg: Option<&tt::Subtree>,
36 ) -> Result<tt::Subtree, mbe::ExpandError> { 41 ) -> Result<tt::Subtree, mbe::ExpandError> {
37 match self.proc_macro_id { 42 match self.proc_macro_id {
38 Some(id) => { 43 Some(id) => {
@@ -46,7 +51,7 @@ impl ProcMacroExpander {
46 // Proc macros have access to the environment variables of the invoking crate. 51 // Proc macros have access to the environment variables of the invoking crate.
47 let env = &krate_graph[calling_crate].env; 52 let env = &krate_graph[calling_crate].env;
48 53
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)
50 } 55 }
51 None => Err(mbe::ExpandError::UnresolvedProcMacro), 56 None => Err(mbe::ExpandError::UnresolvedProcMacro),
52 } 57 }
diff --git a/crates/hir_ty/src/test_db.rs b/crates/hir_ty/src/test_db.rs
index 381b98ba8..4640ea821 100644
--- a/crates/hir_ty/src/test_db.rs
+++ b/crates/hir_ty/src/test_db.rs
@@ -22,11 +22,19 @@ use test_utils::extract_annotations;
22 hir_def::db::DefDatabaseStorage, 22 hir_def::db::DefDatabaseStorage,
23 crate::db::HirDatabaseStorage 23 crate::db::HirDatabaseStorage
24)] 24)]
25#[derive(Default)]
26pub(crate) struct TestDB { 25pub(crate) struct TestDB {
27 storage: salsa::Storage<TestDB>, 26 storage: salsa::Storage<TestDB>,
28 events: Mutex<Option<Vec<salsa::Event>>>, 27 events: Mutex<Option<Vec<salsa::Event>>>,
29} 28}
29
30impl Default for TestDB {
31 fn default() -> Self {
32 let mut this = Self { storage: Default::default(), events: Default::default() };
33 this.set_enable_proc_attr_macros(true);
34 this
35 }
36}
37
30impl fmt::Debug for TestDB { 38impl fmt::Debug for TestDB {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.debug_struct("TestDB").finish() 40 f.debug_struct("TestDB").finish()
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 1f900aef4..105607dca 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -93,6 +93,7 @@ impl RootDatabase {
93 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); 93 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
94 db.set_local_roots_with_durability(Default::default(), Durability::HIGH); 94 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
95 db.set_library_roots_with_durability(Default::default(), Durability::HIGH); 95 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
96 db.set_enable_proc_attr_macros(Default::default());
96 db.update_lru_capacity(lru_capacity); 97 db.update_lru_capacity(lru_capacity);
97 db 98 db
98 } 99 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index c33cdb740..d1f3c1b06 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -126,6 +126,9 @@ config_data! {
126 /// and a blue icon in the `Problems Panel`. 126 /// and a blue icon in the `Problems Panel`.
127 diagnostics_warningsAsInfo: Vec<String> = "[]", 127 diagnostics_warningsAsInfo: Vec<String> = "[]",
128 128
129 /// Expand attribute macros.
130 experimental_procAttrMacros: bool = "false",
131
129 /// Controls file watching implementation. 132 /// Controls file watching implementation.
130 files_watcher: String = "\"client\"", 133 files_watcher: String = "\"client\"",
131 /// These directories will be ignored by rust-analyzer. 134 /// These directories will be ignored by rust-analyzer.
@@ -546,6 +549,9 @@ impl Config {
546 let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?; 549 let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?;
547 Some((path, vec!["proc-macro".into()])) 550 Some((path, vec!["proc-macro".into()]))
548 } 551 }
552 pub fn expand_proc_attr_macros(&self) -> bool {
553 self.data.experimental_procAttrMacros
554 }
549 pub fn files(&self) -> FilesConfig { 555 pub fn files(&self) -> FilesConfig {
550 FilesConfig { 556 FilesConfig {
551 watcher: match self.data.files_watcher.as_str() { 557 watcher: match self.data.files_watcher.as_str() {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index ea9dbf7fc..582a89667 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -119,12 +119,12 @@ impl GlobalState {
119 119
120 let analysis_host = AnalysisHost::new(config.lru_capacity()); 120 let analysis_host = AnalysisHost::new(config.lru_capacity());
121 let (flycheck_sender, flycheck_receiver) = unbounded(); 121 let (flycheck_sender, flycheck_receiver) = unbounded();
122 GlobalState { 122 let mut this = GlobalState {
123 sender, 123 sender,
124 req_queue: ReqQueue::default(), 124 req_queue: ReqQueue::default(),
125 task_pool, 125 task_pool,
126 loader, 126 loader,
127 config: Arc::new(config), 127 config: Arc::new(config.clone()),
128 analysis_host, 128 analysis_host,
129 diagnostics: Default::default(), 129 diagnostics: Default::default(),
130 mem_docs: FxHashMap::default(), 130 mem_docs: FxHashMap::default(),
@@ -151,7 +151,10 @@ impl GlobalState {
151 151
152 fetch_build_data_queue: OpQueue::default(), 152 fetch_build_data_queue: OpQueue::default(),
153 latest_requests: Default::default(), 153 latest_requests: Default::default(),
154 } 154 };
155 // Apply any required database inputs from the config.
156 this.update_configuration(config);
157 this
155 } 158 }
156 159
157 pub(crate) fn process_changes(&mut self) -> bool { 160 pub(crate) fn process_changes(&mut self) -> bool {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 93b5ff55f..bd31d1d13 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -2,6 +2,7 @@
2use std::{mem, sync::Arc}; 2use std::{mem, sync::Arc};
3 3
4use flycheck::{FlycheckConfig, FlycheckHandle}; 4use flycheck::{FlycheckConfig, FlycheckHandle};
5use hir::db::DefDatabase;
5use ide::Change; 6use ide::Change;
6use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath}; 7use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
7use project_model::{BuildDataCollector, BuildDataResult, ProcMacroClient, ProjectWorkspace}; 8use project_model::{BuildDataCollector, BuildDataResult, ProcMacroClient, ProjectWorkspace};
@@ -47,6 +48,11 @@ impl GlobalState {
47 } else if self.config.flycheck() != old_config.flycheck() { 48 } else if self.config.flycheck() != old_config.flycheck() {
48 self.reload_flycheck(); 49 self.reload_flycheck();
49 } 50 }
51
52 // Apply experimental feature flags.
53 self.analysis_host
54 .raw_database_mut()
55 .set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
50 } 56 }
51 pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) { 57 pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
52 if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) { 58 if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 7f405b4d7..4eec8455d 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -181,6 +181,11 @@ List of warnings that should be displayed with info severity.
181The warnings will be indicated by a blue squiggly underline in code 181The warnings will be indicated by a blue squiggly underline in code
182and a blue icon in the `Problems Panel`. 182and a blue icon in the `Problems Panel`.
183-- 183--
184[[rust-analyzer.experimental.procAttrMacros]]rust-analyzer.experimental.procAttrMacros (default: `false`)::
185+
186--
187Expand attribute macros.
188--
184[[rust-analyzer.files.watcher]]rust-analyzer.files.watcher (default: `"client"`):: 189[[rust-analyzer.files.watcher]]rust-analyzer.files.watcher (default: `"client"`)::
185+ 190+
186-- 191--
diff --git a/editors/code/package.json b/editors/code/package.json
index 4a5070d02..bffc1e05b 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -617,6 +617,11 @@
617 "type": "string" 617 "type": "string"
618 } 618 }
619 }, 619 },
620 "rust-analyzer.experimental.procAttrMacros": {
621 "markdownDescription": "Expand attribute macros.",
622 "default": false,
623 "type": "boolean"
624 },
620 "rust-analyzer.files.watcher": { 625 "rust-analyzer.files.watcher": {
621 "markdownDescription": "Controls file watching implementation.", 626 "markdownDescription": "Controls file watching implementation.",
622 "default": "client", 627 "default": "client",