diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 623d9e8c77ca..cc7b0a2c9c4b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -731,7 +731,10 @@ class Definitions { def JavaEnumType = JavaEnumClass.typeRef @tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle") + @tu lazy val MethodHandlesClass: TermSymbol = requiredModule("java.lang.invoke.MethodHandles") + @tu lazy val MethodHandles_lookup: Symbol = MethodHandlesClass.requiredMethod("lookup") @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") + @tu lazy val MethodHandlesLookup_FindVarHandle: Symbol = MethodHandlesLookupClass.requiredMethod("findVarHandle") @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 40ef270ac813..23fc144daf6e 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -398,6 +398,7 @@ object NameKinds { val DirectMethName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") val AdaptedClosureName: SuffixNameKind = new SuffixNameKind(ADAPTEDCLOSURE, "$adapted") { override def definesNewName = true } val SyntheticSetterName: SuffixNameKind = new SuffixNameKind(SETTER, "_$eq") + val LazyVarHandleName: SuffixNameKind = new SuffixNameKind(LAZYVALVARHANDLE, "$lzyHandle") /** A name together with a signature. Used in Tasty trees. */ object SignedName extends NameKind(SIGNED) { diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 59dfaa3d437b..1e45ceac30cd 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -40,6 +40,8 @@ object NameTags extends TastyFormat.NameTags { inline val EXPLICITFIELD = 38 // An explicitly named field, introduce to avoid a clash // with a regular field of the underlying name + inline val LAZYVALVARHANDLE = 39 // A field containing a VarHandle generated for lazy vals + def nameTagToString(tag: Int): String = tag match { case UTF8 => "UTF8" case QUALIFIED => "QUALIFIED" diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index e6cae7010ade..02de56bcf252 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -9,7 +9,7 @@ import core.Contexts.* import core.Decorators.* import core.DenotTransformers.IdentityDenotTransformer import core.Flags.* -import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName} +import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName, LazyVarHandleName} import core.StdNames.nme import core.Symbols.* import core.Types.* @@ -27,8 +27,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * The map contains the list of the offset trees. */ class OffsetInfo(var defs: List[Tree], var ord: Int = 0) + class VarHandleInfo(var defs: List[Tree]) private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] + private val appendVarHandleDefs = mutable.Map.empty[Symbol, VarHandleInfo] override def phaseName: String = LazyVals.name @@ -108,12 +110,19 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { */ override def transformTemplate(template: Template)(using Context): Tree = { val cls = ctx.owner.asClass - appendOffsetDefs.get(cls) match { - case None => template - case Some(data) => - data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span))) - cpy.Template(template)(body = addInFront(data.defs, template.body)) - } + if !selectImpl.useVarHandles then //ctx.settings.YlegacyLazyVals.value then + appendOffsetDefs.get(cls) match { + case None => template + case Some(data) => + data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span))) + cpy.Template(template)(body = addInFront(data.defs, template.body)) + } + else + appendVarHandleDefs.get(cls) match { + case None => template + case Some(data) => + cpy.Template(template)(body = addInFront(data.defs, template.body)) + } } private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match { @@ -273,6 +282,33 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } + private object selectImpl { + inline def apply[T](usingOffset: => T, usingVarHandle: => T)(using Context): T = + if useVarHandles(using ctx) then usingVarHandle else usingOffset + + private def checkVarHandlesAllowed(using Context) = + !ctx.settings.YlegacyLazyVals.value && { + val releaseVersion = ctx.settings.javaOutputVersion.value + if releaseVersion.nonEmpty then + releaseVersion.toInt >= 9 + else + scala.util.Properties.isJavaAtLeast("9") + } + + // Check if environment allows VarHandles, cached per run + private var canUseVarHandles: Boolean = compiletime.uninitialized + private var cachedContext: Context = compiletime.uninitialized + def useVarHandles(using Context) = { + if ctx eq cachedContext then + canUseVarHandles + else + canUseVarHandles = checkVarHandlesAllowed + cachedContext = ctx + canUseVarHandles + } + } + + /** * Create a threadsafe lazy accessor and function that computes the field's value. `Evaluating` and * `NullValue` are represented by `object`s and `Waiting` by a class that allows awaiting the completion @@ -327,20 +363,30 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * @param memberDef the transformed lazy field member definition * @param claz the class containing this lazy val field * @param target the target synthetic field - * @param offset the offset of the field in the storage allocation of the class + * @param offsetOrVarHandle the offset of the field in the storage allocation or its VarHandle * @param thiz a reference to the transformed class */ def mkThreadSafeDef(memberDef: ValOrDefDef, claz: ClassSymbol, target: Symbol, - offset: Tree, + offsetOrVarHandle: Tree, thiz: Tree)(using Context): (DefDef, DefDef) = { + def varHandle = offsetOrVarHandle.ensuring(selectImpl.useVarHandles, "Unexpected VarHandle usage") + def offset = offsetOrVarHandle.ensuring(!selectImpl.useVarHandles, "Unexpected Offset usage") + val tp = memberDef.tpe.widenDealias.resultType.widenDealias val waiting = ref(defn.LazyValsWaitingState) val controlState = ref(defn.LazyValsControlState) val evaluating = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.evaluating) val nullValue = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.nullValue) - val objCasFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas) + val casFlag = selectImpl( + usingOffset = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas), + usingVarHandle = typer.Applications.retypeSignaturePolymorphicFn( // must be retyped to avoid wrapping into Array[Object] + Select(varHandle, lazyNme.compareAndSet), + MethodType(List(defn.ObjectType,defn.ObjectType,defn.ObjectType), defn.BooleanType) + ) + ) + val accessorMethodSymbol = memberDef.symbol.asTerm val lazyInitMethodName = LazyLocalInitName.fresh(memberDef.name.asTermName) val lazyInitMethodSymbol = newSymbol(claz, lazyInitMethodName, Synthetic | Method | Private, MethodType(Nil)(_ => Nil, _ => defn.ObjectType)) @@ -382,12 +428,18 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { val lockRel = { val lockSymb = newSymbol(lazyInitMethodSymbol, lazyNme.lock, Synthetic, waiting.typeOpt) Block(ValDef(lockSymb, ref(target).cast(waiting.typeOpt)) - :: objCasFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)) :: Nil, + :: selectImpl( + usingOffset = casFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)), + usingVarHandle = casFlag.appliedTo(thiz, ref(lockSymb), ref(resSymb)) + ) :: Nil, ref(lockSymb).select(lazyNme.RLazyVals.waitingRelease).ensureApplied) } // finally block val fin = If( - objCasFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone, + selectImpl( + usingOffset = casFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)), + usingVarHandle = casFlag.appliedTo(thiz, evaluating, ref(resSymb)) + ).select(nme.UNARY_!).appliedToNone, lockRel, unitLiteral ) @@ -408,7 +460,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { ) // if CAS(_, null, Evaluating) If( - objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + selectImpl( + usingOffset = casFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + usingVarHandle = casFlag.appliedTo(thiz, nullLiteral, evaluating) + ), Block(ValDef(resSymb, nullLiteral) :: ValDef(resSymbNullable, nullLiteral) :: evaluate :: Nil, // var result: AnyRef = null Return(ref(resSymbNullable), lazyInitMethodSymbol)), unitLiteral @@ -424,7 +479,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { ref(current).select(defn.Object_eq).appliedTo(evaluating), // if is Evaluating then CAS(_, Evaluating, new Waiting) Block( - objCasFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil, + selectImpl( + usingOffset = casFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied), + usingVarHandle = casFlag.appliedTo(thiz, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) + ) :: Nil, unitLiteral ), // if not Evaluating @@ -470,23 +528,43 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic) val containerTree = ValDef(containerSymbol, nullLiteral) - // create an offset for this lazy val - val offsetSymbol: TermSymbol = appendOffsetDefs.get(claz) match - case Some(info) => - newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) - case None => - newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) - val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) - offsetInfo.defs = offsetTree :: offsetInfo.defs - val offset = ref(offsetSymbol.nn) + val offsetOrVarHandle = selectImpl( + usingOffset = { + // create an offset for this lazy val + val offsetSymbol: TermSymbol = appendOffsetDefs.get(claz) match + case Some(info) => + newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) + case None => + newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span)) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) + val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) + val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) + offsetInfo.defs = offsetTree :: offsetInfo.defs + ref(offsetSymbol.nn) + }, + usingVarHandle = { + // create a VarHandle for this lazy val + val varHandleSymbol: TermSymbol = newSymbol(claz, LazyVarHandleName(containerName), Private | Synthetic, defn.VarHandleClass.typeRef).enteredAfter(this) + varHandleSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, varHandleSymbol.span)) + val getVarHandle = + ref(defn.MethodHandlesClass).select(defn.MethodHandles_lookup).appliedToNone + .select(defn.MethodHandlesLookup_FindVarHandle).appliedTo( + thizClass, Literal(Constant(containerName.mangledString)), Literal(Constant(defn.ObjectType)) + ) + val varHandleTree = ValDef(varHandleSymbol, getVarHandle) + val varHandle = ref(varHandleSymbol) + + val varHandleInfo = appendVarHandleDefs.getOrElseUpdate(claz, new VarHandleInfo(Nil)) + varHandleInfo.defs = varHandleTree :: varHandleInfo.defs + varHandle + } + ) val swapOver = This(claz) - val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver) + val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offsetOrVarHandle, swapOver) Thicket(containerTree, accessorDef, initMethodDef) } @@ -686,5 +764,6 @@ object LazyVals { val current: TermName = "current".toTermName val lock: TermName = "lock".toTermName val discard: TermName = "discard".toTermName + val compareAndSet: TermName = "compareAndSet".toTermName } } diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala index 95975ad9e6b8..7a1e55a37324 100644 --- a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -12,13 +12,16 @@ import SymDenotations.SymDenotation import Names.Name import StdNames.nme import NameOps.* +import NameKinds.LazyVarHandleName import ast.* import MegaPhase.* -/** Move static methods from companion to the class itself */ +/** Move static methods from companion to the class itself. Also create the static constructor. + * VarHandles generated by the compiler for lazy vals are left in the original class. + */ class MoveStatics extends MiniPhase with SymTransformer { import ast.tpd.* @@ -28,7 +31,7 @@ class MoveStatics extends MiniPhase with SymTransformer { def transformSym(sym: SymDenotation)(using Context): SymDenotation = if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists && - (sym.is(Flags.Method) || !(sym.is(Flags.Mutable) && sym.owner.companionClass.is(Flags.Trait)))) { + (sym.is(Flags.Method) || !(sym.is(Flags.Mutable) && sym.owner.companionClass.is(Flags.Trait)) && !sym.symbol.name.is(LazyVarHandleName))) { sym.owner.asClass.delete(sym.symbol) sym.owner.companionClass.asClass.enter(sym.symbol) sym.copySymDenotation(owner = sym.owner.companionClass) @@ -65,7 +68,7 @@ class MoveStatics extends MiniPhase with SymTransformer { val moduleTmpl = module.rhs.asInstanceOf[Template] val companionTmpl = companion.rhs.asInstanceOf[Template] val (staticDefs, remainingDefs) = moduleTmpl.body.partition { - case memberDef: MemberDef => memberDef.symbol.isScalaStatic + case memberDef: MemberDef => memberDef.symbol.isScalaStatic && !memberDef.symbol.name.is(LazyVarHandleName) case _ => false } diff --git a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala index 0d235bace899..dd253bc99dee 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala @@ -51,9 +51,10 @@ class PrintingTest { FileDiff.checkAndDumpOrUpdate(path.toString, actualLines.toIndexedSeq, checkFilePath) } - def testIn(testsDir: String, phase: String) = + def testIn(testsDir: String, phase: String, exclude: io.Path => Boolean = _ => false) = val res = Directory(testsDir).list.toList .filter(f => f.extension == "scala") + .filterNot(exclude) .map { f => compileFile(f.jpath, phase) } val failed = res.filter(!_) @@ -66,6 +67,8 @@ class PrintingTest { end testIn + private val isAtLeastJDK9 = scala.util.Properties.isJavaAtLeast("9") + @Test def printing: Unit = testIn("tests/printing", "typer") @@ -73,5 +76,9 @@ class PrintingTest { def untypedPrinting: Unit = testIn("tests/printing/untyped", "parser") @Test - def transformedPrinting: Unit = testIn("tests/printing/transformed", "repeatableAnnotations") + def transformedPrinting: Unit = testIn("tests/printing/transformed", "repeatableAnnotations", + exclude = path => + if isAtLeastJDK9 then false // test all + else Seq("lazy-vals-new-varhandle.scala").contains(path.name) + ) } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index f35b9240ae14..42040fef0e9b 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -14,6 +14,9 @@ object MiMaFilters { ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.relaxedExtensionImports"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$relaxedExtensionImports$"), // end of New experimental features in 3.3.X + + // Changes to lazy vals (added static constructors) + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Tuple."), ) val TastyCore: Seq[ProblemFilter] = Seq( // Backported in 3.3.6 diff --git a/tests/printing/transformed/lazy-vals-new.check b/tests/printing/transformed/lazy-vals-new-offset.check similarity index 95% rename from tests/printing/transformed/lazy-vals-new.check rename to tests/printing/transformed/lazy-vals-new-offset.check index 05471e5677dc..7a4579b5590a 100644 --- a/tests/printing/transformed/lazy-vals-new.check +++ b/tests/printing/transformed/lazy-vals-new-offset.check @@ -1,7 +1,7 @@ -[[syntax trees at end of MegaPhase{dropOuterAccessors, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new.scala +[[syntax trees at end of MegaPhase{dropOuterAccessors, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new-offset.scala package { - @SourceFile("tests/printing/transformed/lazy-vals-new.scala") final module - class A extends Object { + @SourceFile("tests/printing/transformed/lazy-vals-new-offset.scala") final + module class A extends Object { def (): Unit = { super() diff --git a/tests/printing/transformed/lazy-vals-new-offset.flags b/tests/printing/transformed/lazy-vals-new-offset.flags new file mode 100644 index 000000000000..20b03ba8977a --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new-offset.flags @@ -0,0 +1,2 @@ +-java-output-version:8 +-Ylegacy-lazy-vals:false \ No newline at end of file diff --git a/tests/printing/transformed/lazy-vals-new.scala b/tests/printing/transformed/lazy-vals-new-offset.scala similarity index 100% rename from tests/printing/transformed/lazy-vals-new.scala rename to tests/printing/transformed/lazy-vals-new-offset.scala diff --git a/tests/printing/transformed/lazy-vals-new-varhandle.check b/tests/printing/transformed/lazy-vals-new-varhandle.check new file mode 100644 index 000000000000..1ba2be44e2d6 --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new-varhandle.check @@ -0,0 +1,88 @@ +[[syntax trees at end of MegaPhase{dropOuterAccessors, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new-varhandle.scala +package { + @SourceFile("tests/printing/transformed/lazy-vals-new-varhandle.scala") final + module class A extends Object { + def (): Unit = + { + super() + () + } + @static private def (): Unit = + { + A.x$lzy1$lzyHandle = + java.lang.invoke.MethodHandles.lookup().findVarHandle( + classOf[Object {...}], "x$lzy1", classOf[Object]) + () + } + @static private val x$lzy1$lzyHandle: java.lang.invoke.VarHandle = + java.lang.invoke.MethodHandles.lookup().findVarHandle( + classOf[Object {...}], "x$lzy1", classOf[Object]) + private def writeReplace(): Object = + new scala.runtime.ModuleSerializationProxy(classOf[A]) + @volatile private lazy var x$lzy1: Object = null + lazy def x(): Int = + { + val result: Object = A.x$lzy1 + if result.isInstanceOf[Int] then Int.unbox(result) else + if result.eq(scala.runtime.LazyVals.NullValue) then Int.unbox(null) + else Int.unbox(A.x$lzyINIT1()) + } + private def x$lzyINIT1(): Object = + while do + { + val current: Object = A.x$lzy1 + if current.eq(null) then + if + A.x$lzy1$lzyHandle.compareAndSet(this, null, + scala.runtime.LazyVals.Evaluating) + then + { + var result: Object = null + var resultNullable: Object = null + try + { + resultNullable = Int.box(2) + if resultNullable.eq(null) then + result = scala.runtime.LazyVals.NullValue else + result = resultNullable + () + } + finally + if + A.x$lzy1$lzyHandle.compareAndSet(this, + scala.runtime.LazyVals.Evaluating, result).unary_!() + then + { + val lock: scala.runtime.LazyVals.LazyVals$Waiting = + A.x$lzy1.asInstanceOf[ + scala.runtime.LazyVals.LazyVals$Waiting] + A.x$lzy1$lzyHandle.compareAndSet(this, lock, result) + lock.countDown() + } + else () + return resultNullable + } + else () + else + if + current.isInstanceOf[ + scala.runtime.LazyVals.LazyVals$LazyValControlState] + then + if current.eq(scala.runtime.LazyVals.Evaluating) then + { + A.x$lzy1$lzyHandle.compareAndSet(this, current, + new scala.runtime.LazyVals.LazyVals$Waiting()) + () + } + else + if current.isInstanceOf[scala.runtime.LazyVals.LazyVals$Waiting] + then + current.asInstanceOf[scala.runtime.LazyVals.LazyVals$Waiting]. + await() + else return null + else return current + } + } + final lazy module val A: A = new A() +} + diff --git a/tests/printing/transformed/lazy-vals-new-varhandle.flags b/tests/printing/transformed/lazy-vals-new-varhandle.flags new file mode 100644 index 000000000000..f66a9aff4cfe --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new-varhandle.flags @@ -0,0 +1,2 @@ +-java-output-version:9 +-Ylegacy-lazy-vals:false \ No newline at end of file diff --git a/tests/printing/transformed/lazy-vals-new-varhandle.scala b/tests/printing/transformed/lazy-vals-new-varhandle.scala new file mode 100644 index 000000000000..e863bb964b70 --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new-varhandle.scala @@ -0,0 +1,4 @@ +//> using option java-output-version:8 +object A { + lazy val x: Int = 2 +}