@@ -15,8 +15,9 @@ use rustc_span::{Span, sym};
1515use crate :: lints:: {
1616 AttributeKindInFindAttr , BadOptAccessDiag , DefaultHashTypesDiag ,
1717 ImplicitSysrootCrateImportDiag , LintPassByHand , NonGlobImportTypeIrInherent , QueryInstability ,
18- QueryUntracked , SpanUseEqCtxtDiag , SymbolInternStringLiteralDiag , TyQualified , TykindDiag ,
19- TykindKind , TypeIrDirectUse , TypeIrInherentUsage , TypeIrTraitUsage ,
18+ QueryUntracked , RustcMustMatchExhaustivelyNotExhaustive , SpanUseEqCtxtDiag ,
19+ SymbolInternStringLiteralDiag , TyQualified , TykindDiag , TykindKind , TypeIrDirectUse ,
20+ TypeIrInherentUsage , TypeIrTraitUsage ,
2021} ;
2122use crate :: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
2223
@@ -713,3 +714,89 @@ impl EarlyLintPass for BadUseOfFindAttr {
713714 }
714715 }
715716}
717+
718+ declare_tool_lint ! {
719+ pub rustc:: RUSTC_MUST_MATCH_EXHAUSTIVELY ,
720+ Allow ,
721+ "Forbids matches with wildcards, or if-let matching on enums marked with `#[rustc_must_match_exhaustively]`" ,
722+ report_in_external_macro: true
723+ }
724+ declare_lint_pass ! ( RustcMustMatchExhaustively => [ RUSTC_MUST_MATCH_EXHAUSTIVELY ] ) ;
725+
726+ fn is_rustc_must_match_exhaustively ( cx : & LateContext < ' _ > , id : HirId ) -> Option < Span > {
727+ let res = cx. typeck_results ( ) ;
728+
729+ let ty = res. node_type ( id) ;
730+
731+ let ty = if let ty:: Ref ( _, ty, _) = ty. kind ( ) { * ty } else { ty } ;
732+
733+ if let Some ( adt_def) = ty. ty_adt_def ( )
734+ && adt_def. is_enum ( )
735+ {
736+ find_attr ! ( cx. tcx, adt_def. did( ) , RustcMustMatchExhaustively ( span) => * span)
737+ } else {
738+ None
739+ }
740+ }
741+
742+ fn pat_is_not_exhaustive_heuristic ( pat : & hir:: Pat < ' _ > ) -> Option < ( Span , & ' static str ) > {
743+ match pat. kind {
744+ hir:: PatKind :: Missing => None ,
745+ hir:: PatKind :: Wild => Some ( ( pat. span , "because of this wildcard pattern" ) ) ,
746+ hir:: PatKind :: Binding ( _, _, _, Some ( pat) ) => pat_is_not_exhaustive_heuristic ( pat) ,
747+ hir:: PatKind :: Binding ( ..) => Some ( ( pat. span , "because of this variable binding" ) ) ,
748+ hir:: PatKind :: Struct ( ..) => None ,
749+ hir:: PatKind :: TupleStruct ( ..) => None ,
750+ hir:: PatKind :: Or ( ..) => None ,
751+ hir:: PatKind :: Never => None ,
752+ hir:: PatKind :: Tuple ( ..) => None ,
753+ hir:: PatKind :: Box ( pat) => pat_is_not_exhaustive_heuristic ( & * pat) ,
754+ hir:: PatKind :: Deref ( pat) => pat_is_not_exhaustive_heuristic ( & * pat) ,
755+ hir:: PatKind :: Ref ( pat, _, _) => pat_is_not_exhaustive_heuristic ( & * pat) ,
756+ hir:: PatKind :: Expr ( ..) => None ,
757+ hir:: PatKind :: Guard ( ..) => None ,
758+ hir:: PatKind :: Range ( ..) => None ,
759+ hir:: PatKind :: Slice ( ..) => None ,
760+ hir:: PatKind :: Err ( ..) => None ,
761+ }
762+ }
763+
764+ impl < ' tcx > LateLintPass < ' tcx > for RustcMustMatchExhaustively {
765+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & hir:: Expr < ' _ > ) {
766+ match expr. kind {
767+ // This is not perfect exhaustiveness checking, that's why this is just a rustc internal
768+ // attribute. But it catches most reasonable cases
769+ hir:: ExprKind :: Match ( expr, arms, _) => {
770+ if let Some ( attr_span) = is_rustc_must_match_exhaustively ( cx, expr. hir_id ) {
771+ for arm in arms {
772+ if let Some ( ( span, message) ) = pat_is_not_exhaustive_heuristic ( arm. pat ) {
773+ cx. emit_span_lint (
774+ RUSTC_MUST_MATCH_EXHAUSTIVELY ,
775+ expr. span ,
776+ RustcMustMatchExhaustivelyNotExhaustive {
777+ attr_span,
778+ pat_span : span,
779+ message,
780+ } ,
781+ ) ;
782+ }
783+ }
784+ }
785+ }
786+ hir:: ExprKind :: If ( expr, ..) if let ExprKind :: Let ( expr) = expr. kind => {
787+ if let Some ( attr_span) = is_rustc_must_match_exhaustively ( cx, expr. init . hir_id ) {
788+ cx. emit_span_lint (
789+ RUSTC_MUST_MATCH_EXHAUSTIVELY ,
790+ expr. span ,
791+ RustcMustMatchExhaustivelyNotExhaustive {
792+ attr_span,
793+ pat_span : expr. span ,
794+ message : "using if let only matches on one variant (try using `match`)" ,
795+ } ,
796+ ) ;
797+ }
798+ }
799+ _ => { }
800+ }
801+ }
802+ }
0 commit comments