11193: feat: Add config to replace specific proc-macros with dummy expanders r=Veykril a=Veykril

With this one can specify proc-macros from crates to expand into their input as a (temporary) workaround for the current completion problems with some of the bigger attribute proc-macros like `async_trait`.

This could've been done by just not expanding these macros, but that would require fiddling with nameres. I felt like this approach was simpler to pull off while also keeping the behaviour of the attributes/proc-macro in that they still expand instead of being dead syntax to us.

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11052

Usage(`async_trait` as example):
```jsonc
    "rust-analyzer.procMacro.dummies": {
        "async-trait": [ // crate name(as per its cargo.toml definition, not the dependency name)
            "async_trait" // exported proc-macro name
        ]
    },
```

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-01-06 12:53:49 +00:00 committed by GitHub
commit 8887d2016f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 18 deletions

View file

@ -88,7 +88,7 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
}
fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
project_workspace.to_crate_graph(&mut |_| Vec::new(), &mut {
project_workspace.to_crate_graph(&Default::default(), &mut |_, _| Vec::new(), &mut {
let mut counter = 0;
move |_path| {
counter += 1;

View file

@ -387,10 +387,14 @@ impl ProjectWorkspace {
pub fn to_crate_graph(
&self,
load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
dummy_replace: &FxHashMap<Box<str>, Box<[Box<str>]>>,
load_proc_macro: &mut dyn FnMut(&AbsPath, &[Box<str>]) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
) -> CrateGraph {
let _p = profile::span("ProjectWorkspace::to_crate_graph");
let load_proc_macro = &mut |crate_name: &_, path: &_| {
load_proc_macro(path, dummy_replace.get(crate_name).map(|it| &**it).unwrap_or_default())
};
let mut crate_graph = match self {
ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
@ -432,7 +436,7 @@ impl ProjectWorkspace {
fn project_json_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
project: &ProjectJson,
sysroot: &Option<Sysroot>,
@ -452,7 +456,12 @@ fn project_json_to_crate_graph(
})
.map(|(crate_id, krate, file_id)| {
let env = krate.env.clone().into_iter().collect();
let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| load_proc_macro(&it));
let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| {
load_proc_macro(
krate.display_name.as_ref().map(|it| it.canonical_name()).unwrap_or(""),
&it,
)
});
let target_cfgs = match krate.target.as_deref() {
Some(target) => {
@ -513,7 +522,7 @@ fn project_json_to_crate_graph(
fn cargo_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace,
build_scripts: &WorkspaceBuildScripts,
@ -571,7 +580,7 @@ fn cargo_to_crate_graph(
&cargo[pkg],
build_scripts.outputs.get(pkg),
cfg_options,
load_proc_macro,
&mut |path| load_proc_macro(&cargo[tgt].name, path),
file_id,
&cargo[tgt].name,
);
@ -702,7 +711,7 @@ fn handle_rustc_crates(
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
crate_graph: &mut CrateGraph,
cfg_options: &CfgOptions,
load_proc_macro: &mut dyn FnMut(&AbsPath) -> Vec<ProcMacro>,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> Vec<ProcMacro>,
pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
public_deps: &SysrootPublicDeps,
cargo: &CargoWorkspace,
@ -738,7 +747,7 @@ fn handle_rustc_crates(
&rustc_workspace[pkg],
None,
cfg_options,
load_proc_macro,
&mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
file_id,
&rustc_workspace[tgt].name,
);

View file

@ -66,7 +66,8 @@ pub fn load_workspace(
};
let crate_graph = ws.to_crate_graph(
&mut |path: &AbsPath| load_proc_macro(proc_macro_client.as_ref(), path),
&Default::default(),
&mut |path: &AbsPath, _| load_proc_macro(proc_macro_client.as_ref(), path, &[]),
&mut |path: &AbsPath| {
let contents = loader.load_sync(path);
let path = vfs::VfsPath::from(path.to_path_buf());

View file

@ -301,6 +301,10 @@ config_data! {
/// Internal config, path to proc-macro server executable (typically,
/// this is rust-analyzer itself, but we override this in tests).
procMacro_server: Option<PathBuf> = "null",
/// These proc-macros will be ignored when trying to expand them.
///
/// This config takes a map of crate names with the exported proc-macro names to ignore as values.
procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
/// Command to be executed instead of 'cargo' for runnables.
runnables_overrideCargo: Option<String> = "null",
@ -716,6 +720,9 @@ impl Config {
};
Some((path, vec!["proc-macro".into()]))
}
pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
&self.data.procMacro_ignored
}
pub fn expand_proc_attr_macros(&self) -> bool {
self.data.experimental_procAttrMacros
}
@ -1163,7 +1170,13 @@ fn get_field<T: DeserializeOwned>(
.find_map(move |field| {
let mut pointer = field.replace('_', "/");
pointer.insert(0, '/');
json.pointer_mut(&pointer).and_then(|it| serde_json::from_value(it.take()).ok())
json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
Ok(it) => Some(it),
Err(e) => {
tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
None
}
})
})
.unwrap_or(default)
}
@ -1224,6 +1237,9 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"items": { "type": "string" },
"uniqueItems": true,
},
"FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
"type": "object",
},
"FxHashMap<String, SnippetDef>" => set! {
"type": "object",
},

View file

@ -10,6 +10,7 @@ use ide_db::base_db::{
};
use proc_macro_api::{MacroDylib, ProcMacroServer};
use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
use syntax::SmolStr;
use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
use crate::{
@ -306,8 +307,9 @@ impl GlobalState {
// Create crate graph from all the workspaces
let crate_graph = {
let proc_macro_client = self.proc_macro_client.as_ref();
let mut load_proc_macro =
move |path: &AbsPath| load_proc_macro(proc_macro_client, path);
let mut load_proc_macro = move |path: &AbsPath, dummy_replace: &_| {
load_proc_macro(proc_macro_client, path, dummy_replace)
};
let vfs = &mut self.vfs.write().0;
let loader = &mut self.loader;
@ -328,7 +330,11 @@ impl GlobalState {
let mut crate_graph = CrateGraph::default();
for ws in self.workspaces.iter() {
crate_graph.extend(ws.to_crate_graph(&mut load_proc_macro, &mut load));
crate_graph.extend(ws.to_crate_graph(
self.config.dummy_replacements(),
&mut load_proc_macro,
&mut load,
));
}
crate_graph
};
@ -505,7 +511,13 @@ impl SourceRootConfig {
}
}
pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath) -> Vec<ProcMacro> {
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
/// with an identity dummy expander.
pub(crate) fn load_proc_macro(
client: Option<&ProcMacroServer>,
path: &AbsPath,
dummy_replace: &[Box<str>],
) -> Vec<ProcMacro> {
let dylib = match MacroDylib::new(path.to_path_buf()) {
Ok(it) => it,
Err(err) => {
@ -532,17 +544,25 @@ pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath)
Vec::new()
}
})
.map(expander_to_proc_macro)
.map(|expander| expander_to_proc_macro(expander, dummy_replace))
.collect();
fn expander_to_proc_macro(expander: proc_macro_api::ProcMacro) -> ProcMacro {
let name = expander.name().into();
fn expander_to_proc_macro(
expander: proc_macro_api::ProcMacro,
dummy_replace: &[Box<str>],
) -> ProcMacro {
let name = SmolStr::from(expander.name());
let kind = match expander.kind() {
proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
};
let expander = Arc::new(Expander(expander));
let expander: Arc<dyn ProcMacroExpander> =
if dummy_replace.iter().any(|replace| &**replace == name) {
Arc::new(DummyExpander)
} else {
Arc::new(Expander(expander))
};
ProcMacro { name, kind, expander }
}
@ -564,6 +584,21 @@ pub(crate) fn load_proc_macro(client: Option<&ProcMacroServer>, path: &AbsPath)
}
}
}
/// Dummy identity expander, used for proc-macros that are deliberately ignored by the user.
#[derive(Debug)]
struct DummyExpander;
impl ProcMacroExpander for DummyExpander {
fn expand(
&self,
subtree: &tt::Subtree,
_: Option<&tt::Subtree>,
_: &Env,
) -> Result<tt::Subtree, ProcMacroExpansionError> {
Ok(subtree.clone())
}
}
}
pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {

View file

@ -455,6 +455,13 @@ Enable support for procedural macros, implies `#rust-analyzer.cargo.runBuildScri
Internal config, path to proc-macro server executable (typically,
this is rust-analyzer itself, but we override this in tests).
--
[[rust-analyzer.procMacro.ignored]]rust-analyzer.procMacro.ignored (default: `{}`)::
+
--
These proc-macros will be ignored when trying to expand them.
This config takes a map of crate names with the exported proc-macro names to ignore as values.
--
[[rust-analyzer.runnables.overrideCargo]]rust-analyzer.runnables.overrideCargo (default: `null`)::
+
--

View file

@ -880,6 +880,11 @@
"string"
]
},
"rust-analyzer.procMacro.ignored": {
"markdownDescription": "These proc-macros will be ignored when trying to expand them.\n\nThis config takes a map of crate names with the exported proc-macro names to ignore as values.",
"default": {},
"type": "object"
},
"rust-analyzer.runnables.overrideCargo": {
"markdownDescription": "Command to be executed instead of 'cargo' for runnables.",
"default": null,