Basic Support Macro 2.0

This commit is contained in:
Edwin Cheng 2021-03-27 13:44:54 +08:00
parent c8066ebd17
commit a193666361
7 changed files with 209 additions and 63 deletions

View File

@ -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) {

View File

@ -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
"#]],
);
}

View File

@ -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())))
}

View File

@ -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))
});

View File

@ -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))
});

View File

@ -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 {

View File

@ -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),