diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index d4a9331db261..745c1787f5ed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -457,6 +457,12 @@ object PatternMatcher { apply(initializer(plan.sym)) plan } + override def apply(plan: SeqPlan): Plan = { + apply(plan.head) + if (canFallThrough(plan.head)) + apply(plan.tail) + plan + } } refCounter(plan) refCounter.count @@ -564,8 +570,10 @@ object PatternMatcher { new MergeTests()(plan) } - /** Inline let-bound trees that are referenced only once. - * Drop all variables that are not referenced anymore after this. + /** Inline let-bound trees that are referenced only once and eliminate dead code. + * + * - Drop all variables that are not referenced anymore after inlining. + * - Drop the `tail` of `SeqPlan`s whose `head` cannot fall through. */ private def inlineVars(plan: Plan): Plan = { val refCount = varRefCount(plan) @@ -597,6 +605,18 @@ object PatternMatcher { plan } } + override def apply(plan: SeqPlan): Plan = { + val newHead = apply(plan.head) + if (!canFallThrough(newHead)) { + // If the head cannot fall through, the tail is dead code + newHead + } + else { + plan.head = newHead + plan.tail = apply(plan.tail) + plan + } + } } Inliner(plan) } diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index 6a92872e5213..fc76948490df 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -50,7 +50,7 @@ class TestBCode extends DottyBytecodeTest { /** This test verifies that simple matches with `@switch` annotations are * indeed transformed to a switch */ - @Test def basicTransfromAnnotated = { + @Test def basicSwitch = { val source = """ |object Foo { | import scala.annotation.switch @@ -69,6 +69,71 @@ class TestBCode extends DottyBytecodeTest { } } + @Test def switchWithAlternatives = { + val source = + """ + |object Foo { + | import scala.annotation.switch + | def foo(i: Int) = (i: @switch) match { + | case 2 => println(2) + | case 1 | 3 | 5 => println(1) + | case 0 => println(0) + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val moduleIn = dir.lookupName("Foo$.class", directory = false) + val moduleNode = loadClassNode(moduleIn.input) + val methodNode = getMethod(moduleNode, "foo") + assert(verifySwitch(methodNode)) + } + } + + @Test def switchWithGuards = { + val source = + """ + |object Foo { + | import scala.annotation.switch + | def foo(i: Int, b: Boolean) = (i: @switch) match { + | case 2 => println(3) + | case 1 if b => println(2) + | case 1 => println(1) + | case 0 => println(0) + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val moduleIn = dir.lookupName("Foo$.class", directory = false) + val moduleNode = loadClassNode(moduleIn.input) + val methodNode = getMethod(moduleNode, "foo") + assert(verifySwitch(methodNode)) + } + } + + @Test def matchWithDefaultNoThrowMatchError = { + val source = + """class Test { + | def test(s: String) = s match { + | case "Hello" => 1 + | case _ => 2 + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Test.class", directory = false) + val clsNode = loadClassNode(clsIn.input) + val method = getMethod(clsNode, "test") + val throwMatchError = instructionsFromMethod(method).exists { + case Op(Opcodes.ATHROW) => true + case _ => false + } + assertFalse(throwMatchError) + } + } + @Test def failTransform = { val source = """ |object Foo { diff --git a/scala-backend b/scala-backend index 011d5c333d52..0e7ec5de6024 160000 --- a/scala-backend +++ b/scala-backend @@ -1 +1 @@ -Subproject commit 011d5c333d52fc6d9e45b9e33614abe9ac19a851 +Subproject commit 0e7ec5de60247d44aa4609bb8e194437a36596e8