mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Merge #9128
9128: feat: expand procedural attribute macros r=jonas-schievink a=jonas-schievink This adds experimental support for attribute macros. They can be enabled by setting `rust-analyzer.experimental.procAttrMacros` to `true`. Known issues: * Tokens aren't remapped, presumably because we edit the input syntax tree (this causes IDE features to not work inside items with attribute macros on them) * Macro errors aren't reported correctly Closes https://github.com/rust-analyzer/rust-analyzer/issues/8971 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/8964 / https://github.com/la10736/rstest/issues/120 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/2984 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/5412 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6029 Fixes https://github.com/rust-analyzer/rust-analyzer/issues/6687 https://github.com/rust-analyzer/rust-analyzer/issues/6740 is still not fixed – we now expand `#[proc_macro_hack]`, but fail to expand the resulting `proc_macro_call!()` macro. Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
commit
14153671ca
@ -534,6 +534,18 @@ impl Module {
|
|||||||
Some(derive_name.clone()),
|
Some(derive_name.clone()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
MacroCallKind::Attr { ast_id, invoc_attr_index, attr_name, .. } => {
|
||||||
|
let node = ast_id.to_node(db.upcast());
|
||||||
|
let attr =
|
||||||
|
node.attrs().nth((*invoc_attr_index) as usize).unwrap_or_else(
|
||||||
|
|| panic!("cannot find attribute #{}", invoc_attr_index),
|
||||||
|
);
|
||||||
|
(
|
||||||
|
ast_id.file_id,
|
||||||
|
SyntaxNodePtr::from(AstPtr::new(&attr)),
|
||||||
|
Some(attr_name.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
sink.push(UnresolvedProcMacro {
|
sink.push(UnresolvedProcMacro {
|
||||||
file,
|
file,
|
||||||
@ -558,7 +570,9 @@ impl Module {
|
|||||||
let node = ast_id.to_node(db.upcast());
|
let node = ast_id.to_node(db.upcast());
|
||||||
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
|
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
|
||||||
}
|
}
|
||||||
MacroCallKind::Derive { ast_id, .. } => {
|
MacroCallKind::Derive { ast_id, .. }
|
||||||
|
| MacroCallKind::Attr { ast_id, .. } => {
|
||||||
|
// FIXME: point to the attribute instead, this creates very large diagnostics
|
||||||
let node = ast_id.to_node(db.upcast());
|
let node = ast_id.to_node(db.upcast());
|
||||||
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
|
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
|
//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
|
||||||
//!
|
//!
|
||||||
//! It was last synchronized with upstream commit 2225ee1b62ff089917434aefd9b2bf509cfa087f.
|
//! It was last synchronized with upstream commit 835150e70288535bc57bb624792229b9dc94991d.
|
||||||
//!
|
//!
|
||||||
//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
|
//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
|
||||||
//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
|
//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
|
||||||
@ -58,7 +58,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
|
ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
|
||||||
|
|
||||||
// Macros:
|
// Macros:
|
||||||
ungated!(derive, Normal, template!(List: "Trait1, Trait2, ...")),
|
|
||||||
ungated!(automatically_derived, Normal, template!(Word)),
|
ungated!(automatically_derived, Normal, template!(Word)),
|
||||||
// FIXME(#14407)
|
// FIXME(#14407)
|
||||||
ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),
|
ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),
|
||||||
@ -98,8 +97,8 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#),
|
template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...""#),
|
||||||
),
|
),
|
||||||
ungated!(link_name, AssumedUsed, template!(NameValueStr: "name")),
|
ungated!(link_name, AssumedUsed, template!(NameValueStr: "name")),
|
||||||
ungated!(no_link, Normal, template!(Word)),
|
ungated!(no_link, AssumedUsed, template!(Word)),
|
||||||
ungated!(repr, Normal, template!(List: "C")),
|
ungated!(repr, AssumedUsed, template!(List: "C")),
|
||||||
ungated!(export_name, AssumedUsed, template!(NameValueStr: "name")),
|
ungated!(export_name, AssumedUsed, template!(NameValueStr: "name")),
|
||||||
ungated!(link_section, AssumedUsed, template!(NameValueStr: "name")),
|
ungated!(link_section, AssumedUsed, template!(NameValueStr: "name")),
|
||||||
ungated!(no_mangle, AssumedUsed, template!(Word)),
|
ungated!(no_mangle, AssumedUsed, template!(Word)),
|
||||||
@ -112,6 +111,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit,
|
const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit,
|
||||||
experimental!(const_eval_limit)
|
experimental!(const_eval_limit)
|
||||||
),
|
),
|
||||||
|
gated!(
|
||||||
|
move_size_limit, CrateLevel, template!(NameValueStr: "N"), large_assignments,
|
||||||
|
experimental!(move_size_limit)
|
||||||
|
),
|
||||||
|
|
||||||
// Entry point:
|
// Entry point:
|
||||||
ungated!(main, Normal, template!(Word)),
|
ungated!(main, Normal, template!(Word)),
|
||||||
@ -140,6 +143,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
template!(List: "address, memory, thread"),
|
template!(List: "address, memory, thread"),
|
||||||
experimental!(no_sanitize)
|
experimental!(no_sanitize)
|
||||||
),
|
),
|
||||||
|
gated!(no_coverage, AssumedUsed, template!(Word), experimental!(no_coverage)),
|
||||||
|
|
||||||
// FIXME: #14408 assume docs are used since rustdoc looks at them.
|
// FIXME: #14408 assume docs are used since rustdoc looks at them.
|
||||||
ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),
|
ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),
|
||||||
@ -150,11 +154,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
|
|
||||||
// Linking:
|
// Linking:
|
||||||
gated!(naked, AssumedUsed, template!(Word), naked_functions, experimental!(naked)),
|
gated!(naked, AssumedUsed, template!(Word), naked_functions, experimental!(naked)),
|
||||||
gated!(
|
|
||||||
link_args, Normal, template!(NameValueStr: "args"),
|
|
||||||
"the `link_args` attribute is experimental and not portable across platforms, \
|
|
||||||
it is recommended to use `#[link(name = \"foo\")] instead",
|
|
||||||
),
|
|
||||||
gated!(
|
gated!(
|
||||||
link_ordinal, AssumedUsed, template!(List: "ordinal"), raw_dylib,
|
link_ordinal, AssumedUsed, template!(List: "ordinal"), raw_dylib,
|
||||||
experimental!(link_ordinal)
|
experimental!(link_ordinal)
|
||||||
@ -172,7 +171,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
"custom test frameworks are an unstable feature",
|
"custom test frameworks are an unstable feature",
|
||||||
),
|
),
|
||||||
// RFC #1268
|
// RFC #1268
|
||||||
gated!(marker, Normal, template!(Word), marker_trait_attr, experimental!(marker)),
|
gated!(marker, AssumedUsed, template!(Word), marker_trait_attr, experimental!(marker)),
|
||||||
gated!(
|
gated!(
|
||||||
thread_local, AssumedUsed, template!(Word),
|
thread_local, AssumedUsed, template!(Word),
|
||||||
"`#[thread_local]` is an experimental feature, and does not currently handle destructors",
|
"`#[thread_local]` is an experimental feature, and does not currently handle destructors",
|
||||||
@ -291,7 +290,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
// Internal attributes, Macro related:
|
// Internal attributes, Macro related:
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word), IMPL_DETAIL),
|
rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word, NameValueStr: "name"), IMPL_DETAIL),
|
||||||
rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
|
rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
|
||||||
rustc_attr!(
|
rustc_attr!(
|
||||||
rustc_macro_transparency, AssumedUsed,
|
rustc_macro_transparency, AssumedUsed,
|
||||||
@ -319,7 +318,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL),
|
rustc_attr!(rustc_promotable, AssumedUsed, template!(Word), IMPL_DETAIL),
|
||||||
rustc_attr!(rustc_args_required_const, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
|
rustc_attr!(rustc_legacy_const_generics, AssumedUsed, template!(List: "N"), INTERNAL_UNSTABLE),
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Internal attributes, Layout related:
|
// Internal attributes, Layout related:
|
||||||
@ -380,6 +379,15 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
rustc_specialization_trait, Normal, template!(Word),
|
rustc_specialization_trait, Normal, template!(Word),
|
||||||
"the `#[rustc_specialization_trait]` attribute is used to check specializations"
|
"the `#[rustc_specialization_trait]` attribute is used to check specializations"
|
||||||
),
|
),
|
||||||
|
rustc_attr!(
|
||||||
|
rustc_main, Normal, template!(Word),
|
||||||
|
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
|
||||||
|
),
|
||||||
|
rustc_attr!(
|
||||||
|
rustc_skip_array_during_method_dispatch, Normal, template!(Word),
|
||||||
|
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
|
||||||
|
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
|
||||||
|
),
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Internal attributes, Testing:
|
// Internal attributes, Testing:
|
||||||
@ -387,6 +395,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
|
|
||||||
rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)),
|
rustc_attr!(TEST, rustc_outlives, Normal, template!(Word)),
|
||||||
rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)),
|
rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word)),
|
||||||
|
rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word)),
|
||||||
rustc_attr!(TEST, rustc_variance, Normal, template!(Word)),
|
rustc_attr!(TEST, rustc_variance, Normal, template!(Word)),
|
||||||
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")),
|
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ...")),
|
||||||
rustc_attr!(TEST, rustc_regions, Normal, template!(Word)),
|
rustc_attr!(TEST, rustc_regions, Normal, template!(Word)),
|
||||||
@ -395,12 +404,9 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||||||
template!(Word, List: "delay_span_bug_from_inside_query")
|
template!(Word, List: "delay_span_bug_from_inside_query")
|
||||||
),
|
),
|
||||||
rustc_attr!(TEST, rustc_dump_user_substs, AssumedUsed, template!(Word)),
|
rustc_attr!(TEST, rustc_dump_user_substs, AssumedUsed, template!(Word)),
|
||||||
|
rustc_attr!(TEST, rustc_evaluate_where_clauses, AssumedUsed, template!(Word)),
|
||||||
rustc_attr!(TEST, rustc_if_this_changed, AssumedUsed, template!(Word, List: "DepNode")),
|
rustc_attr!(TEST, rustc_if_this_changed, AssumedUsed, template!(Word, List: "DepNode")),
|
||||||
rustc_attr!(TEST, rustc_then_this_would_need, AssumedUsed, template!(List: "DepNode")),
|
rustc_attr!(TEST, rustc_then_this_would_need, AssumedUsed, template!(List: "DepNode")),
|
||||||
rustc_attr!(
|
|
||||||
TEST, rustc_dirty, AssumedUsed,
|
|
||||||
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
|
|
||||||
),
|
|
||||||
rustc_attr!(
|
rustc_attr!(
|
||||||
TEST, rustc_clean, AssumedUsed,
|
TEST, rustc_clean, AssumedUsed,
|
||||||
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
|
template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#),
|
||||||
|
@ -51,6 +51,9 @@ pub trait InternDatabase: SourceDatabase {
|
|||||||
|
|
||||||
#[salsa::query_group(DefDatabaseStorage)]
|
#[salsa::query_group(DefDatabaseStorage)]
|
||||||
pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
||||||
|
#[salsa::input]
|
||||||
|
fn enable_proc_attr_macros(&self) -> bool;
|
||||||
|
|
||||||
#[salsa::invoke(ItemTree::file_item_tree_query)]
|
#[salsa::invoke(ItemTree::file_item_tree_query)]
|
||||||
fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
|
fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use attr::Attr;
|
||||||
use base_db::{impl_intern_key, salsa, CrateId};
|
use base_db::{impl_intern_key, salsa, CrateId};
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
ast_id_map::FileAstId,
|
ast_id_map::FileAstId,
|
||||||
@ -768,3 +769,42 @@ fn derive_macro_as_call_id(
|
|||||||
.into();
|
.into();
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attr_macro_as_call_id(
|
||||||
|
item_attr: &AstIdWithPath<ast::Item>,
|
||||||
|
macro_attr: &Attr,
|
||||||
|
db: &dyn db::DefDatabase,
|
||||||
|
krate: CrateId,
|
||||||
|
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
|
||||||
|
) -> Result<MacroCallId, UnresolvedMacro> {
|
||||||
|
let def: MacroDefId = resolver(item_attr.path.clone())
|
||||||
|
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
|
||||||
|
let last_segment = item_attr
|
||||||
|
.path
|
||||||
|
.segments()
|
||||||
|
.last()
|
||||||
|
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
|
||||||
|
let mut arg = match ¯o_attr.input {
|
||||||
|
Some(input) => match &**input {
|
||||||
|
attr::AttrInput::Literal(_) => tt::Subtree::default(),
|
||||||
|
attr::AttrInput::TokenTree(tt) => tt.clone(),
|
||||||
|
},
|
||||||
|
None => tt::Subtree::default(),
|
||||||
|
};
|
||||||
|
// The parentheses are always disposed here.
|
||||||
|
arg.delimiter = None;
|
||||||
|
|
||||||
|
let res = def
|
||||||
|
.as_lazy_macro(
|
||||||
|
db.upcast(),
|
||||||
|
krate,
|
||||||
|
MacroCallKind::Attr {
|
||||||
|
ast_id: item_attr.ast_id,
|
||||||
|
attr_name: last_segment.to_string(),
|
||||||
|
attr_args: arg,
|
||||||
|
invoc_attr_index: macro_attr.id.ast_index,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ use syntax::ast;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
attr::{Attr, AttrId, AttrInput, Attrs},
|
attr::{Attr, AttrId, AttrInput, Attrs},
|
||||||
builtin_attr,
|
attr_macro_as_call_id, builtin_attr,
|
||||||
db::DefDatabase,
|
db::DefDatabase,
|
||||||
derive_macro_as_call_id,
|
derive_macro_as_call_id,
|
||||||
intern::Interned,
|
intern::Interned,
|
||||||
@ -223,7 +223,7 @@ struct MacroDirective {
|
|||||||
enum MacroDirectiveKind {
|
enum MacroDirectiveKind {
|
||||||
FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
|
FnLike { ast_id: AstIdWithPath<ast::MacroCall>, fragment: FragmentKind },
|
||||||
Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
|
Derive { ast_id: AstIdWithPath<ast::Item>, derive_attr: AttrId },
|
||||||
Attr { ast_id: AstIdWithPath<ast::Item>, attr: AttrId, mod_item: ModItem },
|
Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem },
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DefData<'a> {
|
struct DefData<'a> {
|
||||||
@ -419,7 +419,7 @@ impl DefCollector<'_> {
|
|||||||
let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
|
let mut unresolved_macros = std::mem::replace(&mut self.unresolved_macros, Vec::new());
|
||||||
let pos = unresolved_macros.iter().position(|directive| {
|
let pos = unresolved_macros.iter().position(|directive| {
|
||||||
if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
|
if let MacroDirectiveKind::Attr { ast_id, mod_item, attr } = &directive.kind {
|
||||||
self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), *attr);
|
self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), attr.id);
|
||||||
|
|
||||||
let file_id = ast_id.ast_id.file_id;
|
let file_id = ast_id.ast_id.file_id;
|
||||||
let item_tree = self.db.file_item_tree(file_id);
|
let item_tree = self.db.file_item_tree(file_id);
|
||||||
@ -1050,7 +1050,7 @@ impl DefCollector<'_> {
|
|||||||
let file_id = ast_id.ast_id.file_id;
|
let file_id = ast_id.ast_id.file_id;
|
||||||
let item_tree = self.db.file_item_tree(file_id);
|
let item_tree = self.db.file_item_tree(file_id);
|
||||||
let mod_dir = self.mod_dirs[&directive.module_id].clone();
|
let mod_dir = self.mod_dirs[&directive.module_id].clone();
|
||||||
self.skip_attrs.insert(InFile::new(file_id, *mod_item), *attr);
|
self.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
|
||||||
ModCollector {
|
ModCollector {
|
||||||
def_collector: &mut *self,
|
def_collector: &mut *self,
|
||||||
macro_depth: directive.depth,
|
macro_depth: directive.depth,
|
||||||
@ -1067,8 +1067,56 @@ impl DefCollector<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.db.enable_proc_attr_macros() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Not resolved to a derive helper, so try to resolve as a macro.
|
// Not resolved to a derive helper, so try to resolve as a macro.
|
||||||
// FIXME: not yet :)
|
match attr_macro_as_call_id(
|
||||||
|
ast_id,
|
||||||
|
attr,
|
||||||
|
self.db,
|
||||||
|
self.def_map.krate,
|
||||||
|
&resolver,
|
||||||
|
) {
|
||||||
|
Ok(call_id) => {
|
||||||
|
let loc: MacroCallLoc = self.db.lookup_intern_macro(call_id);
|
||||||
|
if let MacroDefKind::ProcMacro(exp, ..) = &loc.def.kind {
|
||||||
|
if exp.is_dummy() {
|
||||||
|
// Proc macros that cannot be expanded are treated as not
|
||||||
|
// resolved, in order to fall back later.
|
||||||
|
self.def_map.diagnostics.push(
|
||||||
|
DefDiagnostic::unresolved_proc_macro(
|
||||||
|
directive.module_id,
|
||||||
|
loc.kind,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let file_id = ast_id.ast_id.file_id;
|
||||||
|
let item_tree = self.db.file_item_tree(file_id);
|
||||||
|
let mod_dir = self.mod_dirs[&directive.module_id].clone();
|
||||||
|
self.skip_attrs
|
||||||
|
.insert(InFile::new(file_id, *mod_item), attr.id);
|
||||||
|
ModCollector {
|
||||||
|
def_collector: &mut *self,
|
||||||
|
macro_depth: directive.depth,
|
||||||
|
module_id: directive.module_id,
|
||||||
|
file_id,
|
||||||
|
item_tree: &item_tree,
|
||||||
|
mod_dir,
|
||||||
|
}
|
||||||
|
.collect(&[*mod_item]);
|
||||||
|
|
||||||
|
// Remove the macro directive.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolved.push((directive.module_id, call_id, directive.depth));
|
||||||
|
res = ReachedFixedPoint::No;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Err(UnresolvedMacro { .. }) => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1628,7 +1676,7 @@ impl ModCollector<'_, '_> {
|
|||||||
self.def_collector.unresolved_macros.push(MacroDirective {
|
self.def_collector.unresolved_macros.push(MacroDirective {
|
||||||
module_id: self.module_id,
|
module_id: self.module_id,
|
||||||
depth: self.macro_depth + 1,
|
depth: self.macro_depth + 1,
|
||||||
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.id, mod_item },
|
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.clone(), mod_item },
|
||||||
});
|
});
|
||||||
|
|
||||||
return Err(());
|
return Err(());
|
||||||
|
@ -30,12 +30,19 @@ use crate::{
|
|||||||
crate::db::InternDatabaseStorage,
|
crate::db::InternDatabaseStorage,
|
||||||
crate::db::DefDatabaseStorage
|
crate::db::DefDatabaseStorage
|
||||||
)]
|
)]
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct TestDB {
|
pub(crate) struct TestDB {
|
||||||
storage: salsa::Storage<TestDB>,
|
storage: salsa::Storage<TestDB>,
|
||||||
events: Mutex<Option<Vec<salsa::Event>>>,
|
events: Mutex<Option<Vec<salsa::Event>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TestDB {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut this = Self { storage: Default::default(), events: Default::default() };
|
||||||
|
this.set_enable_proc_attr_macros(true);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Upcast<dyn AstDatabase> for TestDB {
|
impl Upcast<dyn AstDatabase> for TestDB {
|
||||||
fn upcast(&self) -> &(dyn AstDatabase + 'static) {
|
fn upcast(&self) -> &(dyn AstDatabase + 'static) {
|
||||||
&*self
|
&*self
|
||||||
|
@ -13,8 +13,8 @@ use syntax::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
|
ast_id_map::AstIdMap, hygiene::HygieneFrame, input::process_macro_input, BuiltinDeriveExpander,
|
||||||
BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId,
|
BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc,
|
||||||
MacroDefKind, MacroFile, ProcMacroExpander,
|
MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Total limit on the number of tokens produced by any macro invocation.
|
/// Total limit on the number of tokens produced by any macro invocation.
|
||||||
@ -377,7 +377,12 @@ fn expand_proc_macro(
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
expander.expand(db, loc.krate, ¯o_arg.0)
|
let attr_arg = match &loc.kind {
|
||||||
|
MacroCallKind::Attr { attr_args, .. } => Some(attr_args),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
expander.expand(db, loc.krate, ¯o_arg.0, attr_arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
|
fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
|
||||||
|
@ -28,6 +28,14 @@ pub(crate) fn process_macro_input(
|
|||||||
|
|
||||||
remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
|
remove_derives_up_to(item, derive_attr_index as usize).syntax().clone()
|
||||||
}
|
}
|
||||||
|
MacroCallKind::Attr { invoc_attr_index, .. } => {
|
||||||
|
let item = match ast::Item::cast(node.clone()) {
|
||||||
|
Some(item) => item,
|
||||||
|
None => return node,
|
||||||
|
};
|
||||||
|
|
||||||
|
remove_attr_invoc(item, invoc_attr_index as usize).syntax().clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +54,17 @@ fn remove_derives_up_to(item: ast::Item, attr_index: usize) -> ast::Item {
|
|||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the attribute invoking an attribute macro from `item`.
|
||||||
|
fn remove_attr_invoc(item: ast::Item, attr_index: usize) -> ast::Item {
|
||||||
|
let item = item.clone_for_update();
|
||||||
|
let attr = item
|
||||||
|
.attrs()
|
||||||
|
.nth(attr_index)
|
||||||
|
.unwrap_or_else(|| panic!("cannot find attribute #{}", attr_index));
|
||||||
|
attr.syntax().detach();
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use base_db::fixture::WithFixture;
|
use base_db::fixture::WithFixture;
|
||||||
|
@ -258,14 +258,29 @@ pub enum MacroCallKind {
|
|||||||
/// out-of-line modules, which may have attributes spread across 2 files!
|
/// out-of-line modules, which may have attributes spread across 2 files!
|
||||||
derive_attr_index: u32,
|
derive_attr_index: u32,
|
||||||
},
|
},
|
||||||
|
Attr {
|
||||||
|
ast_id: AstId<ast::Item>,
|
||||||
|
attr_name: String,
|
||||||
|
attr_args: tt::Subtree,
|
||||||
|
/// Syntactical index of the invoking `#[attribute]`.
|
||||||
|
///
|
||||||
|
/// Outer attributes are counted first, then inner attributes. This does not support
|
||||||
|
/// out-of-line modules, which may have attributes spread across 2 files!
|
||||||
|
invoc_attr_index: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole
|
||||||
|
// `cfg_attr` instead of just one of the attributes it expands to
|
||||||
|
|
||||||
impl MacroCallKind {
|
impl MacroCallKind {
|
||||||
/// Returns the file containing the macro invocation.
|
/// Returns the file containing the macro invocation.
|
||||||
fn file_id(&self) -> HirFileId {
|
fn file_id(&self) -> HirFileId {
|
||||||
match self {
|
match self {
|
||||||
MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id,
|
MacroCallKind::FnLike { ast_id, .. } => ast_id.file_id,
|
||||||
MacroCallKind::Derive { ast_id, .. } => ast_id.file_id,
|
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
|
||||||
|
ast_id.file_id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +289,7 @@ impl MacroCallKind {
|
|||||||
MacroCallKind::FnLike { ast_id, .. } => {
|
MacroCallKind::FnLike { ast_id, .. } => {
|
||||||
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
||||||
}
|
}
|
||||||
MacroCallKind::Derive { ast_id, .. } => {
|
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
|
||||||
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,7 +300,9 @@ impl MacroCallKind {
|
|||||||
MacroCallKind::FnLike { ast_id, .. } => {
|
MacroCallKind::FnLike { ast_id, .. } => {
|
||||||
Some(ast_id.to_node(db).token_tree()?.syntax().clone())
|
Some(ast_id.to_node(db).token_tree()?.syntax().clone())
|
||||||
}
|
}
|
||||||
MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
|
MacroCallKind::Derive { ast_id, .. } | MacroCallKind::Attr { ast_id, .. } => {
|
||||||
|
Some(ast_id.to_node(db).syntax().clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +310,7 @@ impl MacroCallKind {
|
|||||||
match self {
|
match self {
|
||||||
MacroCallKind::FnLike { fragment, .. } => *fragment,
|
MacroCallKind::FnLike { fragment, .. } => *fragment,
|
||||||
MacroCallKind::Derive { .. } => FragmentKind::Items,
|
MacroCallKind::Derive { .. } => FragmentKind::Items,
|
||||||
|
MacroCallKind::Attr { .. } => FragmentKind::Items, // is this always correct?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,16 @@ impl ProcMacroExpander {
|
|||||||
Self { krate, proc_macro_id: None }
|
Self { krate, proc_macro_id: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dummy(&self) -> bool {
|
||||||
|
self.proc_macro_id.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expand(
|
pub fn expand(
|
||||||
self,
|
self,
|
||||||
db: &dyn AstDatabase,
|
db: &dyn AstDatabase,
|
||||||
calling_crate: CrateId,
|
calling_crate: CrateId,
|
||||||
tt: &tt::Subtree,
|
tt: &tt::Subtree,
|
||||||
|
attr_arg: Option<&tt::Subtree>,
|
||||||
) -> Result<tt::Subtree, mbe::ExpandError> {
|
) -> Result<tt::Subtree, mbe::ExpandError> {
|
||||||
match self.proc_macro_id {
|
match self.proc_macro_id {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
@ -46,7 +51,7 @@ impl ProcMacroExpander {
|
|||||||
// Proc macros have access to the environment variables of the invoking crate.
|
// Proc macros have access to the environment variables of the invoking crate.
|
||||||
let env = &krate_graph[calling_crate].env;
|
let env = &krate_graph[calling_crate].env;
|
||||||
|
|
||||||
proc_macro.expander.expand(&tt, None, &env).map_err(mbe::ExpandError::from)
|
proc_macro.expander.expand(&tt, attr_arg, &env).map_err(mbe::ExpandError::from)
|
||||||
}
|
}
|
||||||
None => Err(mbe::ExpandError::UnresolvedProcMacro),
|
None => Err(mbe::ExpandError::UnresolvedProcMacro),
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,19 @@ use test_utils::extract_annotations;
|
|||||||
hir_def::db::DefDatabaseStorage,
|
hir_def::db::DefDatabaseStorage,
|
||||||
crate::db::HirDatabaseStorage
|
crate::db::HirDatabaseStorage
|
||||||
)]
|
)]
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct TestDB {
|
pub(crate) struct TestDB {
|
||||||
storage: salsa::Storage<TestDB>,
|
storage: salsa::Storage<TestDB>,
|
||||||
events: Mutex<Option<Vec<salsa::Event>>>,
|
events: Mutex<Option<Vec<salsa::Event>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TestDB {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut this = Self { storage: Default::default(), events: Default::default() };
|
||||||
|
this.set_enable_proc_attr_macros(true);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for TestDB {
|
impl fmt::Debug for TestDB {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("TestDB").finish()
|
f.debug_struct("TestDB").finish()
|
||||||
|
@ -93,6 +93,7 @@ impl RootDatabase {
|
|||||||
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
|
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
|
||||||
db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
|
db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
|
||||||
db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
|
db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
|
||||||
|
db.set_enable_proc_attr_macros(Default::default());
|
||||||
db.update_lru_capacity(lru_capacity);
|
db.update_lru_capacity(lru_capacity);
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,9 @@ config_data! {
|
|||||||
/// and a blue icon in the `Problems Panel`.
|
/// and a blue icon in the `Problems Panel`.
|
||||||
diagnostics_warningsAsInfo: Vec<String> = "[]",
|
diagnostics_warningsAsInfo: Vec<String> = "[]",
|
||||||
|
|
||||||
|
/// Expand attribute macros.
|
||||||
|
experimental_procAttrMacros: bool = "false",
|
||||||
|
|
||||||
/// Controls file watching implementation.
|
/// Controls file watching implementation.
|
||||||
files_watcher: String = "\"client\"",
|
files_watcher: String = "\"client\"",
|
||||||
/// These directories will be ignored by rust-analyzer.
|
/// These directories will be ignored by rust-analyzer.
|
||||||
@ -546,6 +549,9 @@ impl Config {
|
|||||||
let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?;
|
let path = self.data.procMacro_server.clone().or_else(|| std::env::current_exe().ok())?;
|
||||||
Some((path, vec!["proc-macro".into()]))
|
Some((path, vec!["proc-macro".into()]))
|
||||||
}
|
}
|
||||||
|
pub fn expand_proc_attr_macros(&self) -> bool {
|
||||||
|
self.data.experimental_procAttrMacros
|
||||||
|
}
|
||||||
pub fn files(&self) -> FilesConfig {
|
pub fn files(&self) -> FilesConfig {
|
||||||
FilesConfig {
|
FilesConfig {
|
||||||
watcher: match self.data.files_watcher.as_str() {
|
watcher: match self.data.files_watcher.as_str() {
|
||||||
|
@ -119,12 +119,12 @@ impl GlobalState {
|
|||||||
|
|
||||||
let analysis_host = AnalysisHost::new(config.lru_capacity());
|
let analysis_host = AnalysisHost::new(config.lru_capacity());
|
||||||
let (flycheck_sender, flycheck_receiver) = unbounded();
|
let (flycheck_sender, flycheck_receiver) = unbounded();
|
||||||
GlobalState {
|
let mut this = GlobalState {
|
||||||
sender,
|
sender,
|
||||||
req_queue: ReqQueue::default(),
|
req_queue: ReqQueue::default(),
|
||||||
task_pool,
|
task_pool,
|
||||||
loader,
|
loader,
|
||||||
config: Arc::new(config),
|
config: Arc::new(config.clone()),
|
||||||
analysis_host,
|
analysis_host,
|
||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
mem_docs: FxHashMap::default(),
|
mem_docs: FxHashMap::default(),
|
||||||
@ -151,7 +151,10 @@ impl GlobalState {
|
|||||||
|
|
||||||
fetch_build_data_queue: OpQueue::default(),
|
fetch_build_data_queue: OpQueue::default(),
|
||||||
latest_requests: Default::default(),
|
latest_requests: Default::default(),
|
||||||
}
|
};
|
||||||
|
// Apply any required database inputs from the config.
|
||||||
|
this.update_configuration(config);
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_changes(&mut self) -> bool {
|
pub(crate) fn process_changes(&mut self) -> bool {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
use std::{mem, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
use flycheck::{FlycheckConfig, FlycheckHandle};
|
use flycheck::{FlycheckConfig, FlycheckHandle};
|
||||||
|
use hir::db::DefDatabase;
|
||||||
use ide::Change;
|
use ide::Change;
|
||||||
use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
|
use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
|
||||||
use project_model::{BuildDataCollector, BuildDataResult, ProcMacroClient, ProjectWorkspace};
|
use project_model::{BuildDataCollector, BuildDataResult, ProcMacroClient, ProjectWorkspace};
|
||||||
@ -47,6 +48,11 @@ impl GlobalState {
|
|||||||
} else if self.config.flycheck() != old_config.flycheck() {
|
} else if self.config.flycheck() != old_config.flycheck() {
|
||||||
self.reload_flycheck();
|
self.reload_flycheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply experimental feature flags.
|
||||||
|
self.analysis_host
|
||||||
|
.raw_database_mut()
|
||||||
|
.set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
|
||||||
}
|
}
|
||||||
pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
|
pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
|
||||||
if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
|
if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
|
||||||
|
@ -181,6 +181,11 @@ List of warnings that should be displayed with info severity.
|
|||||||
The warnings will be indicated by a blue squiggly underline in code
|
The warnings will be indicated by a blue squiggly underline in code
|
||||||
and a blue icon in the `Problems Panel`.
|
and a blue icon in the `Problems Panel`.
|
||||||
--
|
--
|
||||||
|
[[rust-analyzer.experimental.procAttrMacros]]rust-analyzer.experimental.procAttrMacros (default: `false`)::
|
||||||
|
+
|
||||||
|
--
|
||||||
|
Expand attribute macros.
|
||||||
|
--
|
||||||
[[rust-analyzer.files.watcher]]rust-analyzer.files.watcher (default: `"client"`)::
|
[[rust-analyzer.files.watcher]]rust-analyzer.files.watcher (default: `"client"`)::
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
|
@ -617,6 +617,11 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rust-analyzer.experimental.procAttrMacros": {
|
||||||
|
"markdownDescription": "Expand attribute macros.",
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"rust-analyzer.files.watcher": {
|
"rust-analyzer.files.watcher": {
|
||||||
"markdownDescription": "Controls file watching implementation.",
|
"markdownDescription": "Controls file watching implementation.",
|
||||||
"default": "client",
|
"default": "client",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user