Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21121 from Zalathar/derive-macro
Basic support for declarative attribute/derive macros
Chayim Refael Friedman 5 months ago
parent 0e899d5 · parent fe1469e · commit b5b4aba
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir-def/src/db.rs2
-rw-r--r--crates/hir-def/src/item_scope.rs17
-rw-r--r--crates/hir-def/src/lib.rs6
-rw-r--r--crates/hir-def/src/nameres.rs55
-rw-r--r--crates/hir-def/src/nameres/collector.rs17
-rw-r--r--crates/hir-def/src/nameres/path_resolution.rs9
-rw-r--r--crates/hir-def/src/nameres/tests/incremental.rs8
-rw-r--r--crates/hir-def/src/nameres/tests/macros.rs129
-rw-r--r--crates/hir-expand/src/db.rs86
-rw-r--r--crates/hir-expand/src/declarative.rs5
-rw-r--r--crates/hir-expand/src/eager.rs2
-rw-r--r--crates/hir-expand/src/lib.rs38
-rw-r--r--crates/hir/src/lib.rs4
-rw-r--r--crates/ide-db/src/test_data/test_symbol_index_collection.txt8
-rw-r--r--crates/intern/src/symbol/symbols.rs1
-rw-r--r--crates/mbe/Cargo.toml1
-rw-r--r--crates/mbe/src/benchmark.rs11
-rw-r--r--crates/mbe/src/expander.rs11
-rw-r--r--crates/mbe/src/lib.rs34
-rw-r--r--crates/mbe/src/macro_call_style.rs32
-rw-r--r--crates/mbe/src/parser.rs29
-rw-r--r--crates/mbe/src/tests.rs1
23 files changed, 382 insertions, 125 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d78c0f765d..2a0d56d72c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1468,6 +1468,7 @@ name = "mbe"
version = "0.0.0"
dependencies = [
"arrayvec",
+ "bitflags 2.9.4",
"cov-mark",
"expect-test",
"intern",
diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs
index 925a078e82..49aafb2b86 100644
--- a/crates/hir-def/src/db.rs
+++ b/crates/hir-def/src/db.rs
@@ -338,7 +338,7 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
let kind = |expander, file_id, m| {
let in_file = InFile::new(file_id, m);
match expander {
- MacroExpander::Declarative => MacroDefKind::Declarative(in_file),
+ MacroExpander::Declarative { styles } => MacroDefKind::Declarative(in_file, styles),
MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(in_file, it),
MacroExpander::BuiltInAttr(it) => MacroDefKind::BuiltInAttr(in_file, it),
MacroExpander::BuiltInDerive(it) => MacroDefKind::BuiltInDerive(in_file, it),
diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs
index 51c42c995c..1bfe649ebd 100644
--- a/crates/hir-def/src/item_scope.rs
+++ b/crates/hir-def/src/item_scope.rs
@@ -17,9 +17,8 @@ use thin_vec::ThinVec;
use crate::{
AdtId, BuiltinType, ConstId, ExternBlockId, ExternCrateId, FxIndexMap, HasModule, ImplId,
- LocalModuleId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId,
+ LocalModuleId, Lookup, MacroCallStyles, MacroId, ModuleDefId, ModuleId, TraitId, UseId,
db::DefDatabase,
- nameres::MacroSubNs,
per_ns::{Item, MacrosItem, PerNs, TypesItem, ValuesItem},
visibility::Visibility,
};
@@ -740,11 +739,15 @@ impl ItemScope {
let mut entries: Vec<_> = self.resolutions().collect();
entries.sort_by_key(|(name, _)| name.clone());
- let print_macro_sub_ns =
- |buf: &mut String, macro_id: MacroId| match MacroSubNs::from_id(db, macro_id) {
- MacroSubNs::Bang => buf.push('!'),
- MacroSubNs::Attr => buf.push('#'),
- };
+ let print_macro_sub_ns = |buf: &mut String, macro_id: MacroId| {
+ let styles = crate::nameres::macro_styles_from_id(db, macro_id);
+ if styles.contains(MacroCallStyles::FN_LIKE) {
+ buf.push('!');
+ }
+ if styles.contains(MacroCallStyles::ATTR) || styles.contains(MacroCallStyles::DERIVE) {
+ buf.push('#');
+ }
+ };
for (name, def) in entries {
let display_name: &dyn fmt::Display = match &name {
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index e5c213ca93..52d99911ac 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -61,8 +61,8 @@ use std::hash::{Hash, Hasher};
use base_db::{Crate, impl_intern_key};
use hir_expand::{
- AstId, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId,
- MacroDefKind,
+ AstId, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallStyles,
+ MacroDefId, MacroDefKind,
builtin::{BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander},
db::ExpandDatabase,
eager::expand_eager_macro_input,
@@ -403,7 +403,7 @@ bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MacroExpander {
- Declarative,
+ Declarative { styles: MacroCallStyles },
BuiltIn(BuiltinFnLikeExpander),
BuiltInAttr(BuiltinAttrExpander),
BuiltInDerive(BuiltinDeriveExpander),
diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index f44187ec59..f910008833 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/crates/hir-def/src/nameres.rs
@@ -77,7 +77,7 @@ use tt::TextRange;
use crate::{
AstId, BlockId, BlockLoc, CrateRootModuleId, ExternCrateId, FunctionId, FxIndexMap,
- LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId,
+ LocalModuleId, Lookup, MacroCallStyles, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId,
db::DefDatabase,
item_scope::{BuiltinShadowMode, ItemScope},
item_tree::TreeId,
@@ -813,26 +813,25 @@ pub enum MacroSubNs {
Attr,
}
-impl MacroSubNs {
- pub(crate) fn from_id(db: &dyn DefDatabase, macro_id: MacroId) -> Self {
- let expander = match macro_id {
- MacroId::Macro2Id(it) => it.lookup(db).expander,
- MacroId::MacroRulesId(it) => it.lookup(db).expander,
- MacroId::ProcMacroId(it) => {
- return match it.lookup(db).kind {
- ProcMacroKind::CustomDerive | ProcMacroKind::Attr => Self::Attr,
- ProcMacroKind::Bang => Self::Bang,
- };
- }
- };
+pub(crate) fn macro_styles_from_id(db: &dyn DefDatabase, macro_id: MacroId) -> MacroCallStyles {
+ let expander = match macro_id {
+ MacroId::Macro2Id(it) => it.lookup(db).expander,
+ MacroId::MacroRulesId(it) => it.lookup(db).expander,
+ MacroId::ProcMacroId(it) => {
+ return match it.lookup(db).kind {
+ ProcMacroKind::CustomDerive => MacroCallStyles::DERIVE,
+ ProcMacroKind::Bang => MacroCallStyles::FN_LIKE,
+ ProcMacroKind::Attr => MacroCallStyles::ATTR,
+ };
+ }
+ };
+ match expander {
+ MacroExpander::Declarative { styles } => styles,
// Eager macros aren't *guaranteed* to be bang macros, but they *are* all bang macros currently.
- match expander {
- MacroExpander::Declarative
- | MacroExpander::BuiltIn(_)
- | MacroExpander::BuiltInEager(_) => Self::Bang,
- MacroExpander::BuiltInAttr(_) | MacroExpander::BuiltInDerive(_) => Self::Attr,
- }
+ MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => MacroCallStyles::FN_LIKE,
+ MacroExpander::BuiltInAttr(_) => MacroCallStyles::ATTR,
+ MacroExpander::BuiltInDerive(_) => MacroCallStyles::DERIVE,
}
}
@@ -842,9 +841,19 @@ impl MacroSubNs {
/// We ignore resolutions from one sub-namespace when searching names in scope for another.
///
/// [rustc]: https://github.com/rust-lang/rust/blob/1.69.0/compiler/rustc_resolve/src/macros.rs#L75
-fn sub_namespace_match(candidate: Option<MacroSubNs>, expected: Option<MacroSubNs>) -> bool {
- match (candidate, expected) {
- (Some(candidate), Some(expected)) => candidate == expected,
- _ => true,
+fn sub_namespace_match(
+ db: &dyn DefDatabase,
+ macro_id: MacroId,
+ expected: Option<MacroSubNs>,
+) -> bool {
+ let candidate = macro_styles_from_id(db, macro_id);
+ match expected {
+ Some(MacroSubNs::Bang) => candidate.contains(MacroCallStyles::FN_LIKE),
+ Some(MacroSubNs::Attr) => {
+ candidate.contains(MacroCallStyles::ATTR) || candidate.contains(MacroCallStyles::DERIVE)
+ }
+ // If we aren't expecting a specific sub-namespace
+ // (e.g. in `use` declarations), match any macro.
+ None => true,
}
}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index a2ce538356..a030ed1e0d 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -2300,7 +2300,10 @@ impl ModCollector<'_, '_> {
}
} else {
// Case 2: normal `macro_rules!` macro
- MacroExpander::Declarative
+ let id = InFile::new(self.file_id(), ast_id);
+ let decl_expander = self.def_collector.db.decl_macro_expander(krate, id.upcast());
+ let styles = decl_expander.mac.rule_styles();
+ MacroExpander::Declarative { styles }
};
let allow_internal_unsafe = attrs.by_key(sym::allow_internal_unsafe).exists();
@@ -2369,7 +2372,10 @@ impl ModCollector<'_, '_> {
}
} else {
// Case 2: normal `macro`
- MacroExpander::Declarative
+ let id = InFile::new(self.file_id(), ast_id);
+ let decl_expander = self.def_collector.db.decl_macro_expander(krate, id.upcast());
+ let styles = decl_expander.mac.rule_styles();
+ MacroExpander::Declarative { styles }
};
let allow_internal_unsafe = attrs.by_key(sym::allow_internal_unsafe).exists();
@@ -2429,12 +2435,7 @@ impl ModCollector<'_, '_> {
})
.or_else(|| def_map[self.module_id].scope.get(name).take_macros())
.or_else(|| Some(def_map.macro_use_prelude.get(name).copied()?.0))
- .filter(|&id| {
- sub_namespace_match(
- Some(MacroSubNs::from_id(db, id)),
- Some(MacroSubNs::Bang),
- )
- })
+ .filter(|&id| sub_namespace_match(db, id, Some(MacroSubNs::Bang)))
.map(|it| self.def_collector.db.macro_def(it))
})
},
diff --git a/crates/hir-def/src/nameres/path_resolution.rs b/crates/hir-def/src/nameres/path_resolution.rs
index 4641b220da..184a57410d 100644
--- a/crates/hir-def/src/nameres/path_resolution.rs
+++ b/crates/hir-def/src/nameres/path_resolution.rs
@@ -85,10 +85,7 @@ impl PerNs {
db: &dyn DefDatabase,
expected: Option<MacroSubNs>,
) -> Self {
- self.macros = self.macros.filter(|def| {
- let this = MacroSubNs::from_id(db, def.def);
- sub_namespace_match(Some(this), expected)
- });
+ self.macros = self.macros.filter(|def| sub_namespace_match(db, def.def, expected));
self
}
@@ -668,9 +665,7 @@ impl DefMap {
// FIXME: shadowing
.and_then(|it| it.last())
.copied()
- .filter(|&id| {
- sub_namespace_match(Some(MacroSubNs::from_id(db, id)), expected_macro_subns)
- })
+ .filter(|&id| sub_namespace_match(db, id, expected_macro_subns))
.map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public, None));
let from_scope = self[module].scope.get(name).filter_macro(db, expected_macro_subns);
let from_builtin = match self.block {
diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs
index 6afa04bc41..40283f67cc 100644
--- a/crates/hir-def/src/nameres/tests/incremental.rs
+++ b/crates/hir-def/src/nameres/tests/incremental.rs
@@ -222,6 +222,7 @@ pub struct S {}
"ast_id_map_shim",
"parse_shim",
"real_span_map_shim",
+ "decl_macro_expander_shim",
"file_item_tree_query",
"ast_id_map_shim",
"parse_shim",
@@ -235,7 +236,6 @@ pub struct S {}
"ast_id_map_shim",
"parse_macro_expansion_shim",
"macro_arg_shim",
- "decl_macro_expander_shim",
]
"#]],
expect![[r#"
@@ -404,6 +404,7 @@ pub struct S {}
"ast_id_map_shim",
"parse_shim",
"real_span_map_shim",
+ "decl_macro_expander_shim",
"file_item_tree_query",
"ast_id_map_shim",
"parse_shim",
@@ -423,7 +424,6 @@ pub struct S {}
"ast_id_map_shim",
"parse_macro_expansion_shim",
"macro_arg_shim",
- "decl_macro_expander_shim",
"crate_local_def_map",
"proc_macros_for_crate_shim",
"file_item_tree_query",
@@ -446,9 +446,9 @@ pub struct S {}
"file_item_tree_query",
"real_span_map_shim",
"macro_arg_shim",
- "macro_arg_shim",
"decl_macro_expander_shim",
"macro_arg_shim",
+ "macro_arg_shim",
]
"#]],
);
@@ -520,6 +520,7 @@ m!(Z);
"ast_id_map_shim",
"parse_shim",
"real_span_map_shim",
+ "decl_macro_expander_shim",
"file_item_tree_query",
"ast_id_map_shim",
"parse_shim",
@@ -533,7 +534,6 @@ m!(Z);
"ast_id_map_shim",
"parse_macro_expansion_shim",
"macro_arg_shim",
- "decl_macro_expander_shim",
"file_item_tree_query",
"ast_id_map_shim",
"parse_macro_expansion_shim",
diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs
index 43b6e12e13..a5fd0488e7 100644
--- a/crates/hir-def/src/nameres/tests/macros.rs
+++ b/crates/hir-def/src/nameres/tests/macros.rs
@@ -1651,3 +1651,132 @@ pub mod prelude {
"#]],
);
}
+
+#[test]
+fn macro_rules_mixed_style() {
+ check(
+ r#"
+
+macro_rules! foo {
+ () => {};
+ attr() () => {};
+ derive() () => {};
+}
+
+use foo;
+"#,
+ expect![[r#"
+ crate
+ - foo : macro!# (import)
+ - (legacy) foo : macro!#
+"#]],
+ );
+}
+
+#[test]
+fn macro_2_mixed_style() {
+ check(
+ r#"
+
+macro foo {
+ () => {};
+ attr() () => {};
+ derive() () => {};
+}
+
+use foo;
+"#,
+ expect![[r#"
+ crate
+ - foo : macro!#
+ "#]],
+ );
+}
+
+#[test]
+fn macro_rules_attr() {
+ check(
+ r#"
+
+macro_rules! my_attr {
+ attr() ($($tt:tt)*) => { fn attr_fn() {} }
+}
+
+#[my_attr]
+enum MyEnum {}
+
+"#,
+ expect![[r#"
+ crate
+ - attr_fn : value
+ - (legacy) my_attr : macro#
+"#]],
+ );
+}
+
+#[test]
+fn macro_2_attr() {
+ check(
+ r#"
+
+macro my_attr {
+ attr() ($($tt:tt)*) => { fn attr_fn() {} }
+}
+
+#[my_attr]
+enum MyEnum {}
+
+"#,
+ expect![[r#"
+ crate
+ - attr_fn : value
+ - my_attr : macro#
+"#]],
+ );
+}
+
+#[test]
+fn macro_rules_derive() {
+ check(
+ r#"
+//- minicore: derive
+
+macro_rules! MyDerive {
+ derive() ($($tt:tt)*) => { fn derived_fn() {} }
+}
+
+#[derive(MyDerive)]
+enum MyEnum {}
+
+"#,
+ expect![[r#"
+ crate
+ - MyEnum : type
+ - derived_fn : value
+ - (legacy) MyDerive : macro#
+ "#]],
+ );
+}
+
+#[test]
+fn macro_2_derive() {
+ check(
+ r#"
+//- minicore: derive
+
+macro MyDerive {
+ derive() ($($tt:tt)*) => { fn derived_fn() {} }
+}
+
+#[derive(MyDerive)]
+enum MyEnum {}
+
+"#,
+ expect![[r#"
+ crate
+ - MyDerive : macro#
+ - MyEnum : type
+ - derived_fn : value
+ "#]],
+ );
+}
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 888c1405a6..f9f10c177e 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -297,9 +297,9 @@ pub fn expand_speculative(
MacroDefKind::BuiltInAttr(_, it) if it.is_derive() => {
pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, span)
}
- MacroDefKind::Declarative(it) => {
- db.decl_macro_expander(loc.krate, it).expand_unhygienic(tt, span, loc.def.edition)
- }
+ MacroDefKind::Declarative(it, _) => db
+ .decl_macro_expander(loc.krate, it)
+ .expand_unhygienic(tt, loc.kind.call_style(), span, loc.def.edition),
MacroDefKind::BuiltIn(_, it) => {
it.expand(db, actual_macro_call, &tt, span).map_err(Into::into)
}
@@ -585,7 +585,7 @@ fn attr_source(invoc_attr_index: AttrId, node: &ast::Item) -> Option<ast::Attr>
impl TokenExpander {
fn macro_expander(db: &dyn ExpandDatabase, id: MacroDefId) -> TokenExpander {
match id.kind {
- MacroDefKind::Declarative(ast_id) => {
+ MacroDefKind::Declarative(ast_id, _) => {
TokenExpander::DeclarativeMacro(db.decl_macro_expander(id.krate, ast_id))
}
MacroDefKind::BuiltIn(_, expander) => TokenExpander::BuiltIn(expander),
@@ -618,48 +618,46 @@ fn macro_expand(
db.macro_arg_considering_derives(macro_call_id, &loc.kind);
let arg = &*macro_arg;
- let res =
- match loc.def.kind {
- MacroDefKind::Declarative(id) => db
- .decl_macro_expander(loc.def.krate, id)
- .expand(db, arg.clone(), macro_call_id, span),
- MacroDefKind::BuiltIn(_, it) => {
- it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
- }
- MacroDefKind::BuiltInDerive(_, it) => {
- it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
- }
- MacroDefKind::BuiltInEager(_, it) => {
- // This might look a bit odd, but we do not expand the inputs to eager macros here.
- // Eager macros inputs are expanded, well, eagerly when we collect the macro calls.
- // That kind of expansion uses the ast id map of an eager macros input though which goes through
- // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query
- // will end up going through here again, whereas we want to just want to inspect the raw input.
- // As such we just return the input subtree here.
- let eager = match &loc.kind {
- MacroCallKind::FnLike { eager: None, .. } => {
- return ExpandResult::ok(CowArc::Arc(macro_arg.clone()))
- .zip_val(None);
- }
- MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager),
- _ => None,
- };
-
- let mut res = it.expand(db, macro_call_id, arg, span).map_err(Into::into);
-
- if let Some(EagerCallInfo { error, .. }) = eager {
- // FIXME: We should report both errors!
- res.err = error.clone().or(res.err);
+ let res = match loc.def.kind {
+ MacroDefKind::Declarative(id, _) => db
+ .decl_macro_expander(loc.def.krate, id)
+ .expand(db, arg.clone(), macro_call_id, span),
+ MacroDefKind::BuiltIn(_, it) => {
+ it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
+ }
+ MacroDefKind::BuiltInDerive(_, it) => {
+ it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
+ }
+ MacroDefKind::BuiltInEager(_, it) => {
+ // This might look a bit odd, but we do not expand the inputs to eager macros here.
+ // Eager macros inputs are expanded, well, eagerly when we collect the macro calls.
+ // That kind of expansion uses the ast id map of an eager macros input though which goes through
+ // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query
+ // will end up going through here again, whereas we want to just want to inspect the raw input.
+ // As such we just return the input subtree here.
+ let eager = match &loc.kind {
+ MacroCallKind::FnLike { eager: None, .. } => {
+ return ExpandResult::ok(CowArc::Arc(macro_arg.clone())).zip_val(None);
}
- res.zip_val(None)
- }
- MacroDefKind::BuiltInAttr(_, it) => {
- let mut res = it.expand(db, macro_call_id, arg, span);
- fixup::reverse_fixups(&mut res.value, &undo_info);
- res.zip_val(None)
+ MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager),
+ _ => None,
+ };
+
+ let mut res = it.expand(db, macro_call_id, arg, span).map_err(Into::into);
+
+ if let Some(EagerCallInfo { error, .. }) = eager {
+ // FIXME: We should report both errors!
+ res.err = error.clone().or(res.err);
}
- MacroDefKind::ProcMacro(_, _, _) => unreachable!(),
- };
+ res.zip_val(None)
+ }
+ MacroDefKind::BuiltInAttr(_, it) => {
+ let mut res = it.expand(db, macro_call_id, arg, span);
+ fixup::reverse_fixups(&mut res.value, &undo_info);
+ res.zip_val(None)
+ }
+ MacroDefKind::ProcMacro(_, _, _) => unreachable!(),
+ };
(ExpandResult { value: res.value, err: res.err }, span)
}
};
diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs
index 0d100c1364..e4375e05d2 100644
--- a/crates/hir-expand/src/declarative.rs
+++ b/crates/hir-expand/src/declarative.rs
@@ -10,6 +10,7 @@ use triomphe::Arc;
use crate::{
AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, Lookup, MacroCallId,
+ MacroCallStyle,
attrs::RawAttrs,
db::ExpandDatabase,
hygiene::{Transparency, apply_mark},
@@ -46,6 +47,7 @@ impl DeclarativeMacroExpander {
s.ctx =
apply_mark(db, s.ctx, call_id.into(), self.transparency, self.edition)
},
+ loc.kind.call_style(),
span,
loc.def.edition,
)
@@ -56,6 +58,7 @@ impl DeclarativeMacroExpander {
pub fn expand_unhygienic(
&self,
tt: tt::TopSubtree,
+ call_style: MacroCallStyle,
call_site: Span,
def_site_edition: Edition,
) -> ExpandResult<tt::TopSubtree> {
@@ -66,7 +69,7 @@ impl DeclarativeMacroExpander {
),
None => self
.mac
- .expand(&tt, |_| (), call_site, def_site_edition)
+ .expand(&tt, |_| (), call_style, call_site, def_site_edition)
.map(TupleExt::head)
.map_err(Into::into),
}
diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 28d3fcdab9..9b65bdac65 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -238,7 +238,7 @@ fn eager_macro_recur(
None => ExpandResult { value: None, err },
}
}
- MacroDefKind::Declarative(_)
+ MacroDefKind::Declarative(..)
| MacroDefKind::BuiltIn(..)
| MacroDefKind::BuiltInAttr(..)
| MacroDefKind::BuiltInDerive(..)
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 472ec83ffe..77f61dd830 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -61,7 +61,7 @@ pub use crate::{
};
pub use base_db::EditionedFileId;
-pub use mbe::{DeclarativeMacro, ValueResult};
+pub use mbe::{DeclarativeMacro, MacroCallStyle, MacroCallStyles, ValueResult};
pub mod tt {
pub use span::Span;
@@ -266,7 +266,7 @@ pub struct MacroDefId {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MacroDefKind {
- Declarative(AstId<ast::Macro>),
+ Declarative(AstId<ast::Macro>, MacroCallStyles),
BuiltIn(AstId<ast::Macro>, BuiltinFnLikeExpander),
BuiltInAttr(AstId<ast::Macro>, BuiltinAttrExpander),
BuiltInDerive(AstId<ast::Macro>, BuiltinDeriveExpander),
@@ -340,6 +340,16 @@ pub enum MacroCallKind {
},
}
+impl MacroCallKind {
+ pub(crate) fn call_style(&self) -> MacroCallStyle {
+ match self {
+ MacroCallKind::FnLike { .. } => MacroCallStyle::FnLike,
+ MacroCallKind::Derive { .. } => MacroCallStyle::Derive,
+ MacroCallKind::Attr { .. } => MacroCallStyle::Attr,
+ }
+ }
+}
+
impl HirFileId {
pub fn edition(self, db: &dyn ExpandDatabase) -> Edition {
match self {
@@ -511,7 +521,7 @@ impl MacroDefId {
pub fn definition_range(&self, db: &dyn ExpandDatabase) -> InFile<TextRange> {
match self.kind {
- MacroDefKind::Declarative(id)
+ MacroDefKind::Declarative(id, _)
| MacroDefKind::BuiltIn(id, _)
| MacroDefKind::BuiltInAttr(id, _)
| MacroDefKind::BuiltInDerive(id, _)
@@ -527,7 +537,7 @@ impl MacroDefId {
pub fn ast_id(&self) -> Either<AstId<ast::Macro>, AstId<ast::Fn>> {
match self.kind {
MacroDefKind::ProcMacro(id, ..) => Either::Right(id),
- MacroDefKind::Declarative(id)
+ MacroDefKind::Declarative(id, _)
| MacroDefKind::BuiltIn(id, _)
| MacroDefKind::BuiltInAttr(id, _)
| MacroDefKind::BuiltInDerive(id, _)
@@ -540,18 +550,22 @@ impl MacroDefId {
}
pub fn is_attribute(&self) -> bool {
- matches!(
- self.kind,
- MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr)
- )
+ match self.kind {
+ MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) => {
+ true
+ }
+ MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::ATTR),
+ _ => false,
+ }
}
pub fn is_derive(&self) -> bool {
- matches!(
- self.kind,
+ match self.kind {
MacroDefKind::BuiltInDerive(..)
- | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive)
- )
+ | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) => true,
+ MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::DERIVE),
+ _ => false,
+ }
}
pub fn is_fn_like(&self) -> bool {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 5400003f59..2d70a8dca1 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -3184,7 +3184,7 @@ impl Macro {
pub fn kind(&self, db: &dyn HirDatabase) -> MacroKind {
match self.id {
MacroId::Macro2Id(it) => match it.lookup(db).expander {
- MacroExpander::Declarative => MacroKind::Declarative,
+ MacroExpander::Declarative { .. } => MacroKind::Declarative,
MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => {
MacroKind::DeclarativeBuiltIn
}
@@ -3192,7 +3192,7 @@ impl Macro {
MacroExpander::BuiltInDerive(_) => MacroKind::DeriveBuiltIn,
},
MacroId::MacroRulesId(it) => match it.lookup(db).expander {
- MacroExpander::Declarative => MacroKind::Declarative,
+ MacroExpander::Declarative { .. } => MacroKind::Declarative,
MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => {
MacroKind::DeclarativeBuiltIn
}
diff --git a/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/crates/ide-db/src/test_data/test_symbol_index_collection.txt
index 973256c470..5ef0ecbcf8 100644
--- a/crates/ide-db/src/test_data/test_symbol_index_collection.txt
+++ b/crates/ide-db/src/test_data/test_symbol_index_collection.txt
@@ -356,7 +356,7 @@
loc: DeclarationLocation {
hir_file_id: MacroFile(
MacroCallId(
- Id(3800),
+ Id(3c00),
),
),
ptr: SyntaxNodePtr {
@@ -694,7 +694,7 @@
Macro {
id: MacroRulesId(
MacroRulesId(
- 3401,
+ 3801,
),
),
},
@@ -796,7 +796,7 @@
Macro {
id: MacroRulesId(
MacroRulesId(
- 3400,
+ 3800,
),
),
},
@@ -862,7 +862,7 @@
Macro {
id: MacroRulesId(
MacroRulesId(
- 3401,
+ 3801,
),
),
},
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index 06244670e0..37eb3d4101 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -127,6 +127,7 @@ define_symbols! {
as_str,
asm,
assert,
+ attr,
attributes,
begin_panic,
bench,
diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml
index eef718b706..9e262c3539 100644
--- a/crates/mbe/Cargo.toml
+++ b/crates/mbe/Cargo.toml
@@ -18,6 +18,7 @@ rustc-hash.workspace = true
smallvec.workspace = true
arrayvec.workspace = true
ra-ap-rustc_lexer.workspace = true
+bitflags.workspace = true
# local deps
parser.workspace = true
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index b185556b5c..9e4b78c2d8 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -16,7 +16,7 @@ use syntax_bridge::{
use test_utils::{bench, bench_fixture, skip_slow_tests};
use crate::{
- DeclarativeMacro,
+ DeclarativeMacro, MacroCallStyle,
parser::{MetaVarKind, Op, RepeatKind, Separator},
};
@@ -52,7 +52,8 @@ fn benchmark_expand_macro_rules() {
invocations
.into_iter()
.map(|(id, tt)| {
- let res = rules[&id].expand(&tt, |_| (), DUMMY, Edition::CURRENT);
+ let res =
+ rules[&id].expand(&tt, |_| (), MacroCallStyle::FnLike, DUMMY, Edition::CURRENT);
assert!(res.err.is_none());
res.value.0.0.len()
})
@@ -123,7 +124,11 @@ fn invocation_fixtures(
}
let subtree = builder.build();
- if it.expand(&subtree, |_| (), DUMMY, Edition::CURRENT).err.is_none() {
+ if it
+ .expand(&subtree, |_| (), MacroCallStyle::FnLike, DUMMY, Edition::CURRENT)
+ .err
+ .is_none()
+ {
res.push((name.clone(), subtree));
break;
}
diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs
index f910f9f9d7..507402197e 100644
--- a/crates/mbe/src/expander.rs
+++ b/crates/mbe/src/expander.rs
@@ -9,17 +9,26 @@ use intern::Symbol;
use rustc_hash::FxHashMap;
use span::{Edition, Span};
-use crate::{ExpandError, ExpandErrorKind, ExpandResult, MatchedArmIndex, parser::MetaVarKind};
+use crate::{
+ ExpandError, ExpandErrorKind, ExpandResult, MacroCallStyle, MatchedArmIndex,
+ parser::MetaVarKind,
+};
pub(crate) fn expand_rules(
rules: &[crate::Rule],
input: &tt::TopSubtree<Span>,
marker: impl Fn(&mut Span) + Copy,
+ call_style: MacroCallStyle,
call_site: Span,
def_site_edition: Edition,
) -> ExpandResult<(tt::TopSubtree<Span>, MatchedArmIndex)> {
let mut match_: Option<(matcher::Match<'_>, &crate::Rule, usize)> = None;
for (idx, rule) in rules.iter().enumerate() {
+ // Skip any rules that aren't relevant to the call style (fn-like/attr/derive).
+ if call_style != rule.style {
+ continue;
+ }
+
let new_match = matcher::match_(&rule.lhs, input, def_site_edition);
if new_match.err.is_none() {
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index 9f9fa36abd..843c2889a0 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -14,6 +14,7 @@ extern crate ra_ap_rustc_lexer as rustc_lexer;
extern crate rustc_lexer;
mod expander;
+mod macro_call_style;
mod parser;
#[cfg(test)]
@@ -29,6 +30,7 @@ use tt::iter::TtIter;
use std::fmt;
use std::sync::Arc;
+pub use crate::macro_call_style::{MacroCallStyle, MacroCallStyles};
use crate::parser::{MetaTemplate, MetaVarKind, Op};
pub use tt::{Delimiter, DelimiterKind, Punct};
@@ -137,6 +139,8 @@ pub struct DeclarativeMacro {
#[derive(Clone, Debug, PartialEq, Eq)]
struct Rule {
+ /// Is this a normal fn-like rule, an `attr()` rule, or a `derive()` rule?
+ style: MacroCallStyle,
lhs: MetaTemplate,
rhs: MetaTemplate,
}
@@ -195,13 +199,18 @@ impl DeclarativeMacro {
let mut err = None;
if let Some(args) = args {
+ // The presence of an argument list means that this macro uses the
+ // "simple" syntax, where the body is the RHS of a single rule.
cov_mark::hit!(parse_macro_def_simple);
let rule = (|| {
let lhs = MetaTemplate::parse_pattern(ctx_edition, args.iter())?;
let rhs = MetaTemplate::parse_template(ctx_edition, body.iter())?;
- Ok(crate::Rule { lhs, rhs })
+ // In the "simple" syntax, there is apparently no way to specify
+ // that the single rule is an attribute or derive rule, so it
+ // must be a function-like rule.
+ Ok(crate::Rule { style: MacroCallStyle::FnLike, lhs, rhs })
})();
match rule {
@@ -209,6 +218,8 @@ impl DeclarativeMacro {
Err(e) => err = Some(Box::new(e)),
}
} else {
+ // There was no top-level argument list, so this macro uses the
+ // list-of-rules syntax, similar to `macro_rules!`.
cov_mark::hit!(parse_macro_def_rules);
let mut src = body.iter();
while !src.is_empty() {
@@ -249,14 +260,28 @@ impl DeclarativeMacro {
self.rules.len()
}
+ pub fn rule_styles(&self) -> MacroCallStyles {
+ if self.rules.is_empty() {
+ // No rules could be parsed, so fall back to assuming that this
+ // is intended to be a function-like macro.
+ MacroCallStyles::FN_LIKE
+ } else {
+ self.rules
+ .iter()
+ .map(|rule| MacroCallStyles::from(rule.style))
+ .fold(MacroCallStyles::empty(), |a, b| a | b)
+ }
+ }
+
pub fn expand(
&self,
tt: &tt::TopSubtree<Span>,
marker: impl Fn(&mut Span) + Copy,
+ call_style: MacroCallStyle,
call_site: Span,
def_site_edition: Edition,
) -> ExpandResult<(tt::TopSubtree<Span>, MatchedArmIndex)> {
- expander::expand_rules(&self.rules, tt, marker, call_site, def_site_edition)
+ expander::expand_rules(&self.rules, tt, marker, call_style, call_site, def_site_edition)
}
}
@@ -265,6 +290,9 @@ impl Rule {
edition: impl Copy + Fn(SyntaxContext) -> Edition,
src: &mut TtIter<'_, Span>,
) -> Result<Self, ParseError> {
+ // Parse an optional `attr()` or `derive()` prefix before the LHS pattern.
+ let style = parser::parse_rule_style(src)?;
+
let (_, lhs) =
src.expect_subtree().map_err(|()| ParseError::expected("expected subtree"))?;
src.expect_char('=').map_err(|()| ParseError::expected("expected `=`"))?;
@@ -275,7 +303,7 @@ impl Rule {
let lhs = MetaTemplate::parse_pattern(edition, lhs)?;
let rhs = MetaTemplate::parse_template(edition, rhs)?;
- Ok(crate::Rule { lhs, rhs })
+ Ok(crate::Rule { style, lhs, rhs })
}
}
diff --git a/crates/mbe/src/macro_call_style.rs b/crates/mbe/src/macro_call_style.rs
new file mode 100644
index 0000000000..311f0cb98d
--- /dev/null
+++ b/crates/mbe/src/macro_call_style.rs
@@ -0,0 +1,32 @@
+//! Types representing the three basic "styles" of macro calls in Rust source:
+//! - Function-like macros ("bang macros"), e.g. `foo!(...)`
+//! - Attribute macros, e.g. `#[foo]`
+//! - Derive macros, e.g. `#[derive(Foo)]`
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum MacroCallStyle {
+ FnLike,
+ Attr,
+ Derive,
+}
+
+bitflags::bitflags! {
+ /// A set of `MacroCallStyle` values, allowing macros to indicate that
+ /// they support more than one style.
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ pub struct MacroCallStyles: u8 {
+ const FN_LIKE = (1 << 0);
+ const ATTR = (1 << 1);
+ const DERIVE = (1 << 2);
+ }
+}
+
+impl From<MacroCallStyle> for MacroCallStyles {
+ fn from(kind: MacroCallStyle) -> Self {
+ match kind {
+ MacroCallStyle::FnLike => Self::FN_LIKE,
+ MacroCallStyle::Attr => Self::ATTR,
+ MacroCallStyle::Derive => Self::DERIVE,
+ }
+ }
+}
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 711101260a..e1cb98abae 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -11,7 +11,34 @@ use tt::{
iter::{TtElement, TtIter},
};
-use crate::ParseError;
+use crate::{MacroCallStyle, ParseError};
+
+pub(crate) fn parse_rule_style(src: &mut TtIter<'_, Span>) -> Result<MacroCallStyle, ParseError> {
+ // Skip an optional `unsafe`. This is only actually allowed for `attr`
+ // rules, but we'll let rustc worry about that.
+ if let Some(TtElement::Leaf(tt::Leaf::Ident(ident))) = src.peek()
+ && ident.sym == sym::unsafe_
+ {
+ src.next().expect("already peeked");
+ }
+
+ let kind = match src.peek() {
+ Some(TtElement::Leaf(tt::Leaf::Ident(ident))) if ident.sym == sym::attr => {
+ src.next().expect("already peeked");
+ // FIXME: Add support for `attr(..)` rules with attribute arguments,
+ // which would be inside these parens.
+ src.expect_subtree().map_err(|_| ParseError::expected("expected `()`"))?;
+ MacroCallStyle::Attr
+ }
+ Some(TtElement::Leaf(tt::Leaf::Ident(ident))) if ident.sym == sym::derive => {
+ src.next().expect("already peeked");
+ src.expect_subtree().map_err(|_| ParseError::expected("expected `()`"))?;
+ MacroCallStyle::Derive
+ }
+ _ => MacroCallStyle::FnLike,
+ };
+ Ok(kind)
+}
/// Consider
///
diff --git a/crates/mbe/src/tests.rs b/crates/mbe/src/tests.rs
index 56034516ef..110a2664ec 100644
--- a/crates/mbe/src/tests.rs
+++ b/crates/mbe/src/tests.rs
@@ -51,6 +51,7 @@ fn check_(
let res = mac.expand(
&arg_tt,
|_| (),
+ crate::MacroCallStyle::FnLike,
Span {
range: TextRange::up_to(TextSize::of(arg)),
anchor: call_anchor,