aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/diagnostics.rs28
-rw-r--r--crates/ra_hir/src/expr/validation.rs53
-rw-r--r--crates/ra_hir/src/name.rs2
-rw-r--r--crates/ra_hir/src/ty/infer.rs19
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs215
-rw-r--r--crates/ra_syntax/src/ptr.rs7
6 files changed, 319 insertions, 5 deletions
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
index 301109cb8..475dd5766 100644
--- a/crates/ra_hir/src/diagnostics.rs
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -143,3 +143,31 @@ impl AstDiagnostic for MissingFields {
143 ast::RecordFieldList::cast(node).unwrap() 143 ast::RecordFieldList::cast(node).unwrap()
144 } 144 }
145} 145}
146
147#[derive(Debug)]
148pub struct MissingOkInTailExpr {
149 pub file: HirFileId,
150 pub expr: AstPtr<ast::Expr>,
151}
152
153impl Diagnostic for MissingOkInTailExpr {
154 fn message(&self) -> String {
155 "wrap return expression in Ok".to_string()
156 }
157 fn source(&self) -> Source<SyntaxNodePtr> {
158 Source { file_id: self.file, ast: self.expr.into() }
159 }
160 fn as_any(&self) -> &(dyn Any + Send + 'static) {
161 self
162 }
163}
164
165impl AstDiagnostic for MissingOkInTailExpr {
166 type AST = ast::Expr;
167
168 fn ast(&self, db: &impl HirDatabase) -> Self::AST {
169 let root = db.parse_or_expand(self.file).unwrap();
170 let node = self.source().ast.to_node(&root);
171 ast::Expr::cast(node).unwrap()
172 }
173}
diff --git a/crates/ra_hir/src/expr/validation.rs b/crates/ra_hir/src/expr/validation.rs
index 62f7d41f5..5d9d59ff8 100644
--- a/crates/ra_hir/src/expr/validation.rs
+++ b/crates/ra_hir/src/expr/validation.rs
@@ -6,11 +6,14 @@ use ra_syntax::ast::{AstNode, RecordLit};
6use super::{Expr, ExprId, RecordLitField}; 6use super::{Expr, ExprId, RecordLitField};
7use crate::{ 7use crate::{
8 adt::AdtDef, 8 adt::AdtDef,
9 diagnostics::{DiagnosticSink, MissingFields}, 9 diagnostics::{DiagnosticSink, MissingFields, MissingOkInTailExpr},
10 expr::AstPtr, 10 expr::AstPtr,
11 ty::InferenceResult, 11 name,
12 Function, HasSource, HirDatabase, Name, Path, 12 path::{PathKind, PathSegment},
13 ty::{ApplicationTy, InferenceResult, Ty, TypeCtor},
14 Function, HasSource, HirDatabase, ModuleDef, Name, Path, PerNs, Resolution,
13}; 15};
16use ra_syntax::ast;
14 17
15pub(crate) struct ExprValidator<'a, 'b: 'a> { 18pub(crate) struct ExprValidator<'a, 'b: 'a> {
16 func: Function, 19 func: Function,
@@ -29,11 +32,17 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
29 32
30 pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) { 33 pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) {
31 let body = self.func.body(db); 34 let body = self.func.body(db);
35
32 for e in body.exprs() { 36 for e in body.exprs() {
33 if let (id, Expr::RecordLit { path, fields, spread }) = e { 37 if let (id, Expr::RecordLit { path, fields, spread }) = e {
34 self.validate_record_literal(id, path, fields, *spread, db); 38 self.validate_record_literal(id, path, fields, *spread, db);
35 } 39 }
36 } 40 }
41
42 let body_expr = &body[body.body_expr()];
43 if let Expr::Block { statements: _, tail: Some(t) } = body_expr {
44 self.validate_results_in_tail_expr(*t, db);
45 }
37 } 46 }
38 47
39 fn validate_record_literal( 48 fn validate_record_literal(
@@ -87,4 +96,42 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
87 }) 96 })
88 } 97 }
89 } 98 }
99
100 fn validate_results_in_tail_expr(&mut self, id: ExprId, db: &impl HirDatabase) {
101 let mismatch = match self.infer.type_mismatch_for_expr(id) {
102 Some(m) => m,
103 None => return,
104 };
105
106 let std_result_path = Path {
107 kind: PathKind::Abs,
108 segments: vec![
109 PathSegment { name: name::STD, args_and_bindings: None },
110 PathSegment { name: name::RESULT_MOD, args_and_bindings: None },
111 PathSegment { name: name::RESULT_TYPE, args_and_bindings: None },
112 ],
113 };
114
115 let resolver = self.func.resolver(db);
116 let std_result_enum =
117 match resolver.resolve_path_segments(db, &std_result_path).into_fully_resolved() {
118 PerNs { types: Some(Resolution::Def(ModuleDef::Enum(e))), .. } => e,
119 _ => return,
120 };
121
122 let std_result_ctor = TypeCtor::Adt(AdtDef::Enum(std_result_enum));
123 let params = match &mismatch.expected {
124 Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters,
125 _ => return,
126 };
127
128 if params.len() == 2 && &params[0] == &mismatch.actual {
129 let source_map = self.func.body_source_map(db);
130 let file_id = self.func.source(db).file_id;
131
132 if let Some(expr) = source_map.expr_syntax(id).and_then(|n| n.cast::<ast::Expr>()) {
133 self.sink.push(MissingOkInTailExpr { file: file_id, expr });
134 }
135 }
136 }
90} 137}
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs
index 6d14eea8e..9c4822d91 100644
--- a/crates/ra_hir/src/name.rs
+++ b/crates/ra_hir/src/name.rs
@@ -120,6 +120,8 @@ pub(crate) const TRY: Name = Name::new(SmolStr::new_inline_from_ascii(3, b"Try")
120pub(crate) const OK: Name = Name::new(SmolStr::new_inline_from_ascii(2, b"Ok")); 120pub(crate) const OK: Name = Name::new(SmolStr::new_inline_from_ascii(2, b"Ok"));
121pub(crate) const FUTURE_MOD: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"future")); 121pub(crate) const FUTURE_MOD: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"future"));
122pub(crate) const FUTURE_TYPE: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"Future")); 122pub(crate) const FUTURE_TYPE: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"Future"));
123pub(crate) const RESULT_MOD: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"result"));
124pub(crate) const RESULT_TYPE: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"Result"));
123pub(crate) const OUTPUT: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"Output")); 125pub(crate) const OUTPUT: Name = Name::new(SmolStr::new_inline_from_ascii(6, b"Output"));
124 126
125fn resolve_name(text: &SmolStr) -> SmolStr { 127fn resolve_name(text: &SmolStr) -> SmolStr {
diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs
index b33de5687..d94e8154b 100644
--- a/crates/ra_hir/src/ty/infer.rs
+++ b/crates/ra_hir/src/ty/infer.rs
@@ -106,6 +106,13 @@ impl Default for BindingMode {
106 } 106 }
107} 107}
108 108
109/// A mismatch between an expected and an inferred type.
110#[derive(Clone, PartialEq, Eq, Debug, Hash)]
111pub struct TypeMismatch {
112 pub expected: Ty,
113 pub actual: Ty,
114}
115
109/// The result of type inference: A mapping from expressions and patterns to types. 116/// The result of type inference: A mapping from expressions and patterns to types.
110#[derive(Clone, PartialEq, Eq, Debug, Default)] 117#[derive(Clone, PartialEq, Eq, Debug, Default)]
111pub struct InferenceResult { 118pub struct InferenceResult {
@@ -120,6 +127,7 @@ pub struct InferenceResult {
120 diagnostics: Vec<InferenceDiagnostic>, 127 diagnostics: Vec<InferenceDiagnostic>,
121 pub(super) type_of_expr: ArenaMap<ExprId, Ty>, 128 pub(super) type_of_expr: ArenaMap<ExprId, Ty>,
122 pub(super) type_of_pat: ArenaMap<PatId, Ty>, 129 pub(super) type_of_pat: ArenaMap<PatId, Ty>,
130 pub(super) type_mismatches: ArenaMap<ExprId, TypeMismatch>,
123} 131}
124 132
125impl InferenceResult { 133impl InferenceResult {
@@ -141,6 +149,9 @@ impl InferenceResult {
141 pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> { 149 pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> {
142 self.assoc_resolutions.get(&id.into()).copied() 150 self.assoc_resolutions.get(&id.into()).copied()
143 } 151 }
152 pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> {
153 self.type_mismatches.get(expr)
154 }
144 pub(crate) fn add_diagnostics( 155 pub(crate) fn add_diagnostics(
145 &self, 156 &self,
146 db: &impl HirDatabase, 157 db: &impl HirDatabase,
@@ -1345,9 +1356,15 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
1345 }; 1356 };
1346 // use a new type variable if we got Ty::Unknown here 1357 // use a new type variable if we got Ty::Unknown here
1347 let ty = self.insert_type_vars_shallow(ty); 1358 let ty = self.insert_type_vars_shallow(ty);
1348 self.unify(&ty, &expected.ty); 1359 let could_unify = self.unify(&ty, &expected.ty);
1349 let ty = self.resolve_ty_as_possible(&mut vec![], ty); 1360 let ty = self.resolve_ty_as_possible(&mut vec![], ty);
1350 self.write_expr_ty(tgt_expr, ty.clone()); 1361 self.write_expr_ty(tgt_expr, ty.clone());
1362 if !could_unify {
1363 self.result.type_mismatches.insert(
1364 tgt_expr,
1365 TypeMismatch { expected: expected.ty.clone(), actual: ty.clone() },
1366 );
1367 }
1351 ty 1368 ty
1352 } 1369 }
1353 1370
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index c2b959cb3..1a4882824 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -75,6 +75,19 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
75 severity: Severity::Error, 75 severity: Severity::Error,
76 fix: Some(fix), 76 fix: Some(fix),
77 }) 77 })
78 })
79 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
80 let node = d.ast(db);
81 let mut builder = TextEditBuilder::default();
82 let replacement = format!("Ok({})", node.syntax());
83 builder.replace(node.syntax().text_range(), replacement);
84 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, builder.finish());
85 res.borrow_mut().push(Diagnostic {
86 range: d.highlight_range(),
87 message: d.message(),
88 severity: Severity::Error,
89 fix: Some(fix),
90 })
78 }); 91 });
79 if let Some(m) = source_binder::module_from_file_id(db, file_id) { 92 if let Some(m) = source_binder::module_from_file_id(db, file_id) {
80 m.diagnostics(db, &mut sink); 93 m.diagnostics(db, &mut sink);
@@ -171,10 +184,11 @@ fn check_struct_shorthand_initialization(
171#[cfg(test)] 184#[cfg(test)]
172mod tests { 185mod tests {
173 use insta::assert_debug_snapshot_matches; 186 use insta::assert_debug_snapshot_matches;
187 use join_to_string::join;
174 use ra_syntax::SourceFile; 188 use ra_syntax::SourceFile;
175 use test_utils::assert_eq_text; 189 use test_utils::assert_eq_text;
176 190
177 use crate::mock_analysis::single_file; 191 use crate::mock_analysis::{analysis_and_position, single_file};
178 192
179 use super::*; 193 use super::*;
180 194
@@ -203,6 +217,48 @@ mod tests {
203 assert_eq_text!(after, &actual); 217 assert_eq_text!(after, &actual);
204 } 218 }
205 219
220 /// Takes a multi-file input fixture with annotated cursor positions,
221 /// and checks that:
222 /// * a diagnostic is produced
223 /// * this diagnostic touches the input cursor position
224 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
225 fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) {
226 let (analysis, file_position) = analysis_and_position(fixture);
227 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
228 let mut fix = diagnostic.fix.unwrap();
229 let edit = fix.source_file_edits.pop().unwrap().edit;
230 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
231 let actual = edit.apply(&target_file_contents);
232
233 // Strip indent and empty lines from `after`, to match the behaviour of
234 // `parse_fixture` called from `analysis_and_position`.
235 let margin = fixture
236 .lines()
237 .filter(|it| it.trim_start().starts_with("//-"))
238 .map(|it| it.len() - it.trim_start().len())
239 .next()
240 .expect("empty fixture");
241 let after = join(after.lines().filter_map(|line| {
242 if line.len() > margin {
243 Some(&line[margin..])
244 } else {
245 None
246 }
247 }))
248 .separator("\n")
249 .suffix("\n")
250 .to_string();
251
252 assert_eq_text!(&after, &actual);
253 assert!(
254 diagnostic.range.start() <= file_position.offset
255 && diagnostic.range.end() >= file_position.offset,
256 "diagnostic range {} does not touch cursor position {}",
257 diagnostic.range,
258 file_position.offset
259 );
260 }
261
206 fn check_apply_diagnostic_fix(before: &str, after: &str) { 262 fn check_apply_diagnostic_fix(before: &str, after: &str) {
207 let (analysis, file_id) = single_file(before); 263 let (analysis, file_id) = single_file(before);
208 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 264 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
@@ -212,6 +268,14 @@ mod tests {
212 assert_eq_text!(after, &actual); 268 assert_eq_text!(after, &actual);
213 } 269 }
214 270
271 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
272 /// apply to the file containing the cursor.
273 fn check_no_diagnostic_for_target_file(fixture: &str) {
274 let (analysis, file_position) = analysis_and_position(fixture);
275 let diagnostics = analysis.diagnostics(file_position.file_id).unwrap();
276 assert_eq!(diagnostics.len(), 0);
277 }
278
215 fn check_no_diagnostic(content: &str) { 279 fn check_no_diagnostic(content: &str) {
216 let (analysis, file_id) = single_file(content); 280 let (analysis, file_id) = single_file(content);
217 let diagnostics = analysis.diagnostics(file_id).unwrap(); 281 let diagnostics = analysis.diagnostics(file_id).unwrap();
@@ -219,6 +283,155 @@ mod tests {
219 } 283 }
220 284
221 #[test] 285 #[test]
286 fn test_wrap_return_type() {
287 let before = r#"
288 //- /main.rs
289 use std::{string::String, result::Result::{self, Ok, Err}};
290
291 fn div(x: i32, y: i32) -> Result<i32, String> {
292 if y == 0 {
293 return Err("div by zero".into());
294 }
295 x / y<|>
296 }
297
298 //- /std/lib.rs
299 pub mod string {
300 pub struct String { }
301 }
302 pub mod result {
303 pub enum Result<T, E> { Ok(T), Err(E) }
304 }
305 "#;
306 let after = r#"
307 use std::{string::String, result::Result::{self, Ok, Err}};
308
309 fn div(x: i32, y: i32) -> Result<i32, String> {
310 if y == 0 {
311 return Err("div by zero".into());
312 }
313 Ok(x / y)
314 }
315 "#;
316 check_apply_diagnostic_fix_from_position(before, after);
317 }
318
319 #[test]
320 fn test_wrap_return_type_handles_generic_functions() {
321 let before = r#"
322 //- /main.rs
323 use std::result::Result::{self, Ok, Err};
324
325 fn div<T>(x: T) -> Result<T, i32> {
326 if x == 0 {
327 return Err(7);
328 }
329 <|>x
330 }
331
332 //- /std/lib.rs
333 pub mod result {
334 pub enum Result<T, E> { Ok(T), Err(E) }
335 }
336 "#;
337 let after = r#"
338 use std::result::Result::{self, Ok, Err};
339
340 fn div<T>(x: T) -> Result<T, i32> {
341 if x == 0 {
342 return Err(7);
343 }
344 Ok(x)
345 }
346 "#;
347 check_apply_diagnostic_fix_from_position(before, after);
348 }
349
350 #[test]
351 fn test_wrap_return_type_handles_type_aliases() {
352 let before = r#"
353 //- /main.rs
354 use std::{string::String, result::Result::{self, Ok, Err}};
355
356 type MyResult<T> = Result<T, String>;
357
358 fn div(x: i32, y: i32) -> MyResult<i32> {
359 if y == 0 {
360 return Err("div by zero".into());
361 }
362 x <|>/ y
363 }
364
365 //- /std/lib.rs
366 pub mod string {
367 pub struct String { }
368 }
369 pub mod result {
370 pub enum Result<T, E> { Ok(T), Err(E) }
371 }
372 "#;
373 let after = r#"
374 use std::{string::String, result::Result::{self, Ok, Err}};
375
376 type MyResult<T> = Result<T, String>;
377 fn div(x: i32, y: i32) -> MyResult<i32> {
378 if y == 0 {
379 return Err("div by zero".into());
380 }
381 Ok(x / y)
382 }
383 "#;
384 check_apply_diagnostic_fix_from_position(before, after);
385 }
386
387 #[test]
388 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
389 let content = r#"
390 //- /main.rs
391 use std::{string::String, result::Result::{self, Ok, Err}};
392
393 fn foo() -> Result<String, i32> {
394 0<|>
395 }
396
397 //- /std/lib.rs
398 pub mod string {
399 pub struct String { }
400 }
401 pub mod result {
402 pub enum Result<T, E> { Ok(T), Err(E) }
403 }
404 "#;
405 check_no_diagnostic_for_target_file(content);
406 }
407
408 #[test]
409 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
410 let content = r#"
411 //- /main.rs
412 use std::{string::String, result::Result::{self, Ok, Err}};
413
414 enum SomeOtherEnum {
415 Ok(i32),
416 Err(String),
417 }
418
419 fn foo() -> SomeOtherEnum {
420 0<|>
421 }
422
423 //- /std/lib.rs
424 pub mod string {
425 pub struct String { }
426 }
427 pub mod result {
428 pub enum Result<T, E> { Ok(T), Err(E) }
429 }
430 "#;
431 check_no_diagnostic_for_target_file(content);
432 }
433
434 #[test]
222 fn test_fill_struct_fields_empty() { 435 fn test_fill_struct_fields_empty() {
223 let before = r" 436 let before = r"
224 struct TestStruct { 437 struct TestStruct {
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs
index d24660ac3..992034ef0 100644
--- a/crates/ra_syntax/src/ptr.rs
+++ b/crates/ra_syntax/src/ptr.rs
@@ -31,6 +31,13 @@ impl SyntaxNodePtr {
31 pub fn kind(self) -> SyntaxKind { 31 pub fn kind(self) -> SyntaxKind {
32 self.kind 32 self.kind
33 } 33 }
34
35 pub fn cast<N: AstNode>(self) -> Option<AstPtr<N>> {
36 if !N::can_cast(self.kind()) {
37 return None;
38 }
39 Some(AstPtr { raw: self, _ty: PhantomData })
40 }
34} 41}
35 42
36/// Like `SyntaxNodePtr`, but remembers the type of node 43/// Like `SyntaxNodePtr`, but remembers the type of node