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
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | crates/hir-def/src/db.rs | 2 | ||||
| -rw-r--r-- | crates/hir-def/src/item_scope.rs | 17 | ||||
| -rw-r--r-- | crates/hir-def/src/lib.rs | 6 | ||||
| -rw-r--r-- | crates/hir-def/src/nameres.rs | 55 | ||||
| -rw-r--r-- | crates/hir-def/src/nameres/collector.rs | 17 | ||||
| -rw-r--r-- | crates/hir-def/src/nameres/path_resolution.rs | 9 | ||||
| -rw-r--r-- | crates/hir-def/src/nameres/tests/incremental.rs | 8 | ||||
| -rw-r--r-- | crates/hir-def/src/nameres/tests/macros.rs | 129 | ||||
| -rw-r--r-- | crates/hir-expand/src/db.rs | 86 | ||||
| -rw-r--r-- | crates/hir-expand/src/declarative.rs | 5 | ||||
| -rw-r--r-- | crates/hir-expand/src/eager.rs | 2 | ||||
| -rw-r--r-- | crates/hir-expand/src/lib.rs | 38 | ||||
| -rw-r--r-- | crates/hir/src/lib.rs | 4 | ||||
| -rw-r--r-- | crates/ide-db/src/test_data/test_symbol_index_collection.txt | 8 | ||||
| -rw-r--r-- | crates/intern/src/symbol/symbols.rs | 1 | ||||
| -rw-r--r-- | crates/mbe/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/mbe/src/benchmark.rs | 11 | ||||
| -rw-r--r-- | crates/mbe/src/expander.rs | 11 | ||||
| -rw-r--r-- | crates/mbe/src/lib.rs | 34 | ||||
| -rw-r--r-- | crates/mbe/src/macro_call_style.rs | 32 | ||||
| -rw-r--r-- | crates/mbe/src/parser.rs | 29 | ||||
| -rw-r--r-- | crates/mbe/src/tests.rs | 1 |
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, |