Skip to content

Commit 0432aa0

Browse files
Publish diagnostics for macro expansion errors
1 parent 1b26520 commit 0432aa0

File tree

8 files changed

+168
-7
lines changed

8 files changed

+168
-7
lines changed

crates/hir/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! FIXME: write short doc here
2-
pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule};
2+
pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule, UnresolvedProcMacro};
33
pub use hir_expand::diagnostics::{
44
Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
55
};

crates/hir_def/src/diagnostics.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,65 @@ impl Diagnostic for InactiveCode {
127127
self
128128
}
129129
}
130+
131+
// Diagnostic: unresolved-proc-macro
132+
//
133+
// This diagnostic is shown when a procedural macro can not be found. This usually means that
134+
// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
135+
// but can also indicate project setup problems.
136+
#[derive(Debug, Clone, Eq, PartialEq)]
137+
pub struct UnresolvedProcMacro {
138+
pub file: HirFileId,
139+
pub node: SyntaxNodePtr,
140+
pub macro_name: Option<String>,
141+
}
142+
143+
impl Diagnostic for UnresolvedProcMacro {
144+
fn code(&self) -> DiagnosticCode {
145+
DiagnosticCode("unresolved-proc-macro")
146+
}
147+
148+
fn message(&self) -> String {
149+
match &self.macro_name {
150+
Some(name) => format!("proc macro `{}` not expanded", name),
151+
None => "proc macro not expanded".to_string(),
152+
}
153+
}
154+
155+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
156+
InFile::new(self.file, self.node.clone())
157+
}
158+
159+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
160+
self
161+
}
162+
}
163+
164+
// Diagnostic: macro-error
165+
//
166+
// This diagnostic is shown for macro expansion errors.
167+
#[derive(Debug, Clone, Eq, PartialEq)]
168+
pub struct MacroError {
169+
pub file: HirFileId,
170+
pub node: SyntaxNodePtr,
171+
pub message: String,
172+
}
173+
174+
impl Diagnostic for MacroError {
175+
fn code(&self) -> DiagnosticCode {
176+
DiagnosticCode("macro-error")
177+
}
178+
fn message(&self) -> String {
179+
self.message.clone()
180+
}
181+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
182+
InFile::new(self.file, self.node.clone())
183+
}
184+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
185+
self
186+
}
187+
fn is_experimental(&self) -> bool {
188+
// Newly added and not very well-tested, might contain false positives.
189+
true
190+
}
191+
}

crates/hir_def/src/nameres.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,8 @@ mod diagnostics {
286286
use cfg::{CfgExpr, CfgOptions};
287287
use hir_expand::diagnostics::DiagnosticSink;
288288
use hir_expand::hygiene::Hygiene;
289-
use hir_expand::InFile;
290-
use syntax::{ast, AstPtr};
289+
use hir_expand::{InFile, MacroCallKind};
290+
use syntax::{ast, AstPtr, SyntaxNodePtr};
291291

292292
use crate::path::ModPath;
293293
use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
@@ -301,6 +301,10 @@ mod diagnostics {
301301
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
302302

303303
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
304+
305+
UnresolvedProcMacro { ast: MacroCallKind },
306+
307+
MacroError { ast: MacroCallKind, message: String },
304308
}
305309

306310
#[derive(Debug, PartialEq, Eq)]
@@ -348,6 +352,18 @@ mod diagnostics {
348352
Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
349353
}
350354

355+
pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
356+
Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
357+
}
358+
359+
pub(super) fn macro_error(
360+
container: LocalModuleId,
361+
ast: MacroCallKind,
362+
message: String,
363+
) -> Self {
364+
Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
365+
}
366+
351367
pub(super) fn add_to(
352368
&self,
353369
db: &dyn DefDatabase,
@@ -407,6 +423,38 @@ mod diagnostics {
407423
opts: opts.clone(),
408424
});
409425
}
426+
427+
DiagnosticKind::UnresolvedProcMacro { ast } => {
428+
let (file, ast, name) = match ast {
429+
MacroCallKind::FnLike(ast) => {
430+
let node = ast.to_node(db.upcast());
431+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
432+
}
433+
MacroCallKind::Attr(ast, name) => {
434+
let node = ast.to_node(db.upcast());
435+
(
436+
ast.file_id,
437+
SyntaxNodePtr::from(AstPtr::new(&node)),
438+
Some(name.to_string()),
439+
)
440+
}
441+
};
442+
sink.push(UnresolvedProcMacro { file, node: ast, macro_name: name });
443+
}
444+
445+
DiagnosticKind::MacroError { ast, message } => {
446+
let (file, ast) = match ast {
447+
MacroCallKind::FnLike(ast) => {
448+
let node = ast.to_node(db.upcast());
449+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
450+
}
451+
MacroCallKind::Attr(ast, _) => {
452+
let node = ast.to_node(db.upcast());
453+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
454+
}
455+
};
456+
sink.push(MacroError { file, node: ast, message: message.clone() });
457+
}
410458
}
411459
}
412460
}

crates/hir_def/src/nameres/collector.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::iter;
77

