Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2023-07-28 23:40:04 +02:00
commit 3d60241841
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
215 changed files with 7469 additions and 1950 deletions

View file

@ -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

View file

@ -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),

View file

@ -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;

View file

@ -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;

View file

@ -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)) |

View file

@ -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"];

View file

@ -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;

View file

@ -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())

View 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)
}

View 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)))
}

View file

@ -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;
}