@@ -795,26 +795,55 @@ trait Checking {
795
795
796
796
/** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
797
797
* This means `sel` is either marked @unchecked or `sel.tpe` conforms to the
798
- * pattern's type. If pattern is an UnApply, do the check recursively.
798
+ * pattern's type. If pattern is an UnApply, also check that the extractor is
799
+ * irrefutable, and do the check recursively.
799
800
*/
800
801
def checkIrrefutable (sel : Tree , pat : Tree , isPatDef : Boolean )(using Context ): Boolean = {
801
802
val pt = sel.tpe
802
803
803
- def fail (pat : Tree , pt : Type ): Boolean = {
804
- var reportedPt = pt.dropAnnot(defn.UncheckedAnnot )
805
- if (! pat.tpe.isSingleton) reportedPt = reportedPt.widen
806
- val problem = if (pat.tpe <:< reportedPt) " is more specialized than" else " does not match"
807
- val fix = if (isPatDef) " adding `: @unchecked` after the expression" else " writing `case ` before the full pattern"
808
- val pos = if (isPatDef) sel.srcPos else pat.srcPos
804
+ enum Reason :
805
+ case NonConforming , RefutableExtractor
806
+
807
+ def fail (pat : Tree , pt : Type , reason : Reason ): Boolean = {
808
+ import Reason ._
809
+ val message = reason match
810
+ case NonConforming =>
811
+ var reportedPt = pt.dropAnnot(defn.UncheckedAnnot )
812
+ if ! pat.tpe.isSingleton then reportedPt = reportedPt.widen
813
+ val problem = if pat.tpe <:< reportedPt then " is more specialized than" else " does not match"
814
+ ex " pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt"
815
+ case RefutableExtractor =>
816
+ val extractor =
817
+ val UnApply (fn, _, _) = pat : @ unchecked
818
+ fn match
819
+ case Select (id, _) => id
820
+ case TypeApply (Select (id, _), _) => id
821
+ em " pattern binding uses refutable extractor ` $extractor` "
822
+
823
+ val fix =
824
+ if isPatDef then " adding `: @unchecked` after the expression"
825
+ else " adding the `case` keyword before the full pattern"
826
+ val addendum =
827
+ if isPatDef then " may result in a MatchError at runtime"
828
+ else " will result in a filtering for expression (using `withFilter`)"
829
+ val usage = reason match
830
+ case NonConforming => " the narrowing"
831
+ case RefutableExtractor => " this usage"
832
+ val pos =
833
+ if isPatDef then reason match
834
+ case NonConforming => sel.srcPos
835
+ case RefutableExtractor => pat.source.atSpan(pat.span union sel.span)
836
+ else pat.srcPos
809
837
report.warning(
810
- ex """ pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt
838
+ em """ $message
811
839
|
812
- |If the narrowing is intentional, this can be communicated by $fix. ${err.rewriteNotice}""" ,
840
+ |If $usage is intentional, this can be communicated by $fix,
841
+ |which $addendum. ${err.rewriteNotice}""" ,
813
842
pos)
814
843
false
815
844
}
816
845
817
- def check (pat : Tree , pt : Type ): Boolean = (pt <:< pat.tpe) || fail(pat, pt)
846
+ def check (pat : Tree , pt : Type ): Boolean = (pt <:< pat.tpe) || fail(pat, pt, Reason . NonConforming )
818
847
819
848
def recur (pat : Tree , pt : Type ): Boolean =
820
849
! sourceVersion.isAtLeast(future) || // only for 3.x for now since mitigations work only after this PR
@@ -825,7 +854,7 @@ trait Checking {
825
854
recur(pat1, pt)
826
855
case UnApply (fn, _, pats) =>
827
856
check(pat, pt) &&
828
- (isIrrefutable(fn, pats.length) || fail(pat, pt)) && {
857
+ (isIrrefutable(fn, pats.length) || fail(pat, pt, Reason . RefutableExtractor )) && {
829
858
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.srcPos)
830
859
pats.corresponds(argPts)(recur)
831
860
}
0 commit comments