Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
3d60241841
215 changed files with 7469 additions and 1950 deletions
|
|
@ -83,9 +83,9 @@ pub fn span_lint_and_help<T: LintContext>(
|
|||
cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
|
||||
let help = help.to_string();
|
||||
if let Some(help_span) = help_span {
|
||||
diag.span_help(help_span, help.to_string());
|
||||
diag.span_help(help_span, help);
|
||||
} else {
|
||||
diag.help(help.to_string());
|
||||
diag.help(help);
|
||||
}
|
||||
docs_link(diag, lint);
|
||||
diag
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ impl<'hir> IfLet<'hir> {
|
|||
}
|
||||
|
||||
/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
|
||||
#[derive(Debug)]
|
||||
pub enum IfLetOrMatch<'hir> {
|
||||
/// Any `match` expression
|
||||
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
|
||||
|
|
|
|||
|
|
@ -252,15 +252,15 @@ impl HirEqInterExpr<'_, '_, '_> {
|
|||
return false;
|
||||
}
|
||||
|
||||
if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
|
||||
if let (Some(l), Some(r)) = (
|
||||
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)) = (
|
||||
constant_simple(self.inner.cx, typeck_lhs, left),
|
||||
constant_simple(self.inner.cx, typeck_rhs, right),
|
||||
) {
|
||||
if l == r {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
)
|
||||
&& l == r
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let is_eq = match (
|
||||
|
|
@ -494,10 +494,13 @@ impl HirEqInterExpr<'_, '_, '_> {
|
|||
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 { .. })
|
||||
}))
|
||||
|| (matches!(
|
||||
left_data.kind,
|
||||
ExpnKind::Macro(MacroKind::Bang, name)
|
||||
if name == sym::cfg || name == sym::option_env
|
||||
) && !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;
|
||||
|
|
|
|||
|
|
@ -89,21 +89,21 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
|
|||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
|
||||
use rustc_hir::{
|
||||
self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr,
|
||||
ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local,
|
||||
MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
|
||||
TraitItem, TraitItemRef, TraitRef, TyKind, UnOp,
|
||||
ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item,
|
||||
ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
|
||||
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
|
||||
};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||
use rustc_middle::hir::place::PlaceBase;
|
||||
use rustc_middle::mir::ConstantKind;
|
||||
use rustc_middle::ty as rustc_ty;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
|
||||
use rustc_middle::ty::binding::BindingMode;
|
||||
use rustc_middle::ty::fast_reject::SimplifiedType;
|
||||
use rustc_middle::ty::layout::IntegerExt;
|
||||
use rustc_middle::ty::{
|
||||
BorrowKind, ClosureKind, FloatTy, IntTy, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture,
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut,
|
||||
TypeVisitableExt, UintTy, UpvarCapture,
|
||||
};
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::source_map::SourceMap;
|
||||
|
|
@ -113,7 +113,10 @@ use rustc_target::abi::Integer;
|
|||
|
||||
use crate::consts::{constant, miri_to_const, Constant};
|
||||
use crate::higher::Range;
|
||||
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
|
||||
use crate::ty::{
|
||||
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
|
||||
ty_is_fn_once_param,
|
||||
};
|
||||
use crate::visitors::for_each_expr;
|
||||
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
|
@ -2445,6 +2448,17 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
|||
.any(is_cfg_test)
|
||||
}
|
||||
|
||||
/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
|
||||
pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
|
||||
let hir = tcx.hir();
|
||||
|
||||
tcx.has_attr(def_id, sym::cfg)
|
||||
|| hir
|
||||
.parent_iter(hir.local_def_id_to_hir_id(def_id))
|
||||
.flat_map(|(parent_id, _)| hir.attrs(parent_id))
|
||||
.any(|attr| attr.has_name(sym::cfg))
|
||||
}
|
||||
|
||||
/// Checks whether item either has `test` attribute applied, or
|
||||
/// is a module with `test` in its name.
|
||||
///
|
||||
|
|
@ -2499,6 +2513,262 @@ pub fn walk_to_expr_usage<'tcx, T>(
|
|||
None
|
||||
}
|
||||
|
||||
/// A type definition as it would be viewed from within a function.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DefinedTy<'tcx> {
|
||||
// Used for locals and closures defined within the function.
|
||||
Hir(&'tcx hir::Ty<'tcx>),
|
||||
/// Used for function signatures, and constant and static values. This includes the `ParamEnv`
|
||||
/// from the definition site.
|
||||
Mir(ParamEnvAnd<'tcx, Binder<'tcx, Ty<'tcx>>>),
|
||||
}
|
||||
|
||||
/// The context an expressions value is used in.
|
||||
pub struct ExprUseCtxt<'tcx> {
|
||||
/// The parent node which consumes the value.
|
||||
pub node: ExprUseNode<'tcx>,
|
||||
/// Any adjustments applied to the type.
|
||||
pub adjustments: &'tcx [Adjustment<'tcx>],
|
||||
/// Whether or not the type must unify with another code path.
|
||||
pub is_ty_unified: bool,
|
||||
/// Whether or not the value will be moved before it's used.
|
||||
pub moved_before_use: bool,
|
||||
}
|
||||
|
||||
/// The node which consumes a value.
|
||||
pub enum ExprUseNode<'tcx> {
|
||||
/// Assignment to, or initializer for, a local
|
||||
Local(&'tcx Local<'tcx>),
|
||||
/// Initializer for a const or static item.
|
||||
ConstStatic(OwnerId),
|
||||
/// Implicit or explicit return from a function.
|
||||
Return(OwnerId),
|
||||
/// Initialization of a struct field.
|
||||
Field(&'tcx ExprField<'tcx>),
|
||||
/// An argument to a function.
|
||||
FnArg(&'tcx Expr<'tcx>, usize),
|
||||
/// An argument to a method.
|
||||
MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
|
||||
/// The callee of a function call.
|
||||
Callee,
|
||||
/// Access of a field.
|
||||
FieldAccess(Ident),
|
||||
}
|
||||
impl<'tcx> ExprUseNode<'tcx> {
|
||||
/// Checks if the value is returned from the function.
|
||||
pub fn is_return(&self) -> bool {
|
||||
matches!(self, Self::Return(_))
|
||||
}
|
||||
|
||||
/// Checks if the value is used as a method call receiver.
|
||||
pub fn is_recv(&self) -> bool {
|
||||
matches!(self, Self::MethodArg(_, _, 0))
|
||||
}
|
||||
|
||||
/// Gets the needed type as it's defined without any type inference.
|
||||
pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
|
||||
match *self {
|
||||
Self::Local(Local { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
|
||||
Self::ConstStatic(id) => Some(DefinedTy::Mir(
|
||||
cx.param_env
|
||||
.and(Binder::dummy(cx.tcx.type_of(id).instantiate_identity())),
|
||||
)),
|
||||
Self::Return(id) => {
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(id.def_id);
|
||||
if let Some(Node::Expr(Expr {
|
||||
kind: ExprKind::Closure(c),
|
||||
..
|
||||
})) = cx.tcx.hir().find(hir_id)
|
||||
{
|
||||
match c.fn_decl.output {
|
||||
FnRetTy::DefaultReturn(_) => None,
|
||||
FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
|
||||
}
|
||||
} else {
|
||||
Some(DefinedTy::Mir(
|
||||
cx.param_env.and(cx.tcx.fn_sig(id).instantiate_identity().output()),
|
||||
))
|
||||
}
|
||||
},
|
||||
Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
|
||||
Some(Expr {
|
||||
hir_id,
|
||||
kind: ExprKind::Struct(path, ..),
|
||||
..
|
||||
}) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
|
||||
.and_then(|(adt, variant)| {
|
||||
variant
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.name == field.ident.name)
|
||||
.map(|f| (adt, f))
|
||||
})
|
||||
.map(|(adt, field_def)| {
|
||||
DefinedTy::Mir(
|
||||
cx.tcx
|
||||
.param_env(adt.did())
|
||||
.and(Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity())),
|
||||
)
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
Self::FnArg(callee, i) => {
|
||||
let sig = expr_sig(cx, callee)?;
|
||||
let (hir_ty, ty) = sig.input_with_hir(i)?;
|
||||
Some(match hir_ty {
|
||||
Some(hir_ty) => DefinedTy::Hir(hir_ty),
|
||||
None => DefinedTy::Mir(
|
||||
sig.predicates_id()
|
||||
.map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id))
|
||||
.and(ty),
|
||||
),
|
||||
})
|
||||
},
|
||||
Self::MethodArg(id, _, i) => {
|
||||
let id = cx.typeck_results().type_dependent_def_id(id)?;
|
||||
let sig = cx.tcx.fn_sig(id).skip_binder();
|
||||
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
|
||||
},
|
||||
Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the context an expression's value is used in.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
|
||||
let mut adjustments = [].as_slice();
|
||||
let mut is_ty_unified = false;
|
||||
let mut moved_before_use = false;
|
||||
let ctxt = e.span.ctxt();
|
||||
walk_to_expr_usage(cx, e, &mut |parent, child_id| {
|
||||
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
|
||||
if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) {
|
||||
adjustments = cx.typeck_results().expr_adjustments(e);
|
||||
}
|
||||
match parent {
|
||||
Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Local(l),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Static(..) | ItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::ConstStatic(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
|
||||
Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Field(field),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
|
||||
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
|
||||
ExprKind::Ret(_) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId {
|
||||
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
||||
}),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Closure(closure) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Call(func, args) => Some(ExprUseCtxt {
|
||||
node: match args.iter().position(|arg| arg.hir_id == child_id) {
|
||||
Some(i) => ExprUseNode::FnArg(func, i),
|
||||
None => ExprUseNode::Callee,
|
||||
},
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::MethodArg(
|
||||
parent.hir_id,
|
||||
name.args,
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
|
||||
),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::FieldAccess(name),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
ExprKind::Block(..) => {
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => 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;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
|
|||
let lhs = place.local;
|
||||
match rvalue {
|
||||
// Only consider `&mut`, which can modify origin place
|
||||
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
|
||||
mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) |
|
||||
// _2: &mut _;
|
||||
// _3 = move _2
|
||||
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
|
|||
pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
|
||||
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
|
||||
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
|
||||
pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"];
|
||||
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
||||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
||||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
||||
|
|
@ -161,3 +162,6 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"];
|
|||
pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"];
|
||||
pub const FORMATTER: [&str; 3] = ["core", "fmt", "Formatter"];
|
||||
pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"];
|
||||
pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"];
|
||||
#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so
|
||||
pub const BOOL_THEN: [&str; 4] = ["core", "bool", "<impl bool>", "then"];
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use rustc_middle::mir::{
|
|||
Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
|
||||
Terminator, TerminatorKind,
|
||||
};
|
||||
use rustc_middle::traits::{ImplSource, ObligationCause, BuiltinImplSource};
|
||||
use rustc_middle::traits::{BuiltinImplSource, ImplSource, ObligationCause};
|
||||
use rustc_middle::ty::adjustment::PointerCoercion;
|
||||
use rustc_middle::ty::{self, BoundConstness, GenericArgKind, TraitRef, Ty, TyCtxt};
|
||||
use rustc_semver::RustcVersion;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ use std::iter;
|
|||
|
||||
use crate::{match_def_path, path_res, paths};
|
||||
|
||||
mod type_certainty;
|
||||
pub use type_certainty::expr_type_is_certain;
|
||||
|
||||
/// Checks if the given type implements copy.
|
||||
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.is_copy_modulo_regions(cx.tcx, cx.param_env)
|
||||
|
|
@ -739,7 +742,11 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option
|
|||
let mut output = None;
|
||||
let lang_items = cx.tcx.lang_items();
|
||||
|
||||
for (pred, _) in cx.tcx.explicit_item_bounds(ty.def_id).iter_instantiated_copied(cx.tcx, ty.args) {
|
||||
for (pred, _) in cx
|
||||
.tcx
|
||||
.explicit_item_bounds(ty.def_id)
|
||||
.iter_instantiated_copied(cx.tcx, ty.args)
|
||||
{
|
||||
match pred.kind().skip_binder() {
|
||||
ty::ClauseKind::Trait(p)
|
||||
if (lang_items.fn_trait() == Some(p.def_id())
|
||||
|
|
|
|||
122
clippy_utils/src/ty/type_certainty/certainty.rs
Normal file
122
clippy_utils/src/ty/type_certainty/certainty.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use rustc_hir::def_id::DefId;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Certainty {
|
||||
/// Determining the type requires contextual information.
|
||||
Uncertain,
|
||||
|
||||
/// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the
|
||||
/// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is
|
||||
/// `Res::Err`.
|
||||
Certain(Option<DefId>),
|
||||
|
||||
/// The heuristic believes that more than one `DefId` applies to a type---this is a bug.
|
||||
Contradiction,
|
||||
}
|
||||
|
||||
pub trait Meet {
|
||||
fn meet(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
pub trait TryJoin: Sized {
|
||||
fn try_join(self, other: Self) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl Meet for Option<DefId> {
|
||||
fn meet(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(None, _) | (_, None) => None,
|
||||
(Some(lhs), Some(rhs)) => (lhs == rhs).then_some(lhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryJoin for Option<DefId> {
|
||||
fn try_join(self, other: Self) -> Option<Self> {
|
||||
match (self, other) {
|
||||
(Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)),
|
||||
(Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)),
|
||||
(None, None) => Some(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Meet for Certainty {
|
||||
fn meet(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Certainty::Uncertain, _) | (_, Certainty::Uncertain) => Certainty::Uncertain,
|
||||
(Certainty::Certain(lhs), Certainty::Certain(rhs)) => Certainty::Certain(lhs.meet(rhs)),
|
||||
(Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner),
|
||||
(Certainty::Contradiction, Certainty::Contradiction) => Certainty::Contradiction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Certainty {
|
||||
/// Join two `Certainty`s preserving their `DefId`s (if any). Generally speaking, this method
|
||||
/// should be used only when `self` and `other` refer directly to types. Otherwise,
|
||||
/// `join_clearing_def_ids` should be used.
|
||||
pub fn join(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Certainty::Contradiction, _) | (_, Certainty::Contradiction) => Certainty::Contradiction,
|
||||
|
||||
(Certainty::Certain(lhs), Certainty::Certain(rhs)) => {
|
||||
if let Some(inner) = lhs.try_join(rhs) {
|
||||
Certainty::Certain(inner)
|
||||
} else {
|
||||
debug_assert!(false, "Contradiction with {lhs:?} and {rhs:?}");
|
||||
Certainty::Contradiction
|
||||
}
|
||||
},
|
||||
|
||||
(Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner),
|
||||
|
||||
(Certainty::Uncertain, Certainty::Uncertain) => Certainty::Uncertain,
|
||||
}
|
||||
}
|
||||
|
||||
/// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self`
|
||||
/// or `other` do not necessarily refer to types, e.g., when they are aggregations of other
|
||||
/// `Certainty`s.
|
||||
pub fn join_clearing_def_ids(self, other: Self) -> Self {
|
||||
self.clear_def_id().join(other.clear_def_id())
|
||||
}
|
||||
|
||||
pub fn clear_def_id(self) -> Certainty {
|
||||
if matches!(self, Certainty::Certain(_)) {
|
||||
Certainty::Certain(None)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_def_id(self, def_id: DefId) -> Certainty {
|
||||
if matches!(self, Certainty::Certain(_)) {
|
||||
Certainty::Certain(Some(def_id))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_def_id(self) -> Option<DefId> {
|
||||
match self {
|
||||
Certainty::Certain(inner) => inner,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_certain(self) -> bool {
|
||||
matches!(self, Self::Certain(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// Think: `iter.all(/* is certain */)`
|
||||
pub fn meet(iter: impl Iterator<Item = Certainty>) -> Certainty {
|
||||
iter.fold(Certainty::Certain(None), Certainty::meet)
|
||||
}
|
||||
|
||||
/// Think: `iter.any(/* is certain */)`
|
||||
pub fn join(iter: impl Iterator<Item = Certainty>) -> Certainty {
|
||||
iter.fold(Certainty::Uncertain, Certainty::join)
|
||||
}
|
||||
315
clippy_utils/src/ty/type_certainty/mod.rs
Normal file
315
clippy_utils/src/ty/type_certainty/mod.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
//! A heuristic to tell whether an expression's type can be determined purely from its
|
||||
//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain`
|
||||
//! tries to tell whether an expression's type can be determined without appeal to the surrounding
|
||||
//! context.
|
||||
//!
|
||||
//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`.
|
||||
//! Intuitively, that function determines whether an expression's type is needed for type inference,
|
||||
//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's
|
||||
//! type.
|
||||
//!
|
||||
//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should
|
||||
//! be considered a bug.
|
||||
|
||||
use crate::def_path_res;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{walk_qpath, walk_ty, Visitor};
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
mod certainty;
|
||||
use certainty::{join, meet, Certainty, Meet};
|
||||
|
||||
pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
expr_type_certainty(cx, expr).is_certain()
|
||||
}
|
||||
|
||||
fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
||||
let certainty = match &expr.kind {
|
||||
ExprKind::Unary(_, expr)
|
||||
| ExprKind::Field(expr, _)
|
||||
| ExprKind::Index(expr, _)
|
||||
| ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr),
|
||||
|
||||
ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
|
||||
|
||||
ExprKind::Call(callee, args) => {
|
||||
let lhs = expr_type_certainty(cx, callee);
|
||||
let rhs = if type_is_inferrable_from_arguments(cx, expr) {
|
||||
meet(args.iter().map(|arg| expr_type_certainty(cx, arg)))
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
};
|
||||
lhs.join_clearing_def_ids(rhs)
|
||||
},
|
||||
|
||||
ExprKind::MethodCall(method, receiver, args, _) => {
|
||||
let mut receiver_type_certainty = expr_type_certainty(cx, receiver);
|
||||
// Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
|
||||
// identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
|
||||
// for example. So update the `DefId` in `receiver_type_certainty` (if any).
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id))
|
||||
{
|
||||
receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id);
|
||||
};
|
||||
let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
|
||||
let rhs = if type_is_inferrable_from_arguments(cx, expr) {
|
||||
meet(
|
||||
std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))),
|
||||
)
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
};
|
||||
lhs.join(rhs)
|
||||
},
|
||||
|
||||
ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
|
||||
|
||||
ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)),
|
||||
|
||||
ExprKind::Lit(_) => Certainty::Certain(None),
|
||||
|
||||
ExprKind::Cast(_, ty) => type_certainty(cx, ty),
|
||||
|
||||
ExprKind::If(_, if_expr, Some(else_expr)) => {
|
||||
expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr))
|
||||
},
|
||||
|
||||
ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false),
|
||||
|
||||
ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true),
|
||||
|
||||
_ => Certainty::Uncertain,
|
||||
};
|
||||
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr);
|
||||
if let Some(def_id) = adt_def_id(expr_ty) {
|
||||
certainty.with_def_id(def_id)
|
||||
} else {
|
||||
certainty
|
||||
}
|
||||
}
|
||||
|
||||
struct CertaintyVisitor<'cx, 'tcx> {
|
||||
cx: &'cx LateContext<'tcx>,
|
||||
certainty: Certainty,
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> {
|
||||
fn new(cx: &'cx LateContext<'tcx>) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
certainty: Certainty::Certain(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> Visitor<'cx> for CertaintyVisitor<'cx, 'tcx> {
|
||||
fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) {
|
||||
self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true));
|
||||
if self.certainty != Certainty::Uncertain {
|
||||
walk_qpath(self, qpath, hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, ty: &'cx hir::Ty<'_>) {
|
||||
if matches!(ty.kind, TyKind::Infer) {
|
||||
self.certainty = Certainty::Uncertain;
|
||||
}
|
||||
if self.certainty != Certainty::Uncertain {
|
||||
walk_ty(self, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty {
|
||||
// Handle `TyKind::Path` specially so that its `DefId` can be preserved.
|
||||
//
|
||||
// Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to
|
||||
// `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing
|
||||
// `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal
|
||||
// certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here,
|
||||
// the resulting certainty would be `Certainty::Certain(None)`.
|
||||
if let TyKind::Path(qpath) = &ty.kind {
|
||||
return qpath_certainty(cx, qpath, true);
|
||||
}
|
||||
|
||||
let mut visitor = CertaintyVisitor::new(cx);
|
||||
visitor.visit_ty(ty);
|
||||
visitor.certainty
|
||||
}
|
||||
|
||||
fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty {
|
||||
let mut visitor = CertaintyVisitor::new(cx);
|
||||
visitor.visit_generic_args(args);
|
||||
visitor.certainty
|
||||
}
|
||||
|
||||
/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path
|
||||
/// segments generic arguments are are instantiated.
|
||||
///
|
||||
/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a
|
||||
/// value. So `DefId`s are retained only when `resolves_to_type` is true.
|
||||
fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty {
|
||||
let certainty = match qpath {
|
||||
QPath::Resolved(ty, path) => {
|
||||
let len = path.segments.len();
|
||||
path.segments.iter().enumerate().fold(
|
||||
ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)),
|
||||
|parent_certainty, (i, path_segment)| {
|
||||
path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
QPath::TypeRelative(ty, path_segment) => {
|
||||
path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type)
|
||||
},
|
||||
|
||||
QPath::LangItem(lang_item, _, _) => {
|
||||
cx.tcx
|
||||
.lang_items()
|
||||
.get(*lang_item)
|
||||
.map_or(Certainty::Uncertain, |def_id| {
|
||||
let generics = cx.tcx.generics_of(def_id);
|
||||
if generics.parent_count == 0 && generics.params.is_empty() {
|
||||
Certainty::Certain(if resolves_to_type { Some(def_id) } else { None })
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
}
|
||||
})
|
||||
},
|
||||
};
|
||||
debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
|
||||
certainty
|
||||
}
|
||||
|
||||
fn path_segment_certainty(
|
||||
cx: &LateContext<'_>,
|
||||
parent_certainty: Certainty,
|
||||
path_segment: &PathSegment<'_>,
|
||||
resolves_to_type: bool,
|
||||
) -> Certainty {
|
||||
let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) {
|
||||
// A definition's type is certain if it refers to something without generics (e.g., a crate or module, or
|
||||
// an unparameterized type), or the generics are instantiated with arguments that are certain.
|
||||
//
|
||||
// If the parent is uncertain, then the current path segment must account for the parent's generic arguments.
|
||||
// Consider the following examples, where the current path segment is `None`:
|
||||
// - `Option::None` // uncertain; parent (i.e., `Option`) is uncertain
|
||||
// - `Option::<Vec<u64>>::None` // certain; parent (i.e., `Option::<..>`) is certain
|
||||
// - `Option::None::<Vec<u64>>` // certain; parent (i.e., `Option`) is uncertain
|
||||
Res::Def(_, def_id) => {
|
||||
// Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE.
|
||||
if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
|
||||
let generics = cx.tcx.generics_of(def_id);
|
||||
let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && generics.params.is_empty()
|
||||
{
|
||||
Certainty::Certain(None)
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
};
|
||||
let rhs = path_segment
|
||||
.args
|
||||
.map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args));
|
||||
// See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value.
|
||||
let certainty = lhs.join_clearing_def_ids(rhs);
|
||||
if resolves_to_type {
|
||||
certainty.with_def_id(def_id)
|
||||
} else {
|
||||
certainty
|
||||
}
|
||||
} else {
|
||||
Certainty::Certain(None)
|
||||
}
|
||||
},
|
||||
|
||||
Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => {
|
||||
Certainty::Certain(None)
|
||||
},
|
||||
|
||||
// `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
|
||||
Res::Local(hir_id) => match cx.tcx.hir().get_parent(hir_id) {
|
||||
// An argument's type is always certain.
|
||||
Node::Param(..) => Certainty::Certain(None),
|
||||
// A local's type is certain if its type annotation is certain or it has an initializer whose
|
||||
// type is certain.
|
||||
Node::Local(local) => {
|
||||
let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty));
|
||||
let rhs = local
|
||||
.init
|
||||
.map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init));
|
||||
let certainty = lhs.join(rhs);
|
||||
if resolves_to_type {
|
||||
certainty
|
||||
} else {
|
||||
certainty.clear_def_id()
|
||||
}
|
||||
},
|
||||
_ => Certainty::Uncertain,
|
||||
},
|
||||
|
||||
_ => Certainty::Uncertain,
|
||||
};
|
||||
debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
|
||||
certainty
|
||||
}
|
||||
|
||||
/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`.
|
||||
/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`.
|
||||
fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option<Res> {
|
||||
if path_segment.res == Res::Err && let Some(def_id) = parent_certainty.to_def_id() {
|
||||
let mut def_path = cx.get_def_path(def_id);
|
||||
def_path.push(path_segment.ident.name);
|
||||
let reses = def_path_res(cx, &def_path.iter().map(Symbol::as_str).collect::<Vec<_>>());
|
||||
if let [res] = reses.as_slice() { Some(*res) } else { None }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let Some(callee_def_id) = (match expr.kind {
|
||||
ExprKind::Call(callee, _) => {
|
||||
let callee_ty = cx.typeck_results().expr_ty(callee);
|
||||
if let ty::FnDef(callee_def_id, _) = callee_ty.kind() {
|
||||
Some(*callee_def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
|
||||
_ => None,
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let generics = cx.tcx.generics_of(callee_def_id);
|
||||
let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
|
||||
|
||||
// Check that all type parameters appear in the functions input types.
|
||||
(0..(generics.parent_count + generics.params.len()) as u32).all(|index| {
|
||||
fn_sig
|
||||
.inputs()
|
||||
.iter()
|
||||
.any(|input_ty| contains_param(*input_ty.skip_binder(), index))
|
||||
})
|
||||
}
|
||||
|
||||
fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> {
|
||||
cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0]
|
||||
}
|
||||
|
||||
fn adt_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
ty.peel_refs().ty_adt_def().map(AdtDef::did)
|
||||
}
|
||||
|
||||
fn contains_param(ty: Ty<'_>, index: u32) -> bool {
|
||||
ty.walk()
|
||||
.any(|arg| matches!(arg.unpack(), GenericArgKind::Type(ty) if ty.is_param(index)))
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
|
||||
use core::ops::ControlFlow;
|
||||
use hir::def::Res;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node};
|
||||
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
|
|
@ -127,7 +128,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
|
|||
}
|
||||
|
||||
fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
|
||||
if let hir::def::Res::Local(id) = path.res {
|
||||
if let Res::Local(id) = path.res {
|
||||
if self.binding_ids.contains(&id) {
|
||||
self.usage_found = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue