match self.coerce(Some(expr), &ty, &target) {
Ok(res) => res,
Err(_) => {
- self.result
- .type_mismatches
- .insert(expr.into(), TypeMismatch { expected: target, actual: ty.clone() });
- // Return actual type when type mismatch.
- // This is needed for diagnostic when return type mismatch.
- ty
+ self.result.type_mismatches.insert(
+ expr.into(),
+ TypeMismatch { expected: target.clone(), actual: ty.clone() },
+ );
+ target
}
}
} else {
self.table.new_maybe_never_var()
} else {
if let Some(t) = expected.only_has_type(&mut self.table) {
- let _ = self.coerce(Some(expr), &TyBuilder::unit(), &t);
+ if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() {
+ self.result.type_mismatches.insert(
+ expr.into(),
+ TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() },
+ );
+ }
+ t
+ } else {
+ TyBuilder::unit()
}
- TyBuilder::unit()
}
}
}
mod macros;
mod display_source_code;
mod incremental;
+mod diagnostics;
use std::{collections::HashMap, env, sync::Arc};
#[test]
fn block_expr_type_mismatch() {
- // FIXME fix double type mismatch
check(
r"
fn test() {
let a: i32 = { 1i64 };
- // ^^^^^^^^ expected i32, got i64
// ^^^^ expected i32, got i64
}
",
--- /dev/null
+use super::check;
+
+#[test]
+fn function_return_type_mismatch_1() {
+ check(
+ r#"
+fn test() -> &'static str {
+ 5
+ //^ expected &str, got i32
+}
+"#,
+ );
+}
+
+#[test]
+fn function_return_type_mismatch_2() {
+ check(
+ r#"
+fn test(x: bool) -> &'static str {
+ if x {
+ return 1;
+ //^ expected &str, got i32
+ }
+ "ok"
+}
+"#,
+ );
+}
+
+#[test]
+fn function_return_type_mismatch_3() {
+ check(
+ r#"
+fn test(x: bool) -> &'static str {
+ if x {
+ return "ok";
+ }
+ 1
+ //^ expected &str, got i32
+}
+"#,
+ );
+}
+
+#[test]
+fn function_return_type_mismatch_4() {
+ check(
+ r#"
+fn test(x: bool) -> &'static str {
+ if x {
+ "ok"
+ } else {
+ 1
+ //^ expected &str, got i32
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn function_return_type_mismatch_5() {
+ check(
+ r#"
+fn test(x: bool) -> &'static str {
+ if x {
+ 1
+ //^ expected &str, got i32
+ } else {
+ "ok"
+ }
+}
+"#,
+ );
+}
expect![[r#"
11..84 '{ ..." }; }': ()
54..55 'x': u32
- 63..81 '{ loop...foo" }': &str
+ 63..81 '{ loop...foo" }': u32
65..72 'loop {}': !
70..72 '{}': ()
74..79 '"foo"': &str
- 63..81: expected u32, got &str
74..79: expected u32, got &str
"#]],
);
let x: u32 = { while true { return; }; };
}
",
- expect![[r"
+ expect![[r#"
11..85 '{ ...} }; }': ()
54..55 'x': u32
- 63..82 '{ loop...k; } }': ()
+ 63..82 '{ loop...k; } }': u32
65..80 'loop { break; }': ()
70..80 '{ break; }': ()
72..77 'break': !
- 63..82: expected u32, got ()
65..80: expected u32, got ()
97..343 '{ ...; }; }': ()
140..141 'x': u32
- 149..175 '{ for ...; }; }': ()
+ 149..175 '{ for ...; }; }': u32
151..172 'for a ...eak; }': ()
155..156 'a': {unknown}
160..161 'b': {unknown}
162..172 '{ break; }': ()
164..169 'break': !
226..227 'x': u32
- 235..253 '{ for ... {}; }': ()
+ 235..253 '{ for ... {}; }': u32
237..250 'for a in b {}': ()
241..242 'a': {unknown}
246..247 'b': {unknown}
248..250 '{}': ()
304..305 'x': u32
- 313..340 '{ for ...; }; }': ()
+ 313..340 '{ for ...; }; }': u32
315..337 'for a ...urn; }': ()
319..320 'a': {unknown}
324..325 'b': {unknown}
313..340: expected u32, got ()
355..654 '{ ...; }; }': ()
398..399 'x': u32
- 407..433 '{ whil...; }; }': ()
+ 407..433 '{ whil...; }; }': u32
409..430 'while ...eak; }': ()
415..419 'true': bool
420..430 '{ break; }': ()
422..427 'break': !
537..538 'x': u32
- 546..564 '{ whil... {}; }': ()
+ 546..564 '{ whil... {}; }': u32
548..561 'while true {}': ()
554..558 'true': bool
559..561 '{}': ()
615..616 'x': u32
- 624..651 '{ whil...; }; }': ()
+ 624..651 '{ whil...; }; }': u32
626..648 'while ...urn; }': ()
632..636 'true': bool
637..648 '{ return; }': ()
407..433: expected u32, got ()
546..564: expected u32, got ()
624..651: expected u32, got ()
- "]],
+ "#]],
);
}
17..18 '1': i32
17..18 '1': i32
21..22 '2': i32
- 28..30 '{}': ()
+ 28..30 '{}': !
28..30: expected !, got ()
"#]],
);
}
"#,
expect![[r#"
- 143..145 '{}': ()
+ 143..145 '{}': HashSet<T, H>
168..197 '{ ...t(); }': ()
174..192 'FxHash...efault': fn default<{unknown}, FxHasher>() -> HashSet<{unknown}, FxHasher>
174..194 'FxHash...ault()': HashSet<{unknown}, FxHasher>
"#,
expect![[r#"
225..229 'iter': T
- 244..246 '{}': ()
+ 244..246 '{}': Vec<A>
258..402 '{ ...r(); }': ()
268..273 'inner': Map<|&f64| -> f64>
276..300 'Map { ... 0.0 }': Map<|&f64| -> f64>
"#,
expect![[r#"
123..127 'self': &Mutex<T>
- 150..152 '{}': ()
+ 150..152 '{}': MutexGuard<T>
234..238 'self': &{unknown}
240..290 '{ ...()); }': ()
250..251 'w': &Mutex<BufWriter>
}
"#,
expect![[r#"
- 14..53 '{ ...)] 9 }': &str
- 20..31 '{ "first" }': &str
+ 14..53 '{ ...)] 9 }': ()
+ 20..31 '{ "first" }': ()
22..29 '"first"': &str
- 72..190 '{ ...] 13 }': &str
+ 72..190 '{ ...] 13 }': ()
78..88 '{ "fake" }': &str
80..86 '"fake"': &str
93..103 '{ "fake" }': &str
95..101 '"fake"': &str
- 108..120 '{ "second" }': &str
+ 108..120 '{ "second" }': ()
110..118 '"second"': &str
- 210..273 '{ ... 15; }': &str
- 216..227 '{ "third" }': &str
+ 210..273 '{ ... 15; }': ()
+ 216..227 '{ "third" }': ()
218..225 '"third"': &str
293..357 '{ ...] 15 }': ()
299..311 '{ "fourth" }': &str
50..106 'if tru... }': ()
53..57 'true': bool
58..66 '{ }': ()
- 72..106 'if fal... }': i32
+ 72..106 'if fal... }': ()
75..80 'false': bool
- 81..106 '{ ... }': i32
+ 81..106 '{ ... }': ()
91..94 'foo': Foo
91..100 'foo.field': i32
"#]],
expect![[r#"
31..35 'self': A
37..38 'x': u32
- 52..54 '{}': ()
+ 52..54 '{}': i32
106..110 'self': &A
112..113 'x': u64
- 127..129 '{}': ()
+ 127..129 '{}': i64
147..148 'a': A
153..201 '{ ...(1); }': ()
159..160 'a': A
"#,
expect![[r#"
39..43 'self': &str
- 52..54 '{}': ()
+ 52..54 '{}': i32
68..88 '{ ...o(); }': ()
74..79 '"foo"': &str
74..85 '"foo".foo()': i32
206..210 'self': A<X, Y>
206..212 'self.y': Y
214..215 't': T
- 244..341 '{ ...(1); }': ()
+ 244..341 '{ ...(1); }': i128
254..255 'a': A<u64, i64>
258..280 'A { x:...1i64 }': A<u64, i64>
265..269 '1u64': u64
"#,
expect![[r#"
77..81 'self': &Option<T>
- 97..99 '{}': ()
+ 97..99 '{}': Option<&T>
110..111 'o': Option<u32>
126..164 '{ ...f(); }': ()
132..145 '(&o).as_ref()': Option<&u32>
}
"#,
expect![[r#"
- 16..58 '{ ...; }; }': ()
+ 16..58 '{ ...; }; }': u32
26..27 'x': || -> usize
30..55 '|| -> ...n 1; }': || -> usize
42..55 '{ return 1; }': usize
}
"#,
expect![[r#"
- 16..47 '{ ...; }; }': ()
+ 16..47 '{ ...; }; }': u32
26..27 'x': || -> ()
30..44 '|| { return; }': || -> ()
33..44 '{ return; }': ()
}
"#,
expect![[r#"
- 16..46 '{ ..." }; }': ()
+ 16..46 '{ ..." }; }': u32
26..27 'x': || -> &str
30..43 '|| { "test" }': || -> &str
33..43 '{ "test" }': &str
expect![[r#"
104..108 'self': &Box<T>
188..192 'self': &Box<Foo<T>>
- 218..220 '{}': ()
+ 218..220 '{}': &T
242..246 'self': &Box<Foo<T>>
- 275..277 '{}': ()
+ 275..277 '{}': &Foo<T>
297..301 'self': Box<Foo<T>>
- 322..324 '{}': ()
+ 322..324 '{}': Foo<T>
338..559 '{ ...r(); }': ()
348..353 'boxed': Box<Foo<i32>>
356..359 'Box': Box<Foo<i32>>(Foo<i32>) -> Box<Foo<i32>>
expect![[r#"
29..33 'self': &Self
54..58 'self': &Self
- 97..99 '{}': ()
+ 97..99 '{}': dyn Trait<u64>
109..110 'x': dyn Trait<u64>
128..129 'y': &dyn Trait<u64>
148..265 '{ ...2(); }': ()
}"#,
expect![[r#"
26..30 'self': &Self
- 60..62 '{}': ()
+ 60..62 '{}': dyn Trait
72..73 'x': dyn Trait
82..83 'y': &dyn Trait
- 100..175 '{ ...o(); }': ()
+ 100..175 '{ ...o(); }': u64
106..107 'x': dyn Trait
113..114 'y': &dyn Trait
124..125 'z': dyn Trait
}"#,
expect![[r#"
49..50 't': T
- 77..79 '{}': ()
+ 77..79 '{}': Trait::Type<T>
111..112 't': T
- 122..124 '{}': ()
+ 122..124 '{}': U
154..155 't': T
165..168 '{t}': T
166..167 't': T
}"#,
expect![[r#"
49..53 'self': &Self
- 62..64 '{}': ()
+ 62..64 '{}': u32
181..182 'x': T
187..188 'y': U
193..222 '{ ...o(); }': ()
}"#,
expect![[r#"
49..53 'self': &Self
- 62..64 '{}': ()
+ 62..64 '{}': u32
115..116 'x': &impl Trait1
132..148 '{ ...o(); }': ()
138..139 'x': &impl Trait1
}"#,
expect![[r#"
102..103 't': T
- 113..115 '{}': ()
+ 113..115 '{}': U
145..146 't': T
156..159 '{t}': T
157..158 't': T
}"#,
expect![[r#"
36..40 'self': &Foo
- 51..53 '{}': ()
+ 51..53 '{}': usize
131..132 'f': F
- 151..153 '{}': ()
+ 151..153 '{}': Lazy<T, F>
251..497 '{ ...o(); }': ()
261..266 'lazy1': Lazy<Foo, || -> Foo>
283..292 'Lazy::new': fn new<Foo, || -> Foo>(|| -> Foo) -> Lazy<Foo, || -> Foo>
478..480 'r2': usize
483..488 'lazy2': Lazy<Foo, fn() -> Foo>
483..494 'lazy2.foo()': usize
- 357..359 '{}': ()
+ 357..359 '{}': Foo
"#]],
);
}
expect![[r#"
9..11 '{}': ()
28..29 'T': {unknown}
- 36..38 '{}': ()
+ 36..38 '{}': T
36..38: expected T, got ()
113..117 'self': &Self
169..249 '{ ...t(); }': ()
}"#,
expect![[r#"
17..73 '{ ... } }': ()
- 39..71 '{ ... }': ()
+ 39..71 '{ ... }': S
53..54 's': S
57..62 'inner': fn inner() -> S
57..64 'inner()': S
use hir::HirDisplay;
-use syntax::{ast, AstNode, SyntaxKind, SyntaxToken, TextRange, TextSize};
+use syntax::{ast, match_ast, AstNode, SyntaxKind, SyntaxToken, TextRange, TextSize};
use crate::{AssistContext, AssistId, AssistKind, Assists};
pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
let module = ctx.sema.scope(tail_expr.syntax()).module()?;
- let ty = ctx.sema.type_of_expr(&tail_expr)?.adjusted();
+ let ty = ctx.sema.type_of_expr(&peel_blocks(tail_expr.clone()))?.original();
if ty.is_unit() {
return None;
}
Closure { wrap_expr: bool },
}
+/// If we're looking at a block that is supposed to return `()`, type inference
+/// will just tell us it has type `()`. We have to look at the tail expression
+/// to see the mismatched actual type. This 'unpeels' the various blocks to
+/// hopefully let us see the type the user intends. (This still doesn't handle
+/// all situations fully correctly; the 'ideal' way to handle this would be to
+/// run type inference on the function again, but with a variable as the return
+/// type.)
+fn peel_blocks(mut expr: ast::Expr) -> ast::Expr {
+ loop {
+ match_ast! {
+ match (expr.syntax()) {
+ ast::BlockExpr(it) => {
+ if let Some(tail) = it.tail_expr() {
+ expr = tail.clone();
+ } else {
+ break;
+ }
+ },
+ ast::IfExpr(it) => {
+ if let Some(then_branch) = it.then_branch() {
+ expr = ast::Expr::BlockExpr(then_branch.clone());
+ } else {
+ break;
+ }
+ },
+ ast::MatchExpr(it) => {
+ if let Some(arm_expr) = it.match_arm_list().and_then(|l| l.arms().next()).and_then(|a| a.expr()) {
+ expr = arm_expr;
+ } else {
+ break;
+ }
+ },
+ _ => break,
+ }
+ }
+ }
+ expr
+}
+
fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
let (fn_type, tail_expr, return_type_range, action) =
if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
);
}
+ #[test]
+ fn infer_return_type_nested_match() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ match true {
+ true => { 3$0 },
+ false => { 5 },
+ }
+}"#,
+ r#"fn foo() -> i32 {
+ match true {
+ true => { 3 },
+ false => { 5 },
+ }
+}"#,
+ );
+ }
+
#[test]
fn not_applicable_ret_type_specified() {
cov_mark::check!(existing_ret_type);
match 6 {
100 => $0{ 100 }$0
_ => 0,
- }
+ };
}
"#,
r#"
match 6 {
100 => fun_name(),
_ => 0,
- }
+ };
}
fn $0fun_name() -> i32 {
match 6 {
100 => $0{ 100 }$0,
_ => 0,
- }
+ };
}
"#,
r#"
match 6 {
100 => fun_name(),
_ => 0,
- }
+ };
}
fn $0fun_name() -> i32 {
-use hir::{db::AstDatabase, HirDisplay, Type, TypeInfo};
-use ide_db::{
- famous_defs::FamousDefs, source_change::SourceChange,
- syntax_helpers::node_ext::for_each_tail_expr,
-};
+use hir::{db::AstDatabase, HirDisplay, Type};
+use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
use syntax::{
ast::{BlockExpr, ExprStmt},
AstNode,
acc: &mut Vec<Assist>,
) -> Option<()> {
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
- let tail_expr = d.expr.value.to_node(&root);
- let tail_expr_range = tail_expr.syntax().text_range();
- let scope = ctx.sema.scope(tail_expr.syntax());
+ let expr = d.expr.value.to_node(&root);
+ let expr_range = expr.syntax().text_range();
+ let scope = ctx.sema.scope(expr.syntax());
let expected_adt = d.expected.as_adt()?;
let expected_enum = expected_adt.as_enum()?;
}
let mut builder = TextEdit::builder();
- for_each_tail_expr(&tail_expr, &mut |expr| {
- if ctx.sema.type_of_expr(expr).map(TypeInfo::adjusted).as_ref() != Some(&d.expected) {
- builder.insert(expr.syntax().text_range().start(), format!("{}(", variant_name));
- builder.insert(expr.syntax().text_range().end(), ")".to_string());
- }
- });
+ builder.insert(expr.syntax().text_range().start(), format!("{}(", variant_name));
+ builder.insert(expr.syntax().text_range().end(), ")".to_string());
let source_change =
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
let name = format!("Wrap in {}", variant_name);
- acc.push(fix("wrap_tail_expr", &name, source_change, tail_expr_range));
+ acc.push(fix("wrap_in_constructor", &name, source_change, expr_range));
Some(())
}
//- minicore: option, result
fn div(x: i32, y: i32) -> Option<i32> {
if y == 0 {
- 0
+ Some(0)
} else if true {
- 100
+ 100$0
} else {
None
- }$0
+ }
}
"#,
r#"
actual
};
- assert_eq_text!(&after, &actual);
assert!(
fix.target.contains_inclusive(file_position.offset),
"diagnostic fix range {:?} does not touch cursor position {:?}",
fix.target,
file_position.offset
);
+ assert_eq_text!(&after, &actual);
}
/// Checks that there's a diagnostic *without* fix at `$0`.