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