Skip to content

Commit 74ebee0

Browse files
authored
Merge pull request #13485 from dwijnand/emit-deferred-reachability-warnings
2 parents bd214f1 + 48790dd commit 74ebee0

File tree

7 files changed

+112
-33
lines changed

7 files changed

+112
-33
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

+14
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,20 @@ class Definitions {
17311731
else sys.error(s"Not a primitive value type: $tp")
17321732
}.typeRef
17331733

1734+
def unboxedType(tp: Type)(using Context): TypeRef = {
1735+
val cls = tp.classSymbol
1736+
if (cls eq BoxedByteClass) ByteType
1737+
else if (cls eq BoxedShortClass) ShortType
1738+
else if (cls eq BoxedCharClass) CharType
1739+
else if (cls eq BoxedIntClass) IntType
1740+
else if (cls eq BoxedLongClass) LongType
1741+
else if (cls eq BoxedFloatClass) FloatType
1742+
else if (cls eq BoxedDoubleClass) DoubleType
1743+
else if (cls eq BoxedUnitClass) UnitType
1744+
else if (cls eq BoxedBooleanClass) BooleanType
1745+
else sys.error(s"Not a boxed primitive value type: $tp")
1746+
}
1747+
17341748
/** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */
17351749
def typeTag(tp: Type)(using Context): Name = typeTags(scalaClassName(tp))
17361750

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

+44-25
Original file line numberDiff line numberDiff line change
@@ -512,23 +512,28 @@ class SpaceEngine(using Context) extends SpaceLogic {
512512
if converted == null then tp else ConstantType(converted)
513513
case _ => tp
514514

515-
/** Adapt types by performing primitive value boxing. #12805 */
516-
def maybeBox(tp1: Type, tp2: Type): Type =
517-
if tp1.classSymbol.isPrimitiveValueClass && !tp2.classSymbol.isPrimitiveValueClass then
518-
defn.boxedType(tp1).narrow
519-
else tp1
515+
def isPrimToBox(tp: Type, pt: Type) =
516+
tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol)
517+
518+
/** Adapt types by performing primitive value unboxing or boxing, or numeric constant conversion. #12805
519+
*
520+
* This makes these isSubType cases work like this:
521+
* {{{
522+
* 1 <:< Integer => (<skolem> : Integer) <:< Integer = true
523+
* ONE <:< Int => (<skolem> : Int) <:< Int = true
524+
* Integer <:< (1: Int) => (<skolem> : Int) <:< (1: Int) = false
525+
* }}}
526+
*/
527+
def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true) {
528+
if isPrimToBox(tp1, tp2) then defn.boxedType(tp1).narrow
529+
else if isPrimToBox(tp2, tp1) then defn.unboxedType(tp1).narrow
530+
else convertConstantType(tp1, tp2)
531+
}
520532

521533
/** Is `tp1` a subtype of `tp2`? */
522-
def isSubType(_tp1: Type, tp2: Type): Boolean = {
523-
val tp1 = maybeBox(convertConstantType(_tp1, tp2), tp2)
524-
//debug.println(TypeComparer.explained(_.isSubType(tp1, tp2)))
525-
val res = if (ctx.explicitNulls) {
526-
tp1 <:< tp2
527-
} else {
528-
(tp1 != constantNullType || tp2 == constantNullType) && tp1 <:< tp2
529-
}
530-
debug.println(i"$tp1 <:< $tp2 = $res")
531-
res
534+
def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
535+
if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType
536+
else adaptType(tp1, tp2) <:< tp2
532537
}
533538

