From 1b2652097183b0a285891c02eea8a7d2af03e4b3 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 26 Nov 2020 19:07:53 +0100 Subject: Add dedicated error for "proc macro not found" --- crates/hir_expand/src/proc_macro.rs | 2 +- crates/mbe/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs index 7505cb061..97edf0fb6 100644 --- a/crates/hir_expand/src/proc_macro.rs +++ b/crates/hir_expand/src/proc_macro.rs @@ -50,7 +50,7 @@ impl ProcMacroExpander { proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) } - None => Err(err!("Unresolved proc macro")), + None => Err(mbe::ExpandError::UnresolvedProcMacro), } } } diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 2d0763c47..3ad609a00 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -35,6 +35,7 @@ pub enum ExpandError { ConversionError, InvalidRepeat, ProcMacroError(tt::ExpansionError), + UnresolvedProcMacro, Other(String), } @@ -53,6 +54,7 @@ impl fmt::Display for ExpandError { ExpandError::ConversionError => f.write_str("could not convert tokens"), ExpandError::InvalidRepeat => f.write_str("invalid repeat expression"), ExpandError::ProcMacroError(e) => e.fmt(f), + ExpandError::UnresolvedProcMacro => f.write_str("unresolved proc macro"), ExpandError::Other(e) => f.write_str(e), } } -- cgit v1.2.3 From 0432aa0ed7be3f41d41928499abc688a956214cf Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 26 Nov 2020 20:09:54 +0100 Subject: Publish diagnostics for macro expansion errors --- crates/hir/src/diagnostics.rs | 2 +- crates/hir_def/src/diagnostics.rs | 62 +++++++++++++++++++++++++++++++++ crates/hir_def/src/nameres.rs | 52 +++++++++++++++++++++++++-- crates/hir_def/src/nameres/collector.rs | 27 ++++++++++++-- crates/hir_expand/src/db.rs | 9 ++++- crates/hir_expand/src/lib.rs | 2 +- crates/ide/src/diagnostics.rs | 7 ++++ docs/user/generated_diagnostic.adoc | 14 ++++++++ 8 files changed, 168 insertions(+), 7 deletions(-) diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index d9ad8db6f..eaf1a14ec 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -1,5 +1,5 @@ //! FIXME: write short doc here -pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule}; +pub use hir_def::diagnostics::{InactiveCode, UnresolvedModule, UnresolvedProcMacro}; pub use hir_expand::diagnostics::{ Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, }; diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index b221b290c..dd06e3f20 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs @@ -127,3 +127,65 @@ impl Diagnostic for InactiveCode { self } } + +// Diagnostic: unresolved-proc-macro +// +// This diagnostic is shown when a procedural macro can not be found. This usually means that +// procedural macro support is simply disabled (and hence is only a weak hint instead of an error), +// but can also indicate project setup problems. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct UnresolvedProcMacro { + pub file: HirFileId, + pub node: SyntaxNodePtr, + pub macro_name: Option, +} + +impl Diagnostic for UnresolvedProcMacro { + fn code(&self) -> DiagnosticCode { + DiagnosticCode("unresolved-proc-macro") + } + + fn message(&self) -> String { + match &self.macro_name { + Some(name) => format!("proc macro `{}` not expanded", name), + None => "proc macro not expanded".to_string(), + } + } + + fn display_source(&self) -> InFile { + InFile::new(self.file, self.node.clone()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +// Diagnostic: macro-error +// +// This diagnostic is shown for macro expansion errors. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MacroError { + pub file: HirFileId, + pub node: SyntaxNodePtr, + pub message: String, +} + +impl Diagnostic for MacroError { + fn code(&self) -> DiagnosticCode { + DiagnosticCode("macro-error") + } + fn message(&self) -> String { + self.message.clone() + } + fn display_source(&self) -> InFile { + InFile::new(self.file, self.node.clone()) + } + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } + fn is_experimental(&self) -> bool { + // Newly added and not very well-tested, might contain false positives. + true + } +} diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 202a7dcb6..3d65a46bf 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs @@ -286,8 +286,8 @@ mod diagnostics { use cfg::{CfgExpr, CfgOptions}; use hir_expand::diagnostics::DiagnosticSink; use hir_expand::hygiene::Hygiene; - use hir_expand::InFile; - use syntax::{ast, AstPtr}; + use hir_expand::{InFile, MacroCallKind}; + use syntax::{ast, AstPtr, SyntaxNodePtr}; use crate::path::ModPath; use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; @@ -301,6 +301,10 @@ mod diagnostics { UnresolvedImport { ast: AstId, index: usize }, UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, + + UnresolvedProcMacro { ast: MacroCallKind }, + + MacroError { ast: MacroCallKind, message: String }, } #[derive(Debug, PartialEq, Eq)] @@ -348,6 +352,18 @@ mod diagnostics { Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } } } + pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self { + Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } } + } + + pub(super) fn macro_error( + container: LocalModuleId, + ast: MacroCallKind, + message: String, + ) -> Self { + Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } } + } + pub(super) fn add_to( &self, db: &dyn DefDatabase, @@ -407,6 +423,38 @@ mod diagnostics { opts: opts.clone(), }); } + + DiagnosticKind::UnresolvedProcMacro { ast } => { + let (file, ast, name) = match ast { + MacroCallKind::FnLike(ast) => { + let node = ast.to_node(db.upcast()); + (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None) + } + MacroCallKind::Attr(ast, name) => { + let node = ast.to_node(db.upcast()); + ( + ast.file_id, + SyntaxNodePtr::from(AstPtr::new(&node)), + Some(name.to_string()), + ) + } + }; + sink.push(UnresolvedProcMacro { file, node: ast, macro_name: name }); + } + + DiagnosticKind::MacroError { ast, message } => { + let (file, ast) = match ast { + MacroCallKind::FnLike(ast) => { + let node = ast.to_node(db.upcast()); + (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) + } + MacroCallKind::Attr(ast, _) => { + let node = ast.to_node(db.upcast()); + (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) + } + }; + sink.push(MacroError { file, node: ast, message: message.clone() }); + } } } } diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 5ed9073e0..19cd713ba 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -7,7 +7,6 @@ use std::iter; use base_db::{CrateId, FileId, ProcMacroId}; use cfg::{CfgExpr, CfgOptions}; -use hir_expand::InFile; use hir_expand::{ ast_id_map::FileAstId, builtin_derive::find_builtin_derive, @@ -16,6 +15,7 @@ use hir_expand::{ proc_macro::ProcMacroExpander, HirFileId, MacroCallId, MacroDefId, MacroDefKind, }; +use hir_expand::{InFile, MacroCallLoc}; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::ast; use test_utils::mark; @@ -812,7 +812,30 @@ impl DefCollector<'_> { log::warn!("macro expansion is too deep"); return; } - let file_id: HirFileId = macro_call_id.as_file(); + let file_id = macro_call_id.as_file(); + + // First, fetch the raw expansion result for purposes of error reporting. This goes through + // `macro_expand_error` to avoid depending on the full expansion result (to improve + // incrementality). + let err = self.db.macro_expand_error(macro_call_id); + if let Some(err) = err { + if let MacroCallId::LazyMacro(id) = macro_call_id { + let loc: MacroCallLoc = self.db.lookup_intern_macro(id); + + let diag = match err { + hir_expand::ExpandError::UnresolvedProcMacro => { + // Missing proc macros are non-fatal, so they are handled specially. + DefDiagnostic::unresolved_proc_macro(module_id, loc.kind) + } + _ => DefDiagnostic::macro_error(module_id, loc.kind, err.to_string()), + }; + + self.def_map.diagnostics.push(diag); + } + // FIXME: Handle eager macros. + } + + // Then, fetch and process the item tree. This will reuse the expansion result from above. let item_tree = self.db.item_tree(file_id); let mod_dir = self.mod_dirs[&module_id].clone(); ModCollector { diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 46ebdbc74..7ea1c6301 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use base_db::{salsa, SourceDatabase}; -use mbe::{ExpandResult, MacroRules}; +use mbe::{ExpandError, ExpandResult, MacroRules}; use parser::FragmentKind; use syntax::{algo::diff, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode}; @@ -81,6 +81,9 @@ pub trait AstDatabase: SourceDatabase { ) -> ExpandResult, Arc)>>; fn macro_expand(&self, macro_call: MacroCallId) -> ExpandResult>>; + /// Firewall query that returns the error from the `macro_expand` query. + fn macro_expand_error(&self, macro_call: MacroCallId) -> Option; + #[salsa::interned] fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; @@ -171,6 +174,10 @@ fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult Option { + db.macro_expand(macro_call).err +} + fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option> { let lazy_id = match id { MacroCallId::LazyMacro(id) => id, diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index d5ba691b7..6dad2507b 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -255,7 +255,7 @@ pub enum MacroDefKind { pub struct MacroCallLoc { pub(crate) def: MacroDefId, pub(crate) krate: CrateId, - pub(crate) kind: MacroCallKind, + pub kind: MacroCallKind, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 3df73ed4f..8b4ceb9a1 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -142,6 +142,13 @@ pub(crate) fn diagnostics( .with_code(Some(d.code())), ); }) + .on::(|d| { + // FIXME: it would be nice to tell the user whether proc macros are currently disabled + res.borrow_mut().push( + Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) + .with_code(Some(d.code())), + ); + }) // Only collect experimental diagnostics when they're enabled. .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) .filter(|diag| !config.disabled.contains(diag.code().as_str())); diff --git a/docs/user/generated_diagnostic.adoc b/docs/user/generated_diagnostic.adoc index 34c4f98a3..1dfba6670 100644 --- a/docs/user/generated_diagnostic.adoc +++ b/docs/user/generated_diagnostic.adoc @@ -17,6 +17,12 @@ This diagnostic is shown for code with inactive `#[cfg]` attributes. 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]. +=== macro-error +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L164[diagnostics.rs] + +This diagnostic is shown for macro expansion errors. + + === mismatched-arg-count **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_ty/src/diagnostics.rs#L267[diagnostics.rs] @@ -103,3 +109,11 @@ This diagnostic is triggered if rust-analyzer is unable to discover imported mod **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L18[diagnostics.rs] This diagnostic is triggered if rust-analyzer is unable to discover referred module. + + +=== unresolved-proc-macro +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L131[diagnostics.rs] + +This diagnostic is shown when a procedural macro can not be found. This usually means that +procedural macro support is simply disabled (and hence is only a weak hint instead of an error), +but can also indicate project setup problems. -- cgit v1.2.3 From d171838d63907d004ab935d6564bfeb4238d5540 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Fri, 27 Nov 2020 16:29:40 +0100 Subject: More accurately place proc-macro diagnostic --- crates/hir_def/src/diagnostics.rs | 5 ++++- crates/hir_def/src/nameres.rs | 41 ++++++++++++++++++++++++++++++++++--- crates/ide/src/diagnostics.rs | 10 +++++---- docs/user/generated_diagnostic.adoc | 2 +- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index dd06e3f20..c71266dc0 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs @@ -6,7 +6,7 @@ use stdx::format_to; use cfg::{CfgExpr, CfgOptions, DnfExpr}; use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; use hir_expand::{HirFileId, InFile}; -use syntax::{ast, AstPtr, SyntaxNodePtr}; +use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; use crate::{db::DefDatabase, DefWithBodyId}; @@ -137,6 +137,9 @@ impl Diagnostic for InactiveCode { pub struct UnresolvedProcMacro { pub file: HirFileId, pub node: SyntaxNodePtr, + /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange` + /// to use instead. + pub precise_location: Option, pub macro_name: Option, } diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 3d65a46bf..ffd0381d4 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs @@ -287,7 +287,8 @@ mod diagnostics { use hir_expand::diagnostics::DiagnosticSink; use hir_expand::hygiene::Hygiene; use hir_expand::{InFile, MacroCallKind}; - use syntax::{ast, AstPtr, SyntaxNodePtr}; + use syntax::ast::AttrsOwner; + use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr}; use crate::path::ModPath; use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; @@ -425,6 +426,7 @@ mod diagnostics { } DiagnosticKind::UnresolvedProcMacro { ast } => { + let mut precise_location = None; let (file, ast, name) = match ast { MacroCallKind::FnLike(ast) => { let node = ast.to_node(db.upcast()); @@ -432,14 +434,47 @@ mod diagnostics { } MacroCallKind::Attr(ast, name) => { let node = ast.to_node(db.upcast()); + + // Compute the precise location of the macro name's token in the derive + // list. + // FIXME: This does not handle paths to the macro, but neither does the + // rest of r-a. + let derive_attrs = + node.attrs().filter_map(|attr| match attr.as_simple_call() { + Some((name, args)) if name == "derive" => Some(args), + _ => None, + }); + 'outer: for attr in derive_attrs { + let tokens = + attr.syntax().children_with_tokens().filter_map(|elem| { + match elem { + syntax::NodeOrToken::Node(_) => None, + syntax::NodeOrToken::Token(tok) => Some(tok), + } + }); + for token in tokens { + if token.kind() == SyntaxKind::IDENT + && token.to_string() == *name + { + precise_location = Some(token.text_range()); + break 'outer; + } + } + } + ( ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), - Some(name.to_string()), + Some(name.clone()), ) } }; - sink.push(UnresolvedProcMacro { file, node: ast, macro_name: name }); + sink.push(UnresolvedProcMacro { + file, + node: ast, + precise_location, + macro_name: name, + }); } DiagnosticKind::MacroError { ast, message } => { diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 8b4ceb9a1..9d3d88289 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -143,11 +143,13 @@ pub(crate) fn diagnostics( ); }) .on::(|d| { + // Use more accurate position if available. + let display_range = + d.precise_location.unwrap_or_else(|| sema.diagnostics_display_range(d).range); + // FIXME: it would be nice to tell the user whether proc macros are currently disabled - res.borrow_mut().push( - Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) - .with_code(Some(d.code())), - ); + res.borrow_mut() + .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); }) // Only collect experimental diagnostics when they're enabled. .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) diff --git a/docs/user/generated_diagnostic.adoc b/docs/user/generated_diagnostic.adoc index 1dfba6670..ec8581a03 100644 --- a/docs/user/generated_diagnostic.adoc +++ b/docs/user/generated_diagnostic.adoc @@ -18,7 +18,7 @@ This diagnostic is triggered if item name doesn't follow https://doc.rust-lang.o === macro-error -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L164[diagnostics.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/hir_def/src/diagnostics.rs#L167[diagnostics.rs] This diagnostic is shown for macro expansion errors. -- cgit v1.2.3