extern crate rustc_hir;
extern crate rustc_middle;
extern crate rustc_span;
use rustc_hir::{
intravisit::{self, Visitor},
AssignOpKind, BinOpKind, Body, Expr, ExprKind, UnOp,
};
use rustc_middle::ty::{TyCtxt, TypeckResults};
use rustc_span::source_map::SourceMap;
use crate::{LintTarget, Rewrite, RewriteKind};
use crate::classify::classify_cast;
fn is_const_context<'tcx>(tcx: TyCtxt<'tcx>, owner: rustc_hir::def_id::LocalDefId) -> bool {
use rustc_hir::def::DefKind;
match tcx.def_kind(owner) {
DefKind::Const
| DefKind::AssocConst
| DefKind::AnonConst
| DefKind::InlineConst => true,
DefKind::Fn | DefKind::AssocFn => tcx.is_const_fn(owner.to_def_id()),
_ => false,
}
}
pub fn collect_rewrites<'tcx>(tcx: TyCtxt<'tcx>, lint: LintTarget) -> Vec<Rewrite> {
let mut all = Vec::new();
for owner in tcx.hir_body_owners() {
let typeck = tcx.typeck(owner);
let body = tcx.hir_maybe_body_owned_by(owner);
if let Some(body) = body {
let in_const = is_const_context(tcx, owner);
let mut visitor = RewriteVisitor {
tcx,
typeck,
source_map: tcx.sess.source_map(),
lint,
in_const,
skipped_const_cast: false,
rewrites: Vec::new(),
};
visitor.visit_body(body);
if visitor.skipped_const_cast && lint.includes_as_conversions() {
if let Some(rw) = make_allow_const_cast_rewrite(tcx, owner, tcx.sess.source_map()) {
all.push(rw);
}
}
all.extend(visitor.rewrites);
}
}
all
}
fn make_allow_const_cast_rewrite<'tcx>(
tcx: TyCtxt<'tcx>,
owner: rustc_hir::def_id::LocalDefId,
source_map: &SourceMap,
) -> Option<Rewrite> {
let node = tcx.hir_node_by_def_id(owner);
let item_span = node.ident()
.map(|_| tcx.def_span(owner))
.unwrap_or_else(|| tcx.def_span(owner));
let lo = source_map.lookup_byte_offset(item_span.lo());
let file = lo.sf.name.prefer_local_unconditionally().to_string();
let path = std::path::PathBuf::from(&file);
let item_byte = lo.pos.0;
let src = lo.sf.src.as_deref()?;
let check_start = item_byte.saturating_sub(200) as usize;
let check_end = item_byte as usize;
let preceding = &src[check_start..check_end];
if preceding.contains("allow(clippy::as_conversions)") {
return None; }
Some(Rewrite {
file: path,
full_start: item_byte,
full_end: item_byte,
inner_snippet: String::new(),
rhs_snippet: None,
kind: crate::RewriteKind::AllowConstCast,
})
}
struct RewriteVisitor<'tcx, 'sm> {
#[allow(dead_code)]
tcx: TyCtxt<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
source_map: &'sm SourceMap,
lint: LintTarget,
in_const: bool,
skipped_const_cast: bool,
rewrites: Vec<Rewrite>,
}
impl<'tcx> RewriteVisitor<'tcx, '_> {
fn snippet(&self, span: rustc_span::Span) -> Option<String> {
self.source_map.span_to_snippet(span).ok()
}
fn ty_name(ty: rustc_middle::ty::Ty<'tcx>) -> Option<String> {
use rustc_middle::ty::{IntTy, TyKind, UintTy};
let name = match ty.kind() {
TyKind::Int(IntTy::I8) => "i8",
TyKind::Int(IntTy::I16) => "i16",
TyKind::Int(IntTy::I32) => "i32",
TyKind::Int(IntTy::I64) => "i64",
TyKind::Int(IntTy::I128) => "i128",
TyKind::Int(IntTy::Isize) => "isize",
TyKind::Uint(UintTy::U8) => "u8",
TyKind::Uint(UintTy::U16) => "u16",
TyKind::Uint(UintTy::U32) => "u32",
TyKind::Uint(UintTy::U64) => "u64",
TyKind::Uint(UintTy::U128) => "u128",
TyKind::Uint(UintTy::Usize) => "usize",
_ => return None, };
Some(name.to_string())
}
fn span_to_file_and_offsets(
&self,
span: rustc_span::Span,
) -> Option<(std::path::PathBuf, u32, u32)> {
let lo = self.source_map.lookup_byte_offset(span.lo());
let hi = self.source_map.lookup_byte_offset(span.hi());
let file = lo.sf.name.prefer_local_unconditionally().to_string();
let path = std::path::PathBuf::from(file);
Some((path, lo.pos.0, hi.pos.0))
}
fn try_cast_rewrite(&mut self, expr: &Expr<'tcx>) {
let ExprKind::Cast(inner, _hir_ty) = expr.kind else { return };
if expr.span.from_expansion() || inner.span.from_expansion() {
return;
}
if self.in_const {
let src_ty = self.typeck.expr_ty(inner);
let dst_ty = self.typeck.expr_ty(expr);
if classify_cast(src_ty, dst_ty).is_some() {
self.skipped_const_cast = true;
}
return;
}
let src_ty = self.typeck.expr_ty(inner);
let dst_ty = self.typeck.expr_ty(expr);
let Some(kind) = classify_cast(src_ty, dst_ty) else { return };
let Some(inner_snippet) = self.snippet(inner.span) else { return };
let Some((file, full_start, full_end)) = self.span_to_file_and_offsets(expr.span) else { return };
self.rewrites.push(Rewrite {
file,
full_start,
full_end,
inner_snippet,
rhs_snippet: None,
kind,
});
}
fn try_binop_rewrite(&mut self, expr: &Expr<'tcx>) {
let ExprKind::Binary(op, lhs, rhs) = expr.kind else { return };
if expr.span.from_expansion() {
return;
}
let method = match op.node {
BinOpKind::Add => "wrapping_add",
BinOpKind::Sub => "wrapping_sub",
BinOpKind::Mul => "wrapping_mul",
BinOpKind::Div => "wrapping_div",
BinOpKind::Rem => "wrapping_rem",
_ => return,
};
let lhs_ty = self.typeck.expr_ty(lhs);
if !lhs_ty.is_integral() {
return;
}
let Some(lhs_ty_name) = Self::ty_name(lhs_ty) else { return };
let Some(inner_snippet) = self.snippet(lhs.span) else { return };
let Some(rhs_snippet) = self.snippet(rhs.span) else { return };
let Some((file, full_start, full_end)) = self.span_to_file_and_offsets(expr.span) else { return };
self.rewrites.push(Rewrite {
file,
full_start,
full_end,
inner_snippet,
rhs_snippet: Some(rhs_snippet),
kind: RewriteKind::WrappingBinop { method, lhs_ty: lhs_ty_name },
});
}
fn try_assign_op_rewrite(&mut self, expr: &Expr<'tcx>) {
let ExprKind::AssignOp(op, lhs, rhs) = expr.kind else { return };
if expr.span.from_expansion() {
return;
}
let method = match op.node {
AssignOpKind::AddAssign => "wrapping_add",
AssignOpKind::SubAssign => "wrapping_sub",
AssignOpKind::MulAssign => "wrapping_mul",
AssignOpKind::DivAssign => "wrapping_div",
AssignOpKind::RemAssign => "wrapping_rem",
_ => return,
};
let lhs_ty = self.typeck.expr_ty(lhs);
if !lhs_ty.is_integral() {
return;
}
if Self::ty_name(lhs_ty).is_none() { return; }
let Some(lhs_snippet) = self.snippet(lhs.span) else { return };
let Some(rhs_snippet) = self.snippet(rhs.span) else { return };
let Some((file, full_start, full_end)) = self.span_to_file_and_offsets(expr.span) else { return };
self.rewrites.push(Rewrite {
file,
full_start,
full_end,
inner_snippet: lhs_snippet.clone(),
rhs_snippet: Some(rhs_snippet),
kind: RewriteKind::WrappingAssignOp {
method,
lhs_snippet,
},
});
}
fn try_neg_rewrite(&mut self, expr: &Expr<'tcx>) {
let ExprKind::Unary(UnOp::Neg, operand) = expr.kind else { return };
if expr.span.from_expansion() {
return;
}
let ty = self.typeck.expr_ty(operand);
if !ty.is_integral() {
return;
}
let Some(operand_ty_name) = Self::ty_name(ty) else { return };
let Some(inner_snippet) = self.snippet(operand.span) else { return };
let Some((file, full_start, full_end)) = self.span_to_file_and_offsets(expr.span) else { return };
self.rewrites.push(Rewrite {
file,
full_start,
full_end,
inner_snippet,
rhs_snippet: None,
kind: RewriteKind::WrappingNeg { operand_ty: operand_ty_name },
});
}
}
impl<'tcx> Visitor<'tcx> for RewriteVisitor<'tcx, '_> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.lint.includes_as_conversions() {
if matches!(expr.kind, ExprKind::Cast(..)) {
self.try_cast_rewrite(expr);
}
}
if self.lint.includes_arithmetic() {
match expr.kind {
ExprKind::Binary(..) => self.try_binop_rewrite(expr),
ExprKind::AssignOp(..) => self.try_assign_op_rewrite(expr),
ExprKind::Unary(UnOp::Neg, _) => self.try_neg_rewrite(expr),
_ => {}
}
}
intravisit::walk_expr(self, expr);
}
fn visit_body(&mut self, body: &Body<'tcx>) {
intravisit::walk_body(self, body);
}
}