-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add unnecessary lazy evaluation lint #5720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
a3ea65c
Implement new lint
bugadani 923d612
Rename the changelog footnote as well
bugadani 848af39
Add note to `or_fun_call`, list checked methods
bugadani a7cc5d4
Also simplify if the closure body is an index expression
bugadani d7220db
Run cargo dev update_lints
bugadani 75637c1
Catch function calls in argument lists, add tests that tuples don't g…
bugadani a7083ee
Removed the extra lifetime parameter
bugadani 94cf90e
Apply suggested change
bugadani 9c41822
Apply suggested change
bugadani d71b418
Moved to submodule, don't trigger if map_unwrap_or does
bugadani 8a14c11
Cleanup, explain return value
bugadani 3b52d7f
run cargo dev fmt
bugadani b7ee868
Fix dogfooding test errors
bugadani b175642
Fix missed rename
bugadani fc1e07e
Rename lint to use plural form
bugadani File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use crate::utils::{is_type_diagnostic_item, match_qpath, snippet, span_lint_and_sugg}; | ||
use if_chain::if_chain; | ||
use rustc_errors::Applicability; | ||
use rustc_hir as hir; | ||
use rustc_lint::LateContext; | ||
|
||
use super::UNNECESSARY_LAZY_EVALUATIONS; | ||
|
||
// Return true if the expression is an accessor of any of the arguments | ||
fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool { | ||
params.iter().any(|arg| { | ||
if_chain! { | ||
if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind; | ||
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind; | ||
if let [p, ..] = path.segments; | ||
then { | ||
ident.name == p.ident.name | ||
} else { | ||
false | ||
} | ||
} | ||
}) | ||
} | ||
|
||
fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool { | ||
paths.iter().any(|candidate| match_qpath(path, candidate)) | ||
} | ||
|
||
fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool { | ||
match expr.kind { | ||
// Closures returning literals can be unconditionally simplified | ||
hir::ExprKind::Lit(_) => true, | ||
|
||
hir::ExprKind::Index(ref object, ref index) => { | ||
// arguments are not being indexed into | ||
if expr_uses_argument(object, params) { | ||
false | ||
} else { | ||
// arguments are not used as index | ||
!expr_uses_argument(index, params) | ||
} | ||
}, | ||
|
||
// Reading fields can be simplified if the object is not an argument of the closure | ||
hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params), | ||
|
||
// Paths can be simplified if the root is not the argument, this also covers None | ||
hir::ExprKind::Path(_) => !expr_uses_argument(expr, params), | ||
|
||
// Calls to Some, Ok, Err can be considered literals if they don't derive an argument | ||
hir::ExprKind::Call(ref func, ref args) => if_chain! { | ||
if variant_calls; // Disable lint when rules conflict with bind_instead_of_map | ||
if let hir::ExprKind::Path(ref path) = func.kind; | ||
if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]); | ||
then { | ||
// Recursively check all arguments | ||
args.iter().all(|arg| can_simplify(arg, params, variant_calls)) | ||
} else { | ||
false | ||
} | ||
}, | ||
|
||
// For anything more complex than the above, a closure is probably the right solution, | ||
// or the case is handled by an other lint | ||
_ => false, | ||
} | ||
} | ||
|
||
/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be | ||
/// replaced with `<fn>(return value of simple closure)` | ||
pub(super) fn lint<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
expr: &'tcx hir::Expr<'_>, | ||
args: &'tcx [hir::Expr<'_>], | ||
allow_variant_calls: bool, | ||
simplify_using: &str, | ||
) { | ||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type)); | ||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(result_type)); | ||
|
||
if is_option || is_result { | ||
if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind { | ||
let body = cx.tcx.hir().body(eid); | ||
let ex = &body.value; | ||
let params = &body.params; | ||
|
||
if can_simplify(ex, params, allow_variant_calls) { | ||
let msg = if is_option { | ||
"unnecessary closure used to substitute value for `Option::None`" | ||
} else { | ||
"unnecessary closure used to substitute value for `Result::Err`" | ||
}; | ||
|
||
span_lint_and_sugg( | ||
cx, | ||
UNNECESSARY_LAZY_EVALUATIONS, | ||
expr.span, | ||
msg, | ||
&format!("Use `{}` instead", simplify_using), | ||
format!( | ||
"{0}.{1}({2})", | ||
snippet(cx, args[0].span, ".."), | ||
simplify_using, | ||
snippet(cx, ex.span, ".."), | ||
), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.