88
use base_db::{CrateId, FileId, ProcMacroId};
99
use cfg::{CfgExpr, CfgOptions};
10-
use hir_expand::InFile;
1110
use hir_expand::{
1211
ast_id_map::FileAstId,
1312
builtin_derive::find_builtin_derive,
@@ -16,6 +15,7 @@ use hir_expand::{
1615
proc_macro::ProcMacroExpander,
1716
HirFileId, MacroCallId, MacroDefId, MacroDefKind,
1817
};
18+
use hir_expand::{InFile, MacroCallLoc};
1919
use rustc_hash::{FxHashMap, FxHashSet};
2020
use syntax::ast;
2121
use test_utils::mark;
@@ -812,7 +812,30 @@ impl DefCollector<'_> {
812812
log::warn!("macro expansion is too deep");
813813
return;
814814
}
815-
let file_id: HirFileId = macro_call_id.as_file();
815+
let file_id = macro_call_id.as_file();
816+
817+
// First, fetch the raw expansion result for purposes of error reporting. This goes through
818+
// `macro_expand_error` to avoid depending on the full expansion result (to improve
819+
// incrementality).
820+
let err = self.db.macro_expand_error(macro_call_id);
821+
if let Some(err) = err {
822+
if let MacroCallId::LazyMacro(id) = macro_call_id {
823+
let loc: MacroCallLoc = self.db.lookup_intern_macro(id);
824+
825+
let diag = match err {
826+
hir_expand::ExpandError::UnresolvedProcMacro => {
827+
// Missing proc macros are non-fatal, so they are handled specially.
828+
DefDiagnostic::unresolved_proc_macro(module_id, loc.kind)
829+
}
830+
_ => DefDiagnostic::macro_error(module_id, loc.kind, err.to_string()),
831+
};
832+
833+
self.def_map.diagnostics.push(diag);
834+
}
835+
// FIXME: Handle eager macros.
836+
}
837+
838+
// Then, fetch and process the item tree. This will reuse the expansion result from above.
816839
let item_tree = self.db.item_tree(file_id);
817840
let mod_dir = self.mod_dirs[&module_id].clone();
818841
ModCollector {

crates/hir_expand/src/db.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::sync::Arc;
44

55
use base_db::{salsa, SourceDatabase};
6-
use mbe::{ExpandResult, MacroRules};
6+
use mbe::{ExpandError, ExpandResult, MacroRules};
77
use parser::FragmentKind;
88
use syntax::{algo::diff, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
99

@@ -81,6 +81,9 @@ pub trait AstDatabase: SourceDatabase {
8181
) -> ExpandResult<Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>>;
8282
fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult<Option<Arc<tt::Subtree>>>;
8383

84+
/// Firewall query that returns the error from the `macro_expand` query.
85+
fn macro_expand_error(&self, macro_call: MacroCallId) -> Option<ExpandError>;
86+
8487
#[salsa::interned]
8588
fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
8689

@@ -171,6 +174,10 @@ fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<Option<Ar
171174
macro_expand_with_arg(db, id, None)
172175
}
173176

177+
fn macro_expand_error(db: &dyn AstDatabase, macro_call: MacroCallId) -> Option<ExpandError> {
178+
db.macro_expand(macro_call).err
179+
}
180+
174181
fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
175182
let lazy_id = match id {
176183
MacroCallId::LazyMacro(id) => id,

crates/hir_expand/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ pub enum MacroDefKind {
255255
pub struct MacroCallLoc {
256256
pub(crate) def: MacroDefId,
257257
pub(crate) krate: CrateId,
258-
pub(crate) kind: MacroCallKind,
258+
pub kind: MacroCallKind,
259259
}
260260

261261
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

crates/ide/src/diagnostics.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ pub(crate) fn diagnostics(
142142
.with_code(Some(d.code())),
143143
);
144144
})
145+
.on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
146+
// FIXME: it would be nice to tell the user whether proc macros are currently disabled
147+
res.borrow_mut().push(
148+
Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message())
149+
.with_code(Some(d.code())),
150+
);
151+
})
145152
// Only collect experimental diagnostics when they're enabled.
146153
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
147154
.filter(|diag| !config.disabled.contains(diag.code().as_str()));

docs/user/generated_diagnostic.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ This diagnostic is shown for code with inactive `#[cfg]` attributes.
1717
This diagnostic is triggered if item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
1818

1919

20+
=== macro-error
21+
**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L164[diagnostics.rs]
22+
23+
This diagnostic is shown for macro expansion errors.
24+
25+
2026
=== mismatched-arg-count
2127
**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_ty/src/diagnostics.rs#L267[diagnostics.rs]
2228

@@ -103,3 +109,11 @@ This diagnostic is triggered if rust-analyzer is unable to discover imported mod
103109
**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L18[diagnostics.rs]
104110

105111
This diagnostic is triggered if rust-analyzer is unable to discover referred module.
112+
113+
114+
=== unresolved-proc-macro
115+
**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L131[diagnostics.rs]
116+
117+
This diagnostic is shown when a procedural macro can not be found. This usually means that
118+
procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
119+
but can also indicate project setup problems.

0 commit comments

Comments
 (0)