diff options
-rw-r--r-- | crates/ra_hir/src/diagnostics.rs | 31 | ||||
-rw-r--r-- | crates/ra_hir/src/expr/validation.rs | 56 | ||||
-rw-r--r-- | crates/ra_hir/src/ty.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 50 |
4 files changed, 136 insertions, 3 deletions
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs index 301109cb8..718345d75 100644 --- a/crates/ra_hir/src/diagnostics.rs +++ b/crates/ra_hir/src/diagnostics.rs | |||
@@ -143,3 +143,34 @@ impl AstDiagnostic for MissingFields { | |||
143 | ast::RecordFieldList::cast(node).unwrap() | 143 | ast::RecordFieldList::cast(node).unwrap() |
144 | } | 144 | } |
145 | } | 145 | } |
146 | |||
147 | #[derive(Debug)] | ||
148 | pub struct MissingOkInTailExpr { | ||
149 | pub file: HirFileId, | ||
150 | pub expr: AstPtr<ast::Expr>, | ||
151 | } | ||
152 | |||
153 | impl Diagnostic for MissingOkInTailExpr { | ||
154 | fn message(&self) -> String { | ||
155 | "wrap return expression in Ok".to_string() | ||
156 | } | ||
157 | fn file(&self) -> HirFileId { | ||
158 | self.file | ||
159 | } | ||
160 | fn syntax_node_ptr(&self) -> SyntaxNodePtr { | ||
161 | self.expr.into() | ||
162 | } | ||
163 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
164 | self | ||
165 | } | ||
166 | } | ||
167 | |||
168 | impl AstDiagnostic for MissingOkInTailExpr { | ||
169 | type AST = ast::Expr; | ||
170 | |||
171 | fn ast(&self, db: &impl HirDatabase) -> Self::AST { | ||
172 | let root = db.parse_or_expand(self.file()).unwrap(); | ||
173 | let node = self.syntax_node_ptr().to_node(&root); | ||
174 | ast::Expr::cast(node).unwrap() | ||
175 | } | ||
176 | } | ||
diff --git a/crates/ra_hir/src/expr/validation.rs b/crates/ra_hir/src/expr/validation.rs index 62f7d41f5..f5e641557 100644 --- a/crates/ra_hir/src/expr/validation.rs +++ b/crates/ra_hir/src/expr/validation.rs | |||
@@ -6,11 +6,12 @@ use ra_syntax::ast::{AstNode, RecordLit}; | |||
6 | use super::{Expr, ExprId, RecordLitField}; | 6 | use super::{Expr, ExprId, RecordLitField}; |
7 | use crate::{ | 7 | use 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 | ty::{InferenceResult, Ty, TypeCtor}, |
12 | Function, HasSource, HirDatabase, Name, Path, | 12 | Function, HasSource, HirDatabase, Name, Path, |
13 | }; | 13 | }; |
14 | use ra_syntax::ast; | ||
14 | 15 | ||
15 | pub(crate) struct ExprValidator<'a, 'b: 'a> { | 16 | pub(crate) struct ExprValidator<'a, 'b: 'a> { |
16 | func: Function, | 17 | func: Function, |
@@ -29,11 +30,23 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
29 | 30 | ||
30 | pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) { | 31 | pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) { |
31 | let body = self.func.body(db); | 32 | let body = self.func.body(db); |
33 | |||
34 | // The final expr in the function body is the whole body, | ||
35 | // so the expression being returned is the penultimate expr. | ||
36 | let mut penultimate_expr = None; | ||
37 | let mut final_expr = None; | ||
38 | |||
32 | for e in body.exprs() { | 39 | for e in body.exprs() { |
40 | penultimate_expr = final_expr; | ||
41 | final_expr = Some(e); | ||
42 | |||
33 | if let (id, Expr::RecordLit { path, fields, spread }) = e { | 43 | if let (id, Expr::RecordLit { path, fields, spread }) = e { |
34 | self.validate_record_literal(id, path, fields, *spread, db); | 44 | self.validate_record_literal(id, path, fields, *spread, db); |
35 | } | 45 | } |
36 | } | 46 | } |
47 | if let Some(e) = penultimate_expr { | ||
48 | self.validate_results_in_tail_expr(e.0, db); | ||
49 | } | ||
37 | } | 50 | } |
38 | 51 | ||
39 | fn validate_record_literal( | 52 | fn validate_record_literal( |
@@ -87,4 +100,43 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
87 | }) | 100 | }) |
88 | } | 101 | } |
89 | } | 102 | } |
103 | |||
104 | fn validate_results_in_tail_expr(&mut self, id: ExprId, db: &impl HirDatabase) { | ||
105 | let expr_ty = &self.infer[id]; | ||
106 | let func_ty = self.func.ty(db); | ||
107 | let func_sig = func_ty.callable_sig(db).unwrap(); | ||
108 | let ret = func_sig.ret(); | ||
109 | let ret = match ret { | ||
110 | Ty::Apply(t) => t, | ||
111 | _ => return, | ||
112 | }; | ||
113 | let ret_enum = match ret.ctor { | ||
114 | TypeCtor::Adt(AdtDef::Enum(e)) => e, | ||
115 | _ => return, | ||
116 | }; | ||
117 | let enum_name = ret_enum.name(db); | ||
118 | if enum_name.is_none() || enum_name.unwrap().to_string() != "Result" { | ||
119 | return; | ||
120 | } | ||
121 | let params = &ret.parameters; | ||
122 | if params.len() == 2 && ¶ms[0] == expr_ty { | ||
123 | let source_map = self.func.body_source_map(db); | ||
124 | let file_id = self.func.source(db).file_id; | ||
125 | let parse = db.parse(file_id.original_file(db)); | ||
126 | let source_file = parse.tree(); | ||
127 | let expr_syntax = source_map.expr_syntax(id); | ||
128 | if expr_syntax.is_none() { | ||
129 | return; | ||
130 | } | ||
131 | let expr_syntax = expr_syntax.unwrap(); | ||
132 | let node = expr_syntax.to_node(source_file.syntax()); | ||
133 | let ast = ast::Expr::cast(node); | ||
134 | if ast.is_none() { | ||
135 | return; | ||
136 | } | ||
137 | let ast = ast.unwrap(); | ||
138 | |||
139 | self.sink.push(MissingOkInTailExpr { file: file_id, expr: AstPtr::new(&ast) }); | ||
140 | } | ||
141 | } | ||
90 | } | 142 | } |
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index b54c80318..0efd94cef 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs | |||
@@ -516,7 +516,7 @@ impl Ty { | |||
516 | } | 516 | } |
517 | } | 517 | } |
518 | 518 | ||
519 | fn callable_sig(&self, db: &impl HirDatabase) -> Option<FnSig> { | 519 | pub fn callable_sig(&self, db: &impl HirDatabase) -> Option<FnSig> { |
520 | match self { | 520 | match self { |
521 | Ty::Apply(a_ty) => match a_ty.ctor { | 521 | Ty::Apply(a_ty) => match a_ty.ctor { |
522 | TypeCtor::FnPtr { .. } => Some(FnSig::from_fn_ptr_substs(&a_ty.parameters)), | 522 | TypeCtor::FnPtr { .. } => Some(FnSig::from_fn_ptr_substs(&a_ty.parameters)), |
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index c2b959cb3..be5197767 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().text()); | ||
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); |
@@ -219,6 +232,43 @@ mod tests { | |||
219 | } | 232 | } |
220 | 233 | ||
221 | #[test] | 234 | #[test] |
235 | fn test_wrap_return_type() { | ||
236 | let before = r#" | ||
237 | enum Result<T, E> { Ok(T), Err(E) } | ||
238 | struct String { } | ||
239 | |||
240 | fn div(x: i32, y: i32) -> Result<i32, String> { | ||
241 | if y == 0 { | ||
242 | return Err("div by zero".into()); | ||
243 | } | ||
244 | x / y | ||
245 | } | ||
246 | "#; | ||
247 | let after = r#" | ||
248 | enum Result<T, E> { Ok(T), Err(E) } | ||
249 | struct String { } | ||
250 | |||
251 | fn div(x: i32, y: i32) -> Result<i32, String> { | ||
252 | if y == 0 { | ||
253 | return Err("div by zero".into()); | ||
254 | } | ||
255 | Ok(x / y) | ||
256 | } | ||
257 | "#; | ||
258 | check_apply_diagnostic_fix(before, after); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn test_wrap_return_type_not_applicable() { | ||
263 | let content = r#" | ||
264 | fn foo() -> Result<String, i32> { | ||
265 | 0 | ||
266 | } | ||
267 | "#; | ||
268 | check_no_diagnostic(content); | ||
269 | } | ||
270 | |||
271 | #[test] | ||
222 | fn test_fill_struct_fields_empty() { | 272 | fn test_fill_struct_fields_empty() { |
223 | let before = r" | 273 | let before = r" |
224 | struct TestStruct { | 274 | struct TestStruct { |