diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 817be9cba633..60125c5a2058 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index af02bfed8411..804a4fd33224 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -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 @@ -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 @@ -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 } @@ -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 diff --git a/tests/patmat/i12805.check b/tests/patmat/i12805.check new file mode 100644 index 000000000000..e855c765d801 --- /dev/null +++ b/tests/patmat/i12805.check @@ -0,0 +1,3 @@ +10: Match case Unreachable +16: Match case Unreachable +22: Match case Unreachable diff --git a/tests/patmat/i12805.scala b/tests/patmat/i12805.scala new file mode 100644 index 000000000000..78240c2f9703 --- /dev/null +++ b/tests/patmat/i12805.scala @@ -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 diff --git a/tests/patmat/i12805b.check b/tests/patmat/i12805b.check new file mode 100644 index 000000000000..62ba8523f65e --- /dev/null +++ b/tests/patmat/i12805b.check @@ -0,0 +1,3 @@ +4: Match case Unreachable +9: Match case Unreachable +14: Match case Unreachable diff --git a/tests/patmat/i12805b.scala b/tests/patmat/i12805b.scala new file mode 100644 index 000000000000..cbff6ae0d2ca --- /dev/null +++ b/tests/patmat/i12805b.scala @@ -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