ide_diagnostics/handlers/
macro_error.rs

1use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
2
3// Diagnostic: macro-error
4//
5// This diagnostic is shown for macro expansion errors.
6
7// Diagnostic: attribute-expansion-disabled
8//
9// This diagnostic is shown for attribute proc macros when attribute expansions have been disabled.
10
11// Diagnostic: proc-macro-disabled
12//
13// This diagnostic is shown for proc macros that have been specifically disabled via `rust-analyzer.procMacro.ignored`.
14pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
15    // Use more accurate position if available.
16    let display_range = ctx.sema.diagnostics_display_range_for_range(d.range);
17    Diagnostic::new(
18        DiagnosticCode::Ra(d.kind, if d.error { Severity::Error } else { Severity::WeakWarning }),
19        d.message.clone(),
20        display_range,
21    )
22    .stable()
23}
24
25// Diagnostic: macro-def-error
26//
27// This diagnostic is shown for macro expansion errors.
28pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefError) -> Diagnostic {
29    // Use more accurate position if available.
30    let display_range = match d.name {
31        Some(name) => ctx.sema.diagnostics_display_range_for_range(d.node.with_value(name)),
32        None => ctx.sema.diagnostics_display_range(d.node.map(|it| it.syntax_node_ptr())),
33    };
34    Diagnostic::new(
35        DiagnosticCode::Ra("macro-def-error", Severity::Error),
36        d.message.clone(),
37        display_range,
38    )
39    .stable()
40}
41
42#[cfg(test)]
43mod tests {
44    use crate::{
45        DiagnosticsConfig,
46        tests::{check_diagnostics, check_diagnostics_with_config},
47    };
48
49    #[test]
50    fn builtin_macro_fails_expansion() {
51        check_diagnostics(
52            r#"
53#[rustc_builtin_macro]
54macro_rules! include { () => {} }
55
56#[rustc_builtin_macro]
57macro_rules! compile_error { () => {} }
58
59  include!("doesntexist");
60         //^^^^^^^^^^^^^ error: failed to load file `doesntexist`
61
62  compile_error!("compile_error macro works");
63//^^^^^^^^^^^^^ error: compile_error macro works
64
65  compile_error! { "compile_error macro braced works" }
66//^^^^^^^^^^^^^ error: compile_error macro braced works
67            "#,
68        );
69    }
70
71    #[test]
72    fn eager_macro_concat() {
73        check_diagnostics(
74            r#"
75//- /lib.rs crate:lib deps:core
76use core::{panic, concat};
77
78mod private {
79    pub use core::concat;
80}
81
82macro_rules! m {
83    () => {
84        panic!(concat!($crate::private::concat!("")));
85    };
86}
87
88fn f() {
89    m!();
90}
91
92//- /core.rs crate:core
93#[macro_export]
94#[rustc_builtin_macro]
95macro_rules! concat { () => {} }
96
97pub macro panic {
98    ($msg:expr) => (
99        $crate::panicking::panic_str($msg)
100    ),
101}
102            "#,
103        );
104    }
105
106    #[test]
107    fn include_macro_should_allow_empty_content() {
108        let mut config = DiagnosticsConfig::test_sample();
109
110        // FIXME: This is a false-positive, the file is actually linked in via
111        // `include!` macro
112        config.disabled.insert("unlinked-file".to_owned());
113
114        check_diagnostics_with_config(
115            config,
116            r#"
117//- /lib.rs
118#[rustc_builtin_macro]
119macro_rules! include { () => {} }
120
121include!("foo/bar.rs");
122//- /foo/bar.rs
123// empty
124"#,
125        );
126    }
127
128    #[test]
129    fn good_out_dir_diagnostic() {
130        // FIXME: The diagnostic here is duplicated for each eager expansion
131        check_diagnostics(
132            r#"
133#[rustc_builtin_macro]
134macro_rules! include { () => {} }
135#[rustc_builtin_macro]
136macro_rules! env { () => {} }
137#[rustc_builtin_macro]
138macro_rules! concat { () => {} }
139
140  include!(concat!(
141        // ^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run
142    env!(
143  //^^^ error: `OUT_DIR` not set, build scripts may have failed to run
144        "OUT_DIR"), "/out.rs"));
145      //^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run
146"#,
147        );
148    }
149
150    #[test]
151    fn register_tool() {
152        cov_mark::check!(register_tool);
153        check_diagnostics(
154            r#"
155#![register_tool(tool)]
156
157#[tool::path]
158struct S;
159"#,
160        );
161        // NB: we don't currently emit diagnostics here
162    }
163
164    #[test]
165    fn macro_diag_builtin() {
166        check_diagnostics(
167            r#"
168//- minicore: fmt
169#[rustc_builtin_macro]
170macro_rules! env {}
171
172#[rustc_builtin_macro]
173macro_rules! include {}
174
175#[rustc_builtin_macro]
176macro_rules! compile_error {}
177#[rustc_builtin_macro]
178macro_rules! concat {}
179
180fn main() {
181    // Test a handful of built-in (eager) macros:
182
183    include!(invalid);
184           //^^^^^^^ error: expected string literal
185    include!("does not exist");
186           //^^^^^^^^^^^^^^^^ error: failed to load file `does not exist`
187
188    include!(concat!("does ", "not ", "exist"));
189                  // ^^^^^^^^^^^^^^^^^^^^^^^^ error: failed to load file `does not exist`
190
191    env!(invalid);
192       //^^^^^^^ error: expected string literal
193
194    env!("OUT_DIR");
195       //^^^^^^^^^ error: `OUT_DIR` not set, build scripts may have failed to run
196
197    compile_error!("compile_error works");
198  //^^^^^^^^^^^^^ error: compile_error works
199
200    // Lazy:
201
202    format_args!();
203  //^^^^^^^^^^^ error: Syntax Error in Expansion: expected expression
204}
205"#,
206        );
207    }
208
209    #[test]
210    fn macro_rules_diag() {
211        check_diagnostics(
212            r#"
213macro_rules! m {
214    () => {};
215}
216fn f() {
217    m!();
218
219    m!(hi);
220    //^ error: leftover tokens
221}
222      "#,
223        );
224    }
225
226    #[test]
227    fn dollar_crate_in_builtin_macro() {
228        check_diagnostics(
229            r#"
230#[macro_export]
231#[rustc_builtin_macro]
232macro_rules! format_args {}
233
234#[macro_export]
235macro_rules! arg { () => {} }
236
237#[macro_export]
238macro_rules! outer {
239    () => {
240        $crate::format_args!( "", $crate::arg!(1) )
241    };
242}
243
244fn f() {
245    outer!();
246} //^^^^^^ error: leftover tokens
247  //^^^^^^ error: Syntax Error in Expansion: expected expression
248"#,
249        )
250    }
251
252    #[test]
253    fn def_diagnostic() {
254        check_diagnostics(
255            r#"
256macro_rules! foo {
257           //^^^ error: expected subtree
258    f => {};
259}
260
261fn f() {
262    foo!();
263  //^^^ error: macro definition has parse errors
264
265}
266"#,
267        )
268    }
269
270    #[test]
271    fn expansion_syntax_diagnostic() {
272        check_diagnostics(
273            r#"
274macro_rules! foo {
275    () => { struct; };
276}
277
278fn f() {
279    foo!();
280  //^^^ error: Syntax Error in Expansion: expected a name
281}
282"#,
283        )
284    }
285
286    #[test]
287    fn include_does_not_break_diagnostics() {
288        check_diagnostics(
289            r#"
290//- minicore: include
291//- /lib.rs crate:lib
292include!("include-me.rs");
293//- /include-me.rs
294/// long doc that pushes the diagnostic range beyond the first file's text length
295  #[err]
296 // ^^^ error: unresolved macro `err`
297mod prim_never {}
298"#,
299        );
300    }
301
302    #[test]
303    fn no_stack_overflow_for_missing_binding() {
304        check_diagnostics(
305            r#"
306#[macro_export]
307macro_rules! boom {
308    (
309        $($code:literal),+,
310        $(param: $param:expr,)?
311    ) => {{
312        let _ = $crate::boom!(@param $($param)*);
313    }};
314    (@param) => { () };
315    (@param $param:expr) => { $param };
316}
317
318fn it_works() {
319    // NOTE: there is an error, but RA crashes before showing it
320    boom!("RAND", param: c7.clone());
321               // ^^^^^ error: expected literal
322}
323
324        "#,
325        );
326    }
327}