Skip to main content

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