Rename eval_simple to eval_local and take the SyntaxContext as an argument.

This commit is contained in:
Jason Newcomb 2025-08-23 04:33:46 -04:00
parent f08486481f
commit 3e24d50407
21 changed files with 134 additions and 82 deletions

View file

@ -50,7 +50,8 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
_ => return,
}
&& let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn)
&& let Some((Constant::Bool(assert_val), const_src)) = ConstEvalCtxt::new(cx).eval_with_source(condition)
&& let Some((Constant::Bool(assert_val), const_src)) =
ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt())
&& let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id)
&& (const_src.is_local() || !in_const_context)
&& !(is_debug && as_bool_lit(condition) == Some(false))

View file

@ -11,6 +11,7 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use rustc_span::source_map::Spanned;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
@ -517,8 +518,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
_ => false,
}
} else {
@ -530,8 +531,8 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
_ => false,
}
} else {
@ -540,8 +541,8 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
}
/// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match ConstEvalCtxt::new(cx).eval_simple(expr) {
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
Some(Int(i)) => i == 0,
Some(F32(f)) => f == 0.0,
Some(F64(f)) => f == 0.0,

View file

@ -60,10 +60,14 @@ impl LateLintPass<'_> for IfNotElse {
),
// Don't lint on `… != 0`, as these are likely to be bit tests.
// For example, `if foo & 0x0F00 != 0 { … } else { … }` is already in the "proper" order.
ExprKind::Binary(op, _, rhs) if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs) => (
"unnecessary `!=` operation",
"change to `==` and swap the blocks of the `if`/`else`",
),
ExprKind::Binary(op, _, rhs)
if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs, e.span.ctxt()) =>
{
(
"unnecessary `!=` operation",
"change to `==` and swap the blocks of the `if`/`else`",
)
},
_ => return,
};

View file

@ -101,7 +101,7 @@ fn upcast_comparison_bounds_err<'tcx>(
invert: bool,
) {
if let Some((lb, ub)) = lhs_bounds
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs)
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt())
{
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {

View file

@ -8,6 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@ -58,13 +59,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
&& add_lhs.span.ctxt() == ctxt
&& add_rhs.span.ctxt() == ctxt
&& !expr.span.in_external_macro(cx.sess().source_map())
&& let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
&& let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs)
&& let Some(const1) = check_for_unsigned_int_constant(cx, ctxt, rem_rhs)
&& let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, ctxt, add_lhs, add_rhs)
&& let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind
&& rem2_op.node == BinOpKind::Rem
&& const1 == const2
&& let Some(hir_id) = path_to_local(rem2_lhs)
&& let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs)
&& let Some(const3) = check_for_unsigned_int_constant(cx, ctxt, rem2_rhs)
// Also ensures the const is nonzero since zero can't be a divisor
&& const2 == const3
&& rem2_lhs.span.ctxt() == ctxt
@ -103,16 +104,21 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
// constant along with the other expression unchanged if so
fn check_for_either_unsigned_int_constant<'a>(
cx: &'a LateContext<'_>,
ctxt: SyntaxContext,
left: &'a Expr<'_>,
right: &'a Expr<'_>,
) -> Option<(u128, &'a Expr<'a>)> {
check_for_unsigned_int_constant(cx, left)
check_for_unsigned_int_constant(cx, ctxt, left)
.map(|int_const| (int_const, right))
.or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
.or_else(|| check_for_unsigned_int_constant(cx, ctxt, right).map(|int_const| (int_const, left)))
}
fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr)?;
fn check_for_unsigned_int_constant<'a>(
cx: &'a LateContext<'_>,
ctxt: SyntaxContext,
expr: &'a Expr<'_>,
) -> Option<u128> {
let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr, ctxt)?;
match int_const {
FullInt::S(s) => s.try_into().ok(),
FullInt::U(u) => Some(u),

View file

@ -155,7 +155,7 @@ fn handle(
&& cx.typeck_results().expr_adjustments(body_some).is_empty()
&& let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx)
&& let Some(indent) = indent_of(cx, expr.span)
&& ConstEvalCtxt::new(cx).eval_simple(body_none).is_some()
&& ConstEvalCtxt::new(cx).eval_local(body_none, expr.span.ctxt()).is_some()
{
let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent));
let mut app = Applicability::MachineApplicable;

