blob: f2e8d3579d85145cd77c2540f650cd6e2b19c6c9 [file] [log] [blame] [edit]
use crate::internal_paths;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::{PathNS, lookup_path};
use clippy_utils::peel_ref_operators;
use clippy_utils::res::MaybeQPath;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint_defs::{declare_lint_pass, declare_tool_lint};
use rustc_middle::mir::ConstValue;
use rustc_span::symbol::Symbol;
declare_tool_lint! {
/// ### What it does
/// Checks for usage of def paths when a diagnostic item or a `LangItem` could be used.
///
/// ### Why is this bad?
/// The path for an item is subject to change and is less efficient to look up than a
/// diagnostic item or a `LangItem`.
///
/// ### Example
/// ```rust,ignore
/// pub static VEC: PathLookup = path!(alloc::vec::Vec);
///
/// VEC.contains_ty(cx, ty)
/// ```
///
/// Use instead:
/// ```rust,ignore
/// ty.is_diag_item(cx, sym::Vec)
/// ```
pub clippy::UNNECESSARY_DEF_PATH,
Warn,
"using a def path when a diagnostic item or a `LangItem` is available",
report_in_external_macro: true
}
declare_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]);
impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Call(ctor, [_, path]) = expr.kind
&& internal_paths::PATH_LOOKUP_NEW.matches_path(cx, ctor)
&& let ExprKind::Array(segments) = peel_ref_operators(cx, path).kind
&& let Some(macro_id) = expr.span.ctxt().outer_expn_data().macro_def_id
{
let ns = match cx.tcx.item_name(macro_id).as_str() {
"type_path" => PathNS::Type,
"value_path" => PathNS::Value,
"macro_path" => PathNS::Macro,
_ => unreachable!(),
};
let path: Vec<Symbol> = segments
.iter()
.map(|segment| {
if let Some(const_def_id) = segment.res(cx).opt_def_id()
&& let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(const_def_id)
&& let Some(value) = value.to_u32().discard_err()
{
Symbol::new(value)
} else {
panic!("failed to resolve path {:?}", expr.span);
}
})
.collect();
for def_id in lookup_path(cx.tcx, ns, &path) {
if let Some(name) = cx.tcx.get_diagnostic_name(def_id) {
span_lint_and_then(
cx,
UNNECESSARY_DEF_PATH,
expr.span.source_callsite(),
format!("a diagnostic name exists for this path: sym::{name}"),
|diag| {
diag.help(
"remove the `PathLookup` and use utilities such as `cx.tcx.is_diagnostic_item` instead",
);
diag.help("see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=diag&filter-crate=clippy_utils");
},
);
} else if let Some(item_name) = get_lang_item_name(cx, def_id) {
span_lint_and_then(
cx,
UNNECESSARY_DEF_PATH,
expr.span.source_callsite(),
format!("a language item exists for this path: LangItem::{item_name}"),
|diag| {
diag.help("remove the `PathLookup` and use utilities such as `cx.tcx.lang_items` instead");
diag.help("see also https://doc.rust-lang.org/nightly/nightly-rustc/?search=lang&filter-crate=clippy_utils");
},
);
}
}
}
}
}
fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> {
if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) {
Some(lang_item.variant_name())
} else {
None
}
}