blob: 16d5608bfed76c1c08b77fe362107686d5ed7705 [file] [log] [blame] [edit]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{EvaluationResult, Obligation, ObligationCause};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArg, TraitRef, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::symbol::Symbol;
use rustc_span::{Span, DUMMY_SP};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_type_ir::Upcast as _;
/// check if a DefId's path matches the given absolute type path
/// usage e.g. with
/// `match_def_path(cx, id, &["core", "option", "Option"])`
pub fn match_def_path(cx: &LateContext, def_id: DefId, path: &[Symbol]) -> bool {
let def_path = cx.tcx.def_path(def_id);
let krate = &cx.tcx.crate_name(def_path.krate);
if krate != &path[0] {
return false;
}
let path = &path[1..];
let other = def_path.data;
if other.len() != path.len() {
return false;
}
other
.into_iter()
.zip(path)
.all(|(e, p)| e.data.get_opt_name().as_ref() == Some(p))
}
pub fn in_derive_expn(span: Span) -> bool {
matches!(
span.ctxt().outer_expn_data().kind,
ExpnKind::Macro(MacroKind::Derive, ..)
)
}
#[macro_export]
macro_rules! symbols {
($($s: ident)+) => {
#[derive(Clone)]
#[allow(non_snake_case)]
pub(crate) struct Symbols {
$( $s: Symbol, )+
}
impl Symbols {
fn new() -> Self {
Symbols {
$( $s: Symbol::intern(stringify!($s)), )+
}
}
}
}
}
pub fn find_first_crate<'tcx>(tcx: &TyCtxt<'tcx>, crate_name: Symbol) -> Option<CrateNum> {
tcx.crates(())
.iter()
.find(|c| tcx.crate_name(**c) == crate_name)
.copied()
}
pub fn trait_in_crate<'tcx>(
tcx: &TyCtxt<'tcx>,
krate: CrateNum,
trait_sym: Symbol,
) -> Option<DefId> {
tcx.traits(krate)
.iter()
.find(|id| tcx.opt_item_name(**id) == Some(trait_sym))
.copied()
}
/*
Stuff copied from clippy:
*/
/// Checks whether a type implements a trait.
/// The function returns false in case the type contains an inference variable.
///
/// See [Common tools for writing lints] for an example how to use this function and other options.
///
/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
pub fn implements_trait<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
args: &[GenericArg<'tcx>],
) -> bool {
implements_trait_with_env_from_iter(
cx.tcx,
cx.typing_env(),
ty,
trait_id,
None,
args.iter().map(|&x| Some(x)),
)
}
/// Same as `implements_trait_from_env` but takes the arguments as an iterator.
pub fn implements_trait_with_env_from_iter<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
callee_id: Option<DefId>,
args: impl IntoIterator<Item = impl Into<Option<GenericArg<'tcx>>>>,
) -> bool {
// Clippy shouldn't have infer types
assert!(!ty.has_infer());
// If a `callee_id` is passed, then we assert that it is a body owner
// through calling `body_owner_kind`, which would panic if the callee
// does not have a body.
if let Some(callee_id) = callee_id {
let _ = tcx.hir_body_owner_kind(callee_id);
}
let ty = tcx.erase_regions(ty);
if ty.has_escaping_bound_vars() {
return false;
}
let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
let args = args
.into_iter()
.map(|arg| {
arg.into()
.unwrap_or_else(|| infcx.next_ty_var(DUMMY_SP).into())
})
.collect::<Vec<_>>();
let trait_ref = TraitRef::new(
tcx,
trait_id,
[GenericArg::from(ty)].into_iter().chain(args),
);
debug_assert!(
matches!(tcx.def_kind(trait_id), DefKind::Trait | DefKind::TraitAlias),
"`DefId` must belong to a trait or trait alias"
);
let obligation = Obligation {
cause: ObligationCause::dummy(),
param_env,
recursion_depth: 0,
predicate: trait_ref.upcast(tcx),
};
infcx
.evaluate_obligation(&obligation)
.is_ok_and(EvaluationResult::must_apply_modulo_regions)
}