@@ -795,26 +795,55 @@ trait Checking {
795795
796796 /** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
797797 * 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.
799800 */
800801 def checkIrrefutable (sel : Tree , pat : Tree , isPatDef : Boolean )(using Context ): Boolean = {
801802 val pt = sel.tpe
802803
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
809837 report.warning(
810- ex """ pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt
838+ em """ $message
811839 |
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}""" ,
813842 pos)
814843 false
815844 }
816845
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 )
818847
819848 def recur (pat : Tree , pt : Type ): Boolean =
820849 ! sourceVersion.isAtLeast(future) || // only for 3.x for now since mitigations work only after this PR
@@ -825,7 +854,7 @@ trait Checking {
825854 recur(pat1, pt)
826855 case UnApply (fn, _, pats) =>
827856 check(pat, pt) &&
828- (isIrrefutable(fn, pats.length) || fail(pat, pt)) && {
857+ (isIrrefutable(fn, pats.length) || fail(pat, pt, Reason . RefutableExtractor )) && {
829858 val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.srcPos)
830859 pats.corresponds(argPts)(recur)
831860 }
0 commit comments