-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Implemented needless question mark lint
- Loading branch information
commit ba87acb44090412f5ace0a5ca655e8298d82b874
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
8000
template>
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,232 @@ | ||
use rustc_errors::Applicability; | ||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; | ||
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath}; | ||
use rustc_lint::{LateContext, LateLintPass, LintContext}; | ||
use rustc_middle::ty::DefIdTree; | ||
use rustc_semver::RustcVersion; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
use rustc_span::sym; | ||
|
||
use crate::utils; | ||
use if_chain::if_chain; | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** | ||
/// Suggests alternatives for useless applications of `?` in terminating expressions | ||
/// | ||
/// **Why is this bad?** There's no reason to use ? to short-circuit when execution of the body will end there anyway. | ||
/// | ||
/// **Known problems:** None. | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// struct TO { | ||
/// magic: Option<usize>, | ||
/// } | ||
/// | ||
/// fn f(to: TO) -> Option<usize> { | ||
/// Some(to.magic?) | ||
/// } | ||
/// | ||
/// struct TR { | ||
/// magic: Result<usize, bool>, | ||
/// } | ||
/// | ||
/// fn g(tr: Result<TR, bool>) -> Result<usize, bool> { | ||
/// tr.and_then(|t| Ok(t.magic?)) | ||
/// } | ||
/// | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// struct TO { | ||
/// magic: Option<usize>, | ||
/// } | ||
/// | ||
/// fn f(to: TO) -> Option<usize> { | ||
/// to.magic | ||
/// } | ||
/// | ||
/// struct TR { | ||
/// magic: Result<usize, bool>, | ||
/// } | ||
/// | ||
/// fn g(tr: Result<TR, bool>) -> Result<usize, bool> { | ||
/// tr.and_then(|t| t.magic) | ||
/// } | ||
/// ``` | ||
pub NEEDLESS_QUESTION_MARK, | ||
complexity, | ||
"Suggest value.inner_option instead of Some(value.inner_option?). The same goes for Result<T, E>." | ||
} | ||
|
||
const NEEDLESS_QUESTION_MARK_RESULT_MSRV: RustcVersion = RustcVersion::new(1, 13, 0); | ||
const NEEDLESS_QUESTION_MARK_OPTION_MSRV: RustcVersion = RustcVersion::new(1, 22, 0); | ||
|
||
pub struct NeedlessQuestionMark { | ||
msrv: Option<RustcVersion>, | ||
} | ||
|
||
impl NeedlessQuestionMark { | ||
#[must_use] | ||
pub fn new(msrv: Option<RustcVersion>) -> Self { | ||
Self { msrv } | ||
} | ||
} | ||
|
||
impl_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]); | ||
|
||
#[derive(Debug)] | ||
enum SomeOkCall<'a> { | ||
SomeCall(&'a Expr<'a>, &'a Expr<'a>), | ||
OkCall(&'a Expr<'a>, &'a Expr<'a>), | ||
} | ||
|
||
impl LateLintPass<'_> for NeedlessQuestionMark { | ||
/* | ||
* The question mark operator is compatible with both Result<T, E> and Option<T>, | ||
* from Rust 1.13 and 1.22 respectively. | ||
*/ | ||
|
||
/* | ||
* What do we match: | ||
* Expressions that look like this: | ||
* Some(option?), Ok(result?) | ||
* | ||
* Where do we match: | ||
* Last expression of a body | ||
* Return statement | ||
* A body's value (single line closure) | ||
* | ||
* What do we not match: | ||
* Implicit calls to `from(..)` on the error value | ||
*/ | ||
|
||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { | ||
let e = match &expr.kind { | ||
ExprKind::Ret(Some(e)) => e, | ||
_ => return, | ||
}; | ||
|
||
if let Some(ok_some_call) = is_some_or_ok_call(self, cx, e) { | ||
emit_lint(cx, &ok_some_call); | ||
} | ||
} | ||
|
||
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { | ||
// Function / Closure block | ||
let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind { | ||
block.expr | ||
} else { | ||
// Single line closure | ||
Some(&body.value) | ||
}; | ||
|
||
if_chain! { | ||
if let Some(expr) = expr_opt; | ||
if let Some(ok_some_call) = is_some_or_ok_call(self, cx, expr); | ||
then { | ||
emit_lint(cx, &ok_some_call); | ||
} | ||
}; | ||
} | ||
|
||
extract_msrv_attr!(LateContext); | ||
} | ||
|
||
fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) { | ||
let (entire_expr, inner_expr) = match expr { | ||
SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner), | ||
}; | ||
|
||
utils::span_lint_and_sugg( | ||
cx, | ||
NEEDLESS_QUESTION_MARK, | ||
entire_expr.span, | ||
"Question mark operator is useless here", | ||
"try", | ||
format!("{}", utils::snippet(cx, inner_expr.span, r#""...""#)), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
|
||
fn is_some_or_ok_call<'a>( | ||
nqml: &NeedlessQuestionMark, | ||
cx: &'a LateContext<'_>, | ||
expr: &'a Expr<'_>, | ||
) -> Option<SomeOkCall<'a>> { | ||
if_chain! { | ||
// Check outer expression matches CALL_IDENT(ARGUMENT) format | ||
if let ExprKind::Call(path, args) = &expr.kind; | ||
if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind; | DDED||
if is_some_ctor(cx, path.res) || is_ok_ctor(cx, path.res); | ||
|
||
// Extract inner expression from ARGUMENT | ||
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind; | ||
if let ExprKind::Call(called, args) = &inner_expr_with_q.kind; | ||
if args.len() == 1; | ||
|
||
if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind; | ||
then { | ||
// Extract inner expr type from match argument generated by | ||
// question mark operator | ||
let inner_expr = &args[0]; | ||
|
||
let inner_ty = cx.typeck_results().expr_ty(inner_expr); | ||
let outer_ty = cx.typeck_results().expr_ty(expr); | ||
|
||
// Check if outer and inner type are Option | ||
let outer_is_some = utils::is_type_diagnostic_item(cx, outer_ty, sym::option_type); | ||
let inner_is_some = utils::is_type_diagnostic_item(cx, inner_ty, sym::option_type); | ||
|
||
// Check for Option MSRV | ||
let meets_option_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_OPTION_MSRV); | ||
if outer_is_some && inner_is_some && meets_option_msrv { | ||
return Some(SomeOkCall::SomeCall(expr, inner_expr)); | ||
} | ||
|
||
// Check if outer and inner type are Result | ||
let outer_is_result = utils::is_type_diagnostic_item(cx, outer_ty, sym::result_type); | ||
let inner_is_result = utils::is_type_diagnostic_item(cx, inner_ty, sym::result_type); | ||
|
||
// Additional check: if the error type of the Result can be converted | ||
// via the From trait, then don't match | ||
let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr); | ||
|
||
// Must meet Result MSRV | ||
let meets_result_msrv = utils::meets_msrv(nqml.msrv.as_ref(), &NEEDLESS_QUESTION_MARK_RESULT_MSRV); | ||
if outer_is_result && inner_is_result && does_not_call_from && meets_result_msrv { | ||
return Some(SomeOkCall::OkCall(expr, inner_expr)); | ||
} | ||
} | ||
} | ||
|
||
None | ||
} | ||
|
||
fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool { | ||
return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr); | ||
} | ||
|
||
fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool { | ||
if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() { | ||
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { | ||
if let Some(variant_id) = cx.tcx.parent(id) { | ||
return variant_id == ok_id; | ||
} | ||
} | ||
} | ||
false | ||
} | ||
|
||
fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool { | ||
if let Some(some_id) = cx.tcx.lang_items().option_some_variant() { | ||
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { | ||
if let Some(variant_id) = cx.tcx.parent(id) { | ||
return variant_id == some_id; | ||
} | ||
} | ||
} | ||
false | ||
} |
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.
Update Clippy #81038
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
Uh oh!
There was an error while loading. Please reload this page.
Update Clippy #81038
Changes from 1 commit
c06793b
469281c
83a458a
275988c
af480a6
0afe2db
b81111b
6b37932
6909055
5d48b91
ba4bf4f
1853f8b
78f6009
053afe4
9427e03
d37ee6f
592f7eb
a02806e
7b5f549
8951916
d141cdc
6dcec6a
dd1929e
ba87acb
ae9ae97
7acfa44
cbbb188
445eb99
bc97f5d
a8d47b4
976850b
4b478a5
dfa5d7e
dd52066
311186b
a6b72d3
ea885d9
42b9e92
a8825e9
92f2bbb
efccfe8
8a45ffa
8e5c5a6
f50ded0
547ce0d
2b3c0ad
e15bef9
7d42172
aa9adbf
0e14a75
ee9b47d
2950c8e
24c700b
121c65f
76ccfb4
cc26919
7871eba
68bcd20
8a6fea4
ee0598e
583715f
9e45a23
2c6dc88
1eed27f
53f8731
7f4599a
13ca5c8
ea02849
dfb41f4
00586df
dcd8c8e
7b3af41
9bd037d
0c5ba9a
f18cf82
953f024
3e236b3
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.