222 lines
7.6 KiB
Rust
222 lines
7.6 KiB
Rust
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
|
use clippy_utils::source::snippet_opt;
|
|
use clippy_utils::{is_doc_hidden, meets_msrv, msrvs};
|
|
use rustc_ast::ast::{self, VisibilityKind};
|
|
use rustc_data_structures::fx::FxHashSet;
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
|
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
|
|
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
|
|
use rustc_middle::ty::DefIdTree;
|
|
use rustc_semver::RustcVersion;
|
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
|
use rustc_span::def_id::{DefId, LocalDefId};
|
|
use rustc_span::{sym, Span};
|
|
|
|
declare_clippy_lint! {
|
|
/// ### What it does
|
|
/// Checks for manual implementations of the non-exhaustive pattern.
|
|
///
|
|
/// ### Why is this bad?
|
|
/// Using the #[non_exhaustive] attribute expresses better the intent
|
|
/// and allows possible optimizations when applied to enums.
|
|
///
|
|
/// ### Example
|
|
/// ```rust
|
|
/// struct S {
|
|
/// pub a: i32,
|
|
/// pub b: i32,
|
|
/// _c: (),
|
|
/// }
|
|
///
|
|
/// enum E {
|
|
/// A,
|
|
/// B,
|
|
/// #[doc(hidden)]
|
|
/// _C,
|
|
/// }
|
|
///
|
|
/// struct T(pub i32, pub i32, ());
|
|
/// ```
|
|
/// Use instead:
|
|
/// ```rust
|
|
/// #[non_exhaustive]
|
|
/// struct S {
|
|
/// pub a: i32,
|
|
/// pub b: i32,
|
|
/// }
|
|
///
|
|
/// #[non_exhaustive]
|
|
/// enum E {
|
|
/// A,
|
|
/// B,
|
|
/// }
|
|
///
|
|
/// #[non_exhaustive]
|
|
/// struct T(pub i32, pub i32);
|
|
/// ```
|
|
#[clippy::version = "1.45.0"]
|
|
pub MANUAL_NON_EXHAUSTIVE,
|
|
style,
|
|
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
|
|
}
|
|
|
|
#[expect(clippy::module_name_repetitions)]
|
|
pub struct ManualNonExhaustiveStruct {
|
|
msrv: Option<RustcVersion>,
|
|
}
|
|
|
|
impl ManualNonExhaustiveStruct {
|
|
#[must_use]
|
|
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
|
Self { msrv }
|
|
}
|
|
}
|
|
|
|
impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
|
|
|
|
#[expect(clippy::module_name_repetitions)]
|
|
pub struct ManualNonExhaustiveEnum {
|
|
msrv: Option<RustcVersion>,
|
|
constructed_enum_variants: FxHashSet<(DefId, DefId)>,
|
|
potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
|
|
}
|
|
|
|
impl ManualNonExhaustiveEnum {
|
|
#[must_use]
|
|
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
|
Self {
|
|
msrv,
|
|
constructed_enum_variants: FxHashSet::default(),
|
|
potential_enums: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
|
|
|
|
impl EarlyLintPass for ManualNonExhaustiveStruct {
|
|
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
|
|
if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
|
|
return;
|
|
}
|
|
|
|
if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
|
|
let (fields, delimiter) = match variant_data {
|
|
ast::VariantData::Struct(fields, _) => (&**fields, '{'),
|
|
ast::VariantData::Tuple(fields, _) => (&**fields, '('),
|
|
ast::VariantData::Unit(_) => return,
|
|
};
|
|
if fields.len() <= 1 {
|
|
return;
|
|
}
|
|
let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
|
|
VisibilityKind::Public => None,
|
|
VisibilityKind::Inherited => Some(Ok(f)),
|
|
VisibilityKind::Restricted { .. } => Some(Err(())),
|
|
});
|
|
if let Some(Ok(field)) = iter.next()
|
|
&& iter.next().is_none()
|
|
&& field.ty.kind.is_unit()
|
|
&& field.ident.map_or(true, |name| name.as_str().starts_with('_'))
|
|
{
|
|
span_lint_and_then(
|
|
cx,
|
|
MANUAL_NON_EXHAUSTIVE,
|
|
item.span,
|
|
"this seems like a manual implementation of the non-exhaustive pattern",
|
|
|diag| {
|
|
if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
|
|
&& let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
|
|
&& let Some(snippet) = snippet_opt(cx, header_span)
|
|
{
|
|
diag.span_suggestion(
|
|
header_span,
|
|
"add the attribute",
|
|
format!("#[non_exhaustive] {}", snippet),
|
|
Applicability::Unspecified,
|
|
);
|
|
}
|
|
diag.span_help(field.span, "remove this field");
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
extract_msrv_attr!(EarlyContext);
|
|
}
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
|
|
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
|
if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
|
|
return;
|
|
}
|
|
|
|
if let hir::ItemKind::Enum(def, _) = &item.kind
|
|
&& def.variants.len() > 1
|
|
{
|
|
let mut iter = def.variants.iter().filter_map(|v| {
|
|
let id = cx.tcx.hir().local_def_id(v.id);
|
|
(matches!(v.data, hir::VariantData::Unit(_))
|
|
&& v.ident.as_str().starts_with('_')
|
|
&& is_doc_hidden(cx.tcx.hir().attrs(v.id)))
|
|
.then_some((id, v.span))
|
|
});
|
|
if let Some((id, span)) = iter.next()
|
|
&& iter.next().is_none()
|
|
{
|
|
self.potential_enums.push((item.def_id, id, item.span, span));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
|
if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
|
|
&& let [.., name] = p.segments
|
|
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
|
|
&& name.ident.as_str().starts_with('_')
|
|
{
|
|
let variant_id = cx.tcx.parent(id);
|
|
let enum_id = cx.tcx.parent(variant_id);
|
|
|
|
self.constructed_enum_variants.insert((enum_id, variant_id));
|
|
}
|
|
}
|
|
|
|
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
|
for &(enum_id, _, enum_span, variant_span) in
|
|
self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
|
|
!self
|
|
.constructed_enum_variants
|
|
.contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
|
|
})
|
|
{
|
|
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(enum_id);
|
|
span_lint_hir_and_then(
|
|
cx,
|
|
MANUAL_NON_EXHAUSTIVE,
|
|
hir_id,
|
|
enum_span,
|
|
"this seems like a manual implementation of the non-exhaustive pattern",
|
|
|diag| {
|
|
if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
|
|
&& let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
|
|
&& let Some(snippet) = snippet_opt(cx, header_span)
|
|
{
|
|
diag.span_suggestion(
|
|
header_span,
|
|
"add the attribute",
|
|
format!("#[non_exhaustive] {}", snippet),
|
|
Applicability::Unspecified,
|
|
);
|
|
}
|
|
diag.span_help(variant_span, "remove this variant");
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
extract_msrv_attr!(LateContext);
|
|
}
|