Skip to content

Restore unreachable warnings by converting literals #13289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
val pat = Typed(untpd.Ident(nme.WILDCARD).withType(patType), TypeTree(patType))
CaseDef(pat, EmptyTree, Literal(Constant(idx)))
}
Match(param, cases)
Match(param.annotated(New(defn.UncheckedAnnot.typeRef, Nil)), cases)
}

/** - If `impl` is the companion of a generic sum, add `deriving.Mirror.Sum` parent
Expand Down
28 changes: 20 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -503,8 +503,21 @@ class SpaceEngine(using Context) extends SpaceLogic {
}
}

/** Numeric literals, while being constant values of unrelated types (e.g. Char and Int),
* when used in a case may end up matching at runtime, because their equals may returns true.
* Because these are universally available, general purpose types, it would be good to avoid
* returning false positive warnings, such as in `(c: Char) match { case 67 => ... }` emitting a
* reachability warning on the case. So the type `ConstantType(Constant(67, IntTag))` is
* converted to `ConstantType(Constant(67, CharTag))`. #12805 */
def convertConstantType(tp: Type, pt: Type): Type = tp match
case tp @ ConstantType(const) =>
val converted = const.convertTo(pt)
if converted == null then tp else ConstantType(converted)
case _ => tp

/** Is `tp1` a subtype of `tp2`? */
def isSubType(tp1: Type, tp2: Type): Boolean = {
def isSubType(_tp1: Type, tp2: Type): Boolean = {
val tp1 = convertConstantType(_tp1, tp2)
debug.println(TypeComparer.explained(_.isSubType(tp1, tp2)))
val res = if (ctx.explicitNulls) {
tp1 <:< tp2
Expand Down Expand Up @@ -750,6 +763,7 @@ class SpaceEngine(using Context) extends SpaceLogic {
}

def show(ss: Seq[Space]): String = ss.map(show).mkString(", ")

/** Display spaces */
def show(s: Space): String = {
def params(tp: Type): List[Type] = tp.classSymbol.primaryConstructor.info.firstParamTypes
Expand Down Expand Up @@ -894,13 +908,15 @@ class SpaceEngine(using Context) extends SpaceLogic {
else
project(OrType(selTyp, constantNullType, soft = false))

debug.println(s"targetSpace: ${show(targetSpace)}")

// in redundancy check, take guard as false in order to soundly approximate
val spaces = cases.map { x =>
val res =
if (x.guard.isEmpty) project(x.pat)
else Empty

debug.println(s"${x.pat.show} ====> ${res}")
debug.println(s"${x.pat.show} ====> ${show(res)}")
res
}

Expand All @@ -914,12 +930,8 @@ class SpaceEngine(using Context) extends SpaceLogic {
debug.println(s"---------------reachable? ${show(curr)}")
debug.println(s"prev: ${show(prevs)}")

var covered = simplify(intersect(curr, targetSpace))
debug.println(s"covered: $covered")

// `covered == Empty` may happen for primitive types with auto-conversion
// see tests/patmat/reader.scala tests/patmat/byte.scala
if (covered == Empty && !isNullLit(pat)) covered = curr
val covered = simplify(intersect(curr, targetSpace))
debug.println(s"covered: ${show(covered)}")

if (isSubspace(covered, prevs)) {
if i == cases.length - 1
Expand Down
3 changes: 3 additions & 0 deletions tests/patmat/i12805.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
10: Match case Unreachable
16: Match case Unreachable
22: Match case Unreachable
22 changes: 22 additions & 0 deletions tests/patmat/i12805.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import scala.language.implicitConversions

type Timeframe = "1m" | "2m" | "1H"
type TimeframeN = 1 | 2 | 60

def manualConvertToN(tf: Timeframe): TimeframeN = tf match
case "1m" => 1
case "2m" => 2
case "1H" => 60
case "4H" => ??? // was: no reachability warning

given Conversion[Timeframe, TimeframeN] =
case "1m" => 1
case "2m" => 2
case "1H" => 60
case "4H" => ??? // was: no reachability warning

given Conversion[TimeframeN, Timeframe] =
case 1 => "1m"
case 2 => "2m"
case 60 => "1H"
case 240 => ??? // was: no reachability warning
3 changes: 3 additions & 0 deletions tests/patmat/i12805b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
4: Match case Unreachable
9: Match case Unreachable
14: Match case Unreachable
14 changes: 14 additions & 0 deletions tests/patmat/i12805b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
def test1(a: 1 | 2) = a match
case 1 => true
case 2 => false
case 4 => ??? // unreachable case, was: no warning

def test2(a: 1 | 2) = a match
case 1 => true
case 2 => false
case _ => ??? // unreachable

def test3(a: 1 | 2) = a match
case 1 => true
case 2 => false
case a if a < 0 => ??? // unreachable