From f85e383b94376d55bb5ee6be375ef3dc0006590f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 13 Jun 2021 17:29:25 +0300 Subject: internal: refactor inactive code diagnostics --- crates/ide/src/diagnostics/inactive_code.rs | 113 ++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 crates/ide/src/diagnostics/inactive_code.rs (limited to 'crates/ide/src/diagnostics') diff --git a/crates/ide/src/diagnostics/inactive_code.rs b/crates/ide/src/diagnostics/inactive_code.rs new file mode 100644 index 000000000..52f97cb4c --- /dev/null +++ b/crates/ide/src/diagnostics/inactive_code.rs @@ -0,0 +1,113 @@ +use cfg::DnfExpr; +use stdx::format_to; + +use crate::diagnostics::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: inactive-code +// +// This diagnostic is shown for code with inactive `#[cfg]` attributes. +pub(super) fn inactive_code( + ctx: &DiagnosticsContext<'_>, + d: &hir::InactiveCode, +) -> Option { + // If there's inactive code somewhere in a macro, don't propagate to the call-site. + if d.node.file_id.expansion_info(ctx.sema.db).is_some() { + return None; + } + + let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts); + let mut message = "code is inactive due to #[cfg] directives".to_string(); + + if let Some(inactive) = inactive { + format_to!(message, ": {}", inactive); + } + + let res = Diagnostic::new( + "inactive-code", + message, + ctx.sema.diagnostics_display_range(d.node.clone()).range, + ) + .with_unused(true); + Some(res) +} + +#[cfg(test)] +mod tests { + use crate::diagnostics::tests::check_diagnostics_with_inactive_code; + + #[test] + fn cfg_diagnostics() { + check_diagnostics_with_inactive_code( + r#" +fn f() { + // The three g̶e̶n̶d̶e̶r̶s̶ statements: + + #[cfg(a)] fn f() {} // Item statement + //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + #[cfg(a)] {} // Expression statement + //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + #[cfg(a)] let x = 0; // let statement + //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + + abc(#[cfg(a)] 0); + //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + let x = Struct { + #[cfg(a)] f: 0, + //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + }; + match () { + () => (), + #[cfg(a)] () => (), + //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + } + + #[cfg(a)] 0 // Trailing expression of block + //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled +} + "#, + true, + ); + } + + #[test] + fn inactive_item() { + // Additional tests in `cfg` crate. This only tests disabled cfgs. + + check_diagnostics_with_inactive_code( + r#" + #[cfg(no)] pub fn f() {} + //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled + + #[cfg(no)] #[cfg(no2)] mod m; + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled + + #[cfg(all(not(a), b))] enum E {} + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled + + #[cfg(feature = "std")] use std; + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled +"#, + true, + ); + } + + /// Tests that `cfg` attributes behind `cfg_attr` is handled properly. + #[test] + fn inactive_via_cfg_attr() { + cov_mark::check!(cfg_attr_active); + check_diagnostics_with_inactive_code( + r#" + #[cfg_attr(not(never), cfg(no))] fn f() {} + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled + + #[cfg_attr(not(never), cfg(not(no)))] fn f() {} + + #[cfg_attr(never, cfg(no))] fn g() {} + + #[cfg_attr(not(never), inline, cfg(no))] fn h() {} + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled +"#, + true, + ); + } +} -- cgit v1.2.3 From 1e4aaee7bbc1d56698e70158aa35f578422623d9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 13 Jun 2021 17:51:44 +0300 Subject: internal: refactor unresolved proc macro diagnostic --- crates/ide/src/diagnostics/inactive_code.rs | 6 ++++- .../ide/src/diagnostics/unresolved_proc_macro.rs | 30 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 crates/ide/src/diagnostics/unresolved_proc_macro.rs (limited to 'crates/ide/src/diagnostics') diff --git a/crates/ide/src/diagnostics/inactive_code.rs b/crates/ide/src/diagnostics/inactive_code.rs index 52f97cb4c..afe333204 100644 --- a/crates/ide/src/diagnostics/inactive_code.rs +++ b/crates/ide/src/diagnostics/inactive_code.rs @@ -1,7 +1,10 @@ use cfg::DnfExpr; use stdx::format_to; -use crate::diagnostics::{Diagnostic, DiagnosticsContext}; +use crate::{ + diagnostics::{Diagnostic, DiagnosticsContext}, + Severity, +}; // Diagnostic: inactive-code // @@ -27,6 +30,7 @@ pub(super) fn inactive_code( message, ctx.sema.diagnostics_display_range(d.node.clone()).range, ) + .severity(Severity::WeakWarning) .with_unused(true); Some(res) } diff --git a/crates/ide/src/diagnostics/unresolved_proc_macro.rs b/crates/ide/src/diagnostics/unresolved_proc_macro.rs new file mode 100644 index 000000000..3dc6ab451 --- /dev/null +++ b/crates/ide/src/diagnostics/unresolved_proc_macro.rs @@ -0,0 +1,30 @@ +use crate::{ + diagnostics::{Diagnostic, DiagnosticsContext}, + Severity, +}; + +// 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. +// +// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the +// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can +// enable support for procedural macros (see `rust-analyzer.procMacro.enable`). +pub(super) fn unresolved_proc_macro( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedProcMacro, +) -> Diagnostic { + // Use more accurate position if available. + let display_range = d + .precise_location + .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range); + // FIXME: it would be nice to tell the user whether proc macros are currently disabled + let message = match &d.macro_name { + Some(name) => format!("proc macro `{}` not expanded", name), + None => "proc macro not expanded".to_string(), + }; + + Diagnostic::new("unresolved-proc-macro", message, display_range).severity(Severity::WeakWarning) +} -- cgit v1.2.3 From 00303284b5cc3a82e32dc3ecbbcfeb2f99de6818 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 13 Jun 2021 18:41:04 +0300 Subject: internal: refactor macro error --- crates/ide/src/diagnostics/macro_error.rs | 163 ++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 crates/ide/src/diagnostics/macro_error.rs (limited to 'crates/ide/src/diagnostics') diff --git a/crates/ide/src/diagnostics/macro_error.rs b/crates/ide/src/diagnostics/macro_error.rs new file mode 100644 index 000000000..8cc8cfb48 --- /dev/null +++ b/crates/ide/src/diagnostics/macro_error.rs @@ -0,0 +1,163 @@ +use crate::diagnostics::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: macro-error +// +// This diagnostic is shown for macro expansion errors. +pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { + Diagnostic::new( + "macro-error", + d.message.clone(), + ctx.sema.diagnostics_display_range(d.node.clone()).range, + ) + .experimental() +} + +#[cfg(test)] +mod tests { + use crate::diagnostics::tests::{check_diagnostics, check_no_diagnostics}; + + #[test] + fn builtin_macro_fails_expansion() { + check_diagnostics( + r#" +#[rustc_builtin_macro] +macro_rules! include { () => {} } + + include!("doesntexist"); +//^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist` + "#, + ); + } + + #[test] + fn include_macro_should_allow_empty_content() { + check_diagnostics( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +include!("foo/bar.rs"); +//- /foo/bar.rs +// empty +"#, + ); + } + + #[test] + fn good_out_dir_diagnostic() { + check_diagnostics( + r#" +#[rustc_builtin_macro] +macro_rules! include { () => {} } +#[rustc_builtin_macro] +macro_rules! env { () => {} } +#[rustc_builtin_macro] +macro_rules! concat { () => {} } + + include!(concat!(env!("OUT_DIR"), "/out.rs")); +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix +"#, + ); + } + + #[test] + fn register_attr_and_tool() { + cov_mark::check!(register_attr); + cov_mark::check!(register_tool); + check_no_diagnostics( + r#" +#![register_tool(tool)] +#![register_attr(attr)] + +#[tool::path] +#[attr] +struct S; +"#, + ); + // NB: we don't currently emit diagnostics here + } + + #[test] + fn macro_diag_builtin() { + check_diagnostics( + r#" +#[rustc_builtin_macro] +macro_rules! env {} + +#[rustc_builtin_macro] +macro_rules! include {} + +#[rustc_builtin_macro] +macro_rules! compile_error {} + +#[rustc_builtin_macro] +macro_rules! format_args { () => {} } + +fn main() { + // Test a handful of built-in (eager) macros: + + include!(invalid); + //^^^^^^^^^^^^^^^^^ could not convert tokens + include!("does not exist"); + //^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist` + + env!(invalid); + //^^^^^^^^^^^^^ could not convert tokens + + env!("OUT_DIR"); + //^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix + + compile_error!("compile_error works"); + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works + + // Lazy: + + format_args!(); + //^^^^^^^^^^^^^^ no rule matches input tokens +} +"#, + ); + } + + #[test] + fn macro_rules_diag() { + check_diagnostics( + r#" +macro_rules! m { + () => {}; +} +fn f() { + m!(); + + m!(hi); + //^^^^^^ leftover tokens +} + "#, + ); + } + #[test] + fn dollar_crate_in_builtin_macro() { + check_diagnostics( + r#" +#[macro_export] +#[rustc_builtin_macro] +macro_rules! format_args {} + +#[macro_export] +macro_rules! arg { () => {} } + +#[macro_export] +macro_rules! outer { + () => { + $crate::format_args!( "", $crate::arg!(1) ) + }; +} + +fn f() { + outer!(); +} //^^^^^^^^ leftover tokens +"#, + ) + } +} -- cgit v1.2.3 From 4af7a35197a1cb159458694e69e17bd83dc9edff Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 13 Jun 2021 18:45:38 +0300 Subject: internal: remove def-level diagnostics tests --- crates/ide/src/diagnostics/unresolved_macro_call.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'crates/ide/src/diagnostics') diff --git a/crates/ide/src/diagnostics/unresolved_macro_call.rs b/crates/ide/src/diagnostics/unresolved_macro_call.rs index a3af332a4..15b6a2730 100644 --- a/crates/ide/src/diagnostics/unresolved_macro_call.rs +++ b/crates/ide/src/diagnostics/unresolved_macro_call.rs @@ -34,6 +34,18 @@ pub(super) fn unresolved_macro_call( mod tests { use crate::diagnostics::tests::check_diagnostics; + #[test] + fn unresolved_macro_diag() { + check_diagnostics( + r#" +fn f() { + m!(); +} //^ unresolved macro `m!` + +"#, + ); + } + #[test] fn test_unresolved_macro_range() { check_diagnostics( -- cgit v1.2.3