Auto merge of #10478 - Jarcho:block_eq, r=xFrednet

`SpanlessEq` improvements

fixes #9775

Also includes a simplification to `consts::constant`'s interface since I was already touching the code.

At the start of `eq_expr` the check:
```rust
if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
    return false;
}
```
was removed. This was added in 49e2501 to handle `cfg` macros. This is better handled by the newly added `check_ctxt`.

changelog: [various lints]: Don't consider different `cfg!` expansions to be the same unless they are for the same config.
changelog: [various lints]: Don't consider the expansion of two different macros to be equal, even when they expand to the same token sequence.
changelog: [various lints]: Don't consider two blocks to be equal if they contain disabled code or empty macro expansions, unless those section contain the exact same token sequence.
This commit is contained in:
bors 2023-05-19 01:13:08 +00:00
commit 1e8d2c59bc
42 changed files with 525 additions and 232 deletions

View File

@ -38,7 +38,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
_ => return,
};
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return };
let Some((Constant::Bool(val), _)) = constant(cx, cx.typeck_results(), condition) else { return };
let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { return };
if val {
span_lint_and_help(
cx,

View File

@ -21,8 +21,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), e) {
Some((Constant::F64(n), _)) => n.is_nan(),
Some((Constant::F32(n), _)) => n.is_nan(),
Some(Constant::F64(n)) => n.is_nan(),
Some(Constant::F32(n)) => n.is_nan(),
_ => false,
}
}

View File

