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