Skip to content

Commit 7f3ba7d

Browse files
Merge #6645
6645: Publish diagnostics for macro expansion errors r=matklad a=jonas-schievink This adds 2 new diagnostics, emitted during name resolution: * `unresolved-proc-macro`, a weak warning that is emitted when a proc macro is supposed to be expanded, but was not provided by the build system. This usually means that proc macro support is turned off, but may also indicate setup issues when using rust-project.json. Being a weak warning, this should help set expectations when users see it, while not being too obstructive. We do not yet emit this for attribute macros though, just custom derives and `!` macros. * `macro-error`, which is emitted when any macro (procedural or `macro_rules!`) fails to expand due to some error. This is an error-level diagnostic, but currently still marked as experimental, because there might be spurious errors and this hasn't been tested too well. This does not yet emit diagnostics when expansion in item bodies fails, just for module-level macros. Known bug: The "proc macro not found" diagnostic points at the whole item for custom derives, it should just point at the macro's name in the `#[derive]` list, but I haven't found an easy way to do that. Screenshots: ![screenshot-2020-11-26-19:54:14](https://user-images.githubusercontent.com/1786438/100385782-f8bc2300-3023-11eb-9f27-e8f8ce9d6114.png) ![screenshot-2020-11-26-19:55:39](https://user-images.githubusercontent.com/1786438/100385784-f954b980-3023-11eb-9617-ac2eb0a0a9dc.png) Co-authored-by: Jonas Schievink <[email protected]>
2 parents b7ece77 + d171838 commit 7f3ba7d

File tree

10 files changed

+212
-9
lines changed

10 files changed

+212
-9
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: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use stdx::format_to;
66
use cfg::{CfgExpr, CfgOptions, DnfExpr};
77
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
88
use hir_expand::{HirFileId, InFile};
9-
use syntax::{ast, AstPtr, SyntaxNodePtr};
9+
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
1010

1111
use crate::{db::DefDatabase, DefWithBodyId};
1212

@@ -127,3 +127,68 @@ 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+
/// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
141+
/// to use instead.
142+
pub precise_location: Option<TextRange>,
143+
pub macro_name: Option<String>,
144+
}
145+
146+
impl Diagnostic for UnresolvedProcMacro {
147+
fn code(&self) -> DiagnosticCode {
148+
DiagnosticCode("unresolved-proc-macro")
149+
}
150+
151+
fn message(&self) -> String {
152+
match &self.macro_name {
153+
Some(name) => format!("proc macro `{}` not expanded", name),
154+
None => "proc macro not expanded".to_string(),
155+
}
156+
}
157+
158+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
159+
InFile::new(self.file, self.node.clone())
160+
}
161+
162+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
163+
self
164+
}
165+
}
166+
167+
// Diagnostic: macro-error
168+
//
169+
// This diagnostic is shown for macro expansion errors.
170+
#[derive(Debug, Clone, Eq, PartialEq)]
171+
pub struct MacroError {
172+
pub file: HirFileId,
173+
pub node: SyntaxNodePtr,
174+
pub message: String,
175+
}
176+
177+
impl Diagnostic for MacroError {
178+
fn code(&self) -> DiagnosticCode {
179+
DiagnosticCode("macro-error")
180+
}
181+
fn message(&self) -> String {
182+
self.message.clone()
183+
}
184+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
185+
InFile::new(self.file, self.node.clone())
186+
}
187+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
188+
self
189+
}
190+
fn is_experimental(&self) -> bool {
191+
// Newly added and not very well-tested, might contain false positives.
192+
true
193+
}
194+
}

crates/hir_def/src/nameres.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,9 @@ 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::AttrsOwner;
291+
use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr};
291292

292293
use crate::path::ModPath;
293294
use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
@@ -301,6 +302,10 @@ mod diagnostics {
301302
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
302303

303304
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
305+
306+
UnresolvedProcMacro { ast: MacroCallKind },
307+
308+
MacroError { ast: MacroCallKind, message: String },
304309
}
305310

306311
#[derive(Debug, PartialEq, Eq)]
@@ -348,6 +353,18 @@ mod diagnostics {
348353
Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
349354
}
350355

