diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e213e1bee01f..39c60ff14682 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -64,8 +64,8 @@ class Compiler { new CheckStatic, // Check restrictions that apply to @static members new BetaReduce, // Reduce closure applications new InlineVals, // Check right hand-sides of an `inline val`s - new ExpandSAMs, // Expand single abstract method closures to anonymous classes - new init.Checker) :: // Check initialization of objects + new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes + List(new init.Checker) :: // Check initialization of objects List(new ElimRepeated, // Rewrite vararg parameters and arguments new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4646751192b4..185dd569a993 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -290,7 +290,7 @@ object Phases { /** If set, implicit search is enabled */ def allowsImplicitSearch: Boolean = false - /** List of names of phases that should precede this phase */ + /** List of names of phases that should precede this phase */ def runsAfter: Set[String] = Set.empty /** @pre `isRunnable` returns true */ diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 5d82e4d8b7e5..d13c4df04547 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -10,15 +10,16 @@ import dotty.tools.dotc.core._ import Contexts._ import Types._ import Symbols._ +import StdNames._ import dotty.tools.dotc.transform._ -import MegaPhase._ +import Phases._ import scala.collection.mutable -class Checker extends MiniPhase { +class Checker extends Phase { import tpd._ val phaseName = "initChecker" @@ -30,9 +31,34 @@ class Checker extends MiniPhase { override def isEnabled(using Context): Boolean = super.isEnabled && ctx.settings.YcheckInit.value - override def transformTypeDef(tree: TypeDef)(using Context): tpd.Tree = { - if (!tree.isClassDef) return tree + override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = + units.foreach { unit => traverser.traverse(unit.tpdTree) } + super.runOn(units) + + val traverser = new TreeTraverser { + override def traverse(tree: Tree)(using Context): Unit = + traverseChildren(tree) + tree match { + case tdef: MemberDef => + // self-type annotation ValDef has no symbol + if tdef.name != nme.WILDCARD then + tdef.symbol.defTree = tree + case _ => + } + } + + override def run(using Context): Unit = { + val unit = ctx.compilationUnit + unit.tpdTree.foreachSubTree { + case tdef: TypeDef if tdef.isClassDef => + transformTypeDef(tdef) + + case _ => + } + } + + private def transformTypeDef(tree: TypeDef)(using Context): tpd.Tree = { val cls = tree.symbol.asClass val instantiable: Boolean = cls.is(Flags.Module) || diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index c5711fe96438..4b423b9b1ae3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -69,7 +69,7 @@ object Errors { /** Promote `this` under initialization to fully-initialized */ case class PromoteError(msg: String, source: Tree, trace: Seq[Tree]) extends Error { - def show(using Context): String = "Promote the value under initialization to fully-initialized. " + msg + "." + def show(using Context): String = "Cannot prove that the value is fully initialized. " + msg + "." } case class AccessCold(field: Symbol, source: Tree, trace: Seq[Tree]) extends Error { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index f96900824f42..1aa9656a2084 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -7,6 +7,7 @@ import Contexts._ import Symbols._ import Types._ import StdNames._ +import NameKinds.OuterSelectName import ast.tpd._ import util.EqHashMap @@ -27,24 +28,20 @@ class Semantic { * Value = Hot | Cold | Warm | ThisRef | Fun | RefSet * * Cold - * ┌──────► ▲ ◄──┐ ◄────┐ - * │ │ │ │ - * │ │ │ │ - * ThisRef(C) │ │ │ - * ▲ │ │ │ - * │ Warm(D) Fun RefSet - * │ ▲ ▲ ▲ - * │ │ │ │ - * Warm(C) │ │ │ - * ▲ │ │ │ - * │ │ │ │ - * └─────────┴──────┴───────┘ + * ┌──────► ▲ ◄────┐ ◄────┐ + * │ │ │ │ + * │ │ │ │ + * | │ │ │ + * | │ │ │ + * ThisRef(C) Warm(D) Fun RefSet + * │ ▲ ▲ ▲ + * │ │ │ │ + * | │ │ │ + * ▲ │ │ │ + * │ │ │ │ + * └─────────┴───────┴───────┘ * Hot * - * The most important ordering is the following: - * - * Hot ⊑ Warm(C) ⊑ ThisRef(C) ⊑ Cold - * * The diagram above does not reflect relationship between `RefSet` * and other values. `RefSet` represents a set of values which could * be `ThisRef`, `Warm` or `Fun`. The following ordering applies for @@ -194,6 +191,7 @@ class Semantic { class PromotionInfo { var isCurrentObjectPromoted: Boolean = false val values = mutable.Set.empty[Value] + override def toString(): String = values.toString() } /** Values that have been safely promoted */ opaque type Promoted = PromotionInfo @@ -300,9 +298,6 @@ class Semantic { case (Cold, _) => Cold case (_, Cold) => Cold - case (a: Warm, b: ThisRef) if a.klass == b.klass => b - case (a: ThisRef, b: Warm) if a.klass == b.klass => a - case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil) case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs) @@ -495,6 +490,46 @@ class Semantic { Result(value2, errors) } } + + def accessLocal(tmref: TermRef, klass: ClassSymbol, source: Tree): Contextual[Result] = + val sym = tmref.symbol + + def default() = Result(Hot, Nil) + + if sym.is(Flags.Param) && sym.owner.isConstructor then + // instances of local classes inside secondary constructors cannot + // reach here, as those values are abstracted by Cold instead of Warm. + // This enables us to simplify the domain without sacrificing + // expressiveness nor soundess, as local classes inside secondary + // constructors are uncommon. + if sym.isContainedIn(klass) then + Result(env.lookup(sym), Nil) + else + // We don't know much about secondary constructor parameters in outer scope. + // It's always safe to approximate them with `Cold`. + Result(Cold, Nil) + else if sym.is(Flags.Param) then + default() + else + sym.defTree match { + case vdef: ValDef => + // resolve this for local variable + val enclosingClass = sym.owner.enclosingClass.asClass + val thisValue2 = resolveThis(enclosingClass, value, klass, source) + thisValue2 match { + case Hot => Result(Hot, Errors.empty) + + case Cold => Result(Cold, Nil) + + case addr: Addr => eval(vdef.rhs, addr, klass) + + case _ => + report.error("unexpected defTree when accessing local variable, sym = " + sym.show + ", defTree = " + sym.defTree.show, source) + default() + } + + case _ => default() + } end extension // ----- Promotion ---------------------------------------------------- @@ -756,7 +791,15 @@ class Semantic { res.call(id.symbol, args, superType = NoType, source = expr) case Select(qualifier, name) => - eval(qualifier, thisV, klass).select(expr.symbol, expr) + val qualRes = eval(qualifier, thisV, klass) + + name match + case OuterSelectName(_, hops) => + val SkolemType(tp) = expr.tpe + val outer = resolveOuterSelect(tp.classSymbol.asClass, qualRes.value, hops, source = expr) + Result(outer, qualRes.errors) + case _ => + qualRes.select(expr.symbol, expr) case _: This => cases(expr.tpe, thisV, klass, expr) @@ -846,7 +889,7 @@ class Semantic { case vdef : ValDef => // local val definition // TODO: support explicit @cold annotation for local definitions - eval(vdef.rhs, thisV, klass).ensureHot("Local definitions may only hold initialized values", vdef) + eval(vdef.rhs, thisV, klass, cacheResult = true) case ddef : DefDef => // local method @@ -874,24 +917,7 @@ class Semantic { Result(Hot, Errors.empty) case tmref: TermRef if tmref.prefix == NoPrefix => - val sym = tmref.symbol - - def default() = Result(Hot, Nil) - - if sym.is(Flags.Param) && sym.owner.isConstructor then - // instances of local classes inside secondary constructors cannot - // reach here, as those values are abstracted by Cold instead of Warm. - // This enables us to simplify the domain without sacrificing - // expressiveness nor soundess, as local classes inside secondary - // constructors are uncommon. - if sym.isContainedIn(klass) then - Result(env.lookup(sym), Nil) - else - // We don't know much about secondary constructor parameters in outer scope. - // It's always safe to approximate them with `Cold`. - Result(Cold, Nil) - else - default() + thisV.accessLocal(tmref, klass, source) case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -939,6 +965,40 @@ class Semantic { } + /** Resolve outer select introduced during inlining. + * + * See `tpd.outerSelect` and `ElimOuterSelect`. + */ + def resolveOuterSelect(target: ClassSymbol, thisV: Value, hops: Int, source: Tree): Contextual[Value] = log("resolving outer " + target.show + ", this = " + thisV.show + ", hops = " + hops, printer, res => res.asInstanceOf[Value].show) { + // Is `target` reachable from `cls` with the given `hops`? + def reachable(cls: ClassSymbol, hops: Int): Boolean = + if hops == 0 then cls == target + else reachable(cls.lexicallyEnclosingClass.asClass, hops - 1) + + thisV match + case Hot => Hot + + case addr: Addr => + val obj = heap(addr) + val curOpt = obj.klass.baseClasses.find(cls => reachable(cls, hops)) + curOpt match + case Some(cur) => + resolveThis(target, thisV, cur, source) + + case None => + report.warning("unexpected outerSelect, thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos) + Cold + + case RefSet(refs) => + refs.map(ref => resolveOuterSelect(target, ref, hops, source)).join + + case fun: Fun => + report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos) + Cold + + case Cold => Cold + } + /** Compute the outer value that correspond to `tref.prefix` */ def outerValue(tref: TypeRef, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = val cls = tref.classSymbol.asClass diff --git a/tests/init/crash/i8892.scala b/tests/init/crash/i8892.scala new file mode 100644 index 000000000000..0a7e7223fe06 --- /dev/null +++ b/tests/init/crash/i8892.scala @@ -0,0 +1,28 @@ +trait Reporter: + def report(m: String): Unit + +class Dummy extends Reporter: + def report(m: String) = () + + object ABug { + sealed trait Nat { + transparent inline def ++ : Succ[this.type] = Succ(this) + + transparent inline def +(inline that: Nat): Nat = + inline this match { + case Zero => that + case Succ(p) => p + that.++ + } + } + + case object Zero extends Nat + case class Succ[N <: Nat](p: N) extends Nat + + transparent inline def toIntg(inline n: Nat): Int = + inline n match { + case Zero => 0 + case Succ(p) => toIntg(p) + 1 + } + + val j31 = toIntg(Zero.++.++.++ + Zero.++) + } \ No newline at end of file diff --git a/tests/init/neg/Desugar.check b/tests/init/neg/Desugar.check deleted file mode 100644 index b9c0ddd86913..000000000000 --- a/tests/init/neg/Desugar.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/Desugar.scala:12:61 --------------------------------------------------------------------------- -12 | val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error - | ^ - | Calling the external method constructor $anon may cause initialization errors. Calling trace: - | -> val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error [ Desugar.scala:12 ] diff --git a/tests/init/neg/InteractiveDriver.check b/tests/init/neg/InteractiveDriver.check deleted file mode 100644 index 32716766aafd..000000000000 --- a/tests/init/neg/InteractiveDriver.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/InteractiveDriver.scala:22:71 ----------------------------------------------------------------- -22 | val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error - | ^ - |Calling the external method constructor $anon may cause initialization errors. Calling trace: - | -> val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error [ InteractiveDriver.scala:22 ] diff --git a/tests/init/neg/closureLeak.check b/tests/init/neg/closureLeak.check index 7a70aeedee2a..8ed6fa79f45b 100644 --- a/tests/init/neg/closureLeak.check +++ b/tests/init/neg/closureLeak.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/closureLeak.scala:11:14 ----------------------------------------------------------------------- 11 | l.foreach(a => a.addX(this)) // error | ^^^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. | - | The unsafe promotion may cause the following problem: - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | The unsafe promotion may cause the following problem: + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/cycle-structure.check b/tests/init/neg/cycle-structure.check index 27b69d25406f..77f48adf766f 100644 --- a/tests/init/neg/cycle-structure.check +++ b/tests/init/neg/cycle-structure.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/cycle-structure.scala:3:14 -------------------------------------------------------------------- 3 | val x = B(this) // error | ^^^^ - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. -- Error: tests/init/neg/cycle-structure.scala:9:14 -------------------------------------------------------------------- 9 | val x = A(this) // error | ^^^^ - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/default-this.check b/tests/init/neg/default-this.check index 8b02d96cfeb7..de8ab00ecdc6 100644 --- a/tests/init/neg/default-this.check +++ b/tests/init/neg/default-this.check @@ -1,5 +1,5 @@ -- Error: tests/init/neg/default-this.scala:9:8 ------------------------------------------------------------------------ 9 | compare() // error | ^^^^^^^ - |Promote the value under initialization to fully-initialized. May only use initialized value as arguments. Calling trace: - | -> val result = updateThenCompare(5) [ default-this.scala:11 ] + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. Calling trace: + | -> val result = updateThenCompare(5) [ default-this.scala:11 ] diff --git a/tests/init/neg/inherit-non-hot.check b/tests/init/neg/inherit-non-hot.check new file mode 100644 index 000000000000..f6f45edfc9a6 --- /dev/null +++ b/tests/init/neg/inherit-non-hot.check @@ -0,0 +1,16 @@ +-- Error: tests/init/neg/inherit-non-hot.scala:6:34 -------------------------------------------------------------------- +6 | if b == null then b = new B(this) // error + | ^^^^^^^^^^^ + | Cannot prove that the value is fully-initialized. May only assign fully initialized value. + | Calling trace: + | -> val c = new C [ inherit-non-hot.scala:19 ] + | -> class C extends A { [ inherit-non-hot.scala:15 ] + | -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] + | + | The unsafe promotion may cause the following problem: + | Call method Foo.B.this.aCopy.toB on a value with an unknown initialization. Calling trace: + | -> val c = new C [ inherit-non-hot.scala:19 ] + | -> class C extends A { [ inherit-non-hot.scala:15 ] + | -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] + | -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ] + | -> def getBAgain: B = aCopy.toB [ inherit-non-hot.scala:12 ] diff --git a/tests/init/neg/inherit-non-hot.scala b/tests/init/neg/inherit-non-hot.scala new file mode 100644 index 000000000000..44be67351630 --- /dev/null +++ b/tests/init/neg/inherit-non-hot.scala @@ -0,0 +1,21 @@ +// This is a minimized test for the warning in Names.scala:174 +object Foo { + abstract class A { + var b: B = null + def toB: B = + if b == null then b = new B(this) // error + b + } + + class B(a: A) { + var aCopy: A = a + def getBAgain: B = aCopy.toB + } + + class C extends A { + val bAgain = toB.getBAgain + } + + val c = new C + assert(c.b == c.bAgain) +} \ No newline at end of file diff --git a/tests/init/neg/leak-warm.check b/tests/init/neg/leak-warm.check index 85f6730cf673..8219283b3c16 100644 --- a/tests/init/neg/leak-warm.check +++ b/tests/init/neg/leak-warm.check @@ -1,8 +1,8 @@ -- Error: tests/init/neg/leak-warm.scala:18:26 ------------------------------------------------------------------------- 18 | val l: List[A] = List(c, d) // error // error | ^ - |Promote the value under initialization to fully-initialized. May only use initialized value as method arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as method arguments. -- Error: tests/init/neg/leak-warm.scala:18:29 ------------------------------------------------------------------------- 18 | val l: List[A] = List(c, d) // error // error | ^ - |Promote the value under initialization to fully-initialized. May only use initialized value as method arguments. + | Cannot prove that the value is fully initialized. May only use initialized value as method arguments. diff --git a/tests/init/neg/local-warm.check b/tests/init/neg/local-warm.check deleted file mode 100644 index 73972c1844f2..000000000000 --- a/tests/init/neg/local-warm.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/init/neg/local-warm.scala:12:12 ------------------------------------------------------------------------ -12 | val t = m1() // error - | ^^^^^^^^^^^^ - |Promote the value under initialization to fully-initialized. Local definitions may only hold initialized values. Calling trace: - | -> val x = g() [ local-warm.scala:15 ] diff --git a/tests/init/neg/local-warm4.check b/tests/init/neg/local-warm4.check new file mode 100644 index 000000000000..fda1ee1b928c --- /dev/null +++ b/tests/init/neg/local-warm4.check @@ -0,0 +1,11 @@ +-- Error: tests/init/neg/local-warm4.scala:18:20 ----------------------------------------------------------------------- +18 | a = newA // error + | ^^^^ + | Cannot prove that the value is fully initialized. May only assign fully initialized value. Calling trace: + | -> val a = new A(5) [ local-warm4.scala:26 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> val b = new B(y) [ local-warm4.scala:10 ] + | -> class B(x: Int) extends A(x) { [ local-warm4.scala:13 ] + | -> class A(x: Int) extends Foo(x) { [ local-warm4.scala:6 ] + | -> increment() [ local-warm4.scala:9 ] + | -> updateA() [ local-warm4.scala:21 ] diff --git a/tests/init/neg/local-warm4.scala b/tests/init/neg/local-warm4.scala new file mode 100644 index 000000000000..a1b3030ba4de --- /dev/null +++ b/tests/init/neg/local-warm4.scala @@ -0,0 +1,27 @@ +object localWarm { + abstract class Foo(x: Int) { + def increment(): Unit + } + + class A(x: Int) extends Foo(x) { + var y = x + override def increment(): Unit = y = y + 1 + increment() + val b = new B(y) + } + + class B(x: Int) extends A(x) { + var a: A = this + override def increment(): Unit = { + def updateA(): Unit = { + val newA = new A(y) + a = newA // error + } + y = y + 1 + updateA() + } + if y < 10 then increment() + val z = b.y + } + val a = new A(5) +} diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index 11ebef3a23ec..e0c9ba597163 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/promotion-loop.scala:16:10 -------------------------------------------------------------------- 16 | println(b) // error | ^ - | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. + | Cannot prove that the value is fully-initialized. May only use initialized value as arguments. | - | The unsafe promotion may cause the following problem: - | Promote the value under initialization to fully-initialized. May only use initialized value as arguments. + | The unsafe promotion may cause the following problem: + | Cannot prove that the value is fully initialized. May only use initialized value as arguments. diff --git a/tests/init/neg/secondary-ctor2.scala b/tests/init/neg/secondary-ctor2.scala index 2e7e14511328..1260cfbd51d1 100644 --- a/tests/init/neg/secondary-ctor2.scala +++ b/tests/init/neg/secondary-ctor2.scala @@ -6,8 +6,8 @@ class A(b: B, x: Int) { } Inner().foo() - val f = () => new A(b, 3) // error: Cannot promote - f() // ok + val f = () => new A(b, 3) + f() } } diff --git a/tests/init/neg/Desugar.scala b/tests/init/pos/Desugar.scala similarity index 69% rename from tests/init/neg/Desugar.scala rename to tests/init/pos/Desugar.scala index 32fb89636a21..4b8f7ca8b460 100644 --- a/tests/init/neg/Desugar.scala +++ b/tests/init/pos/Desugar.scala @@ -9,7 +9,7 @@ case class C[-T >: Int] (lhs: Int, rhs: Tree[T]) extends A { } object DesugarError { - val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} // error + val f: PartialFunction[A, Int] = {case C(_, rhs) => rhs.x} } diff --git a/tests/init/neg/InteractiveDriver.scala b/tests/init/pos/InteractiveDriver.scala similarity index 96% rename from tests/init/neg/InteractiveDriver.scala rename to tests/init/pos/InteractiveDriver.scala index dfec53f0c5cd..a1f3a4d197bc 100644 --- a/tests/init/neg/InteractiveDriver.scala +++ b/tests/init/pos/InteractiveDriver.scala @@ -19,5 +19,5 @@ object InteractiveDriver { case _ => new C[Int] } val l: Seq[Any] = Seq(1, 2, new C[Double], new D[Int]) - val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } // error + val l2: Seq[C[?]] = l.collect{ case x: InteractiveDriver[?] => h(x) } } diff --git a/tests/init/neg/local-warm.scala b/tests/init/pos/local-warm.scala similarity index 100% rename from tests/init/neg/local-warm.scala rename to tests/init/pos/local-warm.scala diff --git a/tests/init/pos/local-warm2.scala b/tests/init/pos/local-warm2.scala new file mode 100644 index 000000000000..ebe5c8900c92 --- /dev/null +++ b/tests/init/pos/local-warm2.scala @@ -0,0 +1,14 @@ +class A { + var v = 5 + val x = { + class B { + def doubleAndReturnV(): Int = { + v = v * 2 + v + } + } + val b = new B + b.doubleAndReturnV() + } + val y = v +} \ No newline at end of file diff --git a/tests/init/pos/local-warm3.scala b/tests/init/pos/local-warm3.scala new file mode 100644 index 000000000000..047dd0d7a9e8 --- /dev/null +++ b/tests/init/pos/local-warm3.scala @@ -0,0 +1,10 @@ +class A() { + var x = 5 + def decreaseX(): Unit = { + val self = this + self.x -= 1 + } + def decreaseXToZero(): Unit = if x > 0 then decreaseX() + decreaseXToZero() + val y = x +} \ No newline at end of file diff --git a/tests/init/pos/local-warm5.scala b/tests/init/pos/local-warm5.scala new file mode 100644 index 000000000000..705b92362c3f --- /dev/null +++ b/tests/init/pos/local-warm5.scala @@ -0,0 +1,12 @@ +object leakWarm5 { + case class A(x: Int) { + def double(): A = { + class C { + def double(): A = if x < 10 then A(x * 2).double() else A.this + } + val c = new C + c.double() + } + } + val a = A(2).double() +}