View file

@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
return;
}
if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix) {
if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix, expr.span.ctxt()) {
let (num, replacement) = match radix_val {
FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),

View file

@ -25,8 +25,9 @@ pub(super) fn check<'tcx>(
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(id)
&& matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max)
{
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)
let ctxt = expr.span.ctxt();
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv, ctxt)
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg, ctxt)
{
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
return;

View file

@ -4,6 +4,7 @@ use clippy_utils::{is_trait_method, sym};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use std::cmp::Ordering::{Equal, Greater, Less};
declare_clippy_lint! {
@ -68,8 +69,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
.qpath_res(qpath, path.hir_id)
.opt_def_id()
.and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min),
Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max),
Some(sym::cmp_min) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Min),
Some(sym::cmp_max) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Max),
_ => None,
})
} else {
@ -79,8 +80,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
ExprKind::MethodCall(path, receiver, args @ [_], _) => {
if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) {
match path.ident.name {
sym::max => fetch_const(cx, Some(receiver), args, MinMax::Max),
sym::min => fetch_const(cx, Some(receiver), args, MinMax::Min),
sym::max => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Max),
sym::min => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Min),
_ => None,
}
} else {
@ -93,6 +94,7 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
fn fetch_const<'a, 'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
receiver: Option<&'a Expr<'a>>,
args: &'a [Expr<'a>],
m: MinMax,
@ -104,7 +106,7 @@ fn fetch_const<'a, 'tcx>(
return None;
}
let ecx = ConstEvalCtxt::new(cx);
match (ecx.eval_simple(first_arg), ecx.eval_simple(second_arg)) {
match (ecx.eval_local(first_arg, ctxt), ecx.eval_local(second_arg, ctxt)) {
(Some(c), None) => Some((m, c, second_arg)),
(None, Some(c)) => Some((m, c, first_arg)),
// otherwise ignore

View file

@ -190,7 +190,7 @@ impl ArithmeticSideEffects {
lhs: &'tcx hir::Expr<'_>,
rhs: &'tcx hir::Expr<'_>,
) {
if ConstEvalCtxt::new(cx).eval_simple(expr).is_some() {
if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_some() {
return;
}
if !matches!(
@ -283,7 +283,7 @@ impl ArithmeticSideEffects {
let Some(arg) = args.first() else {
return;
};
if ConstEvalCtxt::new(cx).eval_simple(receiver).is_some() {
if ConstEvalCtxt::new(cx).eval_local(receiver, expr.span.ctxt()).is_some() {
return;
}
let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver);

View file

@ -39,7 +39,9 @@ fn check_op<'tcx>(
other: &Expr<'tcx>,
parent: &Expr<'tcx>,
) {
if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_simple(op) == Some(Constant::Int(0)) {
if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_local(op, parent.span.ctxt())
== Some(Constant::Int(0))
{
if different_types(tck, other, parent) {
return;
}

View file

@ -18,12 +18,13 @@ pub(crate) fn check<'tcx>(
) {
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) {
let ecx = ConstEvalCtxt::new(cx);
let left_is_local = match ecx.eval_with_source(left) {
let ctxt = expr.span.ctxt();
let left_is_local = match ecx.eval_with_source(left, ctxt) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,
None => true,
};
let right_is_local = match ecx.eval_with_source(right) {
let right_is_local = match ecx.eval_with_source(right, ctxt) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,
None => true,

View file

@ -7,7 +7,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, kw};
use rustc_span::{Span, SyntaxContext, kw};
use super::IDENTITY_OP;
@ -41,42 +41,43 @@ pub(crate) fn check<'tcx>(
(span, is_coerced)
};
let ctxt = expr.span.ctxt();
match op {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
if is_redundant_op(cx, left, 0) {
if is_redundant_op(cx, left, 0, ctxt) {
let paren = needs_parenthesis(cx, expr, right);
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
} else if is_redundant_op(cx, right, 0) {
} else if is_redundant_op(cx, right, 0, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
if is_redundant_op(cx, right, 0) {
if is_redundant_op(cx, right, 0, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::Mul => {
if is_redundant_op(cx, left, 1) {
if is_redundant_op(cx, left, 1, ctxt) {
let paren = needs_parenthesis(cx, expr, right);
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
} else if is_redundant_op(cx, right, 1) {
} else if is_redundant_op(cx, right, 1, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::Div => {
if is_redundant_op(cx, right, 1) {
if is_redundant_op(cx, right, 1, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
},
BinOpKind::BitAnd => {
if is_redundant_op(cx, left, -1) {
if is_redundant_op(cx, left, -1, ctxt) {
let paren = needs_parenthesis(cx, expr, right);
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
} else if is_redundant_op(cx, right, -1) {
} else if is_redundant_op(cx, right, -1, ctxt) {
let paren = needs_parenthesis(cx, expr, left);
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
}
@ -184,14 +185,17 @@ fn is_allowed<'tcx>(
// This lint applies to integers and their references
cx.typeck_results().expr_ty(left).peel_refs().is_integral()
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
// `1 << 0` is a common pattern in bit manipulation code
&& !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1))
&& !(cmp == BinOpKind::Shl
&& is_zero_integer_const(cx, right, expr.span.ctxt())
&& integer_const(cx, left, expr.span.ctxt()) == Some(1))
}
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
let ecx = ConstEvalCtxt::new(cx);
if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) {
let ctxt = span.ctxt();
if match (ecx.eval_full_int(left, ctxt), ecx.eval_full_int(right, ctxt)) {
(Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
_ => return,
@ -200,8 +204,8 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
}
}
fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool {
if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) {
fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, ctxt: SyntaxContext) -> bool {
if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_local(e, ctxt).map(Constant::peel_refs) {
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
ty::Uint(uty) => clip(cx.tcx, !0, uty),

View file

@ -13,14 +13,14 @@ use super::MANUAL_IS_MULTIPLE_OF;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'_>,
expr: &'tcx Expr<'tcx>,
op: BinOpKind,
lhs: &'tcx Expr<'tcx>,
rhs: &'tcx Expr<'tcx>,
msrv: Msrv,
) {
if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF)
&& let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs)
&& let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs)
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
&& operand_op.node == BinOpKind::Rem
&& matches!(
@ -57,18 +57,19 @@ pub(super) fn check<'tcx>(
// If we have a `x == 0`, `x != 0` or `x > 0` (or the reverted ones), return the non-zero operand
fn uint_compare_to_zero<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'tcx>,
op: BinOpKind,
lhs: &'tcx Expr<'tcx>,
rhs: &'tcx Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
let operand = if matches!(lhs.kind, ExprKind::Binary(..))
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Gt)
&& is_zero_integer_const(cx, rhs)
&& is_zero_integer_const(cx, rhs, e.span.ctxt())
{
lhs
} else if matches!(rhs.kind, ExprKind::Binary(..))
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt)
&& is_zero_integer_const(cx, lhs)
&& is_zero_integer_const(cx, lhs, e.span.ctxt())
{
rhs
} else {

View file

@ -55,7 +55,7 @@ impl Context {
return;
}
let ty = cx.typeck_results().expr_ty(arg);
if ConstEvalCtxt::new(cx).eval_simple(expr).is_none() && ty.is_floating_point() {
if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_none() && ty.is_floating_point() {
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
self.expr_id = Some(expr.hir_id);
}

View file

@ -37,8 +37,9 @@ impl<'tcx> LateLintPass<'tcx> for ZeroDiv {
// That's probably fine for this lint - it's pretty unlikely that someone would
// do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too.
&& let ecx = ConstEvalCtxt::new(cx)
&& let Some(lhs_value) = ecx.eval_simple(left)
&& let Some(rhs_value) = ecx.eval_simple(right)
&& let ctxt = expr.span.ctxt()
&& let Some(lhs_value) = ecx.eval_local(left, ctxt)
&& let Some(rhs_value) = ecx.eval_local(right, ctxt)
// FIXME(f16_f128): add these types when eq is available on all platforms
&& (Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value)
&& (Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value)

View file

@ -387,6 +387,7 @@ pub struct ConstEvalCtxt<'tcx> {
typing_env: ty::TypingEnv<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
source: Cell<ConstantSource>,
ctxt: Cell<SyntaxContext>,
}
impl<'tcx> ConstEvalCtxt<'tcx> {
@ -398,6 +399,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
typing_env: cx.typing_env(),
typeck: cx.typeck_results(),
source: Cell::new(ConstantSource::Local),
ctxt: Cell::new(SyntaxContext::root()),
}
}
@ -408,13 +410,15 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
typing_env,
typeck,
source: Cell::new(ConstantSource::Local),
ctxt: Cell::new(SyntaxContext::root()),
}
}
/// Attempts to evaluate the expression and returns both the value and whether it's dependant on
/// other items.
pub fn eval_with_source(&self, e: &Expr<'_>) -> Option<(Constant<'tcx>, ConstantSource)> {
pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant<'tcx>, ConstantSource)> {
self.source.set(ConstantSource::Local);
self.ctxt.set(ctxt);
self.expr(e).map(|c| (c, self.source.get()))
}
@ -424,16 +428,16 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
}
/// Attempts to evaluate the expression without accessing other items.
pub fn eval_simple(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
match self.eval_with_source(e) {
pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<Constant<'tcx>> {
match self.eval_with_source(e, ctxt) {
Some((x, ConstantSource::Local)) => Some(x),
_ => None,
}
}
/// Attempts to evaluate the expression as an integer without accessing other items.
pub fn eval_full_int(&self, e: &Expr<'_>) -> Option<FullInt> {
match self.eval_with_source(e) {
pub fn eval_full_int(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<FullInt> {
match self.eval_with_source(e, ctxt) {
Some((x, ConstantSource::Local)) => x.int_value(self.tcx, self.typeck.expr_ty(e)),
_ => None,
}
@ -455,6 +459,14 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
}
}
fn check_ctxt(&self, ctxt: SyntaxContext) {
self.source.set(if self.ctxt.get() != ctxt {
ConstantSource::Constant
} else {
self.source.get()
});
}
fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option<Constant<'tcx>> {
let is_core_crate = if let Some(def_id) = self.typeck.qpath_res(qpath, hir_id).opt_def_id() {
self.tcx.crate_name(def_id.krate) == sym::core
@ -477,17 +489,18 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
/// Simple constant folding: Insert an expression, get a constant or none.
fn expr(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
self.check_ctxt(e.span.ctxt());
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir_body(body).value),
ExprKind::DropTemps(e) => self.expr(e),
ExprKind::Path(ref qpath) => self.qpath(qpath, e.hir_id),
ExprKind::Block(block, _) => self.block(block),
ExprKind::Block(block, _) => {
self.check_ctxt(block.span.ctxt());
self.block(block)
},
ExprKind::Lit(lit) => {
if is_direct_expn_of(e.span, sym::cfg).is_some() {
None
} else {
Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e)))
}
self.check_ctxt(lit.span.ctxt());
Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e)))
},
ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec),
ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple),
@ -504,7 +517,10 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }),
}),
ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
ExprKind::Binary(op, left, right) => self.binop(op.node, left, right),
ExprKind::Binary(op, left, right) => {
self.check_ctxt(e.span.ctxt());
self.binop(op.node, left, right)
},
ExprKind::Call(callee, []) => {
// We only handle a few const functions for now.
if let ExprKind::Path(qpath) = &callee.kind
@ -525,6 +541,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
ExprKind::Index(arr, index, _) => self.index(arr, index),
ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
ExprKind::Field(local_expr, ref field) => {
self.check_ctxt(field.span.ctxt());
let result = self.expr(local_expr);
if let Some(Constant::Adt(constant)) = &self.expr(local_expr)
&& let ty::Adt(adt_def, _) = constant.ty().kind()
@ -958,8 +975,8 @@ fn field_of_struct<'tcx>(
}
/// If `expr` evaluates to an integer constant, return its value.
pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_simple(expr) {
pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option<u128> {
if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
Some(value)
} else {
None
@ -968,6 +985,6 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
/// Check if `expr` evaluates to an integer constant of 0.
#[inline]
pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
integer_const(cx, expr) == Some(0)
pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
integer_const(cx, expr, ctxt) == Some(0)
}

View file

@ -454,7 +454,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
return Some(VecInitKind::Default);
} else if name.ident.name == sym::with_capacity {
let arg = args.first()?;
return match ConstEvalCtxt::new(cx).eval_simple(arg) {
return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
_ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
};

View file

@ -290,8 +290,10 @@ impl HirEqInterExpr<'_, '_, '_> {
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results
&& typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right)
&& let (Some(l), Some(r)) = (
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs).eval_simple(left),
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs).eval_simple(right),
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs)
.eval_local(left, self.left_ctxt),
ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs)
.eval_local(right, self.right_ctxt),
)
&& l == r
{
@ -842,7 +844,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
#[expect(clippy::too_many_lines)]
pub fn hash_expr(&mut self, e: &Expr<'_>) {
let simple_const = self.maybe_typeck_results.and_then(|typeck_results| {
ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_simple(e)
ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_local(e, e.span.ctxt())
});
// const hashing may result in the same hash as some unrelated node, so add a sort of

View file

@ -43,6 +43,7 @@ fn main() {
assert_const!(-1);
assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf")));
//~^ assertions_on_constants
let flag: bool = cfg!(not(feature = "asdf"));
assert!(flag);

View file

@ -72,8 +72,16 @@ LL | debug_assert!(true);
|
= help: remove the assertion
error: this assertion has a constant value
--> tests/ui/assertions_on_constants.rs:45:5
|
LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf")));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: consider moving this into a const block: `const { assert!(..) }`
error: this assertion is always `true`
--> tests/ui/assertions_on_constants.rs:53:19
--> tests/ui/assertions_on_constants.rs:54:19
|
LL | const _: () = assert!(true);
| ^^^^^^^^^^^^^
@ -81,7 +89,7 @@ LL | const _: () = assert!(true);
= help: remove the assertion
error: this assertion is always `true`
--> tests/ui/assertions_on_constants.rs:56:5
--> tests/ui/assertions_on_constants.rs:57:5
|
LL | assert!(8 == (7 + 1));
| ^^^^^^^^^^^^^^^^^^^^^
@ -89,7 +97,7 @@ LL | assert!(8 == (7 + 1));
= help: remove the assertion
error: this assertion is always `true`
--> tests/ui/assertions_on_constants.rs:67:5
--> tests/ui/assertions_on_constants.rs:68:5
|
LL | assert!(true);
| ^^^^^^^^^^^^^
@ -97,7 +105,7 @@ LL | assert!(true);
= help: remove the assertion
error: this assertion is always `true`
--> tests/ui/assertions_on_constants.rs:70:5
--> tests/ui/assertions_on_constants.rs:71:5
|
LL | assert!(8 == (7 + 1));
| ^^^^^^^^^^^^^^^^^^^^^
@ -105,7 +113,7 @@ LL | assert!(8 == (7 + 1));
= help: remove the assertion
error: this assertion has a constant value
--> tests/ui/assertions_on_constants.rs:78:5
--> tests/ui/assertions_on_constants.rs:79:5
|
LL | assert!(C);
| ^^^^^^^^^^
@ -113,7 +121,7 @@ LL | assert!(C);
= help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }`
error: this assertion has a constant value
--> tests/ui/assertions_on_constants.rs:89:5
--> tests/ui/assertions_on_constants.rs:90:5
|
LL | assert!(C);
| ^^^^^^^^^^
@ -121,12 +129,12 @@ LL | assert!(C);
= help: consider moving this into a const block: `const { assert!(..) }`
error: this assertion has a constant value
--> tests/ui/assertions_on_constants.rs:95:5
--> tests/ui/assertions_on_constants.rs:96:5
|
LL | assert!(C);
| ^^^^^^^^^^
|
= help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }`
error: aborting due to 16 previous errors
error: aborting due to 17 previous errors