Skip to content

Commit 7f9a0a7

Browse files
committed
Support polymorphic signatures
1 parent ad4c4ac commit 7f9a0a7

File tree

16 files changed

+200
-52
lines changed

16 files changed

+200
-52
lines changed

compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp
134134

135135
private lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])
136136
private lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])
137-
private lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandle])
138-
private lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles.Lookup])
137+
private lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(defn.MethodHandleClass)
138+
private lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(defn.MethodHandlesLookupClass)
139139
private lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])
140140
private lazy val jliStringConcatFactoryRef : ClassBType = classBTypeFromSymbol(requiredClass("java.lang.invoke.StringConcatFactory")) // since JDK 9
141141
private lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4726,7 +4726,7 @@ class JSCodeGen()(using genCtx: Context) {
47264726
Set[Symbol](
47274727
defn.BoxedUnitClass, defn.BoxedBooleanClass, defn.BoxedCharClass, defn.BoxedByteClass,
47284728
defn.BoxedShortClass, defn.BoxedIntClass, defn.BoxedLongClass, defn.BoxedFloatClass,
4729-
defn.BoxedDoubleClass, defn.StringClass, jsdefn.JavaLangVoidClass
4729+
defn.BoxedDoubleClass, defn.StringClass, defn.VoidClass
47304730
)
47314731
}
47324732

compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ final class JSDefinitions()(using Context) {
2626
@threadUnsafe lazy val NoinlineAnnotType: TypeRef = requiredClassRef("scala.noinline")
2727
def NoinlineAnnot(using Context) = NoinlineAnnotType.symbol.asClass
2828

29-
@threadUnsafe lazy val JavaLangVoidType: TypeRef = requiredClassRef("java.lang.Void")
30-
def JavaLangVoidClass(using Context) = JavaLangVoidType.symbol.asClass
31-
3229
@threadUnsafe lazy val ScalaJSJSPackageVal = requiredPackage("scala.scalajs.js")
3330
@threadUnsafe lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass
3431
@threadUnsafe lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf")

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ class Definitions {
485485
}
486486
def NullType: TypeRef = NullClass.typeRef
487487

488+
@tu lazy val VoidType: TypeRef = requiredClassRef("java.lang.Void")
489+
def VoidClass(using Context) = VoidType.symbol.asClass
490+
488491
@tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker")
489492
@tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked")
490493

@@ -732,6 +735,26 @@ class Definitions {
732735
}
733736
def JavaEnumType = JavaEnumClass.typeRef
734737

738+
739+
@tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle")
740+
@tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup")
741+
@tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle")
742+
743+
// from the Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3
744+
def isPolymorphicSignature(sym: Symbol) = sym.is(JavaDefined) && {
745+
val owner = sym.maybeOwner
746+
(owner == MethodHandleClass || owner == VarHandleClass)
747+
&& sym.hasAnnotation(NativeAnnot)
748+
&& sym.paramSymss.match
749+
case List(List(p)) => p.info.isRepeatedParam
750+
case _ => false
751+
}
752+
753+
def wasPolymorphicSignature(sym: Symbol) =
754+
val owner = sym.maybeOwner
755+
(owner == MethodHandleClass || owner == VarHandleClass)
756+
&& isPolymorphicSignature(owner.info.member(sym.name).symbol)
757+
735758
@tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder")
736759
@tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError")
737760
@tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class TastyPrinter(bytes: Array[Byte]) {
129129
printName(); printName()
130130
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND =>
131131
printName(); printTrees()
132-
case REFINEDtype | TERMREFin | TYPEREFin | SELECTin =>
132+
case REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly =>
133133
printName(); printTree(); printTrees()
134134
case RETURN | HOLE =>
135135
printNat(); printTrees()

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,14 @@ class TreePickler(pickler: TastyPickler) {
413413
writeByte(if name.isTypeName then SELECTtpt else SELECT)
414414
pickleNameAndSig(name, sig, ename)
415415
pickleTree(qual)
416+
else if defn.wasPolymorphicSignature(tree.symbol) then
417+
writeByte(SELECTinPoly)
418+
withLength {
419+
pickleNameAndSig(name, tree.symbol.signature, ename)
420+
pickleTree(qual)
421+
pickleType(tree.symbol.owner.typeRef)
422+
pickleType(tree.tpe.widenSingleton, richTypes = true) // this widens to a MethodType, so need richTypes
423+
}
416424
else // select from owner
417425
writeByte(SELECTin)
418426
withLength {

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,6 @@ class TreeUnpickler(reader: TastyReader,
11301130

11311131
def makeSelect(qual: Tree, name: Name, denot: Denotation): Select =
11321132
var qualType = qual.tpe.widenIfUnstable
1133-
val owner = denot.symbol.maybeOwner
11341133
val tpe0 = name match
11351134
case name: TypeName => TypeRef(qualType, name, denot)
11361135
case name: TermName => TermRef(qualType, name, denot)
@@ -1142,6 +1141,43 @@ class TreeUnpickler(reader: TastyReader,
11421141
val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target)
11431142
makeSelect(qual, name, denot)
11441143

1144+
def readSelectIn(): Select =
1145+
var sname = readName()
1146+
val qual = readTerm()
1147+
val ownerTpe = readType()
1148+
val owner = ownerTpe.typeSymbol
1149+
val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin
1150+
val qualType = qual.tpe.widenIfUnstable
1151+
val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name)
1152+
1153+
/** Tasty should still be able to resolve a method from another root class,
1154+
* even if it has been moved to a super type,
1155+
* or an override has been removed.
1156+
*
1157+
* This is tested in
1158+
* - sbt-test/tasty-compat/remove-override
1159+
* - sbt-test/tasty-compat/move-method
1160+
*/
1161+
def lookupInSuper =
1162+
val cls = ownerTpe.classSymbol
1163+
if cls.exists then
1164+
cls.asClass.classDenot
1165+
.findMember(name, cls.thisType, EmptyFlags, excluded=Private)
1166+
.atSignature(sig, target)
1167+
else
1168+
NoDenotation
1169+
1170+
val denot =
1171+
val d = ownerTpe.decl(name).atSignature(sig, target)
1172+
(if !d.exists then lookupInSuper else d).asSeenFrom(prefix)
1173+
1174+
makeSelect(qual, name, denot)
1175+
1176+
def readSelectInPoly(): Select =
1177+
val tree = readSelectIn()
1178+
val info = readType()
1179+
tree.withType(tree.symbol.copy(info = info).termRef)
1180+
11451181
def readQualId(): (untpd.Ident, TypeRef) =
11461182
val qual = readTerm().asInstanceOf[untpd.Ident]
11471183
(untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef])
@@ -1290,37 +1326,8 @@ class TreeUnpickler(reader: TastyReader,
12901326
case SELECTouter =>
12911327
val levels = readNat()
12921328
readTerm().outerSelect(levels, SkolemType(readType()))
1293-
case SELECTin =>
1294-
var sname = readName()
1295-
val qual = readTerm()
1296-
val ownerTpe = readType()
1297-
val owner = ownerTpe.typeSymbol
1298-
val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin
1299-
val qualType = qual.tpe.widenIfUnstable
1300-
val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name)
1301-
1302-
/** Tasty should still be able to resolve a method from another root class,
1303-
* even if it has been moved to a super type,
1304-
* or an override has been removed.
1305-
*
1306-
* This is tested in
1307-
* - sbt-test/tasty-compat/remove-override
1308-
* - sbt-test/tasty-compat/move-method
1309-
*/
1310-
def lookupInSuper =
1311-
val cls = ownerTpe.classSymbol
1312-
if cls.exists then
1313-
cls.asClass.classDenot
1314-
.findMember(name, cls.thisType, EmptyFlags, excluded=Private)
1315-
.atSignature(sig, target)
1316-
else
1317-
NoDenotation
1318-
1319-
val denot =
1320-
val d = ownerTpe.decl(name).atSignature(sig, target)
1321-
(if !d.exists then lookupInSuper else d).asSeenFrom(prefix)
1322-
1323-
makeSelect(qual, name, denot)
1329+
case SELECTin => readSelectIn()
1330+
case SELECTinPoly => readSelectInPoly()
13241331
case REPEATED =>
13251332
val elemtpt = readTpt()
13261333
SeqLiteral(until(end)(readTerm()), elemtpt)

compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ class SemanticSymbolBuilder:
9090
case _ =>
9191
end find
9292
val sig = sym.signature
93-
find(_.signature == sig)
93+
// the polymorphic signature methods (invoke/invokeExact) are never overloaded
94+
// we changed the signature at the call site, which means they won't match,
95+
// but we still shouldn't add any overload index in this case.
96+
find(_.signature == sig || defn.wasPolymorphicSignature(sym))
9497

9598
def addDescriptor(sym: Symbol): Unit =
9699
if sym.is(ModuleClass) then

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
203203
return transformSelect(cpy.Select(tree)(qual.select(pobj).withSpan(qual.span), tree.name), targs)
204204
case _ =>
205205
}
206-
val tree1 = super.transform(tree)
206+
val tree0 = super.transform(tree)
207+
val tree1 = if defn.wasPolymorphicSignature(tree.symbol) then tree0.withType(tree.tpe) else tree0
207208
constToLiteral(tree1) match {
208209
case _: Literal => tree1
209210
case _ => superAcc.transformSelect(tree1, targs)

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ abstract class Recheck extends Phase, SymTransformer:
212212
mt.instantiate(argTypes)
213213

214214
def recheckApply(tree: Apply, pt: Type)(using Context): Type =
215-
recheck(tree.fun).widen match
215+
val wasPolymorphicSignature = tree.fun.tpe match
216+
case funRef: TermRef => defn.wasPolymorphicSignature(funRef.symbol)
217+
case _ => false
218+
if wasPolymorphicSignature then tree.fun.tpe.widenTermRefExpr.finalResultType
219+
else recheck(tree.fun).widen match
216220
case fntpe: MethodType =>
217221
assert(fntpe.paramInfos.hasSameLengthAs(tree.args))
218222
val formals =

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import Inferencing._
2424
import reporting._
2525
import transform.TypeUtils._
2626
import transform.SymUtils._
27-
import Nullables._
27+
import Nullables._, NullOpsDecorator.*
2828
import config.Feature
2929

3030
import collection.mutable
@@ -938,11 +938,24 @@ trait Applications extends Compatibility {
938938
def simpleApply(fun1: Tree, proto: FunProto)(using Context): Tree =
939939
methPart(fun1).tpe match {
940940
case funRef: TermRef =>
941-
val app = ApplyTo(tree, fun1, funRef, proto, pt)
942-
convertNewGenericArray(
943-
widenEnumCase(
944-
postProcessByNameArgs(funRef, app).computeNullable(),
945-
pt))
941+
if defn.isPolymorphicSignature(funRef.symbol) then
942+
val originalResultType = funRef.symbol.info.resultType.stripNull
943+
val expectedResultType = AvoidWildcardsMap()(proto.deepenProto.resultType)
944+
val resultType =
945+
if !originalResultType.isRef(defn.ObjectClass) then originalResultType
946+
else if isFullyDefined(expectedResultType, ForceDegree.all) then expectedResultType
947+
else expectedResultType match
948+
case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp
949+
case _ => defn.ObjectType
950+
val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType)
951+
val fun2 = fun1.withType(funRef.symbol.copy(info = info).termRef)
952+
simpleApply(fun2, proto)
953+
else
954+
val app = ApplyTo(tree, fun1, funRef, proto, pt)
955+
convertNewGenericArray(
956+
widenEnumCase(
957+
postProcessByNameArgs(funRef, app).computeNullable(),
958+
pt))
946959
case _ =>
947960
handleUnexpectedFunType(tree, fun1)
948961
}

project/Build.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,9 +1813,10 @@ object Build {
18131813
settings(disableDocSetting).
18141814
settings(
18151815
versionScheme := Some("semver-spec"),
1816-
if (mode == Bootstrapped) {
1817-
commonMiMaSettings
1818-
} else {
1816+
if (mode == Bootstrapped) Def.settings(
1817+
commonMiMaSettings,
1818+
mimaBinaryIssueFilters ++= MiMaFilters.TastyCore,
1819+
) else {
18191820
Nil
18201821
}
18211822
)

project/MiMaFilters.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ object MiMaFilters {
66
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"),
77
ProblemFilters.exclude[MissingClassProblem]("scala.caps"),
88
)
9+
val TastyCore: Seq[ProblemFilter] = Seq(
10+
ProblemFilters.exclude[MissingMethodProblem]("dotty.tools.tasty.TastyFormat.SELECTinPoly"),
11+
)
912
}

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Standard-Section: "ASTs" TopLevelStat*
8686
IDENT NameRef Type -- Used when term ident’s type is not a TermRef
8787
SELECT possiblySigned_NameRef qual_Term -- qual.name
8888
SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below)
89+
SELECTinPoly Length possiblySigned_NameRef qual_Term owner_Type method_Type -- like SELECTin, but with the method_Type too
8990
QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position.
9091
NEW clsType_Term -- new cls
9192
THROW throwableExpr_Term -- throw throwableExpr
@@ -578,6 +579,7 @@ object TastyFormat {
578579
// final val ??? = 178
579580
// final val ??? = 179
580581
final val METHODtype = 180
582+
final val SELECTinPoly = 181
581583

582584
final val MATCHtype = 190
583585
final val MATCHtpt = 191
@@ -777,6 +779,7 @@ object TastyFormat {
777779
case TERMREFin => "TERMREFin"
778780
case TYPEREFin => "TYPEREFin"
779781
case SELECTin => "SELECTin"
782+
case SELECTinPoly => "SELECTinPoly"
780783

781784
case REFINEDtype => "REFINEDtype"
782785
case REFINEDtpt => "REFINEDtpt"
@@ -807,7 +810,7 @@ object TastyFormat {
807810
*/
808811
def numRefs(tag: Int): Int = tag match {
809812
case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND |
810-
SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | HOLE => 1
813+
SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly | HOLE => 1
811814
case RENAMED | PARAMtype => 2
812815
case POLYtype | TYPELAMBDAtype | METHODtype => -1
813816
case _ => 0

tests/run/i11332.scala

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// scalajs: --skip
2+
import scala.language.unsafeNulls
3+
4+
import java.lang.invoke._, MethodType.methodType
5+
6+
object Test extends O:
7+
def main(args: Array[String]): Unit = ()
8+
9+
class O {
10+
def bar(x: Int): Int = -x
11+
def baz(s: String): String = s.reverse
12+
def dingo(l: Long): String = "long"
13+
def dingo(i: Int): String = "int"
14+
def putStr(s: String): Unit = ()
15+
def returnAny(s: String): Object = s
16+
def id[T](x: T): T = x
17+
18+
val l = MethodHandles.lookup()
19+
val mh1 = l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int]))
20+
val mh2 = l.findVirtual(classOf[O], "baz", methodType(classOf[String], classOf[String]))
21+
val mh3 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Long]))
22+
val mh4 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Int]))
23+
val mh6 = l.findVirtual(classOf[O], "putStr", methodType(classOf[Unit], classOf[String]))
24+
val mh7 = l.findVirtual(classOf[O], "returnAny", methodType(classOf[Any], classOf[String]))
25+
val mh8 = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader]))
26+
27+
val test1_1 = assert(-42 == (mh1.invokeExact(this, 42): Int))
28+
val test1_2 = assert(-33 == (mh1.invokeExact(this, 33): Int))
29+
30+
val test2_1 = assert("oof" == (mh2.invokeExact(this, "foo"): String))
31+
val test2_2 = assert("rab" == (mh2.invokeExact(this, "bar"): String))
32+
val test3 = assert("long" == (mh3.invokeExact(this, 1L): String))
33+
val test4 = assert("int" == (mh4.invokeExact(this, 1): String))
34+
val test5_1 = assert(-3 == (id(mh1.invokeExact(this, 3)): Int))
35+
val test5_2 = expectWrongMethod(mh1.invokeExact(this, 4))
36+
37+
val test6_1 = mh6.invokeExact(this, "hi"): Unit
38+
val test6_2 = { val hi2: Unit = mh6.invokeExact(this, "hi2"); assert((()) == hi2) }
39+
val test6_3 = { def hi3: Unit = mh6.invokeExact(this, "hi3"); assert((()) == hi3) }
40+
41+
val test7_statbk = { mh7.invokeExact(this, "any"); () }
42+
val test7_valdef = { val any2 = mh7.invokeExact(this, "any2"); assert("any2" == any2) }
43+
val test7_defdef = { def any3 = mh7.invokeExact(this, "any3"); assert("any3" == any3) }
44+
45+
val cl1: ClassLoader = mh8.invoke()
46+
val cl2: ClassLoader = mh8.invoke().asInstanceOf[ClassLoader]
47+
48+
val test9_1 = expectWrongMethod(assert(-1 == mh1.invokeExact(this, 1)))
49+
val test9_3 = expectWrongMethod {
50+
l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])).invokeExact(this, 3) // testing inline
51+
}
52+
val test9_4 =
53+
val res = l
54+
.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])) // testing inline
55+
.invokeExact(this, 4): Int
56+
assert(-4 == res)
57+
58+
def expectWrongMethod(op: => Any) =
59+
try
60+
op
61+
throw new AssertionError("expected operation to fail but it didn't")
62+
catch case expected: WrongMethodTypeException => ()
63+
}

tests/run/t12348.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// test: -jvm 11+
2+
// scalajs: --skip
3+
import java.lang.invoke._
4+
import scala.runtime.IntRef
5+
6+
object Test {
7+
def main(args: Array[String]): Unit = {
8+
val ref = new scala.runtime.IntRef(0)
9+
val varHandle = MethodHandles.lookup()
10+
.in(classOf[IntRef])
11+
.findVarHandle(classOf[IntRef], "elem", classOf[Int])
12+
assert(0 == (varHandle.getAndSet(ref, 1): Int))
13+
assert(1 == (varHandle.getAndSet(ref, 2): Int))
14+
assert(2 == ref.elem)
15+
16+
assert((()) == (varHandle.set(ref, 3): Any))
17+
assert(3 == (varHandle.get(ref): Int))
18+
19+
assert(true == (varHandle.compareAndSet(ref, 3, 4): Any))
20+
assert(4 == (varHandle.get(ref): Int))
21+
}
22+
}

0 commit comments

Comments
 (0)