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(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 #[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}