356+
pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
357+
Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
358+
}
359+
360+
pub(super) fn macro_error(
361+
container: LocalModuleId,
362+
ast: MacroCallKind,
363+
message: String,
364+
) -> Self {
365+
Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
366+
}
367+
351368
pub(super) fn add_to(
352369
&self,
353370
db: &dyn DefDatabase,
@@ -407,6 +424,72 @@ mod diagnostics {
407424
opts: opts.clone(),
408425
});
409426
}
427+
428+
DiagnosticKind::UnresolvedProcMacro { ast } => {
429+
let mut precise_location = None;
430+
let (file, ast, name) = match ast {
431+
MacroCallKind::FnLike(ast) => {
432+
let node = ast.to_node(db.upcast());
433+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
434+
}
435+
MacroCallKind::Attr(ast, name) => {
436+
let node = ast.to_node(db.upcast());
437+
438+
// Compute the precise location of the macro name's token in the derive
439+
// list.
440+
// FIXME: This does not handle paths to the macro, but neither does the
441+
// rest of r-a.
442+
let derive_attrs =
443+
node.attrs().filter_map(|attr| match attr.as_simple_call() {
444+
Some((name, args)) if name == "derive" => Some(args),
445+
_ => None,
446+
});
447+
'outer: for attr in derive_attrs {
448+
let tokens =
449+
attr.syntax().children_with_tokens().filter_map(|elem| {
450+
match elem {
451+
syntax::NodeOrToken::Node(_) => None,
452+
syntax::NodeOrToken::Token(tok) => Some(tok),
453+
}
454+
});
455+
for token in tokens {
456+
if token.kind() == SyntaxKind::IDENT
457+
&& token.to_string() == *name
458+
{
459+
precise_location = Some(token.text_range());
460+
break 'outer;
461+
}
462+
}
463+
}
464+
465+
(
466+
ast.file_id,
467+
SyntaxNodePtr::from(AstPtr::new(&node)),
468+
Some(name.clone()),
469+
)
470+
}
471+
};
472+
sink.push(UnresolvedProcMacro {
473+
file,
474+
node: ast,
475+
precise_location,
476+
macro_name: name,
477+
});
478+
}
479+
480+
DiagnosticKind::MacroError { ast, message } => {
481+
let (file, ast) = match ast {
482+
MacroCallKind::FnLike(ast) => {
483+
let node = ast.to_node(db.upcast());
484+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
485+
}
486+
MacroCallKind::Attr(ast, _) => {
487+
let node = ast.to_node(db.upcast());
488+
(ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
489+
}
490+
};
491+
sink.push(MacroError { file, node: ast, message: message.clone() });
492+
}
410493
}
411494
}
412495
}

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/hir_expand/src/proc_macro.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl ProcMacroExpander {
5050

5151
proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
5252
}
53-
None => Err(err!("Unresolved proc macro")),
53+
None => Err(mbe::ExpandError::UnresolvedProcMacro),
5454
}
5555
}
5656
}

crates/ide/src/diagnostics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ pub(crate) fn diagnostics(
142142
.with_code(Some(d.code())),
143143
);
144144
})
145+
.on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
146+
// Use more accurate position if available.
147+
let display_range =
148+
d.precise_location.unwrap_or_else(|| sema.diagnostics_display_range(d).range);
149+
150+
// FIXME: it would be nice to tell the user whether proc macros are currently disabled
151+
res.borrow_mut()
152+
.push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
153+
})
145154
// Only collect experimental diagnostics when they're enabled.
146155
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
147156
.filter(|diag| !config.disabled.contains(diag.code().as_str()));

crates/mbe/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub enum ExpandError {
3535
ConversionError,
3636
InvalidRepeat,
3737
ProcMacroError(tt::ExpansionError),
38+
UnresolvedProcMacro,
3839
Other(String),
3940
}
4041

@@ -53,6 +54,7 @@ impl fmt::Display for ExpandError {
5354
ExpandError::ConversionError => f.write_str("could not convert tokens"),
5455
ExpandError::InvalidRepeat => f.write_str("invalid repeat expression"),
5556
ExpandError::ProcMacroError(e) => e.fmt(f),
57+
ExpandError::UnresolvedProcMacro => f.write_str("unresolved proc macro"),
5658
ExpandError::Other(e) => f.write_str(e),
5759
}
5860
}

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#L167[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)