Basic Support Macro 2.0
This commit is contained in:
parent
c8066ebd17
commit
a193666361
@ -25,8 +25,8 @@ use crate::{
|
||||
derive_macro_as_call_id,
|
||||
item_scope::{ImportType, PerNsGlobImports},
|
||||
item_tree::{
|
||||
self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, MacroRules, Mod, ModItem, ModKind,
|
||||
StructDefKind,
|
||||
self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, MacroDef, MacroRules, Mod, ModItem,
|
||||
ModKind, StructDefKind,
|
||||
},
|
||||
macro_call_as_call_id,
|
||||
nameres::{
|
||||
@ -395,7 +395,7 @@ impl DefCollector<'_> {
|
||||
/// macro_rules! foo { () => {} }
|
||||
/// use foo as bar;
|
||||
/// ```
|
||||
fn define_macro(
|
||||
fn define_macro_rules(
|
||||
&mut self,
|
||||
module_id: LocalModuleId,
|
||||
name: Name,
|
||||
@ -430,6 +430,21 @@ impl DefCollector<'_> {
|
||||
self.def_map.modules[module_id].scope.define_legacy_macro(name, mac);
|
||||
}
|
||||
|
||||
/// Define a macro 2.0 macro
|
||||
///
|
||||
/// The scoped of macro 2.0 macro is equal to normal function
|
||||
fn define_macro_def(
|
||||
&mut self,
|
||||
module_id: LocalModuleId,
|
||||
name: Name,
|
||||
macro_: MacroDefId,
|
||||
vis: &RawVisibility,
|
||||
) {
|
||||
let vis =
|
||||
self.def_map.resolve_visibility(self.db, module_id, vis).unwrap_or(Visibility::Public);
|
||||
self.update(module_id, &[(Some(name), PerNs::macros(macro_, vis))], vis, ImportType::Named);
|
||||
}
|
||||
|
||||
/// Define a proc macro
|
||||
///
|
||||
/// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped.
|
||||
@ -1067,40 +1082,7 @@ impl ModCollector<'_, '_> {
|
||||
}
|
||||
ModItem::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac]),
|
||||
ModItem::MacroRules(id) => self.collect_macro_rules(id),
|
||||
ModItem::MacroDef(id) => {
|
||||
let mac = &self.item_tree[id];
|
||||
let ast_id = InFile::new(self.file_id, mac.ast_id.upcast());
|
||||
|
||||
// "Macro 2.0" is not currently supported by rust-analyzer, but libcore uses it
|
||||
// to define builtin macros, so we support at least that part.
|
||||
let attrs = self.item_tree.attrs(
|
||||
self.def_collector.db,
|
||||
krate,
|
||||
ModItem::from(id).into(),
|
||||
);
|
||||
if attrs.by_key("rustc_builtin_macro").exists() {
|
||||
let krate = self.def_collector.def_map.krate;
|
||||
let macro_id = find_builtin_macro(&mac.name, krate, ast_id)
|
||||
.or_else(|| find_builtin_derive(&mac.name, krate, ast_id));
|
||||
if let Some(macro_id) = macro_id {
|
||||
let vis = self
|
||||
.def_collector
|
||||
.def_map
|
||||
.resolve_visibility(
|
||||
self.def_collector.db,
|
||||
self.module_id,
|
||||
&self.item_tree[mac.visibility],
|
||||
)
|
||||
.unwrap_or(Visibility::Public);
|
||||
self.def_collector.update(
|
||||
self.module_id,
|
||||
&[(Some(mac.name.clone()), PerNs::macros(macro_id, vis))],
|
||||
vis,
|
||||
ImportType::Named,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ModItem::MacroDef(id) => self.collect_macro_def(id),
|
||||
ModItem::Impl(imp) => {
|
||||
let module = self.def_collector.def_map.module_id(self.module_id);
|
||||
let impl_id =
|
||||
@ -1420,7 +1402,7 @@ impl ModCollector<'_, '_> {
|
||||
if attrs.by_key("rustc_builtin_macro").exists() {
|
||||
let krate = self.def_collector.def_map.krate;
|
||||
if let Some(macro_id) = find_builtin_macro(&mac.name, krate, ast_id) {
|
||||
self.def_collector.define_macro(
|
||||
self.def_collector.define_macro_rules(
|
||||
self.module_id,
|
||||
mac.name.clone(),
|
||||
macro_id,
|
||||
@ -1436,7 +1418,49 @@ impl ModCollector<'_, '_> {
|
||||
kind: MacroDefKind::Declarative(ast_id),
|
||||
local_inner: is_local_inner,
|
||||
};
|
||||
self.def_collector.define_macro(self.module_id, mac.name.clone(), macro_id, is_export);
|
||||
self.def_collector.define_macro_rules(
|
||||
self.module_id,
|
||||
mac.name.clone(),
|
||||
macro_id,
|
||||
is_export,
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_macro_def(&mut self, id: FileItemTreeId<MacroDef>) {
|
||||
let krate = self.def_collector.def_map.krate;
|
||||
let mac = &self.item_tree[id];
|
||||
let ast_id = InFile::new(self.file_id, mac.ast_id.upcast());
|
||||
|
||||
// Case 1: bulitin macros
|
||||
let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into());
|
||||
if attrs.by_key("rustc_builtin_macro").exists() {
|
||||
let macro_id = find_builtin_macro(&mac.name, krate, ast_id)
|
||||
.or_else(|| find_builtin_derive(&mac.name, krate, ast_id));
|
||||
|
||||
if let Some(macro_id) = macro_id {
|
||||
self.def_collector.define_macro_def(
|
||||
self.module_id,
|
||||
mac.name.clone(),
|
||||
macro_id,
|
||||
&self.item_tree[mac.visibility],
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 2: normal `macro`
|
||||
let macro_id = MacroDefId {
|
||||
krate: self.def_collector.def_map.krate,
|
||||
kind: MacroDefKind::Declarative(ast_id),
|
||||
local_inner: false,
|
||||
};
|
||||
|
||||
self.def_collector.define_macro_def(
|
||||
self.module_id,
|
||||
mac.name.clone(),
|
||||
macro_id,
|
||||
&self.item_tree[mac.visibility],
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_macro_call(&mut self, mac: &MacroCall) {
|
||||
|
@ -837,3 +837,25 @@ fn collects_derive_helpers() {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_macro_def() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
pub macro structs($($i:ident),*) {
|
||||
$(struct $i { field: u32 } )*
|
||||
}
|
||||
|
||||
structs!(Foo);
|
||||
|
||||
//- /nested.rs
|
||||
structs!(Bar, Baz);
|
||||
"#,
|
||||
expect![[r#"
|
||||
crate
|
||||
Foo: t
|
||||
structs: m
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use base_db::{salsa, SourceDatabase};
|
||||
use mbe::{ExpandError, ExpandResult, MacroRules};
|
||||
use mbe::{ExpandError, ExpandResult, MacroDef, MacroRules};
|
||||
use parser::FragmentKind;
|
||||
use syntax::{
|
||||
algo::diff,
|
||||
@ -28,6 +28,7 @@ const TOKEN_LIMIT: usize = 524288;
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum TokenExpander {
|
||||
MacroRules(mbe::MacroRules),
|
||||
MacroDef(mbe::MacroDef),
|
||||
Builtin(BuiltinFnLikeExpander),
|
||||
BuiltinDerive(BuiltinDeriveExpander),
|
||||
ProcMacro(ProcMacroExpander),
|
||||
@ -42,6 +43,7 @@ impl TokenExpander {
|
||||
) -> mbe::ExpandResult<tt::Subtree> {
|
||||
match self {
|
||||
TokenExpander::MacroRules(it) => it.expand(tt),
|
||||
TokenExpander::MacroDef(it) => it.expand(tt),
|
||||
TokenExpander::Builtin(it) => it.expand(db, id, tt),
|
||||
// FIXME switch these to ExpandResult as well
|
||||
TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(),
|
||||
@ -57,6 +59,7 @@ impl TokenExpander {
|
||||
pub fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
|
||||
match self {
|
||||
TokenExpander::MacroRules(it) => it.map_id_down(id),
|
||||
TokenExpander::MacroDef(it) => it.map_id_down(id),
|
||||
TokenExpander::Builtin(..) => id,
|
||||
TokenExpander::BuiltinDerive(..) => id,
|
||||
TokenExpander::ProcMacro(..) => id,
|
||||
@ -66,6 +69,7 @@ impl TokenExpander {
|
||||
pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
|
||||
match self {
|
||||
TokenExpander::MacroRules(it) => it.map_id_up(id),
|
||||
TokenExpander::MacroDef(it) => it.map_id_up(id),
|
||||
TokenExpander::Builtin(..) => (id, mbe::Origin::Call),
|
||||
TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Call),
|
||||
TokenExpander::ProcMacro(..) => (id, mbe::Origin::Call),
|
||||
@ -136,26 +140,40 @@ fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
|
||||
|
||||
fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
|
||||
match id.kind {
|
||||
MacroDefKind::Declarative(ast_id) => {
|
||||
let macro_rules = match ast_id.to_node(db) {
|
||||
syntax::ast::Macro::MacroRules(mac) => mac,
|
||||
syntax::ast::Macro::MacroDef(_) => return None,
|
||||
};
|
||||
let arg = macro_rules.token_tree()?;
|
||||
let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
|
||||
log::warn!("fail on macro_def to token tree: {:#?}", arg);
|
||||
None
|
||||
})?;
|
||||
let rules = match MacroRules::parse(&tt) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
let name = macro_rules.name().map(|n| n.to_string()).unwrap_or_default();
|
||||
log::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Arc::new((TokenExpander::MacroRules(rules), tmap)))
|
||||
}
|
||||
MacroDefKind::Declarative(ast_id) => match ast_id.to_node(db) {
|
||||
syntax::ast::Macro::MacroRules(macro_rules) => {
|
||||
let arg = macro_rules.token_tree()?;
|
||||
let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
|
||||
log::warn!("fail on macro_rules to token tree: {:#?}", arg);
|
||||
None
|
||||
})?;
|
||||
let rules = match MacroRules::parse(&tt) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
let name = macro_rules.name().map(|n| n.to_string()).unwrap_or_default();
|
||||
log::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Arc::new((TokenExpander::MacroRules(rules), tmap)))
|
||||
}
|
||||
syntax::ast::Macro::MacroDef(macro_def) => {
|
||||
let arg = macro_def.body()?;
|
||||
let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
|
||||
log::warn!("fail on macro_def to token tree: {:#?}", arg);
|
||||
None
|
||||
})?;
|
||||
let rules = match MacroDef::parse(&tt) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
let name = macro_def.name().map(|n| n.to_string()).unwrap_or_default();
|
||||
log::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Arc::new((TokenExpander::MacroDef(rules), tmap)))
|
||||
}
|
||||
},
|
||||
MacroDefKind::BuiltIn(expander, _) => {
|
||||
Some(Arc::new((TokenExpander::Builtin(expander), mbe::TokenMap::default())))
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ fn make_hygiene_info(
|
||||
let def_offset = loc.def.ast_id().left().and_then(|id| {
|
||||
let def_tt = match id.to_node(db) {
|
||||
ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
|
||||
ast::Macro::MacroDef(_) => return None,
|
||||
ast::Macro::MacroDef(mac) => mac.body()?.syntax().text_range().start(),
|
||||
};
|
||||
Some(InFile::new(id.file_id, def_tt))
|
||||
});
|
||||
|
@ -151,7 +151,7 @@ impl HirFileId {
|
||||
let def = loc.def.ast_id().left().and_then(|id| {
|
||||
let def_tt = match id.to_node(db) {
|
||||
ast::Macro::MacroRules(mac) => mac.token_tree()?,
|
||||
ast::Macro::MacroDef(_) => return None,
|
||||
ast::Macro::MacroDef(mac) => mac.body()?,
|
||||
};
|
||||
Some(InFile::new(id.file_id, def_tt))
|
||||
});
|
||||
|
@ -135,7 +135,88 @@ fn infer_path_qualified_macros_expanded() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_macro_expanded_in_various_places() {
|
||||
fn expr_macro_def_expanded_in_various_places() {
|
||||
check_infer(
|
||||
r#"
|
||||
macro spam() {
|
||||
1isize
|
||||
}
|
||||
|
||||
fn spam() {
|
||||
spam!();
|
||||
(spam!());
|
||||
spam!().spam(spam!());
|
||||
for _ in spam!() {}
|
||||
|| spam!();
|
||||
while spam!() {}
|
||||
break spam!();
|
||||
return spam!();
|
||||
match spam!() {
|
||||
_ if spam!() => spam!(),
|
||||
}
|
||||
spam!()(spam!());
|
||||
Spam { spam: spam!() };
|
||||
spam!()[spam!()];
|
||||
await spam!();
|
||||
spam!() as usize;
|
||||
&spam!();
|
||||
-spam!();
|
||||
spam!()..spam!();
|
||||
spam!() + spam!();
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
!0..6 '1isize': isize
|
||||
39..442 '{ ...!(); }': ()
|
||||
73..94 'spam!(...am!())': {unknown}
|
||||
100..119 'for _ ...!() {}': ()
|
||||
104..105 '_': {unknown}
|
||||
117..119 '{}': ()
|
||||
124..134 '|| spam!()': || -> isize
|
||||
140..156 'while ...!() {}': ()
|
||||
154..156 '{}': ()
|
||||
161..174 'break spam!()': !
|
||||
180..194 'return spam!()': !
|
||||
200..254 'match ... }': isize
|
||||
224..225 '_': isize
|
||||
259..275 'spam!(...am!())': {unknown}
|
||||
281..303 'Spam {...m!() }': {unknown}
|
||||
309..325 'spam!(...am!()]': {unknown}
|
||||
350..366 'spam!(... usize': usize
|
||||
372..380 '&spam!()': &isize
|
||||
386..394 '-spam!()': isize
|
||||
400..416 'spam!(...pam!()': {unknown}
|
||||
422..439 'spam!(...pam!()': isize
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_macro_rules_expanded_in_various_places() {
|
||||
check_infer(
|
||||
r#"
|
||||
macro_rules! spam {
|
||||
|
@ -40,6 +40,7 @@ fn text_of_first_token(node: &SyntaxNode) -> TokenText {
|
||||
TokenText(first_token)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Macro {
|
||||
MacroRules(ast::MacroRules),
|
||||
MacroDef(ast::MacroDef),
|
||||
|
Loading…
x
Reference in New Issue
Block a user