534539
def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean =
@@ -911,7 +916,8 @@ class SpaceEngine(using Context) extends SpaceLogic {
911916
&& !sel.tpe.widen.isRef(defn.QuotedTypeClass)
912917

913918
def checkRedundancy(_match: Match): Unit = {
914-
val Match(sel, cases) = _match
919+
val Match(sel, _) = _match
920+
val cases = _match.cases.toIndexedSeq
915921
debug.println(i"checking redundancy in $_match")
916922

917923
if (!redundancyCheckable(sel)) return
@@ -925,7 +931,14 @@ class SpaceEngine(using Context) extends SpaceLogic {
925931
else project(selTyp)
926932
debug.println(s"targetSpace: ${show(targetSpace)}")
927933

928-
cases.iterator.zipWithIndex.foldLeft(Nil: List[Space]) { case (prevs, (CaseDef(pat, guard, _), i)) =>
934+
var i = 0
935+
val len = cases.length
936+
var prevs = List.empty[Space]
937+
var deferred = List.empty[Tree]
938+
939+
while (i < len) {
940+
val CaseDef(pat, guard, _) = cases(i)
941+
929942
debug.println(i"case pattern: $pat")
930943

931944
val curr = project(pat)
@@ -937,18 +950,24 @@ class SpaceEngine(using Context) extends SpaceLogic {
937950
val covered = simplify(intersect(curr, targetSpace))
938951
debug.println(s"covered: ${show(covered)}")
939952

940-
if pat != EmptyTree // rethrow case of catch uses EmptyTree
941-
&& prev != Empty // avoid isSubspace(Empty, Empty) - one of the previous cases much be reachable
942-
&& isSubspace(covered, prev)
943-
then {
944-
if isNullable && i == cases.length - 1 && isWildcardArg(pat) then
945-
report.warning(MatchCaseOnlyNullWarning(), pat.srcPos)
946-
else
953+
if prev == Empty && covered == Empty then // defer until a case is reachable
954+
deferred ::= pat
955+
else {
956+
for (pat <- deferred.reverseIterator)
947957
report.warning(MatchCaseUnreachable(), pat.srcPos)
958+
if pat != EmptyTree // rethrow case of catch uses EmptyTree
959+
&& isSubspace(covered, prev)
960+
then {
961+
val nullOnly = isNullable && i == len - 1 && isWildcardArg(pat)
962+
val msg = if nullOnly then MatchCaseOnlyNullWarning() else MatchCaseUnreachable()
963+
report.warning(msg, pat.srcPos)
964+
}
965+
deferred = Nil
948966
}
949967

950968
// in redundancy check, take guard as false in order to soundly approximate
951-
(if guard.isEmpty then covered else Empty) :: prevs
969+
prevs ::= (if guard.isEmpty then covered else Empty)
970+
i += 1
952971
}
953972
}
954973
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import org.junit.*, Assert.*
6+
7+
import core.*, Constants.*, Contexts.*, Decorators.*, Symbols.*, Types.*
8+
9+
class SpaceEngineTest extends DottyTest:
10+
@Test def testAdaptTest(): Unit =
11+
given Context = ctx
12+
val defn = ctx.definitions
13+
import defn._
14+
val e = patmat.SpaceEngine()
15+
16+
val BoxedIntType = BoxedIntClass.typeRef
17+
val ConstOneType = ConstantType(Constant(1))
18+
19+
assertTrue(e.isPrimToBox(IntType, BoxedIntType))
20+
assertFalse(e.isPrimToBox(BoxedIntType, IntType))
21+
assertTrue(e.isPrimToBox(ConstOneType, BoxedIntType))
22+
23+
assertEquals(BoxedIntType, e.adaptType(IntType, BoxedIntType).widenSingleton)
24+
assertEquals(IntType, e.adaptType(BoxedIntType, IntType).widenSingleton)
25+
assertEquals(IntType, e.adaptType(BoxedIntType, ConstOneType).widenSingleton)

compiler/test/dotty/tools/vulpix/FileDiff.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,12 @@ object FileDiff {
6767
val outFilePath = checkFilePath + ".out"
6868
FileDiff.check(sourceTitle, actualLines, checkFilePath) match {
6969
case Some(msg) if dotty.Properties.testsUpdateCheckfile =>
70-
FileDiff.dump(checkFilePath, actualLines)
70+
Files.deleteIfExists(Paths.get(outFilePath))
71+
if actualLines.isEmpty
72+
then Files.deleteIfExists(Paths.get(checkFilePath))
73+
else FileDiff.dump(checkFilePath, actualLines)
7174
println("Updated checkfile: " + checkFilePath)
72-
false
75+
true
7376
case Some(msg) =>
7477
FileDiff.dump(outFilePath, actualLines)
7578
println(msg)
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
-- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:7:9 ---------------------------------------------------------
1+
-- [E030] Match case Unreachable Error: tests/neg-custom-args/fatal-warnings/i8711.scala:7:9 ---------------------------
22
7 | case x: B => x // error: this case is unreachable since class A is not a subclass of class B
3-
| ^
4-
| this case is unreachable since type A and class B are unrelated
5-
-- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:12:9 --------------------------------------------------------
3+
| ^^^^
4+
| Unreachable case
5+
-- [E030] Match case Unreachable Error: tests/neg-custom-args/fatal-warnings/i8711.scala:12:9 --------------------------
66
12 | case x: C => x // error
7-
| ^
8-
| this case is unreachable since type A | B and class C are unrelated
7+
| ^^^^
8+
| Unreachable case

tests/patmat/i13485.check

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
11: Match case Unreachable
2+
16: Match case Unreachable

tests/patmat/i13485.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// The intent of this test is test that changing the order of cases doesn't affect whether
2+
// warnings, originally reachability warnings but exhaustivity warnings too, are emitted.
3+
// To do so we need a case that typechecks but is statically assessed to be unreachable.
4+
// How about... a type pattern on a sealed trait that the scrutinee type doesn't extend?
5+
6+
sealed trait Foo
7+
8+
class Bar
9+
10+
def test1(bar: Bar) = bar match
11+
case _: Foo => 1
12+
case _: Bar => 2
13+
14+
def test2(bar: Bar) = bar match
15+
case _: Bar => 2
16+
case _: Foo => 1

0 commit comments

Comments
 (0)