ide_diagnostics/handlers/
typed_hole.rs1use 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
20pub(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 #[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 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}