diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 52760cf8b6c7..a0d086e3063b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -116,7 +116,16 @@ class Objects(using Context @constructorOnly): * regions ::= List(sourcePosition) */ + import Heap.Addr + sealed abstract class Value: + /** Flatten component values or addresses (non-recursive) + * + * If the value does not contain a component, return an empty collection. + */ + def flatten: Iterable[Value | Addr] + + /** Display the value */ def show(using Context): String /** ValueElement are elements that can be contained in a RefSet */ @@ -167,6 +176,8 @@ class Objects(using Context @constructorOnly): outers(cls) = value } + def flatten: Iterable[Value | Addr] = vals.values ++ vars.values ++ outers.values + /** A reference to a static object */ case class ObjectRef(klass: ClassSymbol) extends Ref(valsMap = mutable.Map.empty, varsMap = mutable.Map.empty, outersMap = mutable.Map.empty): @@ -218,12 +229,54 @@ class Objects(using Context @constructorOnly): val addr: Heap.Addr = Heap.arrayAddr(regions, owner) def show(using Context) = "OfArray(owner = " + owner.show + ")" + def flatten: Iterable[Value | Addr] = Vector(addr) + /** * Represents a lambda expression * @param klass The enclosing class of the anonymous function's creation site */ - case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, env: Env.Data) extends ValueElement: - def show(using Context) = "Fun(" + code.show + ", " + thisV.show + ", " + klass.show + ")" + case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, env: Env.Data)(using @constructorOnly ctx: Context) extends ValueElement: + def show(using Context) = "Fun(" + code.show + ", " + thisV.show + ", " + klass.show + ", " + freeVals + ", " + env.show + ")" + + val freeVals: Set[Symbol] = computeFreeVars()(using ctx) + + def computeFreeVars()(using Context): Set[Symbol] = + // TODO: compute captures transitively for local methods + val refs = mutable.Set.empty[Symbol] + val defs = mutable.Set.empty[Symbol] + val traverser = new TreeTraverser: + def traverse(tree: Tree)(using Context) = + tree match + case ident: Ident => + val sym = ident.symbol + if sym.isTerm && sym.isLocal && !sym.is(Flags.Method) + then refs += sym + + case vdef: ValDef => + val sym = vdef.symbol + if sym.isLocal then defs += sym + traverseChildren(vdef.rhs) + + case _ => + traverseChildren(tree) + end traverse + traverser.traverse(code) + refs.diff(defs).toSet + + // Early compute the flattened value to avoid capturing `ctx` + val flatten = computeflatten()(using ctx) + + def computeflatten()(using Context): Iterable[Value | Addr] = + val captured = freeVals.flatMap: x => + Env.resolveEnv(x.enclosingMethod, thisV, env) match + case Some(thisV -> env) => + val resOpt = Env.get(x)(using env) + resOpt.map(_ :: Nil).getOrElse(Nil) + + case None => + Nil + + captured ++ Vector(thisV) /** * Represents a set of values @@ -233,6 +286,8 @@ class Objects(using Context @constructorOnly): case class ValueSet(values: ListSet[ValueElement]) extends Value: def show(using Context) = values.map(_.show).mkString("[", ",", "]") + def flatten: Iterable[Value | Addr] = values + /** A cold alias which should not be used during initialization. * * Cold is not ValueElement since RefSet containing Cold is equivalent to Cold @@ -240,6 +295,8 @@ class Objects(using Context @constructorOnly): case object Cold extends Value: def show(using Context) = "Cold" + def flatten: Iterable[Value | Addr] = Vector() + val Bottom = ValueSet(ListSet.empty) /** Possible types for 'this' */ @@ -256,6 +313,8 @@ class Objects(using Context @constructorOnly): def currentObject(using data: Data): ClassSymbol = data.checkingObjects.last.klass + def currentObjectRef(using data: Data): ObjectRef = data.checkingObjects.last + private def doCheckObject(classSym: ClassSymbol)(using ctx: Context, data: Data) = val tpl = classSym.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] @@ -273,7 +332,7 @@ class Objects(using Context @constructorOnly): given regions: Regions.Data = Regions.empty // explicit name to avoid naming conflict val obj = ObjectRef(classSym) - log("Iteration " + count) { + log("Iteration " + count + " for " + classSym) { data.checkingObjects += obj init(tpl, obj, classSym) assert(data.checkingObjects.last.klass == classSym, "Expect = " + classSym.show + ", found = " + data.checkingObjects.last.klass) @@ -335,6 +394,8 @@ class Objects(using Context @constructorOnly): def show(using Context): String + def flatten: Iterable[Value | Addr] + /** Local environments can be deeply nested, therefore we need `outer`. * * For local variables in rhs of class field definitions, the `meth` is the primary constructor. @@ -362,6 +423,9 @@ class Objects(using Context @constructorOnly): def widen(height: Int)(using Context): Data = new LocalEnv(params.map(_ -> _.widen(height)), meth, outer.widen(height))(this.vals, this.vars) + def flatten: Iterable[Value | Addr] = + params.values ++ outer.flatten ++ valsMap.values ++ varsMap.values + def show(using Context) = "owner: " + meth.show + "\n" + "params: " + params.map(_.show + " ->" + _.show).mkString("{", ", ", "}") + "\n" + @@ -382,6 +446,8 @@ class Objects(using Context @constructorOnly): def widen(height: Int)(using Context): Data = this + def flatten: Iterable[Value | Addr] = Vector() + def show(using Context): String = "NoEnv" end NoEnv @@ -398,12 +464,16 @@ class Objects(using Context @constructorOnly): case Some(theValue) => theValue case _ => - report.warning("[Internal error] Value not found " + x.show + "\nenv = " + data.show + ". " + Trace.show, Trace.position) + report.warning("[Internal error] Value not found " + x.show + ". " + Trace.show, Trace.position) Bottom - def getVal(x: Symbol)(using data: Data, ctx: Context): Option[Value] = data.getVal(x) + def getVal(x: Symbol)(using data: Data): Option[Value] = data.getVal(x) + + def getVar(x: Symbol)(using data: Data): Option[Heap.Addr] = data.getVar(x) - def getVar(x: Symbol)(using data: Data, ctx: Context): Option[Heap.Addr] = data.getVar(x) + def get(x: Symbol)(using data: Data): Option[Heap.Addr | Value] = + if x.is(Flags.Mutable) then data.getVar(x) + else data.getVal(x) def of(ddef: DefDef, args: List[Value], outer: Data)(using Context): Data = val params = ddef.termParamss.flatten.map(_.symbol) @@ -489,7 +559,7 @@ class Objects(using Context @constructorOnly): opaque type Data = Map[Addr, Value] /** Store the heap as a mutable field to avoid threading it through the program. */ - class MutableData(private[Heap] var heap: Data): + class MutableData(private[Heap] var heap: Data, private[Heap] var changeSet: Set[Addr]): private[Heap] def writeJoin(addr: Addr, value: Value): Unit = heap.get(addr) match case None => @@ -499,15 +569,23 @@ class Objects(using Context @constructorOnly): val value2 = value.join(current) if value2 != current then heap = heap.updated(addr, value2) + changeSet = changeSet + addr end MutableData - def empty(): MutableData = new MutableData(Map.empty) + def show(using mutable: MutableData): String = + "(size = " + mutable.heap.size + ")" + + def empty(): MutableData = new MutableData(Map.empty, Set.empty) def contains(addr: Addr)(using mutable: MutableData): Boolean = mutable.heap.contains(addr) - def read(addr: Addr)(using mutable: MutableData): Value = - mutable.heap(addr) + def read(addr: Addr)(using mutable: MutableData, trace: Trace, ctx: Context): Value = + if mutable.heap.contains(addr) then + mutable.heap(addr) + else + report.warning("[Internal error] Address not found " + addr + ". Trace:\n" + Trace.show, Trace.position) + Bottom def writeJoin(addr: Addr, value: Value)(using mutable: MutableData): Unit = mutable.writeJoin(addr, value) @@ -523,24 +601,161 @@ class Objects(using Context @constructorOnly): def getHeapData()(using mutable: MutableData): Data = mutable.heap - def setHeap(newHeap: Data)(using mutable: MutableData): Unit = mutable.heap = newHeap + def getChangeSet()(using mutable: MutableData): Set[Addr] = mutable.changeSet + + def update(heap: Data, changeSet: Set[Addr])(using mutable: MutableData): Unit = + mutable.heap = heap + mutable.changeSet = changeSet + + def join(heap1: Data, heap2: Data): Data = + heap1.foldLeft(heap2): + case (heap2, (k1, v1)) => + val v2 = heap2.getOrElse(k1, Bottom) + heap2.updated(k1, v1.join(v2)) + + def reachableAddresses(roots: Iterable[Value | Addr], heap: Data, currentObj: ObjectRef, ctx: String)(using Trace): Set[Addr] = + val visited = mutable.Set.empty[Value] + val reachableKeys = mutable.Set.empty[Addr] + + def indent(height: Int, startHeight: Int): String = + (Trace.CONNECTING_INDENT + " ") * (startHeight - height) + + def debug(items: Iterable[Value | Addr], height: Int): Unit = + printItems(items, height, height) + + def printItems(items: Iterable[Value | Addr], height: Int, startHeight: Int): Unit = + for item <- items do printItem(item, height, startHeight) + + def printItem(item: Value | Addr, height: Int, startHeight: Int): Unit = + if height == 0 then + println(indent(height, startHeight) + Trace.CHILD + " ... ") + else item match + case ValueSet(values) => + printItems(values, height, startHeight) + + case value: Value => + val children = value.flatten + println(indent(height, startHeight) + Trace.CHILD + value.show) + printItems(children, height - 1, startHeight) + + case addr: Addr => + heap.get(addr) match + case Some(value) => + println(indent(height, startHeight) + Trace.CHILD + addr) + printItem(value, height - 1, startHeight) + + case None => + println(indent(height, startHeight) + Trace.CHILD + addr + " not found ") + + def visit(item: Value | Addr): Unit = + item match + case addr: Addr => + // Thanks to initialization-time irrelevance, there is no need to + // visit the heap regions owned by other global objects. + if addr.owner == currentObj.klass then + reachableKeys += addr + heap.get(addr) match + case Some(value) => + recur(value) + + case None => + println("[Internal error] Not found addr " + addr) + + case objRef: ObjectRef if objRef != currentObj => + // Thanks to initialization-time irrelevance, there is no need to + // visit the heap regions owned by other global objects. + // + // Other objects may also refer to memory regions of the current + // global object. However, they must do so by referring to fields + // of the current global objects --- which are already visited. + + case value: Value => + recur(value) + end visit + + def recur(value: Value): Unit = + if !visited.contains(value) then + visited += value + for item <- value.flatten do visit(item) + + for item <- roots do visit(item) + + reachableKeys.toSet + end reachableAddresses + + /** Compute the footprint of the heap for evaluating an expression + * + * The regions of the heap not in the footprint do not matter for + * evaluating the underlying expression. + * + * The reasoning above is similar to the frame rule in separation logic. + */ + def footprint(heap: Data, thisV: Value, env: Env.Data, currentObj: ObjectRef)(using Trace): Data = + val roots = env.flatten.toSeq :+ thisV :+ currentObj + val reachableKeys = reachableAddresses(roots, heap, currentObj, "footprint") + heap.filter((k, v) => reachableKeys.contains(k)) + + /** Perform garbage collection on the abstract heap. + * + * A heap address created after evaluating an expression can be reclaimed + * if it is not reachable from the resulting value of the expression and + * not reachable from the values of `heapAfter` for keys of `heapBefore`. + * + * This optimization can help avoid populating the heap with local mutable + * variables that do not leak. + * + * GC may only be performed from method call contexts --- otherwise, we need + * to consider values of the current local environment as well. + */ + def gc(returnValues: List[Value], heapBefore: Data, heapAfter: Data, changeSet: Set[Addr], currentObj: ObjectRef)(using Trace): Data = + val roots: Iterable[Addr | Value] = changeSet.toSeq ++ returnValues + // reachable locations from the return value and change set + val reachableKeys = reachableAddresses(roots, heapAfter, currentObj, "gc") + + val unreachableKeys = heapAfter.keys.filter: addr => + !heapBefore.contains(addr) && !reachableKeys.contains(addr) + + // println("collected keys = " + unreachableKeys) + heapAfter -- unreachableKeys /** Cache used to terminate the check */ object Cache: case class Config(thisV: Value, env: Env.Data, heap: Heap.Data) - case class Res(value: Value, heap: Heap.Data) + case class Res(value: Value, heap: Heap.Data, changeSet: Set[Addr]) class Data extends Cache[Config, Res]: def get(thisV: Value, expr: Tree)(using Heap.MutableData, Env.Data): Option[Value] = val config = Config(thisV, summon[Env.Data], Heap.getHeapData()) super.get(config, expr).map(_.value) - def cachedEval(thisV: ThisValue, expr: Tree, cacheResult: Boolean)(fun: Tree => Value)(using Heap.MutableData, Env.Data): Value = - val config = Config(thisV, summon[Env.Data], Heap.getHeapData()) - val result = super.cachedEval(config, expr, cacheResult, default = Res(Bottom, Heap.getHeapData())) { expr => - Res(fun(expr), Heap.getHeapData()) + def cachedEval(thisV: ThisValue, expr: Tree, ctx: EvalContext)(fun: Tree => Value)(using Heap.MutableData, Env.Data, State.Data, Returns.Data, Trace): Value = + val env = summon[Env.Data] + val heapBefore = Heap.getHeapData() + val changeSetBefore = Heap.getChangeSet() + // Only perform footprint optimization for method context + val footprint = + if ctx == EvalContext.Method then + Heap.footprint(Heap.getHeapData(), thisV, env, State.currentObjectRef) + else + heapBefore + val config = Config(thisV, env, footprint) + + Heap.update(footprint, changeSet = Set.empty) + val cacheResult = ctx != EvalContext.Other + val result = super.cachedEval(config, expr, cacheResult, default = Res(Bottom, footprint, Set.empty)) { expr => + val value = fun(expr) + val heapAfter = Heap.getHeapData() + val changeSetNew = Heap.getChangeSet() + // Only perform garbage collection for method context + val heapGC = + if ctx == EvalContext.Method then + Heap.gc(value :: Nil, footprint, heapAfter, changeSetNew, State.currentObjectRef) + else + heapAfter + Res(value, heapGC, changeSetNew) } - Heap.setHeap(result.heap) + Heap.update(Heap.join(heapBefore, result.heap), changeSetBefore ++ result.changeSet) + result.value end Cache @@ -659,7 +874,9 @@ class Objects(using Context @constructorOnly): * @param superType The type of the super in a super call. NoType for non-super calls. * @param needResolve Whether the target of the call needs resolution? */ - def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) { + def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log( + "call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show) + ", heap = " + Heap.show, printer, (v: Value) => v.show) { + value.filterClass(meth.owner) match case Cold => report.warning("Using cold alias. " + Trace.show, Trace.position) @@ -722,7 +939,7 @@ class Objects(using Context @constructorOnly): val env2 = Env.of(ddef, args.map(_.value), outerEnv) extendTrace(ddef) { given Env.Data = env2 - cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr => + cache.cachedEval(ref, ddef.rhs, EvalContext.Method) { expr => Returns.installHandler(meth) val res = cases(expr, thisV, cls) val returns = Returns.popHandler(meth) @@ -751,7 +968,7 @@ class Objects(using Context @constructorOnly): case ddef: DefDef => if meth.name == nme.apply then given Env.Data = Env.of(ddef, args.map(_.value), env) - extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) } + extendTrace(code) { eval(ddef.rhs, thisV, klass, EvalContext.Function) } else // The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization. if meth.owner == defn.AnyClass || meth.owner == defn.ObjectClass then @@ -766,7 +983,7 @@ class Objects(using Context @constructorOnly): case _ => // by-name closure given Env.Data = env - extendTrace(code) { eval(code, thisV, klass, cacheResult = true) } + extendTrace(code) { eval(code, thisV, klass, EvalContext.Function) } case ValueSet(vs) => vs.map(v => call(v, meth, args, receiver, superType)).join @@ -778,7 +995,7 @@ class Objects(using Context @constructorOnly): * @param ctor The symbol of the target method. * @param args Arguments of the constructor call (all parameter blocks flatten to a list). */ - def callConstructor(value: Value, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("call " + ctor.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) { + def callConstructor(value: Value, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("call " + ctor.show + ", args = " + args.map(_.value.show) + ", heap = " + Heap.show, printer, (_: Value).show) { value match case ref: Ref => if ctor.hasSource then @@ -786,16 +1003,20 @@ class Objects(using Context @constructorOnly): val ddef = ctor.defTree.asInstanceOf[DefDef] val argValues = args.map(_.value) - given Env.Data = Env.of(ddef, argValues, Env.NoEnv) + def doEval(tree: Tree)(using Trace): Value = + given Env.Data = Env.of(ddef, argValues, Env.NoEnv) + // No usage of `return` is possible in constructors --- still install + // return handler for uniform handling of method context. + Returns.installHandler(ctor) + val res = eval(tree, ref, cls, EvalContext.Construtor) + Returns.popHandler(ctor) + res + if ctor.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) } + extendTrace(cls.defTree) { doEval(tpl) } else - extendTrace(ddef) { // The return values for secondary constructors can be ignored - Returns.installHandler(ctor) - eval(ddef.rhs, ref, cls, cacheResult = true) - Returns.popHandler(ctor) - } + extendTrace(ddef) { doEval(ddef.rhs) } else // no source code available Bottom @@ -824,7 +1045,7 @@ class Objects(using Context @constructorOnly): given Env.Data = Env.emptyEnv(target.owner.asInstanceOf[ClassSymbol].primaryConstructor) if target.hasSource then val rhs = target.defTree.asInstanceOf[ValDef].rhs - eval(rhs, ref, target.owner.asClass, cacheResult = true) + eval(rhs, ref, target.owner.asClass, EvalContext.LazyVal) else Bottom else if target.exists then @@ -916,8 +1137,6 @@ class Objects(using Context @constructorOnly): /** * Handle new expression `new p.C(args)`. - * The actual instance might be cached without running the constructor. - * See tests/init-global/pos/cache-constructor.scala * * @param outer The value for `p`. * @param klass The symbol of the class `C`. @@ -957,6 +1176,8 @@ class Objects(using Context @constructorOnly): // klass.enclosingMethod returns its primary constructor Env.resolveEnv(klass.owner.enclosingMethod, thisV, summon[Env.Data]).getOrElse(Cold -> Env.NoEnv) + // The actual instance might be cached without running the constructor. + // See tests/init-global/pos/cache-constructor.scala val instance = OfClass(klass, outerWidened, ctor, args.map(_.value), envWidened) callConstructor(instance, ctor, args) @@ -1000,13 +1221,13 @@ class Objects(using Context @constructorOnly): end if case _ => // Only vals can be lazy - report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) + report.warning("[Internal error] Variable not found " + sym.show + ". " + Trace.show, Trace.position) Bottom else given Env.Data = env if sym.is(Flags.Lazy) then val rhs = sym.defTree.asInstanceOf[ValDef].rhs - eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true) + eval(rhs, thisV, sym.enclosingClass.asClass, EvalContext.LazyVal) else // Assume forward reference check is doing a good job val value = Env.valValue(sym) @@ -1051,7 +1272,7 @@ class Objects(using Context @constructorOnly): else Heap.writeJoin(addr, value) case _ => - report.warning("[Internal error] Variable not found " + sym.show + "\nenv = " + env.show + ". " + Trace.show, Trace.position) + report.warning("[Internal error] Variable not found " + sym.show + ". " + Trace.show, Trace.position) case _ => report.warning("Assigning to variables in outer scope. " + Trace.show, Trace.position) @@ -1079,6 +1300,9 @@ class Objects(using Context @constructorOnly): do accessObject(classSym) + enum EvalContext: + case Method, Construtor, Function, LazyVal, Other + /** Evaluate an expression with the given value for `this` in a given class `klass` * * Note that `klass` might be a super class of the object referred by `thisV`. @@ -1097,11 +1321,12 @@ class Objects(using Context @constructorOnly): * @param expr The expression to be evaluated. * @param thisV The value for `C.this` where `C` is represented by the parameter `klass`. * @param klass The enclosing class where the expression is located. - * @param cacheResult It is used to reduce the size of the cache. + * @param ctx The context where `eval` is called. */ - def eval(expr: Tree, thisV: ThisValue, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + ", regions = " + Regions.show + " in " + klass.show, printer, (_: Value).show) { - cache.cachedEval(thisV, expr, cacheResult) { expr => cases(expr, thisV, klass) } - } + def eval(expr: Tree, thisV: ThisValue, klass: ClassSymbol, ctx: EvalContext = EvalContext.Other): Contextual[Value] = + log("evaluating " + expr.show + ", this = " + thisV.show + ", heap = " + Heap.show + " in " + klass.show, printer, (_: Value).show) { + cache.cachedEval(thisV, expr, ctx) { expr => cases(expr, thisV, klass) } + } /** Evaluate a list of expressions */ @@ -1116,7 +1341,9 @@ class Objects(using Context @constructorOnly): * @param thisV The value for `C.this` where `C` is represented by the parameter `klass`. * @param klass The enclosing class where the expression `expr` is located. */ - def cases(expr: Tree, thisV: ThisValue, klass: ClassSymbol): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + " in " + klass.show, printer, (_: Value).show) { + def cases(expr: Tree, thisV: ThisValue, klass: ClassSymbol): Contextual[Value] = log( + "evaluating " + expr.show + ", this = " + thisV.show + ", heap = " + Heap.show + " in " + klass.show, printer, (_: Value).show) { + val trace2 = trace.add(expr) expr match @@ -1240,9 +1467,11 @@ class Objects(using Context @constructorOnly): withTrace(trace2) { assign(receiver, lhs.symbol, widened, rhs.tpe) } case closureDef(ddef) => + // TODO: trim the environment and `thisV` to only captured locals Fun(ddef, thisV, klass, summon[Env.Data]) case PolyFun(ddef) => + // TODO: trim the environment and `thisV` to only captured locals Fun(ddef, thisV, klass, summon[Env.Data]) case Block(stats, expr) => @@ -1550,7 +1779,7 @@ class Objects(using Context @constructorOnly): val sym = tmref.symbol if sym.isStaticObject then if elideObjectAccess then - ObjectRef(sym.moduleClass.asClass) + Bottom else accessObject(sym.moduleClass.asClass) else @@ -1564,7 +1793,7 @@ class Objects(using Context @constructorOnly): else if sym.isStaticObject && sym != klass then // The typer may use ThisType to refer to an object outside its definition. if elideObjectAccess then - ObjectRef(sym.moduleClass.asClass) + Bottom else accessObject(sym.moduleClass.asClass) @@ -1583,17 +1812,17 @@ class Objects(using Context @constructorOnly): def widenEscapedValue(value: Value, annotatedTree: Tree): Contextual[Value] = def parseAnnotation: Option[Int] = annotatedTree.tpe.getAnnotation(defn.InitWidenAnnot).flatMap: annot => - annot.argument(0).get match - case arg @ Literal(c: Constants.Constant) => - val height = c.intValue - if height < 0 then - report.warning("The argument should be positive", arg) - None - else - Some(height) - case arg => - report.warning("The argument should be a constant integer value", arg) + annot.argument(0).get match + case arg @ Literal(c: Constants.Constant) => + val height = c.intValue + if height < 0 then + report.warning("The argument should be positive", arg) None + else + Some(height) + case arg => + report.warning("The argument should be a constant integer value", arg) + None end parseAnnotation parseAnnotation match @@ -1626,7 +1855,7 @@ class Objects(using Context @constructorOnly): * @param thisV The value of the current object to be initialized. * @param klass The class to which the template belongs. */ - def init(tpl: Template, thisV: Ref, klass: ClassSymbol): Contextual[Ref] = log("init " + klass.show, printer, (_: Value).show) { + def init(tpl: Template, thisV: Ref, klass: ClassSymbol): Contextual[Ref] = log("init " + klass.show + ", heap = " + Heap.show, printer, (_: Value).show) { val paramsMap = tpl.constr.termParamss.flatten.map { vdef => vdef.name -> Env.valValue(vdef.symbol) }.toMap @@ -1762,8 +1991,7 @@ class Objects(using Context @constructorOnly): else if target.is(Flags.Package) then Bottom else if target.isStaticObject then - val res = ObjectRef(target.moduleClass.asClass) - if elideObjectAccess then res + if elideObjectAccess then Bottom else accessObject(target) else thisV match diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index e11d0e1e21a5..d1529f9497c0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -109,8 +109,3 @@ object Util: // A concrete class may not be instantiated if the self type is not satisfied instantiable && cls.enclosingPackageClass != defn.StdLibPatchesPackage.moduleClass - - /** Whether the class or its super class/trait contains any mutable fields? */ - def isMutable(cls: ClassSymbol)(using Context): Boolean = - cls.classInfo.decls.exists(_.is(Flags.Mutable)) || - cls.parentSyms.exists(parentCls => isMutable(parentCls.asClass)) diff --git a/tests/init-global/pos/footprint-2nd-ctor.scala b/tests/init-global/pos/footprint-2nd-ctor.scala new file mode 100644 index 000000000000..6a9eed933693 --- /dev/null +++ b/tests/init-global/pos/footprint-2nd-ctor.scala @@ -0,0 +1,17 @@ +class Box[T](var value: T) + +class A: + var box = new Box[Int](10) + + def update(n: Int) = + box.value = n + + def this(n: Int) = + this() + box.value = n + +class B(n: Int) extends A(n): + this.update(n * n) + +object A: + val a = new B(20) diff --git a/tests/init-global/pos/footprint-local.scala b/tests/init-global/pos/footprint-local.scala new file mode 100644 index 000000000000..954984662eeb --- /dev/null +++ b/tests/init-global/pos/footprint-local.scala @@ -0,0 +1,6 @@ +object A: + val f = foo() + val a = f() + def foo(): () => Int = + var x = 10 + () => x diff --git a/tests/init-global/pos/footprint.scala b/tests/init-global/pos/footprint.scala new file mode 100644 index 000000000000..e0a14b583228 --- /dev/null +++ b/tests/init-global/pos/footprint.scala @@ -0,0 +1,15 @@ +class Box(var x: String) + +object A: + val a = new Box("a") + val c = foo() + def foo(): Box = C.c + +object B: + val b = new Box("b") + val a = bar() + + def bar(): Box = A.a + +object C: + val c = new Box("c") diff --git a/tests/init-global/pos/footprint2.scala b/tests/init-global/pos/footprint2.scala new file mode 100644 index 000000000000..dc88896b7929 --- /dev/null +++ b/tests/init-global/pos/footprint2.scala @@ -0,0 +1,26 @@ +class BreakControl extends Throwable + +object Breaks: + private val breakException = new BreakControl + + def breakable(op: => Unit): Unit = + try op catch { case ex: BreakControl if ex eq breakException => } + + def break(): Nothing = throw breakException + +object A: + val n = foo("hello") + def foo(s: String): Int = + val len = s.length + var i = 0 + + while (i < len) { + Breaks.breakable { + val c = s.charAt(i) + + if c == '\n' then Breaks.break() + } + i += 1 + } + + i diff --git a/tests/init-global/pos/sconfig-array.scala b/tests/init-global/pos/sconfig-array.scala new file mode 100644 index 000000000000..9b90a31c22b3 --- /dev/null +++ b/tests/init-global/pos/sconfig-array.scala @@ -0,0 +1,26 @@ +import java.{lang => jl} + +final class ConfigValueType private (val name: String, val ordinal: Int) + +object ConfigValueType: + final val OBJECT = new ConfigValueType("OBJECT", 0) + final val LIST = new ConfigValueType("LIST", 1) + final val NUMBER = new ConfigValueType("NUMBER", 2) + final val BOOLEAN = new ConfigValueType("BOOLEAN", 3) + final val NULL = new ConfigValueType("NULL", 4) + final val STRING = new ConfigValueType("STRING", 5) + + final val _values: Array[ConfigValueType] = + Array(OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING) + + def values: Array[ConfigValueType] = _values.clone() + + def valueOf(name: String): ConfigValueType = + _values.find(_.name == name).getOrElse { + throw new IllegalArgumentException( + "No enum const ConfigValueType." + name + ) + } + +object Usage: + val c = ConfigValueType.valueOf("LIST") diff --git a/tests/init-global/pos/sconfig.scala b/tests/init-global/pos/sconfig.scala new file mode 100644 index 000000000000..2c1c1fed49fd --- /dev/null +++ b/tests/init-global/pos/sconfig.scala @@ -0,0 +1,89 @@ +abstract class B + +class C(var o: () => B | Int) extends B + +class D(var o: () => B | Int) extends B + +object A: + def f1(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f2(c, i + 1) + c = f1(c, i + 1) + c + + def f2(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f3(c, i + 1) + c + + def f3(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f4(c, i + 1) + c + + def f4(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f5(c, i + 1) + c + + def f5(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f6(c, i + 1) + c + + def f6(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f7(c, i + 1) + c + + def f7(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f8(c, i + 1) + c + + def f8(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f9(c, i + 1) + c + + def f9(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f10(c, i + 1) + c + + def f10(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f11(c, i + 1) + c + + def f11(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f12(c, i + 1) + c + + def f12(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f13(c, i + 1) + c + + def f13(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f14(c, i + 1) // non-termination if use f1 + c + + def f14(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f15(c, i + 1) + c + + def f15(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f16(c, i + 1) + c + + def f16(a: () => B, i: Int): () => B = + var c = () => if i % 2 == 0 then new C(a) else new D(a) + c = f1(c, i + 1) + c + + val c = f1(() => new C(() => 3), 10)