mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-08-19 01:50:32 +00:00
Basic Support Macro 2.0
This commit is contained in:
parent
c8066ebd17
commit
a193666361
7 changed files with 209 additions and 63 deletions
|
@ -25,8 +25,8 @@ use crate::{
|
||||||
derive_macro_as_call_id,
|
derive_macro_as_call_id,
|
||||||
item_scope::{ImportType, PerNsGlobImports},
|
item_scope::{ImportType, PerNsGlobImports},
|
||||||
item_tree::{
|
item_tree::{
|
||||||
self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, MacroRules, Mod, ModItem, ModKind,
|
self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, MacroDef, MacroRules, Mod, ModItem,
|
||||||
StructDefKind,
|
ModKind, StructDefKind,
|
||||||
},
|
},
|
||||||
macro_call_as_call_id,
|
macro_call_as_call_id,
|
||||||
nameres::{
|
nameres::{
|
||||||
|
@ -395,7 +395,7 @@ impl DefCollector<'_> {
|
||||||
/// macro_rules! foo { () => {} }
|
/// macro_rules! foo { () => {} }
|
||||||
/// use foo as bar;
|
/// use foo as bar;
|
||||||
/// ```
|
/// ```
|
||||||
fn define_macro(
|
fn define_macro_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
module_id: LocalModuleId,
|
module_id: LocalModuleId,
|
||||||
name: Name,
|
name: Name,
|
||||||
|
@ -430,6 +430,21 @@ impl DefCollector<'_> {
|
||||||
self.def_map.modules[module_id].scope.define_legacy_macro(name, mac);
|
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
|
/// Define a proc macro
|
||||||
///
|
///
|
||||||
/// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped.
|
/// 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::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac]),
|
||||||
ModItem::MacroRules(id) => self.collect_macro_rules(id),
|
ModItem::MacroRules(id) => self.collect_macro_rules(id),
|
||||||
ModItem::MacroDef(id) => {
|
ModItem::MacroDef(id) => self.collect_macro_def(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::Impl(imp) => {
|
ModItem::Impl(imp) => {
|
||||||
let module = self.def_collector.def_map.module_id(self.module_id);
|
let module = self.def_collector.def_map.module_id(self.module_id);
|
||||||
let impl_id =
|
let impl_id =
|
||||||
|
@ -1420,7 +1402,7 @@ impl ModCollector<'_, '_> {
|
||||||
if attrs.by_key("rustc_builtin_macro").exists() {
|
if attrs.by_key("rustc_builtin_macro").exists() {
|
||||||
let krate = self.def_collector.def_map.krate;
|
let krate = self.def_collector.def_map.krate;
|
||||||
if let Some(macro_id) = find_builtin_macro(&mac.name, krate, ast_id) {
|
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,
|
self.module_id,
|
||||||
mac.name.clone(),
|
mac.name.clone(),
|
||||||
macro_id,
|
macro_id,
|
||||||
|
@ -1436,7 +1418,49 @@ impl ModCollector<'_, '_> {
|
||||||
kind: MacroDefKind::Declarative(ast_id),
|
kind: MacroDefKind::Declarative(ast_id),
|
||||||
local_inner: is_local_inner,
|
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) {
|
fn collect_macro_call(&mut self, mac: &MacroCall) {
|
||||||
|
|
|
@ -837,3 +837,25 @@ fn collects_derive_helpers() {
|
||||||
_ => unreachable!(),
|
_ => 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 std::sync::Arc;
|
||||||
|
|
||||||
use base_db::{salsa, SourceDatabase};
|
use base_db::{salsa, SourceDatabase};
|
||||||
use mbe::{ExpandError, ExpandResult, MacroRules};
|
use mbe::{ExpandError, ExpandResult, MacroDef, MacroRules};
|
||||||
use parser::FragmentKind;
|
use parser::FragmentKind;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo::diff,
|
algo::diff,
|
||||||
|
@ -28,6 +28,7 @@ const TOKEN_LIMIT: usize = 524288;
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum TokenExpander {
|
pub enum TokenExpander {
|
||||||
MacroRules(mbe::MacroRules),
|
MacroRules(mbe::MacroRules),
|
||||||
|
MacroDef(mbe::MacroDef),
|
||||||
Builtin(BuiltinFnLikeExpander),
|
Builtin(BuiltinFnLikeExpander),
|
||||||
BuiltinDerive(BuiltinDeriveExpander),
|
BuiltinDerive(BuiltinDeriveExpander),
|
||||||
ProcMacro(ProcMacroExpander),
|
ProcMacro(ProcMacroExpander),
|
||||||
|
@ -42,6 +43,7 @@ impl TokenExpander {
|
||||||
) -> mbe::ExpandResult<tt::Subtree> {
|
) -> mbe::ExpandResult<tt::Subtree> {
|
||||||
match self {
|
match self {
|
||||||
TokenExpander::MacroRules(it) => it.expand(tt),
|
TokenExpander::MacroRules(it) => it.expand(tt),
|
||||||
|
TokenExpander::MacroDef(it) => it.expand(tt),
|
||||||
TokenExpander::Builtin(it) => it.expand(db, id, tt),
|
TokenExpander::Builtin(it) => it.expand(db, id, tt),
|
||||||
// FIXME switch these to ExpandResult as well
|
// FIXME switch these to ExpandResult as well
|
||||||
TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(),
|
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 {
|
pub fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
|
||||||
match self {
|
match self {
|
||||||
TokenExpander::MacroRules(it) => it.map_id_down(id),
|
TokenExpander::MacroRules(it) => it.map_id_down(id),
|
||||||
|
TokenExpander::MacroDef(it) => it.map_id_down(id),
|
||||||
TokenExpander::Builtin(..) => id,
|
TokenExpander::Builtin(..) => id,
|
||||||
TokenExpander::BuiltinDerive(..) => id,
|
TokenExpander::BuiltinDerive(..) => id,
|
||||||
TokenExpander::ProcMacro(..) => id,
|
TokenExpander::ProcMacro(..) => id,
|
||||||
|
@ -66,6 +69,7 @@ impl TokenExpander {
|
||||||
pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
|
pub fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
|
||||||
match self {
|
match self {
|
||||||
TokenExpander::MacroRules(it) => it.map_id_up(id),
|
TokenExpander::MacroRules(it) => it.map_id_up(id),
|
||||||
|
TokenExpander::MacroDef(it) => it.map_id_up(id),
|
||||||
TokenExpander::Builtin(..) => (id, mbe::Origin::Call),
|
TokenExpander::Builtin(..) => (id, mbe::Origin::Call),
|
||||||
TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Call),
|
TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Call),
|
||||||
TokenExpander::ProcMacro(..) => (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)>> {
|
fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
|
||||||
match id.kind {
|
match id.kind {
|
||||||
MacroDefKind::Declarative(ast_id) => {
|
MacroDefKind::Declarative(ast_id) => match ast_id.to_node(db) {
|
||||||
let macro_rules = match ast_id.to_node(db) {
|
syntax::ast::Macro::MacroRules(macro_rules) => {
|
||||||
syntax::ast::Macro::MacroRules(mac) => mac,
|
let arg = macro_rules.token_tree()?;
|
||||||
syntax::ast::Macro::MacroDef(_) => return None,
|
let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
|
||||||
};
|
log::warn!("fail on macro_rules to token tree: {:#?}", arg);
|
||||||
let arg = macro_rules.token_tree()?;
|
None
|
||||||
let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
|
})?;
|
||||||
log::warn!("fail on macro_def to token tree: {:#?}", arg);
|
let rules = match MacroRules::parse(&tt) {
|
||||||
None
|
Ok(it) => it,
|
||||||
})?;
|
Err(err) => {
|
||||||
let rules = match MacroRules::parse(&tt) {
|
let name = macro_rules.name().map(|n| n.to_string()).unwrap_or_default();
|
||||||
Ok(it) => it,
|
log::warn!("fail on macro_def parse ({}): {:?} {:#?}", name, err, tt);
|
||||||
Err(err) => {
|
return None;
|
||||||
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) => {
|
||||||
Some(Arc::new((TokenExpander::MacroRules(rules), tmap)))
|
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, _) => {
|
MacroDefKind::BuiltIn(expander, _) => {
|
||||||
Some(Arc::new((TokenExpander::Builtin(expander), mbe::TokenMap::default())))
|
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_offset = loc.def.ast_id().left().and_then(|id| {
|
||||||
let def_tt = match id.to_node(db) {
|
let def_tt = match id.to_node(db) {
|
||||||
ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
|
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))
|
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 = loc.def.ast_id().left().and_then(|id| {
|
||||||
let def_tt = match id.to_node(db) {
|
let def_tt = match id.to_node(db) {
|
||||||
ast::Macro::MacroRules(mac) => mac.token_tree()?,
|
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))
|
Some(InFile::new(id.file_id, def_tt))
|
||||||
});
|
});
|
||||||
|
|
|
@ -135,7 +135,88 @@ fn infer_path_qualified_macros_expanded() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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(
|
check_infer(
|
||||||
r#"
|
r#"
|
||||||
macro_rules! spam {
|
macro_rules! spam {
|
||||||
|
|
|
@ -40,6 +40,7 @@ fn text_of_first_token(node: &SyntaxNode) -> TokenText {
|
||||||
TokenText(first_token)
|
TokenText(first_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Macro {
|
pub enum Macro {
|
||||||
MacroRules(ast::MacroRules),
|
MacroRules(ast::MacroRules),
|
||||||
MacroDef(ast::MacroDef),
|
MacroDef(ast::MacroDef),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue