Improve SpanlessEq

* Don't consider expansions of different macros to be the same, even if they expand to the same tokens
* Don't consider `cfg!` expansions to be equal if they check different configs.
This commit is contained in:
Jason Newcomb 2023-03-10 10:43:04 -05:00
parent 5351170744
commit 58132cb3b0
12 changed files with 135 additions and 34 deletions

View file

@ -1,6 +1,6 @@
#![allow(clippy::float_cmp)]
use crate::source::{span_source_range, walk_span_to_context};
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};
@ -516,7 +516,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
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) = span_source_range(self.lcx, span.lo..expr_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};

View file

@ -14,7 +14,7 @@ use rustc_hir::{
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::LateContext;
use rustc_middle::ty::TypeckResults;
use rustc_span::{sym, BytePos, Symbol, SyntaxContext};
use rustc_span::{sym, BytePos, ExpnKind, MacroKind, Symbol, SyntaxContext};
use std::hash::{Hash, Hasher};
use std::ops::Range;
@ -67,6 +67,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
HirEqInterExpr {
inner: self,
left_ctxt: SyntaxContext::root(),
right_ctxt: SyntaxContext::root(),
locals: HirIdMap::default(),
}
}
@ -94,6 +96,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
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 }`,
@ -128,6 +132,7 @@ impl HirEqInterExpr<'_, '_, '_> {
}
/// Checks whether two blocks are the same.
#[expect(clippy::similar_names)]
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
use TokenKind::{BlockComment, LineComment, Semi, Whitespace};
if left.stmts.len() != right.stmts.len() {
@ -166,7 +171,8 @@ impl HirEqInterExpr<'_, '_, '_> {
// Can happen when macros expand to multiple statements, or rearrange statements.
// Nothing in between the statements to check in this case.
continue;
} else if lstmt_span.lo < lstart || rstmt_span.lo < rstart {
}
if lstmt_span.lo < lstart || rstmt_span.lo < rstart {
// Only one of the blocks had a weird macro.
return false;
}
@ -243,7 +249,7 @@ impl HirEqInterExpr<'_, '_, '_> {
#[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;
}
@ -476,6 +482,45 @@ impl HirEqInterExpr<'_, '_, '_> {
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`
@ -1075,6 +1120,7 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
h.finish()
}
#[expect(clippy::similar_names)]
fn eq_span_tokens(
cx: &LateContext<'_>,
left: impl SpanRange,

View file

@ -1499,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 {
@ -1515,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 {