-use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
-use clippy_utils::{meets_msrv, msrvs};
-use if_chain::if_chain;
-use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind};
+use clippy_utils::{is_lint_allowed, meets_msrv, msrvs};
+use rustc_ast::ast::{self, VisibilityKind};
+use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
-use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+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! {
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
}
-#[derive(Clone)]
-pub struct ManualNonExhaustive {
+#[allow(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveStruct {
msrv: Option<RustcVersion>,
}
-impl ManualNonExhaustive {
+impl ManualNonExhaustiveStruct {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
-impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
+impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
-impl EarlyLintPass for ManualNonExhaustive {
- fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
- if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
- return;
- }
+#[allow(clippy::module_name_repetitions)]
+pub struct ManualNonExhaustiveEnum {
+ msrv: Option<RustcVersion>,
+ constructed_enum_variants: FxHashSet<(DefId, DefId)>,
+ potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
+}
- match &item.kind {
- ItemKind::Enum(def, _) => {
- check_manual_non_exhaustive_enum(cx, item, &def.variants);
- },
- ItemKind::Struct(variant_data, _) => {
- if let VariantData::Unit(..) = variant_data {
- return;
- }
-
- check_manual_non_exhaustive_struct(cx, item, variant_data);
- },
- _ => {},
+impl ManualNonExhaustiveEnum {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ constructed_enum_variants: FxHashSet::default(),
+ potential_enums: Vec::new(),
}
}
-
- extract_msrv_attr!(EarlyContext);
}
-fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
- fn is_non_exhaustive_marker(variant: &Variant) -> bool {
- matches!(variant.data, VariantData::Unit(_))
- && variant.ident.as_str().starts_with('_')
- && is_doc_hidden(&variant.attrs)
- }
+impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
- let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
- if_chain! {
- if let Some(marker) = markers.next();
- if markers.count() == 0 && variants.len() > 1;
- then {
- span_lint_and_then(
- cx,
- MANUAL_NON_EXHAUSTIVE,
- item.span,
- "this seems like a manual implementation of the non-exhaustive pattern",
- |diag| {
- if_chain! {
- if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
- let header_span = cx.sess().source_map().span_until_char(item.span, '{');
- if let Some(snippet) = snippet_opt(cx, header_span);
- then {
+impl EarlyLintPass for ManualNonExhaustiveStruct {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if !meets_msrv(self.msrv.as_ref(), &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)),
+ _ => 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",
Applicability::Unspecified,
);
}
+ diag.span_help(field.span, "remove this field");
}
- diag.span_help(marker.span, "remove this variant");
- });
+ );
+ }
}
}
+
+ extract_msrv_attr!(EarlyContext);
}
-fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
- fn is_private(field: &FieldDef) -> bool {
- matches!(field.vis.kind, VisibilityKind::Inherited)
- }
+impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
- fn is_non_exhaustive_marker(field: &FieldDef) -> bool {
- is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
+ 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('_')
+ && cx.tcx.is_doc_hidden(id.to_def_id()))
+ .then(|| (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 find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
- let delimiter = match data {
- VariantData::Struct(..) => '{',
- VariantData::Tuple(..) => '(',
- VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
- };
+ 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);
- cx.sess().source_map().span_until_char(item.span, delimiter)
+ self.constructed_enum_variants.insert((enum_id, variant_id));
+ }
}
- let fields = data.fields();
- let private_fields = fields.iter().filter(|f| is_private(f)).count();
- let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count();
-
- if_chain! {
- if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
- if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
- then {
+ 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()))
+ && !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
+ })
+ {
span_lint_and_then(
cx,
MANUAL_NON_EXHAUSTIVE,
- item.span,
+ enum_span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {
- if_chain! {
- if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
- let header_span = find_header_span(cx, item, data);
- if let Some(snippet) = snippet_opt(cx, header_span);
- then {
+ 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(marker.span, "remove this field");
- });
+ diag.span_help(variant_span, "remove this variant");
+ },
+ );
}
}
+
+ extract_msrv_attr!(LateContext);
}