@ -15,7 +15,7 @@
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
if let Some(Constant::Int(c)) = constant(cx, cx.typeck_results(), expr) {
Some(c)
} else {
None

View File

@ -29,7 +29,7 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
// Don't lint for positive constants.
let const_val = constant(cx, cx.typeck_results(), cast_op);
if_chain! {
if let Some((Constant::Int(n), _)) = const_val;
if let Some(Constant::Int(n)) = const_val;
if let ty::Int(ity) = *cast_from.kind();
if sext(cx.tcx, n, ity) >= 0;
then {

View File

@ -114,7 +114,7 @@
// Returns the specialized log method for a given base if base is constant
// and is one of 2, 10 and e
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
if let Some((value, _)) = constant(cx, cx.typeck_results(), base) {
if let Some(value) = constant(cx, cx.typeck_results(), base) {
if F32(2.0) == value || F64(2.0) == value {
return Some("log2");
} else if F32(10.0) == value || F64(10.0) == value {
@ -193,8 +193,8 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
constant(cx, cx.typeck_results(), lhs),
constant(cx, cx.typeck_results(), rhs),
) {
(Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
(_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
(Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs,
(_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs,
_ => return,
};
@ -237,7 +237,7 @@ fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver
if let Some((value, _)) = constant(cx, cx.typeck_results(), receiver) {
if let Some(value) = constant(cx, cx.typeck_results(), receiver) {
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp")
} else if F32(2.0) == value || F64(2.0) == value {
@ -258,7 +258,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
}
// Check argument
if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) {
let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
(
SUBOPTIMAL_FLOPS,
@ -298,7 +298,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
}
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) {
if value == Int(2) {
if let Some(parent) = get_parent_expr(cx, expr) {
if let Some(grandparent) = get_parent_expr(cx, parent) {
@ -384,8 +384,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
_
) = &add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1);
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1);
if Int(2) == lvalue && Int(2) == rvalue;
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
@ -416,7 +416,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
if let Some(value) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
@ -669,8 +669,8 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
mul_lhs,
mul_rhs,
) = &div_lhs.kind;
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs);
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs);
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs);
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs);
then {
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&

View File

@ -89,11 +89,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Catching:
// (fn_ptr as *<const/mut> <ty>) == <const that evaluates to null_ptr>
_ if matches!(
constant(cx, cx.typeck_results(), to_check),
Some((Constant::RawPtr(0), _))
) =>
{
_ if matches!(constant(cx, cx.typeck_results(), to_check), Some(Constant::RawPtr(0))) => {
lint_expr(cx, expr);
},

View File

@ -101,10 +101,10 @@ fn get_int_max(ty: Ty<'_>) -> Option<u128> {
fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
if let ExprKind::Binary(op, l, r) = expr.kind {
let tr = cx.typeck_results();
if let Some((Constant::Int(c), _)) = constant(cx, tr, r) {
if let Some(Constant::Int(c)) = constant(cx, tr, r) {
return Some((c, op.node, l));
};
if let Some((Constant::Int(c), _)) = constant(cx, tr, l) {
if let Some(Constant::Int(c)) = constant(cx, tr, l) {
return Some((c, invert_op(op.node)?, r));
}
}

View File

@ -254,7 +254,7 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
let parent_id = map.parent_id(expr.hir_id);
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind;
if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr);
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr);
if let Ok(index_value) = index_value.try_into();
if index_value < max_suggested_slice;

View File

@ -191,18 +191,14 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
/// Returns a tuple of options with the start and end (exclusive) values of
/// the range. If the start or end is not constant, None is returned.
fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) {
let s = range
.start
.map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
let s = range.start.map(|expr| constant(cx, cx.typeck_results(), expr));
let start = match s {
Some(Some(Constant::Int(x))) => Some(x),
Some(_) => None,
None => Some(0),
};
let e = range
.end
.map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
let e = range.end.map(|expr| constant(cx, cx.typeck_results(), expr));
let end = match e {
Some(Some(Constant::Int(x))) => {
if range.limits == RangeLimits::Closed {

View File

@ -144,7 +144,7 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
// Returns the length of the `expr` if it's a constant string or char.
fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
let (value, _) = constant(cx, cx.typeck_results(), expr)?;
let value = constant(cx, cx.typeck_results(), expr)?;
match value {
Constant::Str(value) => Some(value.len() as u128),
Constant::Char(value) => Some(value.len_utf8() as u128),

View File

@ -25,9 +25,9 @@
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_opt, walk_span_to_context};
use clippy_utils::{higher, in_constant, is_span_match};
use clippy_utils::{higher, in_constant, is_span_match, tokenize_with_text};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lexer::TokenKind;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -1147,12 +1147,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
// Assume true. This would require either an invalid span, or one which crosses file boundaries.
return true;
};
let mut pos = 0usize;
let mut iter = tokenize(&snip).map(|t| {
let start = pos;
pos += t.len as usize;
(t.kind, start..pos)
});
let mut iter = tokenize_with_text(&snip);
// Search for the token sequence [`#`, `[`, `cfg`]
while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
@ -1163,7 +1158,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
)
});
if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
&& matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg")))
{
return true;
}

View File

@ -34,7 +34,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
if let Arm { pat, guard: None, .. } = *arm {
if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
let lhs_const = match lhs {
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?,
None => {
let min_val_const = ty.numeric_min_val(cx.tcx)?;
let min_constant = mir::ConstantKind::from_value(
@ -45,7 +45,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
},
};
let rhs_const = match rhs {
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0,
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?,
None => {
let max_val_const = ty.numeric_max_val(cx.tcx)?;
let max_constant = mir::ConstantKind::from_value(

View File

@ -13,7 +13,7 @@
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
if_chain! {
if is_trait_method(cx, expr, sym::Iterator);
if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg);
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(

View File

@ -9,7 +9,7 @@
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
if is_trait_method(cx, expr, sym::Iterator) {
if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg) {
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg) {
span_lint(
cx,
ITERATOR_STEP_BY_ZERO,

View File

@ -3770,13 +3770,13 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
unnecessary_sort_by::check(cx, expr, recv, arg, true);
},
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
str_splitn::check(cx, name, expr, recv, pat_arg, count, &self.msrv);
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
}
},

View File

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant_context, Constant};
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_lang_item;
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
recv: &'tcx Expr<'_>,
repeat_arg: &'tcx Expr<'_>,
) {
if constant_context(cx, cx.typeck_results()).expr(repeat_arg) == Some(Constant::Int(1)) {
if constant(cx, cx.typeck_results(), repeat_arg) == Some(Constant::Int(1)) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if ty.is_str() {
span_lint_and_sugg(

View File

@ -316,7 +316,7 @@ fn parse_iter_usage<'tcx>(
};
},
("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
if let Some(Constant::Int(idx)) = constant(cx, cx.typeck_results(), idx_expr) {
let span = if name.ident.as_str() == "nth" {
e.span
} else {

View File

@ -121,7 +121,7 @@ fn detect_absurd_comparison<'tcx>(
fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
let ty = cx.typeck_results().expr_ty(expr);
let cv = constant(cx, cx.typeck_results(), expr)?.0;
let cv = constant(cx, cx.typeck_results(), expr)?;
let which = match (ty.kind(), cv) {
(&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,

View File

@ -113,7 +113,7 @@ fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> {
if let hir::ExprKind::Lit(lit) = actual.kind && let ast::LitKind::Int(n, _) = lit.node {
return Some(n)
}
if let Some((Constant::Int(n), _)) = constant(cx, cx.typeck_results(), expr) {
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), expr) {
return Some(n);
}
None

View File

@ -166,7 +166,7 @@ fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op:
}
fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option<u128> {
match constant(cx, cx.typeck_results(), lit)?.0 {
match constant(cx, cx.typeck_results(), lit)? {
Constant::Int(n) => Some(n),
_ => None,
}

View File

@ -18,7 +18,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Exp
}
fn is_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Some((value, _)) = constant(cx, cx.typeck_results(), e) {
if let Some(value) = constant(cx, cx.typeck_results(), e) {
match value {
Constant::F32(num) => num.is_nan(),
Constant::F64(num) => num.is_nan(),

View File

@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(
if op == BinOpKind::Div
&& let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration)
&& let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right)
&& let Some(Constant::Int(divisor)) = constant(cx, cx.typeck_results(), right)
{
let suggested_fn = match (method_path.ident.as_str(), divisor) {
("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",

View File

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::consts::{constant_with_source, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_item_name;
use clippy_utils::sugg::Sugg;
@ -18,9 +18,16 @@ pub(crate) fn check<'tcx>(
right: &'tcx Expr<'_>,
) {
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if is_allowed(cx, left) || is_allowed(cx, right) {
return;
}
let left_is_local = match constant_with_source(cx, cx.typeck_results(), left) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,
None => true,
};
let right_is_local = match constant_with_source(cx, cx.typeck_results(), right) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,
None => true,
};
// Allow comparing the results of signum()
if is_signum(cx, left) && is_signum(cx, right) {
@ -34,10 +41,7 @@ pub(crate) fn check<'tcx>(
}
}
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
let (lint, msg) = get_lint_and_message(
is_named_constant(cx, left) || is_named_constant(cx, right),
is_comparing_arrays,
);
let (lint, msg) = get_lint_and_message(left_is_local && right_is_local, is_comparing_arrays);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let lhs = Sugg::hir(cx, left, "..");
let rhs = Sugg::hir(cx, right, "..");
@ -59,20 +63,8 @@ pub(crate) fn check<'tcx>(
}
}
fn get_lint_and_message(
is_comparing_constants: bool,
is_comparing_arrays: bool,
) -> (&'static rustc_lint::Lint, &'static str) {
if is_comparing_constants {
(
FLOAT_CMP_CONST,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` constant arrays"
} else {
"strict comparison of `f32` or `f64` constant"
},
)
} else {
fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static rustc_lint::Lint, &'static str) {
if is_local {
(
FLOAT_CMP,
if is_comparing_arrays {
@ -81,22 +73,23 @@ fn get_lint_and_message(
"strict comparison of `f32` or `f64`"
},
)
}
}
fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
res
} else {
false
(
FLOAT_CMP_CONST,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` constant arrays"
} else {
"strict comparison of `f32` or `f64` constant"
},
)
}
}
fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), expr) {
Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
fn is_allowed(val: &Constant) -> bool {
match val {
&Constant::F32(f) => f == 0.0 || f.is_infinite(),
&Constant::F64(f) => f == 0.0 || f.is_infinite(),
Constant::Vec(vec) => vec.iter().all(|f| match f {
Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
_ => false,

View File

@ -40,7 +40,7 @@ struct OperandInfo {
fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OperandInfo> {
match constant(cx, cx.typeck_results(), operand) {
Some((Constant::Int(v), _)) => match *cx.typeck_results().expr_ty(expr).kind() {
Some(Constant::Int(v)) => match *cx.typeck_results().expr_ty(expr).kind() {
ty::Int(ity) => {
let value = sext(cx.tcx, v, ity);
return Some(OperandInfo {
@ -58,10 +58,10 @@ fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) ->
},
_ => {},
},
Some((Constant::F32(f), _)) => {
Some(Constant::F32(f)) => {
return Some(floating_point_operand_info(&f));
},
Some((Constant::F64(f), _)) => {
Some(Constant::F64(f)) => {
return Some(floating_point_operand_info(&f));
},
_ => {},

View File

@ -319,7 +319,7 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R
_ => return None,
};
if let Some(id) = path_to_local(l) {
if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
if let Some(c) = constant(cx, cx.typeck_results(), r) {
return Some(RangeBounds {
val: c,
expr: r,
@ -331,7 +331,7 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R
});
}
} else if let Some(id) = path_to_local(r) {
if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
if let Some(c) = constant(cx, cx.typeck_results(), l) {
return Some(RangeBounds {
val: c,
expr: l,
@ -451,8 +451,8 @@ fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool {
if let Some(higher::Range { start: Some(start), end: Some(end), limits }) = higher::Range::hir(expr);
let ty = cx.typeck_results().expr_ty(start);
if let ty::Int(_) | ty::Uint(_) = ty.kind();
if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start);
if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end);
if let Some(start_idx) = constant(cx, cx.typeck_results(), start);
if let Some(end_idx) = constant(cx, cx.typeck_results(), end);
if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx);
if is_empty_range(limits, ordering);
then {

View File

@ -122,7 +122,7 @@ fn lint_syntax_error(cx: &LateContext<'_>, error: &regex_syntax::Error, unescape
}
fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> {
constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c {
constant(cx, cx.typeck_results(), e).and_then(|c| match c {
Constant::Str(s) => Some(s),
_ => None,
})

View File

@ -31,9 +31,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
match arg.kind {
// Catching:
// transmute over constants that resolve to `null`.
ExprKind::Path(ref _qpath)
if matches!(constant(cx, cx.typeck_results(), arg), Some((Constant::RawPtr(0), _))) =>
{
ExprKind::Path(ref _qpath) if matches!(constant(cx, cx.typeck_results(), arg), Some(Constant::RawPtr(0))) => {
lint_expr(cx, expr);
true
},

View File

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant_context, Constant};
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_integer_literal, is_path_diagnostic_item};
use rustc_hir::{Expr, ExprKind};
@ -16,9 +16,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
}
// Catching transmute over constants that resolve to `null`.
let mut const_eval_context = constant_context(cx, cx.typeck_results());
if let ExprKind::Path(ref _qpath) = arg.kind &&
let Some(Constant::RawPtr(0)) = const_eval_context.expr(arg)
let Some(Constant::RawPtr(0)) = constant(cx, cx.typeck_results(), arg)
{
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
return true;

View File

@ -84,7 +84,7 @@ fn check_vec_macro<'tcx>(
let mut applicability = Applicability::MachineApplicable;
let snippet = match *vec_args {
higher::VecArgs::Repeat(elem, len) => {
if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
#[expect(clippy::cast_possible_truncation)]
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
return;

View File

@ -1,18 +1,21 @@
#![allow(clippy::float_cmp)]
use crate::source::{get_source_text, walk_span_to_context};
use crate::{clip, is_direct_expn_of, sext, unsext};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_data_structures::sync::Lrc;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
use rustc_lexer::tokenize;
use rustc_lint::LateContext;
use rustc_middle::mir;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::ty::SubstsRef;
use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt};
use rustc_middle::ty::{List, SubstsRef};
use rustc_middle::{bug, span_bug};
use rustc_span::symbol::Symbol;
use rustc_span::SyntaxContext;
use std::cmp::Ordering::{self, Equal};
use std::hash::{Hash, Hasher};
use std::iter;
@ -227,27 +230,46 @@ pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
}
}
/// The source of a constant value.
pub enum ConstantSource {
/// The value is determined solely from the expression.
Local,
/// The value is dependent on a defined constant.
Constant,
}
impl ConstantSource {
pub fn is_local(&self) -> bool {
matches!(self, Self::Local)
}
}
/// Attempts to evaluate the expression as a constant.
pub fn constant<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
e: &Expr<'_>,
) -> Option<(Constant, bool)> {
let mut cx = ConstEvalLateContext {
lcx,
typeck_results,
param_env: lcx.param_env,
needed_resolution: false,
substs: ty::List::empty(),
};
cx.expr(e).map(|cst| (cst, cx.needed_resolution))
) -> Option<Constant> {
ConstEvalLateContext::new(lcx, typeck_results).expr(e)
}
/// Attempts to evaluate the expression as a constant.
pub fn constant_with_source<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
e: &Expr<'_>,
) -> Option<(Constant, ConstantSource)> {
let mut ctxt = ConstEvalLateContext::new(lcx, typeck_results);
let res = ctxt.expr(e);
res.map(|x| (x, ctxt.source))
}
/// Attempts to evaluate an expression only if it's value is not dependent on other items.
pub fn constant_simple<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
e: &Expr<'_>,
) -> Option<Constant> {
constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) })
constant_with_source(lcx, typeck_results, e).and_then(|(c, s)| s.is_local().then_some(c))
}
pub fn constant_full_int<'tcx>(
@ -296,29 +318,25 @@ fn cmp_s_u(s: i128, u: u128) -> Ordering {
}
}
/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`.
pub fn constant_context<'a, 'tcx>(
lcx: &'a LateContext<'tcx>,
typeck_results: &'a ty::TypeckResults<'tcx>,
) -> ConstEvalLateContext<'a, 'tcx> {
ConstEvalLateContext {
lcx,
typeck_results,
param_env: lcx.param_env,
needed_resolution: false,
substs: ty::List::empty(),
}
}
pub struct ConstEvalLateContext<'a, 'tcx> {
lcx: &'a LateContext<'tcx>,
typeck_results: &'a ty::TypeckResults<'tcx>,
param_env: ty::ParamEnv<'tcx>,
needed_resolution: bool,
source: ConstantSource,
substs: SubstsRef<'tcx>,
}
impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self {
Self {
lcx,
typeck_results,
param_env: lcx.param_env,
source: ConstantSource::Local,
substs: List::empty(),
}
}
/// Simple constant folding: Insert an expression, get a constant or none.
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> {
match e.kind {
@ -453,11 +471,9 @@ fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<C
.const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, substs), None)
.ok()
.map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?;
let result = miri_to_const(self.lcx.tcx, result);
if result.is_some() {
self.needed_resolution = true;
}
result
let result = miri_to_const(self.lcx.tcx, result)?;
self.source = ConstantSource::Constant;
Some(result)
},
// FIXME: cover all usable cases.
_ => None,
@ -491,8 +507,33 @@ fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant>
/// A block can only yield a constant if it only has one constant expression.
fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
if block.stmts.is_empty() {
block.expr.as_ref().and_then(|b| self.expr(b))
if block.stmts.is_empty()
&& let Some(expr) = block.expr
{
// Try to detect any `cfg`ed statements or empty macro expansions.
let span = block.span.data();
if span.ctxt == SyntaxContext::root() {
if let Some(expr_span) = walk_span_to_context(expr.span, span.ctxt)
&& let expr_lo = expr_span.lo()
&& expr_lo >= span.lo
&& let Some(src) = get_source_text(self.lcx, span.lo..expr_lo)
&& let Some(src) = src.as_str()
{
use rustc_lexer::TokenKind::{Whitespace, LineComment, BlockComment, Semi, OpenBrace};
if !tokenize(src)
.map(|t| t.kind)
.filter(|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi))
.eq([OpenBrace])
{
self.source = ConstantSource::Constant;
}
} else {
// Unable to access the source. Assume a non-local dependency.
self.source = ConstantSource::Constant;
}
}
self.expr(expr)
} else {
None
}

View File

@ -1,6 +1,7 @@
use crate::consts::constant_simple;
use crate::macros::macro_backtrace;
use crate::source::snippet_opt;
use crate::source::{get_source_text, snippet_opt, walk_span_to_context, SpanRange};
use crate::tokenize_with_text;
use rustc_ast::ast::InlineAsmTemplatePiece;
use rustc_data_structures::fx::FxHasher;
use rustc_hir::def::Res;
@ -13,8 +14,9 @@
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::LateContext;
use rustc_middle::ty::TypeckResults;
use rustc_span::{sym, Symbol};
use rustc_span::{sym, BytePos, ExpnKind, MacroKind, Symbol, SyntaxContext};
use std::hash::{Hash, Hasher};
use std::ops::Range;
/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
/// other conditions would make them equal.
@ -65,6 +67,8 @@ pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bo
pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
HirEqInterExpr {
inner: self,
left_ctxt: SyntaxContext::root(),
right_ctxt: SyntaxContext::root(),
locals: HirIdMap::default(),
}
}
@ -92,6 +96,8 @@ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegmen
pub struct HirEqInterExpr<'a, 'b, 'tcx> {
inner: &'a mut SpanlessEq<'b, 'tcx>,
left_ctxt: SyntaxContext,
right_ctxt: SyntaxContext,
// When binding are declared, the binding ID in the left expression is mapped to the one on the
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
@ -126,52 +132,88 @@ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
}
/// Checks whether two blocks are the same.
#[expect(clippy::similar_names)]
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
match (left.stmts, left.expr, right.stmts, right.expr) {
([], None, [], None) => {
// For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
// expanded to nothing, or the cfg attribute was used.
let (Some(left), Some(right)) = (
snippet_opt(self.inner.cx, left.span),
snippet_opt(self.inner.cx, right.span),
) else { return true };
let mut left_pos = 0;
let left = tokenize(&left)
.map(|t| {
let end = left_pos + t.len as usize;
let s = &left[left_pos..end];
left_pos = end;
(t, s)
})
.filter(|(t, _)| {
!matches!(
t.kind,
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
)
})
.map(|(_, s)| s);
let mut right_pos = 0;
let right = tokenize(&right)
.map(|t| {
let end = right_pos + t.len as usize;
let s = &right[right_pos..end];
right_pos = end;
(t, s)
})
.filter(|(t, _)| {
!matches!(
t.kind,
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
)
})
.map(|(_, s)| s);
left.eq(right)
},
_ => {
over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r))
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
},
use TokenKind::{BlockComment, LineComment, Semi, Whitespace};
if left.stmts.len() != right.stmts.len() {
return false;
}
let lspan = left.span.data();
let rspan = right.span.data();
if lspan.ctxt != SyntaxContext::root() && rspan.ctxt != SyntaxContext::root() {
// Don't try to check in between statements inside macros.
return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right))
&& both(&left.expr, &right.expr, |left, right| self.eq_expr(left, right));
}
if lspan.ctxt != rspan.ctxt {
return false;
}
let mut lstart = lspan.lo;
let mut rstart = rspan.lo;
for (left, right) in left.stmts.iter().zip(right.stmts) {
if !self.eq_stmt(left, right) {
return false;
}
// Try to detect any `cfg`ed statements or empty macro expansions.
let Some(lstmt_span) = walk_span_to_context(left.span, lspan.ctxt) else {
return false;
};
let Some(rstmt_span) = walk_span_to_context(right.span, rspan.ctxt) else {
return false;
};
let lstmt_span = lstmt_span.data();
let rstmt_span = rstmt_span.data();
if lstmt_span.lo < lstart && rstmt_span.lo < rstart {
// Can happen when macros expand to multiple statements, or rearrange statements.
// Nothing in between the statements to check in this case.
continue;
}
if lstmt_span.lo < lstart || rstmt_span.lo < rstart {
// Only one of the blocks had a weird macro.
return false;
}
if !eq_span_tokens(self.inner.cx, lstart..lstmt_span.lo, rstart..rstmt_span.lo, |t| {
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
}) {
return false;
}
lstart = lstmt_span.hi;
rstart = rstmt_span.hi;
}
let (lend, rend) = match (left.expr, right.expr) {
(Some(left), Some(right)) => {
if !self.eq_expr(left, right) {
return false;
}
let Some(lexpr_span) = walk_span_to_context(left.span, lspan.ctxt) else {
return false;
};
let Some(rexpr_span) = walk_span_to_context(right.span, rspan.ctxt) else {
return false;
};
(lexpr_span.lo(), rexpr_span.lo())
},
(None, None) => (lspan.hi, rspan.hi),
(Some(_), None) | (None, Some(_)) => return false,
};
if lend < lstart && rend < rstart {
// Can happen when macros rearrange the input.
// Nothing in between the statements to check in this case.
return true;
} else if lend < lstart || rend < rstart {
// Only one of the blocks had a weird macro
return false;
}
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
})
}
fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
@ -207,7 +249,7 @@ pub fn eq_body(&mut self, left: BodyId, right: BodyId) -> bool {
#[expect(clippy::similar_names)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
return false;
}
@ -440,6 +482,45 @@ pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
left.ident.name == right.ident.name && self.eq_ty(left.ty(), right.ty())
}
fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool {
if self.left_ctxt == left && self.right_ctxt == right {
return true;
} else if self.left_ctxt == left || self.right_ctxt == right {
// Only one context has changed. This can only happen if the two nodes are written differently.
return false;
} else if left != SyntaxContext::root() {
let mut left_data = left.outer_expn_data();
let mut right_data = right.outer_expn_data();
loop {
use TokenKind::{BlockComment, LineComment, Whitespace};
if left_data.macro_def_id != right_data.macro_def_id
|| (matches!(left_data.kind, ExpnKind::Macro(MacroKind::Bang, name) if name == sym::cfg)
&& !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| {
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. })
}))
{
// Either a different chain of macro calls, or different arguments to the `cfg` macro.
return false;
}
let left_ctxt = left_data.call_site.ctxt();
let right_ctxt = right_data.call_site.ctxt();
if left_ctxt == SyntaxContext::root() && right_ctxt == SyntaxContext::root() {
break;
}
if left_ctxt == SyntaxContext::root() || right_ctxt == SyntaxContext::root() {
// Different lengths for the expansion stack. This can only happen if nodes are written differently,
// or shouldn't be compared to start with.
return false;
}
left_data = left_ctxt.outer_expn_data();
right_data = right_ctxt.outer_expn_data();
}
}
self.left_ctxt = left;
self.right_ctxt = right;
true
}
}
/// Some simple reductions like `{ return }` => `return`
@ -1038,3 +1119,34 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
h.hash_expr(e);
h.finish()
}
#[expect(clippy::similar_names)]
fn eq_span_tokens(
cx: &LateContext<'_>,
left: impl SpanRange,
right: impl SpanRange,
pred: impl Fn(TokenKind) -> bool,
) -> bool {
fn f(cx: &LateContext<'_>, left: Range<BytePos>, right: Range<BytePos>, pred: impl Fn(TokenKind) -> bool) -> bool {
if let Some(lsrc) = get_source_text(cx, left)
&& let Some(lsrc) = lsrc.as_str()
&& let Some(rsrc) = get_source_text(cx, right)
&& let Some(rsrc) = rsrc.as_str()
{
let pred = |t: &(_, _)| pred(t.0);
let map = |(_, x)| x;
let ltok = tokenize_with_text(lsrc)
.filter(pred)
.map(map);
let rtok = tokenize_with_text(rsrc)
.filter(pred)
.map(map);
ltok.eq(rtok)
} else {
// Unable to access the source. Conservatively assume the blocks aren't equal.
false
}
}
f(cx, left.into_range(), right.into_range(), pred)
}

View File

@ -77,6 +77,7 @@
use std::sync::{Mutex, MutexGuard};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashMap;
@ -1498,7 +1499,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
&& let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
&& let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
&& let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
&& let Some(start_const) = constant(cx, cx.typeck_results(), start)
{
start_const == min_const
} else {
@ -1514,7 +1515,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
&& let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
&& let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
&& let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
&& let Some(end_const) = constant(cx, cx.typeck_results(), end)
{
end_const == max_const
} else {
@ -1546,7 +1547,7 @@ pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool
return true;
}
let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id);
if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
if let Some(Constant::Int(v)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
return value == v;
}
false
@ -2490,6 +2491,17 @@ pub fn walk_to_expr_usage<'tcx, T>(
None
}
/// Tokenizes the input while keeping the text associated with each token.
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
let mut pos = 0;
tokenize(s).map(move |t| {
let end = pos + t.len;
let range = pos as usize..end as usize;
pos = end;
(t.kind, s.get(range).unwrap_or_default())
})
}
/// Checks whether a given span has any comment token
/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
@ -2506,23 +2518,11 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
/// Comments are returned wrapped with their relevant delimiters
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
let snippet = sm.span_to_snippet(span).unwrap_or_default();
let mut comments_buf: Vec<String> = Vec::new();
let mut index: usize = 0;
for token in tokenize(&snippet) {
let token_range = index..(index + token.len as usize);
index += token.len as usize;
match token.kind {
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => {
if let Some(comment) = snippet.get(token_range) {
comments_buf.push(comment.to_string());
}
},
_ => (),
}
}
comments_buf.join("\n")
let res = tokenize_with_text(&snippet)
.filter(|(t, _)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
.map(|(_, s)| s)
.join("\n");
res
}
pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {

View File

@ -2,14 +2,64 @@
#![allow(clippy::module_name_repetitions)]
use rustc_data_structures::sync::Lrc;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LintContext};
use rustc_session::Session;
use rustc_span::hygiene;
use rustc_span::source_map::{original_sp, SourceMap};
use rustc_span::{hygiene, SourceFile};
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
use std::borrow::Cow;
use std::ops::Range;
/// A type which can be converted to the range portion of a `Span`.
pub trait SpanRange {
fn into_range(self) -> Range<BytePos>;
}
impl SpanRange for Span {
fn into_range(self) -> Range<BytePos> {
let data = self.data();
data.lo..data.hi
}
}
impl SpanRange for SpanData {
fn into_range(self) -> Range<BytePos> {
self.lo..self.hi
}
}
impl SpanRange for Range<BytePos> {
fn into_range(self) -> Range<BytePos> {
self
}
}
pub struct SourceFileRange {
pub sf: Lrc<SourceFile>,
pub range: Range<usize>,
}
impl SourceFileRange {
/// Attempts to get the text from the source file. This can fail if the source text isn't
/// loaded.
pub fn as_str(&self) -> Option<&str> {
self.sf.src.as_ref().and_then(|x| x.get(self.range.clone()))
}
}
/// Gets the source file, and range in the file, of the given span. Returns `None` if the span
/// extends through multiple files, or is malformed.
pub fn get_source_text(cx: &impl LintContext, sp: impl SpanRange) -> Option<SourceFileRange> {
fn f(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
let start = sm.lookup_byte_offset(sp.start);
let end = sm.lookup_byte_offset(sp.end);
if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
return None;
}
let range = start.pos.to_usize()..end.pos.to_usize();
Some(SourceFileRange { sf: start.sf, range })
}
f(cx.sess().source_map(), sp.into_range())
}
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
pub fn expr_block<T: LintContext>(

View File

@ -1,5 +1,10 @@
//@run-rustfix
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let)]
#![allow(
clippy::assertions_on_constants,
clippy::equatable_if_let,
clippy::nonminimal_bool,
clippy::eq_op
)]
#[rustfmt::skip]
#[warn(clippy::collapsible_if)]

View File

@ -1,5 +1,10 @@
//@run-rustfix
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let)]
#![allow(
clippy::assertions_on_constants,
clippy::equatable_if_let,
clippy::nonminimal_bool,
clippy::eq_op
)]
#[rustfmt::skip]
#[warn(clippy::collapsible_if)]

View File

@ -1,5 +1,5 @@
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:9:5
--> $DIR/collapsible_if.rs:14:5
|
LL | / if x == "hello" {
LL | | if y == "world" {
@ -17,7 +17,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:15:5
--> $DIR/collapsible_if.rs:20:5
|
LL | / if x == "hello" || x == "world" {
LL | | if y == "world" || y == "hello" {
@ -34,7 +34,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:21:5
--> $DIR/collapsible_if.rs:26:5
|
LL | / if x == "hello" && x == "world" {
LL | | if y == "world" || y == "hello" {
@ -51,7 +51,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:27:5
--> $DIR/collapsible_if.rs:32:5
|
LL | / if x == "hello" || x == "world" {
LL | | if y == "world" && y == "hello" {
@ -68,7 +68,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:33:5
--> $DIR/collapsible_if.rs:38:5
|
LL | / if x == "hello" && x == "world" {
LL | | if y == "world" && y == "hello" {
@ -85,7 +85,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:39:5
--> $DIR/collapsible_if.rs:44:5
|
LL | / if 42 == 1337 {
LL | | if 'a' != 'A' {
@ -102,7 +102,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:95:5
--> $DIR/collapsible_if.rs:100:5
|
LL | / if x == "hello" {
LL | | if y == "world" { // Collapsible
@ -119,7 +119,7 @@ LL + }
|
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:154:5
--> $DIR/collapsible_if.rs:159:5
|
LL | / if matches!(true, true) {
LL | | if matches!(true, true) {}
@ -127,7 +127,7 @@ LL | | }
| |_____^ help: collapse nested if block: `if matches!(true, true) && matches!(true, true) {}`
error: this `if` statement can be collapsed
--> $DIR/collapsible_if.rs:159:5
--> $DIR/collapsible_if.rs:164:5
|
LL | / if matches!(true, true) && truth() {
LL | | if matches!(true, true) {}

View File

@ -53,4 +53,84 @@ pub fn name(&self) -> String {
}
}
fn main() {}
macro_rules! m {
(foo) => {};
(bar) => {};
}
macro_rules! foo {
() => {
1
};
}
macro_rules! bar {
() => {
1
};
}
fn main() {
let x = 0;
let _ = match 0 {
0 => {
m!(foo);
x
},
1 => {
m!(bar);
x
},
_ => 1,
};
let _ = match 0 {
0 => {
m!(foo);
0
},
1 => {
m!(bar);
0
},
_ => 1,
};
let _ = match 0 {
0 => {
let mut x = 0;
#[cfg(not_enabled)]
{
x = 5;
}
#[cfg(not(not_enabled))]
{
x = 6;
}
x
},
1 => {
let mut x = 0;
#[cfg(also_not_enabled)]
{
x = 5;
}
#[cfg(not(also_not_enabled))]
{
x = 6;
}
x
},
_ => 0,
};
let _ = match 0 {
0 => foo!(),
1 => bar!(),
_ => 1,
};
let _ = match 0 {
0 => cfg!(not_enabled),
1 => cfg!(also_not_enabled),
_ => false,
};
}

View File

@ -239,4 +239,10 @@ struct Bar {
3 => core::convert::identity::<u32>(todo!()),
_ => 5,
};
let _ = match 0 {
0 => cfg!(not_enable),
1 => cfg!(not_enable),
_ => false,
};
}

View File

@ -192,5 +192,20 @@ note: other arm here
LL | Some(Bar { x: 0, y: 5, .. }) => 1,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 12 previous errors
error: this match arm has an identical body to another arm
--> $DIR/match_same_arms2.rs:245:9
|
LL | 1 => cfg!(not_enable),
| -^^^^^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `1 | 0`
|
= help: or try changing either arm body
note: other arm here
--> $DIR/match_same_arms2.rs:244:9
|
LL | 0 => cfg!(not_enable),
| ^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 13 previous errors

View File

@ -1,5 +1,6 @@
//@run-rustfix
#![warn(clippy::partialeq_to_none)]
#![allow(clippy::eq_op)]
struct Foobar;

View File

@ -1,5 +1,6 @@
//@run-rustfix
#![warn(clippy::partialeq_to_none)]
#![allow(clippy::eq_op)]
struct Foobar;

View File

@ -1,5 +1,5 @@
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:14:8
--> $DIR/partialeq_to_none.rs:15:8
|
LL | if f != None { "yay" } else { "nay" }
| ^^^^^^^^^ help: use `Option::is_some()` instead: `f.is_some()`
@ -7,55 +7,55 @@ LL | if f != None { "yay" } else { "nay" }
= note: `-D clippy::partialeq-to-none` implied by `-D warnings`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:44:13
--> $DIR/partialeq_to_none.rs:45:13
|
LL | let _ = x == None;
| ^^^^^^^^^ help: use `Option::is_none()` instead: `x.is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:45:13
--> $DIR/partialeq_to_none.rs:46:13
|
LL | let _ = x != None;
| ^^^^^^^^^ help: use `Option::is_some()` instead: `x.is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:46:13
--> $DIR/partialeq_to_none.rs:47:13
|
LL | let _ = None == x;
| ^^^^^^^^^ help: use `Option::is_none()` instead: `x.is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:47:13
--> $DIR/partialeq_to_none.rs:48:13
|
LL | let _ = None != x;
| ^^^^^^^^^ help: use `Option::is_some()` instead: `x.is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:49:8
--> $DIR/partialeq_to_none.rs:50:8
|
LL | if foobar() == None {}
| ^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `foobar().is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:51:8
--> $DIR/partialeq_to_none.rs:52:8
|
LL | if bar().ok() != None {}
| ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `bar().ok().is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:53:13
--> $DIR/partialeq_to_none.rs:54:13
|
LL | let _ = Some(1 + 2) != None;
| ^^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `Some(1 + 2).is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:55:13
--> $DIR/partialeq_to_none.rs:56:13
|
LL | let _ = { Some(0) } == None;
| ^^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `{ Some(0) }.is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:57:13
--> $DIR/partialeq_to_none.rs:58:13
|
LL | let _ = {
| _____________^
@ -77,31 +77,31 @@ LL ~ }.is_some();
|
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:67:13
--> $DIR/partialeq_to_none.rs:68:13
|
LL | let _ = optref() == &&None;
| ^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `optref().is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:68:13
--> $DIR/partialeq_to_none.rs:69:13
|
LL | let _ = &&None != optref();
| ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `optref().is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:69:13
--> $DIR/partialeq_to_none.rs:70:13
|
LL | let _ = **optref() == None;
| ^^^^^^^^^^^^^^^^^^ help: use `Option::is_none()` instead: `optref().is_none()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:70:13
--> $DIR/partialeq_to_none.rs:71:13
|
LL | let _ = &None != *optref();
| ^^^^^^^^^^^^^^^^^^ help: use `Option::is_some()` instead: `optref().is_some()`
error: binary comparison to literal `Option::None`
--> $DIR/partialeq_to_none.rs:73:13
--> $DIR/partialeq_to_none.rs:74:13
|
LL | let _ = None != *x;
| ^^^^^^^^^^ help: use `Option::is_some()` instead: `(*x).is_some()`