-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
New lints: impossible_comparisons
and redundant_comparisons
#10843
Merged
bors
merged 11 commits into
rust-lang:master
from
MortenLohne:feat/double-const-comparisons
Aug 6, 2023
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ab1281f
fix: Make ConstEvalLateContext::new() public, to match the 'constant_…
MortenLohne 046d3df
New lints: `impossible_double_const_comparisons` and `ineffective_dou…
MortenLohne e16a2ac
Add descriptions for 'impossible_double_const_comparisons' and 'ineff…
MortenLohne 08e1333
Add missing variable decl to doc comment
MortenLohne 8f40d09
Add tests for const comparisons that compare two different types
MortenLohne b5ef66f
Optimize by doing a cheap check for double binary expression first
MortenLohne 1d61fc1
Rename 'impossible_double_const_comparisons' -> 'impossible_compariso…
MortenLohne 9646446
Add lifetime parameter to 'Constant', after rebasing on upstream
MortenLohne 0e064d5
Replace ConstEvalLateContext::new() with two calls to constant() to s…
MortenLohne 3157b96
Provide fallback code snippets, if the snippet is not available
MortenLohne d628046
Update clippy_lints/src/operators/mod.rs
Manishearth 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 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 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 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,207 @@ | ||
#![allow(clippy::match_same_arms)] | ||
|
||
use std::cmp::Ordering; | ||
|
||
use clippy_utils::consts::{constant, Constant}; | ||
use if_chain::if_chain; | ||
use rustc_hir::{BinOpKind, Expr, ExprKind}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty::layout::HasTyCtxt; | ||
use rustc_middle::ty::{Ty, TypeckResults}; | ||
use rustc_span::source_map::{Span, Spanned}; | ||
|
||
use clippy_utils::diagnostics::span_lint_and_note; | ||
use clippy_utils::source::snippet; | ||
use clippy_utils::SpanlessEq; | ||
|
||
use super::{IMPOSSIBLE_COMPARISONS, REDUNDANT_COMPARISONS}; | ||
|
||
// Extract a comparison between a const and non-const | ||
// Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42` | ||
fn comparison_to_const<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
typeck: &TypeckResults<'tcx>, | ||
expr: &'tcx Expr<'tcx>, | ||
) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> { | ||
if_chain! { | ||
if let ExprKind::Binary(operator, left, right) = expr.kind; | ||
if let Ok(cmp_op) = CmpOp::try_from(operator.node); | ||
then { | ||
match (constant(cx, typeck, left), constant(cx, typeck, right)) { | ||
(Some(_), Some(_)) => None, | ||
(_, Some(con)) => Some((cmp_op, left, right, con, typeck.expr_ty(right))), | ||
(Some(con), _) => Some((cmp_op.reverse(), right, left, con, typeck.expr_ty(left))), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
pub(super) fn check<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
and_op: Spanned<BinOpKind>, | ||
left_cond: &'tcx Expr<'tcx>, | ||
right_cond: &'tcx Expr<'tcx>, | ||
span: Span, | ||
) { | ||
if_chain! { | ||
// Ensure that the binary operator is && | ||
if and_op.node == BinOpKind::And; | ||
|
||
// Check that both operands to '&&' are themselves a binary operation | ||
// The `comparison_to_const` step also checks this, so this step is just an optimization | ||
if let ExprKind::Binary(_, _, _) = left_cond.kind; | ||
if let ExprKind::Binary(_, _, _) = right_cond.kind; | ||
|
||
let typeck = cx.typeck_results(); | ||
|
||
// Check that both operands to '&&' compare a non-literal to a literal | ||
if let Some((left_cmp_op, left_expr, left_const_expr, left_const, left_type)) = | ||
comparison_to_const(cx, typeck, left_cond); | ||
if let Some((right_cmp_op, right_expr, right_const_expr, right_const, right_type)) = | ||
comparison_to_const(cx, typeck, right_cond); | ||
|
||
if left_type == right_type; | ||
|
||
// Check that the same expression is compared in both comparisons | ||
if SpanlessEq::new(cx).eq_expr(left_expr, right_expr); | ||
|
||
if !left_expr.can_have_side_effects(); | ||
|
||
// Compare the two constant expressions | ||
if let Some(ordering) = Constant::partial_cmp(cx.tcx(), left_type, &left_const, &right_const); | ||
|
||
// Rule out the `x >= 42 && x <= 42` corner case immediately | ||
// Mostly to simplify the implementation, but it is also covered by `clippy::double_comparisons` | ||
if !matches!( | ||
(&left_cmp_op, &right_cmp_op, ordering), | ||
(CmpOp::Le | CmpOp::Ge, CmpOp::Le | CmpOp::Ge, Ordering::Equal) | ||
); | ||
|
||
then { | ||
if left_cmp_op.direction() == right_cmp_op.direction() { | ||
let lhs_str = snippet(cx, left_cond.span, "<lhs>"); | ||
let rhs_str = snippet(cx, right_cond.span, "<rhs>"); | ||
// We already know that either side of `&&` has no effect, | ||
// but emit a different error message depending on which side it is | ||
if left_side_is_useless(left_cmp_op, ordering) { | ||
span_lint_and_note( | ||
cx, | ||
REDUNDANT_COMPARISONS, | ||
span, | ||
"left-hand side of `&&` operator has no effect", | ||
Some(left_cond.span.until(right_cond.span)), | ||
&format!("`if `{rhs_str}` evaluates to true, {lhs_str}` will always evaluate to true as well"), | ||
); | ||
} else { | ||
span_lint_and_note( | ||
cx, | ||
REDUNDANT_COMPARISONS, | ||
span, | ||
"right-hand side of `&&` operator has no effect", | ||
Some(and_op.span.to(right_cond.span)), | ||
&format!("`if `{lhs_str}` evaluates to true, {rhs_str}` will always evaluate to true as well"), | ||
); | ||
} | ||
// We could autofix this error but choose not to, | ||
// because code triggering this lint probably not behaving correctly in the first place | ||
} | ||
else if !comparison_is_possible(left_cmp_op.direction(), ordering) { | ||
let expr_str = snippet(cx, left_expr.span, ".."); | ||
let lhs_str = snippet(cx, left_const_expr.span, "<lhs>"); | ||
let rhs_str = snippet(cx, right_const_expr.span, "<rhs>"); | ||
let note = match ordering { | ||
Ordering::Less => format!("since `{lhs_str}` < `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"), | ||
Ordering::Equal => format!("`{expr_str}` cannot simultaneously be greater than and less than `{lhs_str}`"), | ||
Ordering::Greater => format!("since `{lhs_str}` > `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"), | ||
}; | ||
span_lint_and_note( | ||
cx, | ||
IMPOSSIBLE_COMPARISONS, | ||
span, | ||
"boolean expression will never evaluate to 'true'", | ||
None, | ||
¬e, | ||
); | ||
}; | ||
} | ||
} | ||
} | ||
|
||
fn left_side_is_useless(left_cmp_op: CmpOp, ordering: Ordering) -> bool { | ||
// Special-case for equal constants with an inclusive comparison | ||
if ordering == Ordering::Equal { | ||
match left_cmp_op { | ||
CmpOp::Lt | CmpOp::Gt => false, | ||
CmpOp::Le | CmpOp::Ge => true, | ||
} | ||
} else { | ||
match (left_cmp_op.direction(), ordering) { | ||
(CmpOpDirection::Lesser, Ordering::Less) => false, | ||
(CmpOpDirection::Lesser, Ordering::Equal) => false, | ||
(CmpOpDirection::Lesser, Ordering::Greater) => true, | ||
(CmpOpDirection::Greater, Ordering::Less) => true, | ||
(CmpOpDirection::Greater, Ordering::Equal) => false, | ||
(CmpOpDirection::Greater, Ordering::Greater) => false, | ||
} | ||
} | ||
} | ||
|
||
fn comparison_is_possible(left_cmp_direction: CmpOpDirection, ordering: Ordering) -> bool { | ||
match (left_cmp_direction, ordering) { | ||
(CmpOpDirection::Lesser, Ordering::Less | Ordering::Equal) => false, | ||
(CmpOpDirection::Lesser, Ordering::Greater) => true, | ||
(CmpOpDirection::Greater, Ordering::Greater | Ordering::Equal) => false, | ||
(CmpOpDirection::Greater, Ordering::Less) => true, | ||
} | ||
} | ||
|
||
#[derive(PartialEq, Eq, Clone, Copy)] | ||
enum CmpOpDirection { | ||
Lesser, | ||
Greater, | ||
} | ||
|
||
#[derive(Clone, Copy)] | ||
enum CmpOp { | ||
Lt, | ||
Le, | ||
Ge, | ||
Gt, | ||
} | ||
|
||
impl CmpOp { | ||
fn reverse(self) -> Self { | ||
match self { | ||
CmpOp::Lt => CmpOp::Gt, | ||
CmpOp::Le => CmpOp::Ge, | ||
CmpOp::Ge => CmpOp::Le, | ||
CmpOp::Gt => CmpOp::Lt, | ||
} | ||
} | ||
|
||
fn direction(self) -> CmpOpDirection { | ||
match self { | ||
CmpOp::Lt => CmpOpDirection::Lesser, | ||
CmpOp::Le => CmpOpDirection::Lesser, | ||
CmpOp::Ge => CmpOpDirection::Greater, | ||
CmpOp::Gt => CmpOpDirection::Greater, | ||
} | ||
} | ||
} | ||
|
||
impl TryFrom<BinOpKind> for CmpOp { | ||
type Error = (); | ||
|
||
fn try_from(bin_op: BinOpKind) -> Result<Self, Self::Error> { | ||
match bin_op { | ||
BinOpKind::Lt => Ok(CmpOp::Lt), | ||
BinOpKind::Le => Ok(CmpOp::Le), | ||
BinOpKind::Ge => Ok(CmpOp::Ge), | ||
BinOpKind::Gt => Ok(CmpOp::Gt), | ||
_ => Err(()), | ||
} | ||
} | ||
} |
This file contains 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 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, just add a period to the end of both of these sentences :)