|  | 
|  | 1 | +package dotty.tools.backend.jvm | 
|  | 2 | + | 
|  | 3 | +import scala.language.unsafeNulls | 
|  | 4 | + | 
|  | 5 | +import org.junit.Assert._ | 
|  | 6 | +import org.junit.Test | 
|  | 7 | + | 
|  | 8 | +import scala.tools.asm | 
|  | 9 | +import asm._ | 
|  | 10 | +import asm.tree._ | 
|  | 11 | + | 
|  | 12 | +import scala.tools.asm.Opcodes | 
|  | 13 | +import scala.jdk.CollectionConverters._ | 
|  | 14 | +import Opcodes._ | 
|  | 15 | + | 
|  | 16 | +class LabelBytecodeTests extends DottyBytecodeTest { | 
|  | 17 | +  import ASMConverters._ | 
|  | 18 | + | 
|  | 19 | +   @Test def localLabelBreak = { | 
|  | 20 | +    testLabelBytecodeEquals( | 
|  | 21 | +      """val local = boundary.Label[Long]() | 
|  | 22 | +        |try break(5L)(using local) | 
|  | 23 | +        |catch case ex: boundary.Break[Long] @unchecked => | 
|  | 24 | +        |  if ex.label eq local then ex.value | 
|  | 25 | +        |  else throw ex | 
|  | 26 | +      """.stripMargin, | 
|  | 27 | +      "Long", | 
|  | 28 | +      Ldc(LDC, 5), | 
|  | 29 | +      Op(LRETURN) | 
|  | 30 | +    ) | 
|  | 31 | +  } | 
|  | 32 | + | 
|  | 33 | +  @Test def simpleBoundaryBreak = { | 
|  | 34 | +    testLabelBytecodeEquals( | 
|  | 35 | +      """boundary: l ?=> | 
|  | 36 | +        |  break(2)(using l) | 
|  | 37 | +      """.stripMargin, | 
|  | 38 | +      "Int", | 
|  | 39 | +      Op(ICONST_2), | 
|  | 40 | +      Op(IRETURN) | 
|  | 41 | +    ) | 
|  | 42 | + | 
|  | 43 | +    testLabelBytecodeEquals( | 
|  | 44 | +      """boundary: | 
|  | 45 | +        |  break(3) | 
|  | 46 | +      """.stripMargin, | 
|  | 47 | +      "Int", | 
|  | 48 | +      Op(ICONST_3), | 
|  | 49 | +      Op(IRETURN) | 
|  | 50 | +    ) | 
|  | 51 | + | 
|  | 52 | +    testLabelBytecodeEquals( | 
|  | 53 | +      """boundary: | 
|  | 54 | +        |  break() | 
|  | 55 | +      """.stripMargin, | 
|  | 56 | +      "Unit", | 
|  | 57 | +      Op(RETURN) | 
|  | 58 | +    ) | 
|  | 59 | +  } | 
|  | 60 | + | 
|  | 61 | +  @Test def labelExtraction = { | 
|  | 62 | +    // Test extra Inlined around the label | 
|  | 63 | +    testLabelBytecodeEquals( | 
|  | 64 | +      """boundary: | 
|  | 65 | +        |  break(2)(using summon[boundary.Label[Int]]) | 
|  | 66 | +      """.stripMargin, | 
|  | 67 | +      "Int", | 
|  | 68 | +      Op(ICONST_2), | 
|  | 69 | +      Op(IRETURN) | 
|  | 70 | +    ) | 
|  | 71 | + | 
|  | 72 | +    // Test extra Block around the label | 
|  | 73 | +    testLabelBytecodeEquals( | 
|  | 74 | +      """boundary: l ?=> | 
|  | 75 | +        |  break(2)(using { l }) | 
|  | 76 | +      """.stripMargin, | 
|  | 77 | +      "Int", | 
|  | 78 | +      Op(ICONST_2), | 
|  | 79 | +      Op(IRETURN) | 
|  | 80 | +    ) | 
|  | 81 | +  } | 
|  | 82 | + | 
|  | 83 | +  @Test def boundaryLocalBreak = { | 
|  | 84 | +    testLabelBytecodeExpect( | 
|  | 85 | +      """val x: Boolean = true | 
|  | 86 | +        |boundary[Unit]: | 
|  | 87 | +        |  var i = 0 | 
|  | 88 | +        |  while true do | 
|  | 89 | +        |    i += 1 | 
|  | 90 | +        |    if i > 10 then break() | 
|  | 91 | +      """.stripMargin, | 
|  | 92 | +      "Unit", | 
|  | 93 | +      !throws(_) | 
|  | 94 | +    ) | 
|  | 95 | +  } | 
|  | 96 | + | 
|  | 97 | +  @Test def boundaryNonLocalBreak = { | 
|  | 98 | +    testLabelBytecodeExpect( | 
|  | 99 | +      """boundary[Unit]: | 
|  | 100 | +        |  nonLocalBreak() | 
|  | 101 | +      """.stripMargin, | 
|  | 102 | +      "Unit", | 
|  | 103 | +      throws | 
|  | 104 | +    ) | 
|  | 105 | + | 
|  | 106 | +    testLabelBytecodeExpect( | 
|  | 107 | +      """boundary[Unit]: | 
|  | 108 | +        |  def f() = break() | 
|  | 109 | +        |  f() | 
|  | 110 | +      """.stripMargin, | 
|  | 111 | +      "Unit", | 
|  | 112 | +      throws | 
|  | 113 | +    ) | 
|  | 114 | +  } | 
|  | 115 | + | 
|  | 116 | +  @Test def boundaryLocalAndNonLocalBreak = { | 
|  | 117 | +    testLabelBytecodeExpect( | 
|  | 118 | +      """boundary[Unit]: l ?=> | 
|  | 119 | +        |  break() | 
|  | 120 | +        |  nonLocalBreak() | 
|  | 121 | +      """.stripMargin, | 
|  | 122 | +      "Unit", | 
|  | 123 | +      throws | 
|  | 124 | +    ) | 
|  | 125 | +  } | 
|  | 126 | + | 
|  | 127 | +  private def throws(instructions: List[Instruction]): Boolean = | 
|  | 128 | +    instructions.exists { | 
|  | 129 | +      case Op(ATHROW) => true | 
|  | 130 | +      case _ => false | 
|  | 131 | +    } | 
|  | 132 | + | 
|  | 133 | +  private def testLabelBytecodeEquals(code: String, tpe: String, expected: Instruction*): Unit = | 
|  | 134 | +    checkLabelBytecodeInstructions(code, tpe) { instructions => | 
|  | 135 | +      val expectedList = expected.toList | 
|  | 136 | +      assert(instructions == expectedList, | 
|  | 137 | +        "`test` was not properly generated\n" + diffInstructions(instructions, expectedList)) | 
|  | 138 | +    } | 
|  | 139 | + | 
|  | 140 | +  private def testLabelBytecodeExpect(code: String, tpe: String, expected: List[Instruction] => Boolean): Unit = | 
|  | 141 | +    checkLabelBytecodeInstructions(code, tpe) { instructions => | 
|  | 142 | +      assert(expected(instructions), | 
|  | 143 | +        "`test` was not properly generated\n" + instructions) | 
|  | 144 | +    } | 
|  | 145 | + | 
|  | 146 | +   private def checkLabelBytecodeInstructions(code: String, tpe: String)(checkOutput: List[Instruction] => Unit): Unit = { | 
|  | 147 | +    val source = | 
|  | 148 | +      s"""import scala.util.* | 
|  | 149 | +         |class Test: | 
|  | 150 | +         |  def test: $tpe = { | 
|  | 151 | +         |    ${code.lines().toList().asScala.mkString("", "\n    ", "")} | 
|  | 152 | +         |  } | 
|  | 153 | +         |  def nonLocalBreak[T](value: T)(using boundary.Label[T]): Nothing = break(value) | 
|  | 154 | +         |  def nonLocalBreak()(using boundary.Label[Unit]): Nothing = break(()) | 
|  | 155 | +      """.stripMargin | 
|  | 156 | + | 
|  | 157 | +    checkBCode(source) { dir => | 
|  | 158 | +      val clsIn   = dir.lookupName("Test.class", directory = false).input | 
|  | 159 | +      val clsNode = loadClassNode(clsIn) | 
|  | 160 | +      val method  = getMethod(clsNode, "test") | 
|  | 161 | + | 
|  | 162 | +      checkOutput(instructionsFromMethod(method)) | 
|  | 163 | +    } | 
|  | 164 | +  } | 
|  | 165 | + | 
|  | 166 | +} | 
0 commit comments