Skip to content

Commit 0fb5d9d

Browse files
bors[bot]Jonas Schievink
andauthored
Merge #6033
6033: Make name resolution resolve proc macros instead of relying purely on the build system r=matklad a=jonas-schievink This makes name resolution look at proc-macro declaration attributes like `#[proc_macro_derive]` and defines the right proc macro in the macro namespace, fixing unresolved custom derives like `thiserror::Error` (which can cause false positives, now that we emit diagnostics for unresolved imports). This works even when proc-macro support is turned off, in which case we fall back to a dummy expander that always returns an error. IMO this is the right way to handle at least the name resolution part of proc. macros, while the *expansion* itself should rely on the build system to build and provide the macro DLL. It does mean that they may go out of sync, but we can provide diagnostics if that happens (something like "could not find macro X in crate Y – ensure that all files of crate Y are saved"). I think it is valuable to be able to reason about proc macros even when we can't expand them, since proc macro expansion can break between Rust releases or users might not want to turn it on for performance reasons. It allows us to provide better diagnostics on any proc macro invocation we're not expanding (like a weak warning that informs the user that proc macro support is turned off, or that it has been disabled because the server crashed). Fixes #5763 Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
2 parents 000046c + e88e4fb commit 0fb5d9d

File tree

4 files changed

+201
-29
lines changed

4 files changed

+201
-29
lines changed

crates/hir_def/src/item_scope.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ use std::collections::hash_map::Entry;
55

66
use base_db::CrateId;
77
use hir_expand::name::Name;
8+
use hir_expand::MacroDefKind;
89
use once_cell::sync::Lazy;
910
use rustc_hash::{FxHashMap, FxHashSet};
1011
use test_utils::mark;
1112

13+
use crate::ModuleId;
1214
use crate::{
1315
db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
1416
LocalModuleId, Lookup, MacroDefId, ModuleDefId, TraitId,
@@ -265,6 +267,26 @@ impl ItemScope {
265267
pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> {
266268
self.legacy_macros.clone()
267269
}
270+
271+
/// Marks everything that is not a procedural macro as private to `this_module`.
272+
pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) {
273+
self.types
274+
.values_mut()
275+
.chain(self.values.values_mut())
276+
.map(|(_, v)| v)
277+
.chain(self.unnamed_trait_imports.values_mut())
278+
.for_each(|vis| *vis = Visibility::Module(this_module));
279+
280+
for (mac, vis) in self.macros.values_mut() {
281+
if let MacroDefKind::ProcMacro(_) = mac.kind {
282+
// FIXME: Technically this is insufficient since reexports of proc macros are also
283+
// forbidden. Practically nobody does that.
284+
continue;
285+
}
286+
287+
*vis = Visibility::Module(this_module);
288+
}
289+
}
268290
}
269291

270292
impl PerNs {

crates/hir_def/src/nameres/collector.rs

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use hir_expand::{
1616
proc_macro::ProcMacroExpander,
1717
HirFileId, MacroCallId, MacroDefId, MacroDefKind,
1818
};
19-
use rustc_hash::FxHashMap;
20-
use rustc_hash::FxHashSet;
19+
use rustc_hash::{FxHashMap, FxHashSet};
2120
use syntax::ast;
2221
use test_utils::mark;
22+
use tt::{Leaf, TokenTree};
2323

2424
use crate::{
2525
attr::Attrs,
@@ -87,6 +87,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> Cr
8787
mod_dirs: FxHashMap::default(),
8888
cfg_options,
8989
proc_macros,
90+
exports_proc_macros: false,
9091
from_glob_import: Default::default(),
9192
};
9293
collector.collect();
@@ -202,7 +203,12 @@ struct DefCollector<'a> {
202203
unexpanded_attribute_macros: Vec<DeriveDirective>,
203204
mod_dirs: FxHashMap<LocalModuleId, ModDir>,
204205
cfg_options: &'a CfgOptions,
206+
/// List of procedural macros defined by this crate. This is read from the dynamic library
207+
/// built by the build system, and is the list of proc. macros we can actually expand. It is
208+
/// empty when proc. macro support is disabled (in which case we still do name resolution for
209+
/// them).
205210
proc_macros: Vec<(Name, ProcMacroExpander)>,
211+
exports_proc_macros: bool,
206212
from_glob_import: PerNsGlobImports,
207213
}
208214

@@ -261,24 +267,56 @@ impl DefCollector<'_> {
261267
}
262268
self.unresolved_imports = unresolved_imports;
263269

