Skip to main content

ide_diagnostics/handlers/
typed_hole.rs

1use std::ops::Not;
2
3use hir::{
4    ClosureStyle, FindPathConfig, HirDisplay,
5    db::ExpandDatabase,
6    term_search::{TermSearchConfig, TermSearchCtx, term_search},
7};
8use ide_db::text_edit::TextEdit;
9use ide_db::{
10    assists::{Assist, AssistId, GroupLabel},
11    label::Label,
12    source_change::SourceChange,
13};
14use itertools::Itertools;
15
16use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
17
18use syntax::AstNode;
19
20// Diagnostic: typed-hole
21//
22// This diagnostic is triggered when an underscore expression is used in an invalid position.
23pub(crate) fn typed_hole<'db>(
24    ctx: &DiagnosticsContext<'_, 'db>,
25    d: &hir::TypedHole<'db>,
26) -> Diagnostic {
27    let display_range = ctx.sema.diagnostics_display_range(d.expr.map(|it| it.into()));
28    let (message, fixes) = if d.expected.is_unknown() {
29        ("`_` expressions may only appear on the left-hand side of an assignment".to_owned(), None)
30    } else {
31        (
32            format!(
33                "invalid `_` expression, expected type `{}`",
34                d.expected
35                    .display(ctx.sema.db, ctx.display_target)
36                    .with_closure_style(ClosureStyle::ClosureWithId),
37            ),
38            fixes(ctx, d),
39        )
40    };
41
42    Diagnostic::new(DiagnosticCode::RustcHardError("typed-hole"), message, display_range)
43        .stable()
44        .with_fixes(fixes)
45}
46
47fn fixes<'db>(ctx: &DiagnosticsContext<'_, 'db>, d: &hir::TypedHole<'db>) -> Option<Vec<Assist>> {
48    let db = ctx.sema.db;
49    let root = db.parse_or_expand(d.expr.file_id);
50    let (original_range, _) =
51        d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?;
52    let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?;
53
54    let term_search_ctx = TermSearchCtx {
55        sema: &ctx.sema,
56        scope: &scope,
57        goal: d.expected.clone(),
58        config: TermSearchConfig {
59            fuel: ctx.config.term_search_fuel,
60            enable_borrowcheck: ctx.config.term_search_borrowck,
61
62            ..Default::default()
63        },
64    };
65    let paths = term_search(&term_search_ctx);
66
67    let mut formatter = |_: &hir::Type<'_>| String::from("_");
68
69    let assists: Vec<Assist> = d
70        .expected
71        .is_unknown()
72        .not()
73        .then(|| "todo!()".to_owned())
74        .into_iter()
75        .chain(paths.into_iter().filter_map(|path| {
76            path.gen_source_code(
77                &scope,
78                &mut formatter,
79                FindPathConfig {
80                    prefer_no_std: ctx.config.prefer_no_std,
81                    prefer_prelude: ctx.config.prefer_prelude,
82                    prefer_absolute: ctx.config.prefer_absolute,
83                    allow_unstable: ctx.is_nightly,
84                },
85                ctx.display_target,
86            )
87            .ok()
88        }))
89        .unique()
90        .map(|code| Assist {
91            id: AssistId::quick_fix("typed-hole"),
92            label: Label::new(format!("Replace `_` with `{code}`")),
93            group: Some(GroupLabel("Replace `_` with a term".to_owned())),
94            target: original_range.range,
95            source_change: Some(SourceChange::from_text_edit(
96                original_range.file_id.file_id(ctx.sema.db),
97                TextEdit::replace(original_range.range, code),
98            )),
99            command: None,
100        })
101        .collect();
102
103    if !assists.is_empty() { Some(assists) } else { None }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::tests::{check_diagnostics, check_fixes_unordered, check_has_fix};
109
110    #[test]
111    fn unknown() {
112        check_diagnostics(
113            r#"
114fn main() {
115    _;
116  //^ error: `_` expressions may only appear on the left-hand side of an assignment
117}
118"#,
119        );
120    }
121
122    #[test]
123    fn concrete_expectation() {
124        check_diagnostics(
125            r#"
126fn main() {
127    if _ {}
128     //^ 💡 error: invalid `_` expression, expected type `bool`
129    let _: fn() -> i32 = _;
130                       //^ 💡 error: invalid `_` expression, expected type `fn() -> i32`
131    let _: fn() -> () = _; // FIXME: This should trigger an assist because `main` matches via *coercion*
132                      //^ 💡 error: invalid `_` expression, expected type `fn()`
133}
134"#,
135        );
136    }
137
138    #[test]
139    fn integer_ty_var() {
140        check_diagnostics(
141            r#"
142fn main() {
143    let mut x = 3;
144    x = _;
145      //^ 💡 error: invalid `_` expression, expected type `i32`
146}
147"#,
148        );
149    }
150
151    #[test]
152    fn ty_var_resolved() {
153        check_diagnostics(
154            r#"
155fn main() {
156    let mut x = t();
157    x = _;
158      //^ 💡 error: invalid `_` expression, expected type `&str`
159    x = "";
160}
161fn t<T>() -> T { loop {} }
162"#,
163        );
164    }
165
166    #[test]
167    fn valid_positions() {
168        check_diagnostics(
169            r#"
170fn main() {
171    let _x = [(); _];
172               // ^ error: type annotations needed
173               // | full type: `[(); _]`
174    // FIXME: This should trigger error
175    // let _y: [(); 10] = [(); _];
176    _ = 0;
177    (_,) = (1,);
178}
179"#,
180        );
181    }
182
183    #[test]
184    fn check_quick_fix() {
185        check_fixes_unordered(
186            r#"
187enum Foo {
188    Bar
189}
190use Foo::Bar;
191const C: Foo = Foo::Bar;
192fn main<const CP: Foo>(param: Foo) {
193    let local = Foo::Bar;
194    let _: Foo = _$0;
195               //^ error: invalid `_` expression, expected type `fn()`
196}
197"#,
198            vec![
199                r#"
200enum Foo {
201    Bar
202}
203use Foo::Bar;
204const C: Foo = Foo::Bar;
205fn main<const CP: Foo>(param: Foo) {
206    let local = Foo::Bar;
207    let _: Foo = Bar;
208               //^ error: invalid `_` expression, expected type `fn()`
209}
210"#,
211                r#"
212enum Foo {
213    Bar
214}
215use Foo::Bar;
216const C: Foo = Foo::Bar;
217fn main<const CP: Foo>(param: Foo) {
218    let local = Foo::Bar;
219    let _: Foo = local;
220               //^ error: invalid `_` expression, expected type `fn()`
221}
222"#,
223                r#"
224enum Foo {
225    Bar
226}
227use Foo::Bar;
228const C: Foo = Foo::Bar;
229fn main<const CP: Foo>(param: Foo) {
230    let local = Foo::Bar;
231    let _: Foo = param;
232               //^ error: invalid `_` expression, expected type `fn()`
233}
234"#,
235                r#"
236enum Foo {
237    Bar
238}
239use Foo::Bar;
240const C: Foo = Foo::Bar;
241fn main<const CP: Foo>(param: Foo) {
242    let local = Foo::Bar;
243    let _: Foo = CP;
244               //^ error: invalid `_` expression, expected type `fn()`
245}
246"#,
247                r#"
248enum Foo {
249    Bar
250}
251use Foo::Bar;
252const C: Foo = Foo::Bar;
253fn main<const CP: Foo>(param: Foo) {
254    let local = Foo::Bar;
255    let _: Foo = C;
256               //^ error: invalid `_` expression, expected type `fn()`
257}
258"#,
259            ],
260        );
261    }
262
263    #[test]
264    fn local_item_use_trait() {
265        check_has_fix(
266            r#"
267struct Bar;
268struct Baz;
269trait Foo {
270    fn foo(self) -> Bar;
271}
272impl Foo for Baz {
273    fn foo(self) -> Bar {
274        unimplemented!()
275    }
276}
277fn asd() -> Bar {
278    let a = Baz;
279    _$0
280}
281"#,
282            r"
283struct Bar;
284struct Baz;
285trait Foo {
286    fn foo(self) -> Bar;
287}
288impl Foo for Baz {
289    fn foo(self) -> Bar {
290        unimplemented!()
291    }
292}
293fn asd() -> Bar {
294    let a = Baz;
295    Foo::foo(a)
296}
297",
298        );
299    }
300
301    #[test]
302    fn init_struct() {
303        check_has_fix(
304            r#"struct Abc {}
305struct Qwe { a: i32, b: Abc }
306fn main() {
307    let a: i32 = 1;
308    let c: Qwe = _$0;
309}"#,
310            r#"struct Abc {}
311struct Qwe { a: i32, b: Abc }
312fn main() {
313    let a: i32 = 1;
314    let c: Qwe = Qwe { a: a, b: Abc {  } };
315}"#,
316        );
317    }
318
319    #[test]
320    fn ignore_impl_func_with_incorrect_return() {
321        check_fixes_unordered(
322            r#"
323struct Bar {}
324trait Foo {
325    type Res;
326    fn foo(&self) -> Self::Res;
327}
328impl Foo for i32 {
329    type Res = Self;
330    fn foo(&self) -> Self::Res { 1 }
331}
332fn main() {
333    let a: i32 = 1;
334    let c: Bar = _$0;
335}"#,
336            vec![
337                r#"
338struct Bar {}
339trait Foo {
340    type Res;
341    fn foo(&self) -> Self::Res;
342}
343impl Foo for i32 {
344    type Res = Self;
345    fn foo(&self) -> Self::Res { 1 }
346}
347fn main() {
348    let a: i32 = 1;
349    let c: Bar = Bar {  };
350}"#,
351                r#"
352struct Bar {}
353trait Foo {
354    type Res;
355    fn foo(&self) -> Self::Res;
356}
357impl Foo for i32 {
358    type Res = Self;
359    fn foo(&self) -> Self::Res { 1 }
360}
361fn main() {
362    let a: i32 = 1;
363    let c: Bar = todo!();
364}"#,
365            ],
366        );
367    }
368
369    #[test]
370    fn use_impl_func_with_correct_return() {
371        check_has_fix(
372            r#"
373struct Bar {}
374struct A;
375trait Foo {
376    type Res;
377    fn foo(&self) -> Self::Res;
378}
379impl Foo for A {
380    type Res = Bar;
381    fn foo(&self) -> Self::Res { Bar { } }
382}
383fn main() {
384    let a = A;
385    let c: Bar = _$0;
386}"#,
387            r#"
388struct Bar {}
389struct A;
390trait Foo {
391    type Res;
392    fn foo(&self) -> Self::Res;
393}
394impl Foo for A {
395    type Res = Bar;
396    fn foo(&self) -> Self::Res { Bar { } }
397}
398fn main() {
399    let a = A;
400    let c: Bar = Foo::foo(&a);
401}"#,
402        );
403    }
404
405    // FIXME
406    #[test]
407    fn local_shadow_fn() {
408        check_fixes_unordered(
409            r#"
410fn f() {
411    let f: i32 = 0;
412    _$0
413}"#,
414            vec![
415                r#"
416fn f() {
417    let f: i32 = 0;
418    ()
419}"#,
420                r#"
421fn f() {
422    let f: i32 = 0;
423    f()
424}"#,
425            ],
426        );
427    }
428
429    #[test]
430    fn underscore_in_asm() {
431        check_diagnostics(
432            r#"
433//- minicore: asm
434fn rdtscp() -> u64 {
435    let hi: u64;
436    let lo: u64;
437    unsafe {
438        core::arch::asm!(
439            "rdtscp",
440            out("rdx") hi,
441            out("rax") lo,
442            out("rcx") _,
443            options(nomem, nostack, preserves_flags)
444        );
445    }
446    (hi << 32) | lo
447}"#,
448        );
449    }
450
451    #[test]
452    fn asm_sym_with_macro_expr_fragment() {
453        // Regression test for issue #21582
454        // When `$e:expr` captures a path and is used in `sym $e`, the path gets
455        // wrapped in parentheses during macro expansion due to invisible delimiters.
456        // This should not cause false positive typed-hole errors.
457        check_diagnostics(
458            r#"
459//- minicore: asm
460macro_rules! m {
461    ($e:expr) => {
462        core::arch::asm!("/*{f}*/", f = sym $e, out("ax") _)
463    };
464}
465
466fn generic<T>() {}
467
468fn main() {
469    unsafe {
470        m!(generic::<i32>);
471    }
472}
473"#,
474        );
475    }
476}