From 173e1ba966d92570cbf64d65e2f7353bcab1c661 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 23 Nov 2020 12:26:35 -0600 Subject: [PATCH 001/123] Fix default initialized fields in suggestion The default value for a field type does not necessarily match the default value for that field in the struct Default. --- clippy_lints/src/default.rs | 6 ------ tests/ui/field_reassign_with_default.stderr | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index f69f6f1412af9..adcc17266d22e 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -165,12 +165,6 @@ impl LateLintPass<'_> for Default { let stmt = &block.stmts[stmt_idx]; if let StmtKind::Local(preceding_local) = &stmt.kind { - // filter out fields like `= Default::default()`, because the FRU already covers them - let assigned_fields = assigned_fields - .into_iter() - .filter(|(_, rhs)| !is_expr_default(rhs, cx)) - .collect::)>>(); - // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion. let ext_with_default = !fields_of_type(binding_type) .iter() diff --git a/tests/ui/field_reassign_with_default.stderr b/tests/ui/field_reassign_with_default.stderr index c788ebae5526c..9a2bc778c3ff7 100644 --- a/tests/ui/field_reassign_with_default.stderr +++ b/tests/ui/field_reassign_with_default.stderr @@ -53,7 +53,7 @@ error: field assignment outside of initializer for an instance created with Defa LL | a.i = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: consider initializing the variable with `A::default()` and removing relevant reassignments +note: consider initializing the variable with `A { i: Default::default(), ..Default::default() }` and removing relevant reassignments --> $DIR/field_reassign_with_default.rs:90:5 | LL | let mut a: A = Default::default(); @@ -65,7 +65,7 @@ error: field assignment outside of initializer for an instance created with Defa LL | a.i = Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: consider initializing the variable with `A { j: 45, ..Default::default() }` and removing relevant reassignments +note: consider initializing the variable with `A { i: Default::default(), j: 45 }` and removing relevant reassignments --> $DIR/field_reassign_with_default.rs:94:5 | LL | let mut a: A = Default::default(); From f74e2001b94fa67ecef00bdb7de1e120f9cf0ec8 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 23 Nov 2020 13:20:53 -0600 Subject: [PATCH 002/123] Remove redundant shadow check There is already an assertion that consecutive lines assign to a struct field. --- clippy_lints/src/default.rs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index adcc17266d22e..c3fe77f625017 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -122,15 +122,8 @@ impl LateLintPass<'_> for Default { let mut assigned_fields = Vec::new(); let mut cancel_lint = false; for consecutive_statement in &block.stmts[stmt_idx + 1..] { - // interrupt if the statement is a let binding (`Local`) that shadows the original - // binding - if stmt_shadows_binding(consecutive_statement, binding_name) { - break; - } // find out if and which field was set by this `consecutive_statement` - else if let Some((field_ident, assign_rhs)) = - field_reassigned_by_stmt(consecutive_statement, binding_name) - { + if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) { // interrupt and cancel lint if assign_rhs references the original binding if contains_name(binding_name, assign_rhs) { cancel_lint = true; @@ -152,7 +145,7 @@ impl LateLintPass<'_> for Default { first_assign = Some(consecutive_statement); } } - // interrupt also if no field was assigned, since we only want to look at consecutive statements + // interrupt if no field was assigned, since we only want to look at consecutive statements else { break; } @@ -256,15 +249,6 @@ fn enumerate_bindings_using_default<'tcx>( .collect() } -fn stmt_shadows_binding(this: &Stmt<'_>, shadowed: Symbol) -> bool { - if let StmtKind::Local(local) = &this.kind { - if let PatKind::Binding(_, _, ident, _) = local.pat.kind { - return ident.name == shadowed; - } - } - false -} - /// Returns the reassigned field and the assigning expression (right-hand side of assign). fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { if_chain! { From fa75f63690c9a7e0c2b491e3e1d13a3ed8eb5b0c Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 23 Nov 2020 09:23:24 -0600 Subject: [PATCH 003/123] Fix field_reassign_with_default for private fields --- clippy_lints/src/default.rs | 162 ++++++++++-------------- tests/ui/field_reassign_with_default.rs | 12 ++ 2 files changed, 81 insertions(+), 93 deletions(-) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index c3fe77f625017..b0d7c7b3baab1 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Adt, Ty}; +use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::Span; @@ -103,18 +103,41 @@ impl LateLintPass<'_> for Default { } fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { - // find all binding statements like `let mut _ = T::default()` where `T::default()` is the - // `default` method of the `Default` trait, and store statement index in current block being - // checked and the name of the bound variable - let binding_statements_using_default = enumerate_bindings_using_default(cx, block); - // start from the `let mut _ = _::default();` and look at all the following // statements, see if they re-assign the fields of the binding - for (stmt_idx, binding_name, binding_type, span) in binding_statements_using_default { - // the last statement of a block cannot trigger the lint - if stmt_idx == block.stmts.len() - 1 { - break; - } + let stmts_head = match block.stmts { + // Skip the last statement since there cannot possibly be any following statements that re-assign fields. + [head @ .., _] if !head.is_empty() => head, + _ => return, + }; + for (stmt_idx, stmt) in stmts_head.iter().enumerate() { + // find all binding statements like `let mut _ = T::default()` where `T::default()` is the + // `default` method of the `Default` trait, and store statement index in current block being + // checked and the name of the bound variable + let (local, variant, binding_name, binding_type, span) = if_chain! { + // only take `let ...` statements + if let StmtKind::Local(local) = stmt.kind; + if let Some(expr) = local.init; + // only take bindings to identifiers + if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind; + // only when assigning `... = Default::default()` + if is_expr_default(expr, cx); + let binding_type = cx.typeck_results().node_type(binding_id); + if let Some(adt) = binding_type.ty_adt_def(); + if adt.is_struct(); + let variant = adt.non_enum_variant(); + if adt.did.is_local() || !variant.is_field_list_non_exhaustive(); + let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id(); + if variant + .fields + .iter() + .all(|field| field.vis.is_accessible_from(module_did, cx.tcx)); + then { + (local, variant, ident.name, binding_type, expr.span) + } else { + continue; + } + }; // find all "later statement"'s where the fields of the binding set as // Default::default() get reassigned, unless the reassignment refers to the original binding @@ -154,49 +177,45 @@ impl LateLintPass<'_> for Default { // if there are incorrectly assigned fields, do a span_lint_and_note to suggest // construction using `Ty { fields, ..Default::default() }` if !assigned_fields.is_empty() && !cancel_lint { - // take the original assignment as span - let stmt = &block.stmts[stmt_idx]; - - if let StmtKind::Local(preceding_local) = &stmt.kind { - // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion. - let ext_with_default = !fields_of_type(binding_type) - .iter() - .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name)); + // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion. + let ext_with_default = !variant + .fields + .iter() + .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name)); - let field_list = assigned_fields - .into_iter() - .map(|(field, rhs)| { - // extract and store the assigned value for help message - let value_snippet = snippet(cx, rhs.span, ".."); - format!("{}: {}", field, value_snippet) - }) - .collect::>() - .join(", "); + let field_list = assigned_fields + .into_iter() + .map(|(field, rhs)| { + // extract and store the assigned value for help message + let value_snippet = snippet(cx, rhs.span, ".."); + format!("{}: {}", field, value_snippet) + }) + .collect::>() + .join(", "); - let sugg = if ext_with_default { - if field_list.is_empty() { - format!("{}::default()", binding_type) - } else { - format!("{} {{ {}, ..Default::default() }}", binding_type, field_list) - } + let sugg = if ext_with_default { + if field_list.is_empty() { + format!("{}::default()", binding_type) } else { - format!("{} {{ {} }}", binding_type, field_list) - }; + format!("{} {{ {}, ..Default::default() }}", binding_type, field_list) + } + } else { + format!("{} {{ {} }}", binding_type, field_list) + }; - // span lint once per statement that binds default - span_lint_and_note( - cx, - FIELD_REASSIGN_WITH_DEFAULT, - first_assign.unwrap().span, - "field assignment outside of initializer for an instance created with Default::default()", - Some(preceding_local.span), - &format!( - "consider initializing the variable with `{}` and removing relevant reassignments", - sugg - ), - ); - self.reassigned_linted.insert(span); - } + // span lint once per statement that binds default + span_lint_and_note( + cx, + FIELD_REASSIGN_WITH_DEFAULT, + first_assign.unwrap().span, + "field assignment outside of initializer for an instance created with Default::default()", + Some(local.span), + &format!( + "consider initializing the variable with `{}` and removing relevant reassignments", + sugg + ), + ); + self.reassigned_linted.insert(span); } } } @@ -217,38 +236,6 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool } } -/// Returns the block indices, identifiers and types of bindings set as `Default::default()`, except -/// for when the pattern type is a tuple. -fn enumerate_bindings_using_default<'tcx>( - cx: &LateContext<'tcx>, - block: &Block<'tcx>, -) -> Vec<(usize, Symbol, Ty<'tcx>, Span)> { - block - .stmts - .iter() - .enumerate() - .filter_map(|(idx, stmt)| { - if_chain! { - // only take `let ...` statements - if let StmtKind::Local(ref local) = stmt.kind; - // only take bindings to identifiers - if let PatKind::Binding(_, _, ident, _) = local.pat.kind; - // that are not tuples - let ty = cx.typeck_results().pat_ty(local.pat); - if !matches!(ty.kind(), ty::Tuple(_)); - // only when assigning `... = Default::default()` - if let Some(ref expr) = local.init; - if is_expr_default(expr, cx); - then { - Some((idx, ident.name, ty, expr.span)) - } else { - None - } - } - }) - .collect() -} - /// Returns the reassigned field and the assigning expression (right-hand side of assign). fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { if_chain! { @@ -268,14 +255,3 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op } } } - -/// Returns the vec of fields for a struct and an empty vec for non-struct ADTs. -fn fields_of_type(ty: Ty<'_>) -> Vec { - if let Adt(adt, _) = ty.kind() { - if adt.is_struct() { - let variant = &adt.non_enum_variant(); - return variant.fields.iter().map(|f| f.ident).collect(); - } - } - vec![] -} diff --git a/tests/ui/field_reassign_with_default.rs b/tests/ui/field_reassign_with_default.rs index 79a30c22f9536..3e0921022b417 100644 --- a/tests/ui/field_reassign_with_default.rs +++ b/tests/ui/field_reassign_with_default.rs @@ -107,4 +107,16 @@ fn main() { x.i = side_effect.next(); x.j = 2; x.i = side_effect.next(); + + // don't lint - some private fields + let mut x = m::F::default(); + x.a = 1; +} + +mod m { + #[derive(Default)] + pub struct F { + pub a: u64, + b: u64, + } } From bfbc0835870f8bf6cde79a01dfe7de351dde14aa Mon Sep 17 00:00:00 2001 From: Pierre-Andre Gagnon Date: Tue, 2 Feb 2021 18:39:23 -0500 Subject: [PATCH 004/123] Fix for issue 6640 --- clippy_lints/src/unnecessary_wraps.rs | 92 ++++++++++++++-------- tests/ui/unnecessary_wraps.rs | 28 +++++++ tests/ui/unnecessary_wraps.stderr | 109 ++------------------------ 3 files changed, 93 insertions(+), 136 deletions(-) diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 8ac5dd696b762..eec76ce8b038d 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -1,6 +1,6 @@ use crate::utils::{ - contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then, - visitors::find_all_ret_expressions, + contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_sugg, + span_lint_and_then, visitors::find_all_ret_expressions, }; use if_chain::if_chain; use rustc_errors::Applicability; @@ -64,6 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { span: Span, hir_id: HirId, ) { + // Abort if public function/method or closure. match fn_kind { FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => { if visibility.node.is_pub() { @@ -74,6 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { _ => (), } + // Abort if the method is implementing a trait or of it a trait method. if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { if matches!( item.kind, @@ -83,22 +85,43 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { } } - let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::option_type) { + // Check if return type is Option or Result. If neither, abort. + let return_ty = return_ty(cx, hir_id); + let (return_type_label, path) = if is_type_diagnostic_item(cx, return_ty, sym::option_type) { ("Option", &paths::OPTION_SOME) - } else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) { + } else if is_type_diagnostic_item(cx, return_ty, sym::result_type) { ("Result", &paths::RESULT_OK) } else { return; }; + // Take the first inner type of the Option or Result. If can't, abort. + let inner_ty = if_chain! { + // Skip Option or Result and take the first outermost inner type. + if let Some(inner_ty) = return_ty.walk().nth(1); + if let GenericArgKind::Type(inner_ty) = inner_ty.unpack(); + then { + inner_ty + } else { + return; + } + }; + + // Check if all return expression respect the following condition and collect them. let mut suggs = Vec::new(); let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { if_chain! { + // Abort if in macro. if !in_macro(ret_expr.span); + // Check if a function call. if let ExprKind::Call(ref func, ref args) = ret_expr.kind; + // Get the Path of the function call. if let ExprKind::Path(ref qpath) = func.kind; + // Check if OPTION_SOME or RESULT_OK, depending on return type. if match_qpath(qpath, path); + // Make sure the function call has only one argument. if args.len() == 1; + // Make sure the function argument does not contain a return expression. if !contains_return(&args[0]); then { suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string())); @@ -110,39 +133,42 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { }); if can_sugg && !suggs.is_empty() { - span_lint_and_then( - cx, - UNNECESSARY_WRAPS, - span, - format!( - "this function's return value is unnecessarily wrapped by `{}`", - return_type - ) - .as_str(), - |diag| { - let inner_ty = return_ty(cx, hir_id) - .walk() - .skip(1) // skip `std::option::Option` or `std::result::Result` - .take(1) // take the first outermost inner type - .filter_map(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()), - _ => None, - }); - inner_ty.for_each(|inner_ty| { + // Issue 6640: If the inner type is Unit, emit lint similar to clippy::unused_unit. + if inner_ty.is_unit() { + span_lint_and_sugg( + cx, + UNNECESSARY_WRAPS, + fn_decl.output.span(), + "unneeded wrapped unit return type", + format!("remove the `-> {}<()>`", return_type_label).as_str(), + String::new(), + Applicability::MaybeIncorrect, + ); + } else { + span_lint_and_then( + cx, + UNNECESSARY_WRAPS, + span, + format!( + "this function's return value is unnecessarily wrapped by `{}`", + return_type_label + ) + .as_str(), + |diag| { diag.span_suggestion( fn_decl.output.span(), - format!("remove `{}` from the return type...", return_type).as_str(), - inner_ty, + format!("remove `{}` from the return type...", return_type_label).as_str(), + inner_ty.to_string(), Applicability::MaybeIncorrect, ); - }); - diag.multipart_suggestion( - "...and change the returning expressions", - suggs, - Applicability::MaybeIncorrect, - ); - }, - ); + diag.multipart_suggestion( + "...and change the returning expressions", + suggs, + Applicability::MaybeIncorrect, + ); + }, + ); + } } } } diff --git a/tests/ui/unnecessary_wraps.rs b/tests/ui/unnecessary_wraps.rs index a4570098d7167..2d6aedc97ef08 100644 --- a/tests/ui/unnecessary_wraps.rs +++ b/tests/ui/unnecessary_wraps.rs @@ -116,8 +116,36 @@ fn issue_6384(s: &str) -> Option<&str> { }) } +// should be linted +fn issue_6640_1(a: bool, b: bool) -> Option<()> { + if a && b { + return Some(()); + } + if a { + Some(()); + Some(()) + } else { + return Some(()); + } +} + +// should be linted +fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { + if a && b { + return Ok(()); + } + if a { + Ok(()); + Ok(()) + } else { + return Ok(()); + } +} + fn main() { // method calls are not linted func1(true, true); func2(true, true); + issue_6640_1(true, true); + issue_6640_2(true, true); } diff --git a/tests/ui/unnecessary_wraps.stderr b/tests/ui/unnecessary_wraps.stderr index 410f054b8efca..3ca5a8d1702b8 100644 --- a/tests/ui/unnecessary_wraps.stderr +++ b/tests/ui/unnecessary_wraps.stderr @@ -1,106 +1,9 @@ -error: this function's return value is unnecessarily wrapped by `Option` - --> $DIR/unnecessary_wraps.rs:8:1 - | -LL | / fn func1(a: bool, b: bool) -> Option { -LL | | if a && b { -LL | | return Some(42); -LL | | } -... | -LL | | } -LL | | } - | |_^ - | - = note: `-D clippy::unnecessary-wraps` implied by `-D warnings` -help: remove `Option` from the return type... - | -LL | fn func1(a: bool, b: bool) -> i32 { - | ^^^ -help: ...and change the returning expressions - | -LL | return 42; -LL | } -LL | if a { -LL | Some(-1); -LL | 2 -LL | } else { - ... - -error: this function's return value is unnecessarily wrapped by `Option` - --> $DIR/unnecessary_wraps.rs:21:1 - | -LL | / fn func2(a: bool, b: bool) -> Option { -LL | | if a && b { -LL | | return Some(10); -LL | | } -... | -LL | | } -LL | | } - | |_^ - | -help: remove `Option` from the return type... - | -LL | fn func2(a: bool, b: bool) -> i32 { - | ^^^ -help: ...and change the returning expressions - | -LL | return 10; -LL | } -LL | if a { -LL | 20 -LL | } else { -LL | 30 - | - -error: this function's return value is unnecessarily wrapped by `Option` - --> $DIR/unnecessary_wraps.rs:51:1 - | -LL | / fn func5() -> Option { -LL | | Some(1) -LL | | } - | |_^ - | -help: remove `Option` from the return type... - | -LL | fn func5() -> i32 { - | ^^^ -help: ...and change the returning expressions - | -LL | 1 - | - -error: this function's return value is unnecessarily wrapped by `Result` - --> $DIR/unnecessary_wraps.rs:61:1 - | -LL | / fn func7() -> Result { -LL | | Ok(1) -LL | | } - | |_^ - | -help: remove `Result` from the return type... - | -LL | fn func7() -> i32 { - | ^^^ -help: ...and change the returning expressions - | -LL | 1 - | - -error: this function's return value is unnecessarily wrapped by `Option` - --> $DIR/unnecessary_wraps.rs:93:5 - | -LL | / fn func12() -> Option { -LL | | Some(1) -LL | | } - | |_____^ - | -help: remove `Option` from the return type... - | -LL | fn func12() -> i32 { - | ^^^ -help: ...and change the returning expressions - | -LL | 1 +error[E0282]: type annotations needed + --> $DIR/unnecessary_wraps.rs:138:9 | +LL | Ok(()); + | ^^ cannot infer type for type parameter `E` declared on the enum `Result` -error: aborting due to 5 previous errors +error: aborting due to previous error +For more information about this error, try `rustc --explain E0282`. From e0e51e418906916123b3bd15c175d80d3dc74bf2 Mon Sep 17 00:00:00 2001 From: Pierre-Andre Gagnon Date: Tue, 2 Feb 2021 19:14:13 -0500 Subject: [PATCH 005/123] Fixed test --- clippy_lints/src/unnecessary_wraps.rs | 2 +- tests/ui/unnecessary_wraps.rs | 1 - tests/ui/unnecessary_wraps.stderr | 121 ++++++++++++++++++++++++-- 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index eec76ce8b038d..7da42ba3934f2 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -140,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { UNNECESSARY_WRAPS, fn_decl.output.span(), "unneeded wrapped unit return type", - format!("remove the `-> {}<()>`", return_type_label).as_str(), + format!("remove the `-> {}<[...]>`", return_type_label).as_str(), String::new(), Applicability::MaybeIncorrect, ); diff --git a/tests/ui/unnecessary_wraps.rs b/tests/ui/unnecessary_wraps.rs index 2d6aedc97ef08..5aaa99bbe5a39 100644 --- a/tests/ui/unnecessary_wraps.rs +++ b/tests/ui/unnecessary_wraps.rs @@ -135,7 +135,6 @@ fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { return Ok(()); } if a { - Ok(()); Ok(()) } else { return Ok(()); diff --git a/tests/ui/unnecessary_wraps.stderr b/tests/ui/unnecessary_wraps.stderr index 3ca5a8d1702b8..f28981f9e34a8 100644 --- a/tests/ui/unnecessary_wraps.stderr +++ b/tests/ui/unnecessary_wraps.stderr @@ -1,9 +1,118 @@ -error[E0282]: type annotations needed - --> $DIR/unnecessary_wraps.rs:138:9 +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:8:1 | -LL | Ok(()); - | ^^ cannot infer type for type parameter `E` declared on the enum `Result` +LL | / fn func1(a: bool, b: bool) -> Option { +LL | | if a && b { +LL | | return Some(42); +LL | | } +... | +LL | | } +LL | | } + | |_^ + | + = note: `-D clippy::unnecessary-wraps` implied by `-D warnings` +help: remove `Option` from the return type... + | +LL | fn func1(a: bool, b: bool) -> i32 { + | ^^^ +help: ...and change the returning expressions + | +LL | return 42; +LL | } +LL | if a { +LL | Some(-1); +LL | 2 +LL | } else { + ... + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:21:1 + | +LL | / fn func2(a: bool, b: bool) -> Option { +LL | | if a && b { +LL | | return Some(10); +LL | | } +... | +LL | | } +LL | | } + | |_^ + | +help: remove `Option` from the return type... + | +LL | fn func2(a: bool, b: bool) -> i32 { + | ^^^ +help: ...and change the returning expressions + | +LL | return 10; +LL | } +LL | if a { +LL | 20 +LL | } else { +LL | 30 + | + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:51:1 + | +LL | / fn func5() -> Option { +LL | | Some(1) +LL | | } + | |_^ + | +help: remove `Option` from the return type... + | +LL | fn func5() -> i32 { + | ^^^ +help: ...and change the returning expressions + | +LL | 1 + | + +error: this function's return value is unnecessarily wrapped by `Result` + --> $DIR/unnecessary_wraps.rs:61:1 + | +LL | / fn func7() -> Result { +LL | | Ok(1) +LL | | } + | |_^ + | +help: remove `Result` from the return type... + | +LL | fn func7() -> i32 { + | ^^^ +help: ...and change the returning expressions + | +LL | 1 + | + +error: this function's return value is unnecessarily wrapped by `Option` + --> $DIR/unnecessary_wraps.rs:93:5 + | +LL | / fn func12() -> Option { +LL | | Some(1) +LL | | } + | |_____^ + | +help: remove `Option` from the return type... + | +LL | fn func12() -> i32 { + | ^^^ +help: ...and change the returning expressions + | +LL | 1 + | + +error: unneeded wrapped unit return type + --> $DIR/unnecessary_wraps.rs:120:38 + | +LL | fn issue_6640_1(a: bool, b: bool) -> Option<()> { + | ^^^^^^^^^^ help: remove the `-> Option<[...]>` + +error: unneeded wrapped unit return type + --> $DIR/unnecessary_wraps.rs:133:38 + | +LL | fn issue_6640_2(a: bool, b: bool) -> Result<(), i32> { + | ^^^^^^^^^^^^^^^ help: remove the `-> Result<[...]>` -error: aborting due to previous error +error: aborting due to 7 previous errors -For more information about this error, try `rustc --explain E0282`. From e32e4dedf1781a4696c34f31d69e68c7c0eaf6a9 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Tue, 2 Feb 2021 12:26:20 +0900 Subject: [PATCH 006/123] New lint: default_numeric_fallback --- CHANGELOG.md | 1 + clippy_lints/src/default_numeric_fallback.rs | 388 +++++++++++++++++++ clippy_lints/src/lib.rs | 4 + tests/ui/default_numeric_fallback.rs | 99 +++++ tests/ui/default_numeric_fallback.stderr | 187 +++++++++ 5 files changed, 679 insertions(+) create mode 100644 clippy_lints/src/default_numeric_fallback.rs create mode 100644 tests/ui/default_numeric_fallback.rs create mode 100644 tests/ui/default_numeric_fallback.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index c1032204a22cb..5eed9664088ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1909,6 +1909,7 @@ Released 2018-09-13 [`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call [`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation [`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const +[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs new file mode 100644 index 0000000000000..f049e64d0fbdb --- /dev/null +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -0,0 +1,388 @@ +use rustc_ast::ast::{Label, LitFloatType, LitIntType, LitKind}; +use rustc_hir::{ + self as hir, + intravisit::{walk_expr, walk_stmt, walk_ty, FnKind, NestedVisitorMap, Visitor}, + Body, Expr, ExprKind, FnDecl, FnRetTy, Guard, HirId, Lit, Stmt, StmtKind, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{ + hir::map::Map, + ty::{self, subst::GenericArgKind, FloatTy, IntTy, Ty, TyCtxt}, +}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; +use rustc_typeck::hir_ty_to_ty; + +use if_chain::if_chain; + +use crate::utils::span_lint_and_help; + +declare_clippy_lint! { + /// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type + /// inference. + /// + /// Default numeric fallback means that if numeric types have not yet been bound to concrete + /// types at the end of type inference, then integer type is bound to `i32`, and similarly + /// floating type is bound to `f64`. + /// + /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. + /// + /// **Why is this bad?** For those who are very careful about types, default numeric fallback + /// can be a pitfall that cause unexpected runtime behavior. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let i = 10; + /// let f = 1.23; + /// ``` + /// + /// Use instead: + /// ```rust + /// let i = 10i32; + /// let f = 1.23f64; + /// ``` + pub DEFAULT_NUMERIC_FALLBACK, + restriction, + "usage of unconstrained numeric literals which may cause default numeric fallback." +} + +declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); + +fn enclosing_body_owner_opt(tcx: TyCtxt<'_>, hir_id: HirId) -> Option { + let hir_map = tcx.hir(); + for (parent, _) in hir_map.parent_iter(hir_id) { + if let Some(body) = hir_map.maybe_body_owned_by(parent) { + return Some(hir_map.body_owner(body)); + } + } + None +} + +impl LateLintPass<'_> for DefaultNumericFallback { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: FnKind<'tcx>, + fn_decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + hir_id: HirId, + ) { + let ret_ty_bound = match fn_decl.output { + FnRetTy::DefaultReturn(_) => None, + FnRetTy::Return(ty) => Some(ty), + } + .and_then(|ty| { + let mut infer_ty_finder = InferTyFinder::new(); + infer_ty_finder.visit_ty(ty); + if infer_ty_finder.found { + None + } else if enclosing_body_owner_opt(cx.tcx, hir_id).is_some() { + cx.typeck_results().node_type_opt(ty.hir_id) + } else { + Some(hir_ty_to_ty(cx.tcx, ty)) + } + }); + + let mut visitor = NumericFallbackVisitor::new(ret_ty_bound, cx); + visitor.visit_body(body); + } +} + +struct NumericFallbackVisitor<'a, 'tcx> { + /// Stack manages type bound of exprs. The top element holds current expr type. + ty_bounds: Vec>>, + + /// Ret type bound. + ret_ty_bound: Option>, + + /// Break type bounds. + break_ty_bounds: Vec<(Option