264-
// Record proc-macros
265-
self.collect_proc_macro();
270+
// FIXME: This condition should instead check if this is a `proc-macro` type crate.
271+
if self.exports_proc_macros {
272+
// A crate exporting procedural macros is not allowed to export anything else.
273+
//
274+
// Additionally, while the proc macro entry points must be `pub`, they are not publicly
275+
// exported in type/value namespace. This function reduces the visibility of all items
276+
// in the crate root that aren't proc macros.
277+
let root = self.def_map.root;
278+
let root = &mut self.def_map.modules[root];
279+
root.scope.censor_non_proc_macros(ModuleId {
280+
krate: self.def_map.krate,
281+
local_id: self.def_map.root,
282+
});
283+
}
266284
}
267285

268-
fn collect_proc_macro(&mut self) {
269-
let proc_macros = std::mem::take(&mut self.proc_macros);
270-
for (name, expander) in proc_macros {
271-
let krate = self.def_map.krate;
272-
273-
let macro_id = MacroDefId {
286+
/// Adds a definition of procedural macro `name` to the root module.
287+
///
288+
/// # Notes on procedural macro resolution
289+
///
290+
/// Procedural macro functionality is provided by the build system: It has to build the proc
291+
/// macro and pass the resulting dynamic library to rust-analyzer.
292+
///
293+
/// When procedural macro support is enabled, the list of proc macros exported by a crate is
294+
/// known before we resolve names in the crate. This list is stored in `self.proc_macros` and is
295+
/// derived from the dynamic library.
296+
///
297+
/// However, we *also* would like to be able to at least *resolve* macros on our own, without
298+
/// help by the build system. So, when the macro isn't found in `self.proc_macros`, we instead
299+
/// use a dummy expander that always errors. This comes with the drawback of macros potentially
300+
/// going out of sync with what the build system sees (since we resolve using VFS state, but
301+
/// Cargo builds only on-disk files). We could and probably should add diagnostics for that.
302+
fn resolve_proc_macro(&mut self, name: &Name) {
303+
self.exports_proc_macros = true;
304+
let macro_def = match self.proc_macros.iter().find(|(n, _)| n == name) {
305+
Some((_, expander)) => MacroDefId {
306+
ast_id: None,
307+
krate: Some(self.def_map.krate),
308+
kind: MacroDefKind::ProcMacro(*expander),
309+
local_inner: false,
310+
},
311+
None => MacroDefId {
274312
ast_id: None,
275-
krate: Some(krate),
276-
kind: MacroDefKind::ProcMacro(expander),
313+
krate: Some(self.def_map.krate),
314+
kind: MacroDefKind::ProcMacro(ProcMacroExpander::dummy(self.def_map.krate)),
277315
local_inner: false,
278-
};
316+
},
317+
};
279318

280-
self.define_proc_macro(name.clone(), macro_id);
281-
}
319+
self.define_proc_macro(name.clone(), macro_def);
282320
}
283321

284322
/// Define a macro with `macro_rules`.
@@ -917,6 +955,9 @@ impl ModCollector<'_, '_> {
917955
}
918956
ModItem::Function(id) => {
919957
let func = &self.item_tree[id];
958+
959+
self.collect_proc_macro_def(&func.name, attrs);
960+
920961
def = Some(DefData {
921962
id: FunctionLoc {
922963
container: container.into(),
@@ -1177,6 +1218,30 @@ impl ModCollector<'_, '_> {
11771218
}
11781219
}
11791220

1221+
/// If `attrs` registers a procedural macro, collects its definition.
1222+
fn collect_proc_macro_def(&mut self, func_name: &Name, attrs: &Attrs) {
1223+
// FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere
1224+
// FIXME: distinguish the type of macro
1225+
let macro_name = if attrs.by_key("proc_macro").exists()
1226+
|| attrs.by_key("proc_macro_attribute").exists()
1227+
{
1228+
func_name.clone()
1229+
} else {
1230+
let derive = attrs.by_key("proc_macro_derive");
1231+
if let Some(arg) = derive.tt_values().next() {
1232+
if let [TokenTree::Leaf(Leaf::Ident(trait_name))] = &*arg.token_trees {
1233+
trait_name.as_name()
1234+
} else {
1235+
return;
1236+
}
1237+
} else {
1238+
return;
1239+
}
1240+
};
1241+
1242+
self.def_collector.resolve_proc_macro(&macro_name);
1243+
}
1244+
11801245
fn collect_macro(&mut self, mac: &MacroCall) {
11811246
let mut ast_id = AstIdWithPath::new(self.file_id, mac.ast_id, mac.path.clone());
11821247

@@ -1283,6 +1348,7 @@ mod tests {
12831348
mod_dirs: FxHashMap::default(),
12841349
cfg_options: &CfgOptions::default(),
12851350
proc_macros: Default::default(),
1351+
exports_proc_macros: false,
12861352
from_glob_import: Default::default(),
12871353
};
12881354
collector.collect();

crates/hir_def/src/nameres/tests/macros.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,3 +667,76 @@ b! { static = #[] (); }
667667
"#]],
668668
);
669669
}
670+
671+
#[test]
672+
fn resolves_proc_macros() {
673+
check(
674+
r"
675+
struct TokenStream;
676+
677+
#[proc_macro]
678+
pub fn function_like_macro(args: TokenStream) -> TokenStream {
679+
args
680+
}
681+
682+
#[proc_macro_attribute]
683+
pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream {
684+
item
685+
}
686+
687+
#[proc_macro_derive(DummyTrait)]
688+
pub fn derive_macro(_item: TokenStream) -> TokenStream {
689+
TokenStream
690+
}
691+
",
692+
expect![[r#"
693+
crate
694+
DummyTrait: m
695+
TokenStream: t v
696+
attribute_macro: v m
697+
derive_macro: v
698+
function_like_macro: v m
699+
"#]],
700+
);
701+
}
702+
703+
#[test]
704+
fn proc_macro_censoring() {
705+
// Make sure that only proc macros are publicly exported from proc-macro crates.
706+
707+
check(
708+
r"
709+
//- /main.rs crate:main deps:macros
710+
pub use macros::*;
711+
712+
//- /macros.rs crate:macros
713+
pub struct TokenStream;
714+
715+
#[proc_macro]
716+
pub fn function_like_macro(args: TokenStream) -> TokenStream {
717+
args
718+
}
719+
720+
#[proc_macro_attribute]
721+
pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream {
722+
item
723+
}
724+
725+
#[proc_macro_derive(DummyTrait)]
726+
pub fn derive_macro(_item: TokenStream) -> TokenStream {
727+
TokenStream
728+
}
729+
730+
#[macro_export]
731+
macro_rules! mbe {
732+
() => {};
733+
}
734+
",
735+
expect![[r#"
736+
crate
737+
DummyTrait: m
738+
attribute_macro: m
739+
function_like_macro: m
740+
"#]],
741+
);
742+
}

crates/hir_expand/src/proc_macro.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use tt::buffer::{Cursor, TokenBuffer};
77
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
88
pub struct ProcMacroExpander {
99
krate: CrateId,
10-
proc_macro_id: ProcMacroId,
10+
proc_macro_id: Option<ProcMacroId>,
1111
}
1212

1313
macro_rules! err {
@@ -20,8 +20,14 @@ macro_rules! err {
2020
}
2121

2222
impl ProcMacroExpander {
23-
pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander {
24-
ProcMacroExpander { krate, proc_macro_id }
23+
pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> Self {
24+
Self { krate, proc_macro_id: Some(proc_macro_id) }
25+
}
26+
27+
pub fn dummy(krate: CrateId) -> Self {
28+
// FIXME: Should store the name for better errors
29+
// FIXME: I think this is the second layer of "dummy" expansion, we should reduce that
30+
Self { krate, proc_macro_id: None }
2531
}
2632

2733
pub fn expand(
@@ -30,17 +36,22 @@ impl ProcMacroExpander {
3036
_id: LazyMacroId,
3137
tt: &tt::Subtree,
3238
) -> Result<tt::Subtree, mbe::ExpandError> {
33-
let krate_graph = db.crate_graph();
34-
let proc_macro = krate_graph[self.krate]
35-
.proc_macro
36-
.get(self.proc_macro_id.0 as usize)
37-
.clone()
38-
.ok_or_else(|| err!("No derive macro found."))?;
39-
40-
let tt = remove_derive_attrs(tt)
41-
.ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
42-
43-
proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
39+
match self.proc_macro_id {
40+
Some(id) => {
41+
let krate_graph = db.crate_graph();
42+
let proc_macro = krate_graph[self.krate]
43+
.proc_macro
44+
.get(id.0 as usize)
45+
.clone()
46+
.ok_or_else(|| err!("No derive macro found."))?;
47+
48+
let tt = remove_derive_attrs(tt)
49+
.ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
50+
51+
proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
52+
}
53+
None => Err(err!("Unresolved proc macro")),
54+
}
4455
}
4556
}
4657

0 commit comments

Comments
 (0)