diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index d9ad8db6f7..eaf1a14ec3 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 b221b290c5..dd06e3f200 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 202a7dcb6d..3d65a46bf7 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 5ed9073e08..19cd713ba0 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 46ebdbc74d..7ea1c63013 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 d5ba691b7d..6dad2507bf 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 3df73ed4fa..8b4ceb9a10 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 34c4f98a3f..1dfba66703 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.