From 3a8274cb7e3f2bbeefa1b081d9b8d445be4459c1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 8 Sep 2021 16:15:58 +0100 Subject: [PATCH 1/7] Update checkfiles is success & rm out file --- compiler/test/dotty/tools/vulpix/FileDiff.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/FileDiff.scala b/compiler/test/dotty/tools/vulpix/FileDiff.scala index 835e8c6a2d6f..4cb68005ecb7 100644 --- a/compiler/test/dotty/tools/vulpix/FileDiff.scala +++ b/compiler/test/dotty/tools/vulpix/FileDiff.scala @@ -67,9 +67,12 @@ object FileDiff { val outFilePath = checkFilePath + ".out" FileDiff.check(sourceTitle, actualLines, checkFilePath) match { case Some(msg) if dotty.Properties.testsUpdateCheckfile => - FileDiff.dump(checkFilePath, actualLines) + Files.deleteIfExists(Paths.get(outFilePath)) + if actualLines.isEmpty + then Files.deleteIfExists(Paths.get(checkFilePath)) + else FileDiff.dump(checkFilePath, actualLines) println("Updated checkfile: " + checkFilePath) - false + true case Some(msg) => FileDiff.dump(outFilePath, actualLines) println(msg) From 3340ba67de66adc5bc1baad67363d9cf811ed5d3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 10 Sep 2021 09:48:18 +0100 Subject: [PATCH 2/7] Test order-dependent reachability warnings --- tests/patmat/i13485.check | 1 + tests/patmat/i13485.scala | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/patmat/i13485.check create mode 100644 tests/patmat/i13485.scala diff --git a/tests/patmat/i13485.check b/tests/patmat/i13485.check new file mode 100644 index 000000000000..f05a4c6c0788 --- /dev/null +++ b/tests/patmat/i13485.check @@ -0,0 +1 @@ +16: Match case Unreachable diff --git a/tests/patmat/i13485.scala b/tests/patmat/i13485.scala new file mode 100644 index 000000000000..72cc3a3d2cd3 --- /dev/null +++ b/tests/patmat/i13485.scala @@ -0,0 +1,16 @@ +// The intent of this test is test that changing the order of cases doesn't affect whether +// warnings, originally reachability warnings but exhaustivity warnings too, are emitted. +// To do so we need a case that typechecks but is statically assessed to be unreachable. +// How about... a type pattern on a sealed trait that the scrutinee type doesn't extend? + +sealed trait Foo + +class Bar + +def test1(bar: Bar) = bar match + case _: Foo => 1 + case _: Bar => 2 + +def test2(bar: Bar) = bar match + case _: Bar => 2 + case _: Foo => 1 From f45c11dc9c33cb2cde6d06cfe36d9ee51902982e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 6 Sep 2021 15:25:29 +0100 Subject: [PATCH 3/7] Emit deferred reachability warnings There are three kinds of unreachable cases: 1. the cases that are provably disjoint from the scrutinee type 2. the cases that are not subtypes of the scrutinee type 3. the cases that are covered by a previous cases, aka an overlap The first one should be (I didn't review or stress test it) handled when type-checking the pattern. The third one is the safest, in terms of not emitting false positives to the user, because it's within the conservative approximation of the scrutinee space and the previous cases spaces. The second one is where it gets tricky, because we don't know they're part of the scrutinee type but we also don't know they're _not_ part of the scrutinee type. Erasure is the simplest example: if the scrutinee type is `type ThisTree <: Tree` then if you write cases for subtypes of Tree that aren't subtypes of ThisTree, then they're not subtypes nor are they provably disjoint from the scrutinee type. Martin mentioned that it's been tried a few times to restrict patterns to subtypes, but that it's been found too restrictive and provably disjoint is the right choice for type errors. So the logic that I recently reimplemented was to only emit reachability warnings after a reachable a case was found, therefore avoiding to emit any warnings in the ThisTree case, emitting for all the overlap unreachable cases. The problem was that accidentally introduced a dependency on the ordering of the cases, where unreachable initial cases aren't warned but reordered they would. So I hold onto those references and unbuffer as soon as we find a provably reachable case (covered != Empty) or a provably unreachable case (prev != Empty). Also, I've had separate conversations with Seth and Lukas about whether match analysis should just widen those non-class types, but it's also plausible that a user has created a whole type hierarchy with type members and would prefer for it to not discard it all by widening to class/object types, but instead reason within the bounds of the given scrutinee type. --- .../tools/dotc/transform/patmat/Space.scala | 34 +++++++++++++------ tests/patmat/i13485.check | 1 + 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index b928ff79dd53..9e4ed4a2eec3 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -911,7 +911,8 @@ class SpaceEngine(using Context) extends SpaceLogic { && !sel.tpe.widen.isRef(defn.QuotedTypeClass) def checkRedundancy(_match: Match): Unit = { - val Match(sel, cases) = _match + val Match(sel, _) = _match + val cases = _match.cases.toIndexedSeq debug.println(i"checking redundancy in $_match") if (!redundancyCheckable(sel)) return @@ -925,7 +926,14 @@ class SpaceEngine(using Context) extends SpaceLogic { else project(selTyp) debug.println(s"targetSpace: ${show(targetSpace)}") - cases.iterator.zipWithIndex.foldLeft(Nil: List[Space]) { case (prevs, (CaseDef(pat, guard, _), i)) => + 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) + debug.println(i"case pattern: $pat") val curr = project(pat) @@ -937,18 +945,24 @@ class SpaceEngine(using Context) extends SpaceLogic { val covered = simplify(intersect(curr, targetSpace)) debug.println(s"covered: ${show(covered)}") - if pat != EmptyTree // rethrow case of catch uses EmptyTree - && prev != Empty // avoid isSubspace(Empty, Empty) - one of the previous cases much be reachable - && isSubspace(covered, prev) - then { - if isNullable && i == cases.length - 1 && isWildcardArg(pat) then - report.warning(MatchCaseOnlyNullWarning(), pat.srcPos) - else + 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 + && 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 - (if guard.isEmpty then covered else Empty) :: prevs + prevs ::= (if guard.isEmpty then covered else Empty) + i += 1 } } } diff --git a/tests/patmat/i13485.check b/tests/patmat/i13485.check index f05a4c6c0788..f9d066905a86 100644 --- a/tests/patmat/i13485.check +++ b/tests/patmat/i13485.check @@ -1 +1,2 @@ +11: Match case Unreachable 16: Match case Unreachable From 85747b5225f250c39bb17f883a0a36acc6564727 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 7 Sep 2021 17:56:43 +0100 Subject: [PATCH 4/7] Redo & organise adaptType in SpaceEngine Emitting deferred reachability warnings (the previous commit) caused tests/patmat/boxing.scala to start failing, because my logic on boxing and unboxing wasn't actually right. So this fixes how the case type (tp1) gets adapted for the scrutinee type (tp2), with two examples in the comments from that boxing test case. Also organise that constant types are converted if boxing isn't involved. --- .../tools/dotc/transform/patmat/Space.scala | 53 +++++++++++++------ .../fatal-warnings/i8711.check | 12 ++--- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 9e4ed4a2eec3..0cc3b5645adc 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -512,23 +512,46 @@ class SpaceEngine(using Context) extends SpaceLogic { if converted == null then tp else ConstantType(converted) case _ => tp - /** Adapt types by performing primitive value boxing. #12805 */ - def maybeBox(tp1: Type, tp2: Type): Type = - if tp1.classSymbol.isPrimitiveValueClass && !tp2.classSymbol.isPrimitiveValueClass then - defn.boxedType(tp1).narrow - else tp1 + private lazy val defn = ctx.definitions + private lazy val ByteClass = defn.ByteClass + private lazy val ShortClass = defn.ShortClass + private lazy val CharClass = defn.CharClass + private lazy val IntClass = defn.IntClass + private lazy val LongClass = defn.LongClass + private lazy val FloatClass = defn.FloatClass + private lazy val DoubleClass = defn.DoubleClass + private lazy val UnitClass = defn.UnitClass + private lazy val BooleanClass = defn.BooleanClass + + /** Adapt types by performing primitive value unboxing or boxing, or numeric constant conversion. #12805 */ + def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true)((tp1.classSymbol, tp2.classSymbol) match { + case ( ByteClass, defn.BoxedByteClass) => defn.BoxedByteClass.typeRef.narrow + case ( ShortClass, defn.BoxedShortClass) => defn.BoxedShortClass.typeRef.narrow + case ( CharClass, defn.BoxedCharClass) => defn.BoxedCharClass.typeRef.narrow + case ( IntClass, defn.BoxedIntClass) => defn.BoxedIntClass.typeRef.narrow // 1 <:< Integer => ( : Integer) <:< Integer = true + case ( LongClass, defn.BoxedLongClass) => defn.BoxedLongClass.typeRef.narrow + case ( FloatClass, defn.BoxedFloatClass) => defn.BoxedFloatClass.typeRef.narrow + case ( DoubleClass, defn.BoxedDoubleClass) => defn.BoxedDoubleClass.typeRef.narrow + case ( UnitClass, defn.BoxedUnitClass) => defn.BoxedUnitClass.typeRef.narrow + case (BooleanClass, defn.BoxedBooleanClass) => defn.BoxedBooleanClass.typeRef.narrow + + case ( defn.BoxedByteClass, ByteClass) => defn.ByteType.narrow + case ( defn.BoxedShortClass, ShortClass) => defn.ShortType.narrow + case ( defn.BoxedCharClass, CharClass) => defn.CharType.narrow + case ( defn.BoxedIntClass, IntClass) => defn.IntType.narrow // ONE <:< Int => ( : Int) <:< Int = true + case ( defn.BoxedLongClass, LongClass) => defn.LongType.narrow + case ( defn.BoxedFloatClass, FloatClass) => defn.FloatType.narrow + case ( defn.BoxedDoubleClass, DoubleClass) => defn.DoubleType.narrow + case ( defn.BoxedUnitClass, UnitClass) => defn.UnitType.narrow + case (defn.BoxedBooleanClass, BooleanClass) => defn.BooleanType.narrow + + case _ => convertConstantType(tp1, tp2) + }) /** Is `tp1` a subtype of `tp2`? */ - def isSubType(_tp1: Type, tp2: Type): Boolean = { - val tp1 = maybeBox(convertConstantType(_tp1, tp2), tp2) - //debug.println(TypeComparer.explained(_.isSubType(tp1, tp2))) - val res = if (ctx.explicitNulls) { - tp1 <:< tp2 - } else { - (tp1 != constantNullType || tp2 == constantNullType) && tp1 <:< tp2 - } - debug.println(i"$tp1 <:< $tp2 = $res") - res + def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) { + if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType + else adaptType(tp1, tp2) <:< tp2 } def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean = diff --git a/tests/neg-custom-args/fatal-warnings/i8711.check b/tests/neg-custom-args/fatal-warnings/i8711.check index 0abda7a77ed6..0035af0755d4 100644 --- a/tests/neg-custom-args/fatal-warnings/i8711.check +++ b/tests/neg-custom-args/fatal-warnings/i8711.check @@ -1,8 +1,8 @@ --- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:7:9 --------------------------------------------------------- +-- [E030] Match case Unreachable Error: tests/neg-custom-args/fatal-warnings/i8711.scala:7:9 --------------------------- 7 | case x: B => x // error: this case is unreachable since class A is not a subclass of class B - | ^ - | this case is unreachable since type A and class B are unrelated --- Error: tests/neg-custom-args/fatal-warnings/i8711.scala:12:9 -------------------------------------------------------- + | ^^^^ + | Unreachable case +-- [E030] Match case Unreachable Error: tests/neg-custom-args/fatal-warnings/i8711.scala:12:9 -------------------------- 12 | case x: C => x // error - | ^ - | this case is unreachable since type A | B and class C are unrelated + | ^^^^ + | Unreachable case From 404d53c9fa44f45f4cf775f5d0d0351085522ee5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 8 Sep 2021 17:36:08 +0100 Subject: [PATCH 5/7] Simplify adaptType by introducing unboxedType --- .../dotty/tools/dotc/core/Definitions.scala | 14 +++++++ .../tools/dotc/transform/patmat/Space.scala | 42 ++++--------------- .../dotc/transform/SpaceEngineTest.scala | 19 +++++++++ 3 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 667dcf913ee3..c3b462f3b179 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1731,6 +1731,20 @@ class Definitions { else sys.error(s"Not a primitive value type: $tp") }.typeRef + def unboxedType(tp: Type)(using Context): TypeRef = { + val cls = tp.classSymbol + if (cls eq BoxedByteClass) ByteType + else if (cls eq BoxedShortClass) ShortType + else if (cls eq BoxedCharClass) CharType + else if (cls eq BoxedIntClass) IntType + else if (cls eq BoxedLongClass) LongType + else if (cls eq BoxedFloatClass) FloatType + else if (cls eq BoxedDoubleClass) DoubleType + else if (cls eq BoxedUnitClass) UnitType + else if (cls eq BoxedBooleanClass) BooleanType + else sys.error(s"Not a boxed primitive value type: $tp") + } + /** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */ def typeTag(tp: Type)(using Context): Name = typeTags(scalaClassName(tp)) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 0cc3b5645adc..d178d3b0efbc 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -512,41 +512,13 @@ class SpaceEngine(using Context) extends SpaceLogic { if converted == null then tp else ConstantType(converted) case _ => tp - private lazy val defn = ctx.definitions - private lazy val ByteClass = defn.ByteClass - private lazy val ShortClass = defn.ShortClass - private lazy val CharClass = defn.CharClass - private lazy val IntClass = defn.IntClass - private lazy val LongClass = defn.LongClass - private lazy val FloatClass = defn.FloatClass - private lazy val DoubleClass = defn.DoubleClass - private lazy val UnitClass = defn.UnitClass - private lazy val BooleanClass = defn.BooleanClass - - /** Adapt types by performing primitive value unboxing or boxing, or numeric constant conversion. #12805 */ - def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true)((tp1.classSymbol, tp2.classSymbol) match { - case ( ByteClass, defn.BoxedByteClass) => defn.BoxedByteClass.typeRef.narrow - case ( ShortClass, defn.BoxedShortClass) => defn.BoxedShortClass.typeRef.narrow - case ( CharClass, defn.BoxedCharClass) => defn.BoxedCharClass.typeRef.narrow - case ( IntClass, defn.BoxedIntClass) => defn.BoxedIntClass.typeRef.narrow // 1 <:< Integer => ( : Integer) <:< Integer = true - case ( LongClass, defn.BoxedLongClass) => defn.BoxedLongClass.typeRef.narrow - case ( FloatClass, defn.BoxedFloatClass) => defn.BoxedFloatClass.typeRef.narrow - case ( DoubleClass, defn.BoxedDoubleClass) => defn.BoxedDoubleClass.typeRef.narrow - case ( UnitClass, defn.BoxedUnitClass) => defn.BoxedUnitClass.typeRef.narrow - case (BooleanClass, defn.BoxedBooleanClass) => defn.BoxedBooleanClass.typeRef.narrow - - case ( defn.BoxedByteClass, ByteClass) => defn.ByteType.narrow - case ( defn.BoxedShortClass, ShortClass) => defn.ShortType.narrow - case ( defn.BoxedCharClass, CharClass) => defn.CharType.narrow - case ( defn.BoxedIntClass, IntClass) => defn.IntType.narrow // ONE <:< Int => ( : Int) <:< Int = true - case ( defn.BoxedLongClass, LongClass) => defn.LongType.narrow - case ( defn.BoxedFloatClass, FloatClass) => defn.FloatType.narrow - case ( defn.BoxedDoubleClass, DoubleClass) => defn.DoubleType.narrow - case ( defn.BoxedUnitClass, UnitClass) => defn.UnitType.narrow - case (defn.BoxedBooleanClass, BooleanClass) => defn.BooleanType.narrow - - case _ => convertConstantType(tp1, tp2) - }) + def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true) { + def isPrimToBox(tp: Type, pt: Type) = + tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol) + if isPrimToBox(tp1, tp2) then defn.boxedType(tp1).narrow // 1 <:< Integer => ( : Integer) <:< Integer = true + else if isPrimToBox(tp2, tp1) then defn.unboxedType(tp1).narrow // ONE <:< Int => ( : Int) <:< Int = true + else convertConstantType(tp1, tp2) + } /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) { diff --git a/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala new file mode 100644 index 000000000000..e6266b21fa1b --- /dev/null +++ b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala @@ -0,0 +1,19 @@ +package dotty.tools +package dotc +package transform + +import org.junit.*, Assert.* + +import core.*, Contexts.*, Decorators.*, Symbols.*, Types.* + +class SpaceEngineTest extends DottyTest: + @Test def testAdaptTest(): Unit = + given Context = ctx + val defn = ctx.definitions + import defn._ + val e = patmat.SpaceEngine() + + val BoxedIntType = BoxedIntClass.typeRef + + assertEquals(BoxedIntType, e.adaptType(IntType, BoxedIntType).widenSingleton) + assertEquals(IntType, e.adaptType(BoxedIntType, IntType).widenSingleton) From b9ecea29555dd581168b10c469d0f1ebf0020ea7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 8 Sep 2021 21:27:37 +0100 Subject: [PATCH 6/7] Avoid generating skolems just to return true Co-authored-by: Guillaume Martres --- .../src/dotty/tools/dotc/core/Definitions.scala | 14 -------------- .../dotty/tools/dotc/transform/patmat/Space.scala | 13 +++++-------- .../tools/dotc/transform/SpaceEngineTest.scala | 4 ++-- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c3b462f3b179..667dcf913ee3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1731,20 +1731,6 @@ class Definitions { else sys.error(s"Not a primitive value type: $tp") }.typeRef - def unboxedType(tp: Type)(using Context): TypeRef = { - val cls = tp.classSymbol - if (cls eq BoxedByteClass) ByteType - else if (cls eq BoxedShortClass) ShortType - else if (cls eq BoxedCharClass) CharType - else if (cls eq BoxedIntClass) IntType - else if (cls eq BoxedLongClass) LongType - else if (cls eq BoxedFloatClass) FloatType - else if (cls eq BoxedDoubleClass) DoubleType - else if (cls eq BoxedUnitClass) UnitType - else if (cls eq BoxedBooleanClass) BooleanType - else sys.error(s"Not a boxed primitive value type: $tp") - } - /** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */ def typeTag(tp: Type)(using Context): Name = typeTags(scalaClassName(tp)) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index d178d3b0efbc..79d5eeaa44cb 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -512,18 +512,15 @@ class SpaceEngine(using Context) extends SpaceLogic { if converted == null then tp else ConstantType(converted) case _ => tp - def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true) { - def isPrimToBox(tp: Type, pt: Type) = - tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol) - if isPrimToBox(tp1, tp2) then defn.boxedType(tp1).narrow // 1 <:< Integer => ( : Integer) <:< Integer = true - else if isPrimToBox(tp2, tp1) then defn.unboxedType(tp1).narrow // ONE <:< Int => ( : Int) <:< Int = true - else convertConstantType(tp1, tp2) - } + def isPrimToBox(tp: Type, pt: Type) = + tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol) /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) { if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType - else adaptType(tp1, tp2) <:< tp2 + else + isPrimToBox(tp1, tp2) || isPrimToBox(tp2, tp1) || + convertConstantType(tp1, tp2) <:< tp2 } def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean = diff --git a/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala index e6266b21fa1b..af96b8378317 100644 --- a/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala @@ -15,5 +15,5 @@ class SpaceEngineTest extends DottyTest: val BoxedIntType = BoxedIntClass.typeRef - assertEquals(BoxedIntType, e.adaptType(IntType, BoxedIntType).widenSingleton) - assertEquals(IntType, e.adaptType(BoxedIntType, IntType).widenSingleton) + assertTrue(e.isPrimToBox(IntType, BoxedIntType)) + assertFalse(e.isPrimToBox(BoxedIntType, IntType)) From 48790dd3692d06f34ef023bd8be5a486f345e84b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 8 Sep 2021 22:02:12 +0100 Subject: [PATCH 7/7] Restore adaptType, with expanded docs & tests --- .../dotty/tools/dotc/core/Definitions.scala | 14 ++++++++++++++ .../tools/dotc/transform/patmat/Space.scala | 19 ++++++++++++++++--- .../dotc/transform/SpaceEngineTest.scala | 8 +++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 667dcf913ee3..c3b462f3b179 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1731,6 +1731,20 @@ class Definitions { else sys.error(s"Not a primitive value type: $tp") }.typeRef + def unboxedType(tp: Type)(using Context): TypeRef = { + val cls = tp.classSymbol + if (cls eq BoxedByteClass) ByteType + else if (cls eq BoxedShortClass) ShortType + else if (cls eq BoxedCharClass) CharType + else if (cls eq BoxedIntClass) IntType + else if (cls eq BoxedLongClass) LongType + else if (cls eq BoxedFloatClass) FloatType + else if (cls eq BoxedDoubleClass) DoubleType + else if (cls eq BoxedUnitClass) UnitType + else if (cls eq BoxedBooleanClass) BooleanType + else sys.error(s"Not a boxed primitive value type: $tp") + } + /** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */ def typeTag(tp: Type)(using Context): Name = typeTags(scalaClassName(tp)) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 79d5eeaa44cb..2bfff0baad6e 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -515,12 +515,25 @@ class SpaceEngine(using Context) extends SpaceLogic { def isPrimToBox(tp: Type, pt: Type) = tp.classSymbol.isPrimitiveValueClass && (defn.boxedType(tp).classSymbol eq pt.classSymbol) + /** Adapt types by performing primitive value unboxing or boxing, or numeric constant conversion. #12805 + * + * This makes these isSubType cases work like this: + * {{{ + * 1 <:< Integer => ( : Integer) <:< Integer = true + * ONE <:< Int => ( : Int) <:< Int = true + * Integer <:< (1: Int) => ( : Int) <:< (1: Int) = false + * }}} + */ + def adaptType(tp1: Type, tp2: Type): Type = trace(i"adaptType($tp1, $tp2)", show = true) { + if isPrimToBox(tp1, tp2) then defn.boxedType(tp1).narrow + else if isPrimToBox(tp2, tp1) then defn.unboxedType(tp1).narrow + else convertConstantType(tp1, tp2) + } + /** Is `tp1` a subtype of `tp2`? */ def isSubType(tp1: Type, tp2: Type): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) { if tp1 == constantNullType && !ctx.explicitNulls then tp2 == constantNullType - else - isPrimToBox(tp1, tp2) || isPrimToBox(tp2, tp1) || - convertConstantType(tp1, tp2) <:< tp2 + else adaptType(tp1, tp2) <:< tp2 } def isSameUnapply(tp1: TermRef, tp2: TermRef): Boolean = diff --git a/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala index af96b8378317..09fb5ee960fd 100644 --- a/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/SpaceEngineTest.scala @@ -4,7 +4,7 @@ package transform import org.junit.*, Assert.* -import core.*, Contexts.*, Decorators.*, Symbols.*, Types.* +import core.*, Constants.*, Contexts.*, Decorators.*, Symbols.*, Types.* class SpaceEngineTest extends DottyTest: @Test def testAdaptTest(): Unit = @@ -14,6 +14,12 @@ class SpaceEngineTest extends DottyTest: val e = patmat.SpaceEngine() val BoxedIntType = BoxedIntClass.typeRef + val ConstOneType = ConstantType(Constant(1)) assertTrue(e.isPrimToBox(IntType, BoxedIntType)) assertFalse(e.isPrimToBox(BoxedIntType, IntType)) + assertTrue(e.isPrimToBox(ConstOneType, BoxedIntType)) + + assertEquals(BoxedIntType, e.adaptType(IntType, BoxedIntType).widenSingleton) + assertEquals(IntType, e.adaptType(BoxedIntType, IntType).widenSingleton) + assertEquals(IntType, e.adaptType(BoxedIntType, ConstOneType).widenSingleton)