Skip to content

Commit

Permalink
Better warning for flexible types
Browse files Browse the repository at this point in the history
  • Loading branch information
noti0na1 committed Oct 8, 2024
1 parent 35c7d74 commit 32fa0da
Showing 1 changed file with 34 additions and 41 deletions.
75 changes: 34 additions & 41 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import typer.*, Applications.*, Inferencing.*, ProtoTypes.*
import util.*

import scala.annotation.internal.sharable
import scala.annotation.tailrec
import scala.collection.mutable

import SpaceEngine.*
Expand Down Expand Up @@ -621,6 +622,7 @@ object SpaceEngine {
case tp if tp.isRef(defn.UnitClass) => ConstantType(Constant(())) :: Nil
case tp @ NamedType(Parts(parts), _) => if parts.exists(_ eq tp) then ListOfNoType else parts.map(tp.derivedSelect)
case _: SingletonType => ListOfNoType
case tp: FlexibleType => List(tp.underlying, ConstantType(Constant(null)))
case tp if tp.classSymbol.isAllOf(JavaEnum) => tp.classSymbol.children.map(_.termRef)
// the class of a java enum value is the enum class, so this must follow SingletonType to not loop infinitely

Expand Down Expand Up @@ -859,6 +861,8 @@ object SpaceEngine {
case tp: SingletonType => toUnderlying(tp.underlying)
case tp: ExprType => toUnderlying(tp.resultType)
case AnnotatedType(tp, annot) => AnnotatedType(toUnderlying(tp), annot)
// case tp: AndOrType => tp.derivedAndOrType(toUnderlying(tp.tp1), toUnderlying(tp.tp2))
case tp: FlexibleType => tp.derivedFlexibleType(toUnderlying(tp.underlying))
case _ => tp
})

Expand Down Expand Up @@ -893,52 +897,41 @@ object SpaceEngine {
&& !sel.tpe.widen.isRef(defn.QuotedExprClass)
&& !sel.tpe.widen.isRef(defn.QuotedTypeClass)

def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)") {
val cases = m.cases.toIndexedSeq

def checkReachability(m: Match)(using Context): Unit = trace(i"checkReachability($m)"):
val selTyp = toUnderlying(m.selector.tpe).dealias

val isNullable = selTyp.classSymbol.isNullableClass
val targetSpace = trace(i"targetSpace($selTyp)")(if isNullable
val isNullable = selTyp.isInstanceOf[FlexibleType] || selTyp.classSymbol.isNullableClass
val targetSpace = trace(i"targetSpace($selTyp)"):
if isNullable && !ctx.mode.is(Mode.SafeNulls)
then project(OrType(selTyp, ConstantType(Constant(null)), soft = false))
else project(selTyp)
)

var i = 0
val len = cases.length
var prevs = List.empty[Space]
var deferred = List.empty[Tree]

while (i < len) {
val CaseDef(pat, guard, _) = cases(i)

val curr = trace(i"project($pat)")(project(pat))
@tailrec def recur(cases: List[CaseDef], prevs: List[Space], deferred: List[Tree]): Unit =
cases match
case Nil =>
case CaseDef(pat, guard, _) :: rest =>
val curr = trace(i"project($pat)")(project(pat))
val covered = trace("covered")(simplify(intersect(curr, targetSpace)))
val prev = trace("prev")(simplify(Or(prevs)))

val covered = trace("covered")(simplify(intersect(curr, targetSpace)))

val prev = trace("prev")(simplify(Or(prevs)))

if prev == Empty && covered == Empty then // defer until a case is reachable
deferred ::= pat
else {
for (pat <- deferred.reverseIterator)
report.warning(MatchCaseUnreachable(), pat.srcPos)
if pat != EmptyTree // rethrow case of catch uses EmptyTree
&& !pat.symbol.isAllOf(SyntheticCase, butNot=Method) // ExpandSAMs default cases use SyntheticCase
&& isSubspace(covered, prev)
then {
val nullOnly = isNullable && i == len - 1 && isWildcardArg(pat)
val msg = if nullOnly then MatchCaseOnlyNullWarning() else MatchCaseUnreachable()
report.warning(msg, pat.srcPos)
}
deferred = Nil
}

// in redundancy check, take guard as false in order to soundly approximate
prevs ::= (if guard.isEmpty then covered else Empty)
i += 1
}
}
if prev == Empty && covered == Empty then // defer until a case is reachable
recur(rest, prevs, pat :: deferred)
else
for pat <- deferred.reverseIterator
do report.warning(MatchCaseUnreachable(), pat.srcPos)

if pat != EmptyTree // rethrow case of catch uses EmptyTree
&& !pat.symbol.isAllOf(SyntheticCase, butNot=Method) // ExpandSAMs default cases use SyntheticCase
&& isSubspace(covered, prev)
then
val nullOnly = isNullable && rest.isEmpty && isWildcardArg(pat)
val msg = if nullOnly then MatchCaseOnlyNullWarning() else MatchCaseUnreachable()
report.warning(msg, pat.srcPos)

val newPrev = if guard.isEmpty then covered :: prevs else prevs
recur(rest, newPrev, Nil)

recur(m.cases, Nil, Nil)
end checkReachability

def checkMatch(m: Match)(using Context): Unit =
if exhaustivityCheckable(m.selector) then checkExhaustivity(m)
Expand Down

0 comments on commit 32fa0da

Please sign in to comment.