From 4a4284d5acf2c1a75e98a5475287e7311ecd887f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 26 May 2021 16:09:20 +0200 Subject: [PATCH 01/33] Copy file --- .../tools/dotc/transform/init/Objects.scala | 992 ++++++++++++++++++ 1 file changed, 992 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/transform/init/Objects.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala new file mode 100644 index 000000000000..d8040a8c213e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -0,0 +1,992 @@ +package dotty.tools.dotc +package transform +package init + +import core._ +import Contexts._ +import Symbols._ +import Types._ +import StdNames._ + +import ast.tpd._ +import util.EqHashMap +import config.Printers.init as printer +import reporting.trace as log + +import Errors._ + +import scala.collection.mutable + +class Semantic { + import Semantic._ + +// ----- Domain definitions -------------------------------- + + /** Abstract values + * + * Value = Hot | Cold | Warm | ThisRef | Fun | RefSet + * + * Cold + * ┌──────► ▲ ◄──┐ ◄────┐ + * │ │ │ │ + * │ │ │ │ + * ThisRef(C) │ │ │ + * ▲ │ │ │ + * │ Warm(D) Fun RefSet + * │ ▲ ▲ ▲ + * │ │ │ │ + * Warm(C) │ │ │ + * ▲ │ │ │ + * │ │ │ │ + * └─────────┴──────┴───────┘ + * 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 + * RefSet: + * + * R_a ⊑ R_b if R_a ⊆ R_b + * + * V ⊑ R if V ∈ R + * + */ + sealed abstract class Value { + def show: String = this.toString() + } + + /** A transitively initialized object */ + case object Hot extends Value + + /** An object with unknown initialization status */ + case object Cold extends Value + + sealed abstract class Addr extends Value { + def klass: ClassSymbol + } + + /** A reference to the object under initialization pointed by `this` + */ + case class ThisRef(klass: ClassSymbol) extends Addr + + /** An object with all fields initialized but reaches objects under initialization + * + * We need to restrict nesting levels of `outer` to finitize the domain. + */ + case class Warm(klass: ClassSymbol, outer: Value) extends Addr + + /** A function value */ + case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value + + /** A value which represents a set of addresses + * + * It comes from `if` expressions. + */ + case class RefSet(refs: List[Fun | Addr]) extends Value + + // end of value definition + + /** The abstract object which stores value about its fields and immediate outers. + * + * Semantically it suffices to store the outer for `klass`. We cache other outers + * for performance reasons. + * + * Note: Object is NOT a value. + */ + case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value], outers: mutable.Map[ClassSymbol, Value]) + + /** Abstract heap stores abstract objects + * + * As in the OOPSLA paper, the abstract heap is monotonistic. + * + * This is only one object we need to care about, hence it's just `Objekt`. + */ + object Heap { + opaque type Heap = mutable.Map[Addr, Objekt] + + /** Note: don't use `val` to avoid incorrect sharing */ + def empty: Heap = mutable.Map.empty + + extension (heap: Heap) + def contains(addr: Addr): Boolean = heap.contains(addr) + def apply(addr: Addr): Objekt = heap(addr) + def update(addr: Addr, obj: Objekt): Unit = + heap(addr) = obj + end extension + + extension (ref: Addr) + def updateField(field: Symbol, value: Value): Contextual[Unit] = + heap(ref).fields(field) = value + + def updateOuter(klass: ClassSymbol, value: Value): Contextual[Unit] = + heap(ref).outers(klass) = value + end extension + } + type Heap = Heap.Heap + + import Heap._ + val heap: Heap = Heap.empty + + object Promoted { + /** Values that have been safely promoted */ + opaque type Promoted = mutable.Set[Value] + + /** Note: don't use `val` to avoid incorrect sharing */ + def empty: Promoted = mutable.Set.empty + + extension (promoted: Promoted) + def contains(value: Value): Boolean = promoted.contains(value) + def add(value: Value): Unit = promoted += value + def remove(value: Value): Unit = promoted -= value + end extension + } + type Promoted = Promoted.Promoted + + import Promoted._ + def promoted(using p: Promoted): Promoted = p + + /** Interpreter configuration + * + * The (abstract) interpreter can be seen as a push-down automaton + * that transits between the configurations where the stack is the + * implicit call stack of the meta-language. + * + * It's important that the configuration is finite for the analysis + * to terminate. + * + * For soundness, we need to compute fixed point of the cache, which + * maps configuration to evaluation result. + * + * Thanks to heap monotonicity, heap is not part of the configuration. + * Which also avoid computing fix-point on the cache, as the cache is + * immutable. + */ + case class Config(thisV: Value, expr: Tree) + + /** Cache used to terminate the analysis + * + * A finitary configuration is not enough for the analysis to + * terminate. We need to use cache to let the interpreter "know" + * that it can terminate. + * + * For performance reasons we use curried key. + * + * Note: It's tempting to use location of trees as key. That should + * be avoided as a template may have the same location as its single + * statement body. Macros may also create incorrect locations. + * + */ + type Cache = mutable.Map[Value, EqHashMap[Tree, Value]] + val cache: Cache = mutable.Map.empty[Value, EqHashMap[Tree, Value]] + + /** Result of abstract interpretation */ + case class Result(value: Value, errors: Seq[Error]) { + def show(using Context) = value.show + ", errors = " + errors.map(_.toString) + + def ++(errors: Seq[Error]): Result = this.copy(errors = this.errors ++ errors) + + def +(error: Error): Result = this.copy(errors = this.errors :+ error) + + def ensureHot(msg: String, source: Tree): Contextual[Result] = + this ++ value.promote(msg, source) + + def select(f: Symbol, source: Tree): Contextual[Result] = + value.select(f, source) ++ errors + + def call(meth: Symbol, superType: Type, source: Tree): Contextual[Result] = + value.call(meth, superType, source) ++ errors + + def instantiate(klass: ClassSymbol, ctor: Symbol, source: Tree): Contextual[Result] = + value.instantiate(klass, ctor, source) ++ errors + } + + /** The state that threads through the interpreter */ + type Contextual[T] = (Context, Trace, Promoted) ?=> T + +// ----- Error Handling ----------------------------------- + + object Trace { + opaque type Trace = Vector[Tree] + + val empty: Trace = Vector.empty + + extension (trace: Trace) + def add(node: Tree): Trace = trace :+ node + def toVector: Vector[Tree] = trace + } + + type Trace = Trace.Trace + + import Trace._ + def trace(using t: Trace): Trace = t + +// ----- Operations on domains ----------------------------- + extension (a: Value) + def join(b: Value): Value = + (a, b) match + case (Hot, _) => b + case (_, Hot) => a + + 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) + case (RefSet(refs), b: (Fun | Warm | ThisRef)) => RefSet(b :: refs) + + case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) + + extension (values: Seq[Value]) + def join: Value = + if values.isEmpty then Hot + else values.reduce { (v1, v2) => v1.join(v2) } + + extension (value: Value) + def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = + value match { + case Hot => + Result(Hot, Errors.empty) + + case Cold => + val error = AccessCold(field, source, trace.toVector) + Result(Hot, error :: Nil) + + case addr: Addr => + val target = if needResolve then resolve(addr.klass, field) else field + if target.is(Flags.Lazy) then + value.call(target, superType = NoType, source, needResolve = false) + else + val obj = heap(addr) + if obj.fields.contains(target) then + Result(obj.fields(target), Nil) + else if addr.isInstanceOf[Warm] then + if target.is(Flags.ParamAccessor) then + // possible for trait parameters + // see tests/init/neg/trait2.scala + // + // return `Hot` here, errors are reported in checking `ThisRef` + Result(Hot, Nil) + else if target.hasSource then + val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs + eval(rhs, addr, target.owner.asClass, cacheResult = true) + else + val error = CallUnknown(field, source, trace.toVector) + Result(Hot, error :: Nil) + else + val error = AccessNonInit(target, trace.add(source).toVector) + Result(Hot, error :: Nil) + + case _: Fun => + ??? + + case RefSet(refs) => + val resList = refs.map(_.select(field, source)) + val value2 = resList.map(_.value).join + val errors = resList.flatMap(_.errors) + Result(value2, errors) + } + + def call(meth: Symbol, superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = + value match { + case Hot => + Result(Hot, Errors.empty) + + case Cold => + val error = CallCold(meth, source, trace.toVector) + Result(Hot, error :: Nil) + + case addr: Addr => + val target = + if !needResolve then + meth + else if superType.exists then + resolveSuper(addr.klass, superType, meth) + else + resolve(addr.klass, meth) + if target.isOneOf(Flags.Method | Flags.Lazy) then + if target.hasSource then + val cls = target.owner.enclosingClass.asClass + if target.isPrimaryConstructor then + val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] + eval(tpl, addr, cls, cacheResult = true)(using ctx, trace.add(tpl), promoted) + else + val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs + eval(rhs, addr, cls, cacheResult = true) + else if addr.canIgnoreMethodCall(target) then + Result(Hot, Nil) + else + val error = CallUnknown(target, source, trace.toVector) + Result(Hot, error :: Nil) + else + val obj = heap(addr) + if obj.fields.contains(target) then + Result(obj.fields(target), Nil) + else + value.select(target, source, needResolve = false) + + case Fun(body, thisV, klass) => + // meth == NoSymbol for poly functions + if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` + else eval(body, thisV, klass, cacheResult = true) + + case RefSet(refs) => + val resList = refs.map(_.call(meth, superType, source)) + val value2 = resList.map(_.value).join + val errors = resList.flatMap(_.errors) + Result(value2, errors) + } + + /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ + def instantiate(klass: ClassSymbol, ctor: Symbol, source: Tree): Contextual[Result] = + value match { + case Hot => + Result(Hot, Errors.empty) + + case Cold => + val error = CallCold(ctor, source, trace.toVector) + Result(Hot, error :: Nil) + + case addr: Addr => + // widen the outer to finitize addresses + val outer = addr match + case Warm(_, _: Warm) => Cold + case _ => addr + + val value = Warm(klass, outer) + if !heap.contains(value) then + val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) + heap.update(value, obj) + val res = value.call(ctor, superType = NoType, source) + Result(value, res.errors) + + case Fun(body, thisV, klass) => + ??? // impossible + + case RefSet(refs) => + val resList = refs.map(_.instantiate(klass, ctor, source)) + val value2 = resList.map(_.value).join + val errors = resList.flatMap(_.errors) + Result(value2, errors) + } + end extension + +// ----- Promotion ---------------------------------------------------- + + extension (value: Value) + /** Can we promote the value by checking the extrinsic values? + * + * The extrinsic values are environment values, e.g. outers for `Warm` + * and `thisV` captured in functions. + * + * This is a fast track for early promotion of values. + */ + def canPromoteExtrinsic: Contextual[Boolean] = + value match + case Hot => true + case Cold => false + + case warm: Warm => + warm.outer.canPromoteExtrinsic && { + promoted.add(warm) + true + } + + case thisRef: ThisRef => + promoted.contains(thisRef) || { + val obj = heap(thisRef) + // If we have all fields initialized, then we can promote This to hot. + val allFieldsInitialized = thisRef.klass.appliedRef.fields.forall { denot => + val sym = denot.symbol + sym.isOneOf(Flags.Lazy | Flags.Deferred) || obj.fields.contains(sym) + } + if allFieldsInitialized then promoted.add(thisRef) + allFieldsInitialized + } + + case fun: Fun => + fun.thisV.canPromoteExtrinsic && { + promoted.add(fun) + true + } + + case RefSet(refs) => + refs.forall(_.canPromoteExtrinsic) + + end canPromoteExtrinsic + + /** Promotion of values to hot */ + def promote(msg: String, source: Tree): Contextual[List[Error]] = + value match + case Hot => Nil + + case Cold => PromoteError(msg, source, trace.toVector) :: Nil + + case thisRef: ThisRef => + if promoted.contains(thisRef) then Nil + else if thisRef.canPromoteExtrinsic then Nil + else PromoteError(msg, source, trace.toVector) :: Nil + + case warm: Warm => + if promoted.contains(warm) then Nil + else if warm.canPromoteExtrinsic then Nil + else { + promoted.add(warm) + val errors = warm.tryPromote(msg, source) + if errors.nonEmpty then promoted.remove(warm) + errors + } + + case fun @ Fun(body, thisV, klass) => + if promoted.contains(fun) then Nil + else + val res = eval(body, thisV, klass) + val errors2 = res.value.promote(msg, source) + if (res.errors.nonEmpty || errors2.nonEmpty) + UnsafePromotion(msg, source, trace.toVector, res.errors ++ errors2) :: Nil + else + promoted.add(fun) + Nil + + case RefSet(refs) => + refs.flatMap(_.promote(msg, source)) + end extension + + extension (warm: Warm) + /** Try early promotion of warm objects + * + * Promotion is expensive and should only be performed for small classes. + * + * 1. for each concrete method `m` of the warm object: + * call the method and promote the result + * + * 2. for each concrete field `f` of the warm object: + * promote the field value + * + * If the object contains nested classes as members, the checker simply + * reports a warning to avoid expensive checks. + * + * TODO: we need to revisit whether this is needed once we make the + * system more flexible in other dimentions: e.g. leak to + * methods or constructors, or use ownership for creating cold data structures. + */ + def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show, printer) { + val classRef = warm.klass.appliedRef + if classRef.memberClasses.nonEmpty then + return PromoteError(msg, source, trace.toVector) :: Nil + + val fields = classRef.fields + val methods = classRef.membersBasedOnFlags(Flags.Method, Flags.Deferred | Flags.Accessor) + val buffer = new mutable.ArrayBuffer[Error] + + fields.exists { denot => + val f = denot.symbol + if !f.isOneOf(Flags.Deferred | Flags.Private | Flags.Protected) && f.hasSource then + val trace2 = trace.add(f.defTree) + val res = warm.select(f, source) + locally { + given Trace = trace2 + buffer ++= res.ensureHot(msg, source).errors + } + buffer.nonEmpty + } + + buffer.nonEmpty || methods.exists { denot => + val m = denot.symbol + if !m.isConstructor && m.hasSource then + val trace2 = trace.add(m.defTree) + locally { + given Trace = trace2 + val res = warm.call(m, superType = NoType, source = source) + buffer ++= res.ensureHot(msg, source).errors + } + buffer.nonEmpty + } + + if buffer.isEmpty then Nil + else UnsafePromotion(msg, source, trace.toVector, buffer.toList) :: Nil + } + + end extension + +// ----- Policies ------------------------------------------------------ + extension (value: Addr) + /** Can the method call on `value` be ignored? + * + * Note: assume overriding resolution has been performed. + */ + def canIgnoreMethodCall(meth: Symbol)(using Context): Boolean = + val cls = meth.owner + cls == defn.AnyClass || + cls == defn.AnyValClass || + cls == defn.ObjectClass + +// ----- Semantic definition -------------------------------- + + /** 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`. + * The parameter `klass` is needed for `this` resolution. Consider the following code: + * + * class A { + * A.this + * class B extends A { A.this } + * } + * + * As can be seen above, the meaning of the expression `A.this` depends on where + * it is located. + * + * This method only handles cache logic and delegates the work to `cases`. + */ + def eval(expr: Tree, thisV: Addr, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Result] = log("evaluating " + expr.show + ", this = " + thisV.show, printer, res => res.asInstanceOf[Result].show) { + val innerMap = cache.getOrElseUpdate(thisV, new EqHashMap[Tree, Value]) + if (innerMap.contains(expr)) Result(innerMap(expr), Errors.empty) + else { + // no need to compute fix-point, because + // 1. the result is decided by `cfg` for a legal program + // (heap change is irrelevant thanks to monotonicity) + // 2. errors will have been reported for an illegal program + innerMap(expr) = Hot + val res = cases(expr, thisV, klass) + if cacheResult then innerMap(expr) = res.value else innerMap.remove(expr) + res + } + } + + /** Evaluate a list of expressions */ + def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + exprs.map { expr => eval(expr, thisV, klass) } + + /** Evaluate arguments of methods */ + def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = + val ress = args.map { arg => + val res = + if arg.isByName then + val fun = Fun(arg.tree, thisV, klass) + Result(fun, Nil) + else + eval(arg.tree, thisV, klass) + + res.ensureHot("May only use initialized value as arguments", arg.tree) + } + ress.flatMap(_.errors) + + /** Handles the evaluation of different expressions + * + * Note: Recursive call should go to `eval` instead of `cases`. + */ + def cases(expr: Tree, thisV: Addr, klass: ClassSymbol): Contextual[Result] = + expr match { + case Ident(nme.WILDCARD) => + // TODO: disallow `var x: T = _` + Result(Hot, Errors.empty) + + case id @ Ident(name) if !id.symbol.is(Flags.Method) => + assert(name.isTermName, "type trees should not reach here") + cases(expr.tpe, thisV, klass, expr) + + case NewExpr(tref, New(tpt), ctor, argss) => + // check args + val errors = evalArgs(argss.flatten, thisV, klass) + + val cls = tref.classSymbol.asClass + val res = outerValue(tref, thisV, klass, tpt) + val trace2 = trace.add(expr) + locally { + given Trace = trace2 + (res ++ errors).instantiate(cls, ctor, expr) + } + + case Call(ref, argss) => + // check args + val errors = evalArgs(argss.flatten, thisV, klass) + + val trace2: Trace = trace.add(expr) + + ref match + case Select(supert: Super, _) => + val SuperType(thisTp, superTp) = supert.tpe + val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) + Result(thisValue2, errors).call(ref.symbol, superTp, expr)(using ctx, trace2) + + case Select(qual, _) => + val res = eval(qual, thisV, klass) ++ errors + res.call(ref.symbol, superType = NoType, source = expr)(using ctx, trace2) + + case id: Ident => + id.tpe match + case TermRef(NoPrefix, _) => + // resolve this for the local method + val enclosingClass = id.symbol.owner.enclosingClass.asClass + val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) + // local methods are not a member, but we can reuse the method `call` + thisValue2.call(id.symbol, superType = NoType, expr, needResolve = false) + case TermRef(prefix, _) => + val res = cases(prefix, thisV, klass, id) ++ errors + res.call(id.symbol, superType = NoType, source = expr)(using ctx, trace2) + + case Select(qualifier, name) => + eval(qualifier, thisV, klass).select(expr.symbol, expr) + + case _: This => + cases(expr.tpe, thisV, klass, expr) + + case Literal(_) => + Result(Hot, Errors.empty) + + case Typed(expr, tpt) => + if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Hot, Errors.empty) + else eval(expr, thisV, klass) ++ checkTermUsage(tpt, thisV, klass) + + case NamedArg(name, arg) => + eval(arg, thisV, klass) + + case Assign(lhs, rhs) => + lhs match + case Select(qual, _) => + val res = eval(qual, thisV, klass) + eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) ++ res.errors + case id: Ident => + eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) + + case closureDef(ddef) => + val value = Fun(ddef.rhs, thisV, klass) + Result(value, Nil) + + case PolyFun(body) => + val value = Fun(body, thisV, klass) + Result(value, Nil) + + case Block(stats, expr) => + val ress = eval(stats, thisV, klass) + eval(expr, thisV, klass) ++ ress.flatMap(_.errors) + + case If(cond, thenp, elsep) => + val ress = eval(cond :: thenp :: elsep :: Nil, thisV, klass) + val value = ress.map(_.value).join + val errors = ress.flatMap(_.errors) + Result(value, errors) + + case Annotated(arg, annot) => + if (expr.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Hot, Errors.empty) + else eval(arg, thisV, klass) + + case Match(selector, cases) => + val res1 = eval(selector, thisV, klass).ensureHot("The value to be matched needs to be fully initialized", selector) + val ress = eval(cases.map(_.body), thisV, klass) + val value = ress.map(_.value).join + val errors = res1.errors ++ ress.flatMap(_.errors) + Result(value, errors) + + case Return(expr, from) => + eval(expr, thisV, klass).ensureHot("return expression may only be initialized value", expr) + + case WhileDo(cond, body) => + val ress = eval(cond :: body :: Nil, thisV, klass) + Result(Hot, ress.flatMap(_.errors)) + + case Labeled(_, expr) => + eval(expr, thisV, klass) + + case Try(block, cases, finalizer) => + val res1 = eval(block, thisV, klass) + val ress = eval(cases.map(_.body), thisV, klass) + val errors = ress.flatMap(_.errors) + val resValue = ress.map(_.value).join + if finalizer.isEmpty then + Result(resValue, res1.errors ++ errors) + else + val res2 = eval(finalizer, thisV, klass) + Result(resValue, res1.errors ++ errors ++ res2.errors) + + case SeqLiteral(elems, elemtpt) => + val ress = elems.map { elem => + eval(elem, thisV, klass).ensureHot("May only use initialized value as method arguments", elem) + } + Result(Hot, ress.flatMap(_.errors)) + + case Inlined(call, bindings, expansion) => + val ress = eval(bindings, thisV, klass) + eval(expansion, thisV, klass) ++ ress.flatMap(_.errors) + + case Thicket(List()) => + // possible in try/catch/finally, see tests/crash/i6914.scala + Result(Hot, Errors.empty) + + 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) + + case ddef : DefDef => + // local method + Result(Hot, Errors.empty) + + case tdef: TypeDef => + // local type definition + if tdef.isClassDef then Result(Hot, Errors.empty) + else Result(Hot, checkTermUsage(tdef.rhs, thisV, klass)) + + case tpl: Template => + init(tpl, thisV, klass) + + case _: Import | _: Export => + Result(Hot, Errors.empty) + + case _ => + throw new Exception("unexpected tree: " + expr.show) + } + + /** Handle semantics of leaf nodes */ + def cases(tp: Type, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { + tp match { + case _: ConstantType => + Result(Hot, Errors.empty) + + case tmref: TermRef if tmref.prefix == NoPrefix => + Result(Hot, Errors.empty) + + case tmref: TermRef => + cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) + + case tp @ ThisType(tref) => + if tref.symbol.is(Flags.Package) then Result(Hot, Errors.empty) + else + val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) + Result(value, Errors.empty) + + case _: TermParamRef | _: RecThis => + // possible from checking effects of types + Result(Hot, Errors.empty) + + case _ => + throw new Exception("unexpected type: " + tp) + } + } + + /** Resolve C.this that appear in `klass` */ + def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Value] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Value].show) { + if target == klass then thisV + else if target.is(Flags.Package) || target.isStaticOwner then Hot + else + thisV match + case Hot | _: ThisRef => Hot + case warm: Warm => + val obj = heap(warm) + val outerCls = klass.owner.enclosingClass.asClass + resolveThis(target, obj.outers(klass), outerCls, source) + case RefSet(refs) => + refs.map(ref => resolveThis(target, ref, klass, source)).join + case fun: Fun => + report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, 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 + if tref.prefix == NoPrefix then + val enclosing = cls.owner.lexicallyEnclosingClass.asClass + val outerV = resolveThis(enclosing, thisV, klass, source) + Result(outerV, Errors.empty) + else + if cls.isAllOf(Flags.JavaInterface) then Result(Hot, Nil) + else cases(tref.prefix, thisV, klass, source) + + /** Initialize part of an abstract object in `klass` of the inheritance chain */ + def init(tpl: Template, thisV: Addr, klass: ClassSymbol): Contextual[Result] = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) { + val errorBuffer = new mutable.ArrayBuffer[Error] + + // init param fields + klass.paramAccessors.foreach { acc => + if (!acc.is(Flags.Method)) { + printer.println(acc.show + " initialized") + thisV.updateField(acc, Hot) + } + } + + def superCall(tref: TypeRef, ctor: Symbol, source: Tree): Unit = + val cls = tref.classSymbol.asClass + // update outer for super class + val res = outerValue(tref, thisV, klass, source) + errorBuffer ++= res.errors + thisV.updateOuter(cls, res.value) + + // follow constructor + if cls.hasSource then + val res2 = thisV.call(ctor, superType = NoType, source)(using ctx, trace.add(source)) + errorBuffer ++= res2.errors + + // parents + def initParent(parent: Tree) = parent match { + case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen + eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } + errorBuffer ++= evalArgs(argss.flatten, thisV, klass) + superCall(tref, ctor, tree) + + case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) + errorBuffer ++= evalArgs(argss.flatten, thisV, klass) + superCall(tref, ctor, tree) + + case _ => // extends A or extends A[T] + val tref = typeRefOf(parent.tpe) + superCall(tref, tref.classSymbol.primaryConstructor, parent) + } + + // see spec 5.1 about "Template Evaluation". + // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html + if !klass.is(Flags.Trait) then + // 1. first init parent class recursively + // 2. initialize traits according to linearization order + val superParent = tpl.parents.head + val superCls = superParent.tpe.classSymbol.asClass + initParent(superParent) + + val parents = tpl.parents.tail + val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) + mixins.reverse.foreach { mixin => + parents.find(_.tpe.classSymbol == mixin) match + case Some(parent) => initParent(parent) + case None => + // According to the language spec, if the mixin trait requires + // arguments, then the class must provide arguments to it explicitly + // in the parent list. That means we will encounter it in the Some + // branch. + // + // When a trait A extends a parameterized trait B, it cannot provide + // term arguments to B. That can only be done in a concrete class. + val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor) + val ctor = tref.classSymbol.primaryConstructor + if ctor.exists then superCall(tref, ctor, superParent) + } + + + // class body + tpl.body.foreach { + case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) => + val res = eval(vdef.rhs, thisV, klass, cacheResult = true) + errorBuffer ++= res.errors + thisV.updateField(vdef.symbol, res.value) + + case _: MemberDef => + + case tree => + errorBuffer ++= eval(tree, thisV, klass).errors + } + + Result(thisV, errorBuffer.toList) + } + + /** Check that path in path-dependent types are initialized + * + * This is intended to avoid type soundness issues in Dotty. + */ + def checkTermUsage(tpt: Tree, thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = + val buf = new mutable.ArrayBuffer[Error] + val traverser = new TypeTraverser { + def traverse(tp: Type): Unit = tp match { + case TermRef(_: SingletonType, _) => + buf ++= cases(tp, thisV, klass, tpt).errors + case _ => + traverseChildren(tp) + } + } + traverser.traverse(tpt.tpe) + buf.toList + +} + +object Semantic { + +// ----- Utility methods and extractors -------------------------------- + + def typeRefOf(tp: Type)(using Context): TypeRef = tp.dealias.typeConstructor match { + case tref: TypeRef => tref + case hklambda: HKTypeLambda => typeRefOf(hklambda.resType) + } + + opaque type Arg = Tree | ByNameArg + case class ByNameArg(tree: Tree) + + extension (arg: Arg) + def isByName = arg.isInstanceOf[ByNameArg] + def tree: Tree = arg match + case t: Tree => t + case ByNameArg(t) => t + + object Call { + + def unapply(tree: Tree)(using Context): Option[(Tree, List[List[Arg]])] = + tree match + case Apply(fn, args) => + val argTps = fn.tpe.widen match + case mt: MethodType => mt.paramInfos + val normArgs: List[Arg] = args.zip(argTps).map { + case (arg, _: ExprType) => ByNameArg(arg) + case (arg, _) => arg + } + unapply(fn) match + case Some((ref, args0)) => Some((ref, args0 :+ normArgs)) + case None => None + + case TypeApply(fn, targs) => + unapply(fn) + + case ref: RefTree if ref.tpe.widenSingleton.isInstanceOf[MethodicType] => + Some((ref, Nil)) + + case _ => None + } + + object NewExpr { + def unapply(tree: Tree)(using Context): Option[(TypeRef, New, Symbol, List[List[Arg]])] = + tree match + case Call(fn @ Select(newTree: New, init), argss) if init == nme.CONSTRUCTOR => + val tref = typeRefOf(newTree.tpe) + Some((tref, newTree, fn.symbol, argss)) + case _ => None + } + + object PolyFun { + def unapply(tree: Tree)(using Context): Option[Tree] = + tree match + case Block((cdef: TypeDef) :: Nil, Typed(NewExpr(tref, _, _, _), _)) + if tref.symbol.isAnonymousClass && tref <:< defn.PolyFunctionType + => + val body = cdef.rhs.asInstanceOf[Template].body + val apply = body.head.asInstanceOf[DefDef] + Some(apply.rhs) + case _ => + None + } + + extension (symbol: Symbol) def hasSource(using Context): Boolean = + !symbol.defTree.isEmpty + + def resolve(cls: ClassSymbol, sym: Symbol)(using Context): Symbol = + if (sym.isEffectivelyFinal || sym.isConstructor) sym + else sym.matchingMember(cls.appliedRef) + + def resolveSuper(cls: ClassSymbol, superType: Type, sym: Symbol)(using Context): Symbol = { + import annotation.tailrec + @tailrec def loop(bcs: List[ClassSymbol]): Symbol = bcs match { + case bc :: bcs1 => + val cand = sym.matchingDecl(bcs.head, cls.thisType) + .suchThat(alt => !alt.is(Flags.Deferred)).symbol + if (cand.exists) cand else loop(bcs.tail) + case _ => + NoSymbol + } + loop(cls.info.baseClasses.dropWhile(sym.owner != _)) + } + +} From afd082286df232012c511af0737a2081837af4a0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 26 May 2021 16:23:15 +0200 Subject: [PATCH 02/33] Define domains --- .../tools/dotc/transform/init/Objects.scala | 347 +----------------- 1 file changed, 20 insertions(+), 327 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index d8040a8c213e..4317c5953c05 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -17,38 +17,17 @@ import Errors._ import scala.collection.mutable -class Semantic { +class Objects { import Semantic._ // ----- Domain definitions -------------------------------- /** Abstract values * - * Value = Hot | Cold | Warm | ThisRef | Fun | RefSet + * Value = Cold | ObjectRef | Instance | Fun | RefSet * - * Cold - * ┌──────► ▲ ◄──┐ ◄────┐ - * │ │ │ │ - * │ │ │ │ - * ThisRef(C) │ │ │ - * ▲ │ │ │ - * │ Warm(D) Fun RefSet - * │ ▲ ▲ ▲ - * │ │ │ │ - * Warm(C) │ │ │ - * ▲ │ │ │ - * │ │ │ │ - * └─────────┴──────┴───────┘ - * 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 - * RefSet: + * `RefSet` represents a set of values which could be `ObjectRef`, + * `Instance` or `Fun`. The following ordering applies for RefSet: * * R_a ⊑ R_b if R_a ⊆ R_b * @@ -59,25 +38,19 @@ class Semantic { def show: String = this.toString() } - /** A transitively initialized object */ - case object Hot extends Value - - /** An object with unknown initialization status */ + /** An unknown object */ case object Cold extends Value sealed abstract class Addr extends Value { def klass: ClassSymbol } - /** A reference to the object under initialization pointed by `this` + /** A reference to a static object */ - case class ThisRef(klass: ClassSymbol) extends Addr + case class ObjectRef(klass: ClassSymbol) extends Addr - /** An object with all fields initialized but reaches objects under initialization - * - * We need to restrict nesting levels of `outer` to finitize the domain. - */ - case class Warm(klass: ClassSymbol, outer: Value) extends Addr + /** An instance of class */ + case class Instance(klass: ClassSymbol, outer: Value) extends Addr /** A function value */ case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value @@ -88,6 +61,8 @@ class Semantic { */ case class RefSet(refs: List[Fun | Addr]) extends Value + val Bottom: Value = RefSet(Nil) + // end of value definition /** The abstract object which stores value about its fields and immediate outers. @@ -131,42 +106,6 @@ class Semantic { import Heap._ val heap: Heap = Heap.empty - object Promoted { - /** Values that have been safely promoted */ - opaque type Promoted = mutable.Set[Value] - - /** Note: don't use `val` to avoid incorrect sharing */ - def empty: Promoted = mutable.Set.empty - - extension (promoted: Promoted) - def contains(value: Value): Boolean = promoted.contains(value) - def add(value: Value): Unit = promoted += value - def remove(value: Value): Unit = promoted -= value - end extension - } - type Promoted = Promoted.Promoted - - import Promoted._ - def promoted(using p: Promoted): Promoted = p - - /** Interpreter configuration - * - * The (abstract) interpreter can be seen as a push-down automaton - * that transits between the configurations where the stack is the - * implicit call stack of the meta-language. - * - * It's important that the configuration is finite for the analysis - * to terminate. - * - * For soundness, we need to compute fixed point of the cache, which - * maps configuration to evaluation result. - * - * Thanks to heap monotonicity, heap is not part of the configuration. - * Which also avoid computing fix-point on the cache, as the cache is - * immutable. - */ - case class Config(thisV: Value, expr: Tree) - /** Cache used to terminate the analysis * * A finitary configuration is not enough for the analysis to @@ -205,7 +144,7 @@ class Semantic { } /** The state that threads through the interpreter */ - type Contextual[T] = (Context, Trace, Promoted) ?=> T + type Contextual[T] = (Context, Trace) ?=> T // ----- Error Handling ----------------------------------- @@ -228,25 +167,22 @@ class Semantic { extension (a: Value) def join(b: Value): Value = (a, b) match - case (Hot, _) => b - case (_, Hot) => a + case (Bottom, _) => b + case (_, Bottom) => a 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 | Addr), b: (Fun | Addr)) => RefSet(a :: b :: Nil) - case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil) - - case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs) - case (RefSet(refs), b: (Fun | Warm | ThisRef)) => RefSet(b :: refs) + case (a: (Fun | Addr), RefSet(refs)) => RefSet(a :: refs) + case (RefSet(refs), b: (Fun | Addr)) => RefSet(b :: refs) case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) extension (values: Seq[Value]) def join: Value = - if values.isEmpty then Hot + if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) } extension (value: Value) @@ -378,144 +314,6 @@ class Semantic { } end extension -// ----- Promotion ---------------------------------------------------- - - extension (value: Value) - /** Can we promote the value by checking the extrinsic values? - * - * The extrinsic values are environment values, e.g. outers for `Warm` - * and `thisV` captured in functions. - * - * This is a fast track for early promotion of values. - */ - def canPromoteExtrinsic: Contextual[Boolean] = - value match - case Hot => true - case Cold => false - - case warm: Warm => - warm.outer.canPromoteExtrinsic && { - promoted.add(warm) - true - } - - case thisRef: ThisRef => - promoted.contains(thisRef) || { - val obj = heap(thisRef) - // If we have all fields initialized, then we can promote This to hot. - val allFieldsInitialized = thisRef.klass.appliedRef.fields.forall { denot => - val sym = denot.symbol - sym.isOneOf(Flags.Lazy | Flags.Deferred) || obj.fields.contains(sym) - } - if allFieldsInitialized then promoted.add(thisRef) - allFieldsInitialized - } - - case fun: Fun => - fun.thisV.canPromoteExtrinsic && { - promoted.add(fun) - true - } - - case RefSet(refs) => - refs.forall(_.canPromoteExtrinsic) - - end canPromoteExtrinsic - - /** Promotion of values to hot */ - def promote(msg: String, source: Tree): Contextual[List[Error]] = - value match - case Hot => Nil - - case Cold => PromoteError(msg, source, trace.toVector) :: Nil - - case thisRef: ThisRef => - if promoted.contains(thisRef) then Nil - else if thisRef.canPromoteExtrinsic then Nil - else PromoteError(msg, source, trace.toVector) :: Nil - - case warm: Warm => - if promoted.contains(warm) then Nil - else if warm.canPromoteExtrinsic then Nil - else { - promoted.add(warm) - val errors = warm.tryPromote(msg, source) - if errors.nonEmpty then promoted.remove(warm) - errors - } - - case fun @ Fun(body, thisV, klass) => - if promoted.contains(fun) then Nil - else - val res = eval(body, thisV, klass) - val errors2 = res.value.promote(msg, source) - if (res.errors.nonEmpty || errors2.nonEmpty) - UnsafePromotion(msg, source, trace.toVector, res.errors ++ errors2) :: Nil - else - promoted.add(fun) - Nil - - case RefSet(refs) => - refs.flatMap(_.promote(msg, source)) - end extension - - extension (warm: Warm) - /** Try early promotion of warm objects - * - * Promotion is expensive and should only be performed for small classes. - * - * 1. for each concrete method `m` of the warm object: - * call the method and promote the result - * - * 2. for each concrete field `f` of the warm object: - * promote the field value - * - * If the object contains nested classes as members, the checker simply - * reports a warning to avoid expensive checks. - * - * TODO: we need to revisit whether this is needed once we make the - * system more flexible in other dimentions: e.g. leak to - * methods or constructors, or use ownership for creating cold data structures. - */ - def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show, printer) { - val classRef = warm.klass.appliedRef - if classRef.memberClasses.nonEmpty then - return PromoteError(msg, source, trace.toVector) :: Nil - - val fields = classRef.fields - val methods = classRef.membersBasedOnFlags(Flags.Method, Flags.Deferred | Flags.Accessor) - val buffer = new mutable.ArrayBuffer[Error] - - fields.exists { denot => - val f = denot.symbol - if !f.isOneOf(Flags.Deferred | Flags.Private | Flags.Protected) && f.hasSource then - val trace2 = trace.add(f.defTree) - val res = warm.select(f, source) - locally { - given Trace = trace2 - buffer ++= res.ensureHot(msg, source).errors - } - buffer.nonEmpty - } - - buffer.nonEmpty || methods.exists { denot => - val m = denot.symbol - if !m.isConstructor && m.hasSource then - val trace2 = trace.add(m.defTree) - locally { - given Trace = trace2 - val res = warm.call(m, superType = NoType, source = source) - buffer ++= res.ensureHot(msg, source).errors - } - buffer.nonEmpty - } - - if buffer.isEmpty then Nil - else UnsafePromotion(msg, source, trace.toVector, buffer.toList) :: Nil - } - - end extension - // ----- Policies ------------------------------------------------------ extension (value: Addr) /** Can the method call on `value` be ignored? @@ -643,7 +441,7 @@ class Semantic { case Typed(expr, tpt) => if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Hot, Errors.empty) - else eval(expr, thisV, klass) ++ checkTermUsage(tpt, thisV, klass) + else eval(expr, thisV, klass) case NamedArg(name, arg) => eval(arg, thisV, klass) @@ -731,8 +529,7 @@ class Semantic { case tdef: TypeDef => // local type definition - if tdef.isClassDef then Result(Hot, Errors.empty) - else Result(Hot, checkTermUsage(tdef.rhs, thisV, klass)) + Result(Hot, Errors.empty) case tpl: Template => init(tpl, thisV, klass) @@ -885,108 +682,4 @@ class Semantic { Result(thisV, errorBuffer.toList) } - - /** Check that path in path-dependent types are initialized - * - * This is intended to avoid type soundness issues in Dotty. - */ - def checkTermUsage(tpt: Tree, thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = - val buf = new mutable.ArrayBuffer[Error] - val traverser = new TypeTraverser { - def traverse(tp: Type): Unit = tp match { - case TermRef(_: SingletonType, _) => - buf ++= cases(tp, thisV, klass, tpt).errors - case _ => - traverseChildren(tp) - } - } - traverser.traverse(tpt.tpe) - buf.toList - -} - -object Semantic { - -// ----- Utility methods and extractors -------------------------------- - - def typeRefOf(tp: Type)(using Context): TypeRef = tp.dealias.typeConstructor match { - case tref: TypeRef => tref - case hklambda: HKTypeLambda => typeRefOf(hklambda.resType) - } - - opaque type Arg = Tree | ByNameArg - case class ByNameArg(tree: Tree) - - extension (arg: Arg) - def isByName = arg.isInstanceOf[ByNameArg] - def tree: Tree = arg match - case t: Tree => t - case ByNameArg(t) => t - - object Call { - - def unapply(tree: Tree)(using Context): Option[(Tree, List[List[Arg]])] = - tree match - case Apply(fn, args) => - val argTps = fn.tpe.widen match - case mt: MethodType => mt.paramInfos - val normArgs: List[Arg] = args.zip(argTps).map { - case (arg, _: ExprType) => ByNameArg(arg) - case (arg, _) => arg - } - unapply(fn) match - case Some((ref, args0)) => Some((ref, args0 :+ normArgs)) - case None => None - - case TypeApply(fn, targs) => - unapply(fn) - - case ref: RefTree if ref.tpe.widenSingleton.isInstanceOf[MethodicType] => - Some((ref, Nil)) - - case _ => None - } - - object NewExpr { - def unapply(tree: Tree)(using Context): Option[(TypeRef, New, Symbol, List[List[Arg]])] = - tree match - case Call(fn @ Select(newTree: New, init), argss) if init == nme.CONSTRUCTOR => - val tref = typeRefOf(newTree.tpe) - Some((tref, newTree, fn.symbol, argss)) - case _ => None - } - - object PolyFun { - def unapply(tree: Tree)(using Context): Option[Tree] = - tree match - case Block((cdef: TypeDef) :: Nil, Typed(NewExpr(tref, _, _, _), _)) - if tref.symbol.isAnonymousClass && tref <:< defn.PolyFunctionType - => - val body = cdef.rhs.asInstanceOf[Template].body - val apply = body.head.asInstanceOf[DefDef] - Some(apply.rhs) - case _ => - None - } - - extension (symbol: Symbol) def hasSource(using Context): Boolean = - !symbol.defTree.isEmpty - - def resolve(cls: ClassSymbol, sym: Symbol)(using Context): Symbol = - if (sym.isEffectivelyFinal || sym.isConstructor) sym - else sym.matchingMember(cls.appliedRef) - - def resolveSuper(cls: ClassSymbol, superType: Type, sym: Symbol)(using Context): Symbol = { - import annotation.tailrec - @tailrec def loop(bcs: List[ClassSymbol]): Symbol = bcs match { - case bc :: bcs1 => - val cand = sym.matchingDecl(bcs.head, cls.thisType) - .suchThat(alt => !alt.is(Flags.Deferred)).symbol - if (cand.exists) cand else loop(bcs.tail) - case _ => - NoSymbol - } - loop(cls.info.baseClasses.dropWhile(sym.owner != _)) - } - } From 27d931f33574e6b15b3270c86df806e139d23ff1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 26 May 2021 16:49:13 +0200 Subject: [PATCH 03/33] Add environment --- .../tools/dotc/transform/init/Objects.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 4317c5953c05..1e2ed3fe2bec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -74,6 +74,20 @@ class Objects { */ case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value], outers: mutable.Map[ClassSymbol, Value]) + /** The environment for method parameters */ + object Env { + opaque type Env = Map[Symbol, Value] + def apply(bindings: Map[Symbol, Value]): Env = bindings + + extension (env: Env) + def lookup(sym: Symbol): Value = env(sym) + } + + type Env = Env.Env + def env(using env: Env) = env + + import Env._ + /** Abstract heap stores abstract objects * * As in the OOPSLA paper, the abstract heap is monotonistic. @@ -144,7 +158,7 @@ class Objects { } /** The state that threads through the interpreter */ - type Contextual[T] = (Context, Trace) ?=> T + type Contextual[T] = (Env, Context, Trace) ?=> T // ----- Error Handling ----------------------------------- From ab877a9dd9e0682ca630368effdcf78070b1f19c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 26 May 2021 18:35:41 +0200 Subject: [PATCH 04/33] Fix domain operations --- .../tools/dotc/transform/init/Objects.scala | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 1e2ed3fe2bec..2fef6357d46d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -50,7 +50,7 @@ class Objects { case class ObjectRef(klass: ClassSymbol) extends Addr /** An instance of class */ - case class Instance(klass: ClassSymbol, outer: Value) extends Addr + case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr /** A function value */ case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value @@ -79,6 +79,11 @@ class Objects { opaque type Env = Map[Symbol, Value] def apply(bindings: Map[Symbol, Value]): Env = bindings + def apply(ddef: DefDef, args: List[Value])(using Context): Env = + val params = ddef.paramss.collect(l => ValDefs.unapply(l)).asInstanceOf[List[ValDef]].map(_.symbol) + assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size) + params.zip(args).toMap + extension (env: Env) def lookup(sym: Symbol): Value = env(sym) } @@ -144,17 +149,14 @@ class Objects { def +(error: Error): Result = this.copy(errors = this.errors :+ error) - def ensureHot(msg: String, source: Tree): Contextual[Result] = - this ++ value.promote(msg, source) - def select(f: Symbol, source: Tree): Contextual[Result] = value.select(f, source) ++ errors - def call(meth: Symbol, superType: Type, source: Tree): Contextual[Result] = - value.call(meth, superType, source) ++ errors + def call(meth: Symbol, args: List[Value], superType: Type, source: Tree): Contextual[Result] = + value.call(meth, args, superType, source) ++ errors - def instantiate(klass: ClassSymbol, ctor: Symbol, source: Tree): Contextual[Result] = - value.instantiate(klass, ctor, source) ++ errors + def instantiate(klass: ClassSymbol, args: List[Value], ctor: Symbol, source: Tree): Contextual[Result] = + value.instantiate(klass, ctor, args, source) ++ errors } /** The state that threads through the interpreter */ @@ -202,37 +204,34 @@ class Objects { extension (value: Value) def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = value match { - case Hot => - Result(Hot, Errors.empty) + case Bottom => + Result(Bottom, Errors.empty) case Cold => val error = AccessCold(field, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Bottom, error :: Nil) case addr: Addr => val target = if needResolve then resolve(addr.klass, field) else field if target.is(Flags.Lazy) then - value.call(target, superType = NoType, source, needResolve = false) + val rhs = target.defTree.asInstanceOf[ValDef].rhs + eval(rhs, addr, target.owner.asClass, cacheResult = true) else val obj = heap(addr) if obj.fields.contains(target) then Result(obj.fields(target), Nil) - else if addr.isInstanceOf[Warm] then - if target.is(Flags.ParamAccessor) then - // possible for trait parameters - // see tests/init/neg/trait2.scala - // - // return `Hot` here, errors are reported in checking `ThisRef` - Result(Hot, Nil) - else if target.hasSource then - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, addr, target.owner.asClass, cacheResult = true) - else - val error = CallUnknown(field, source, trace.toVector) - Result(Hot, error :: Nil) + else if target.is(Flags.ParamAccessor) then + // possible for trait parameters + // see tests/init/neg/trait2.scala + // + // return `Bottom` here, errors are reported in checking `ThisRef` + Result(Bottom, Nil) + else if target.hasSource then + val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs + eval(rhs, addr, target.owner.asClass, cacheResult = true) else - val error = AccessNonInit(target, trace.add(source).toVector) - Result(Hot, error :: Nil) + val error = CallUnknown(field, source, trace.toVector) + Result(Bottom, error :: Nil) case _: Fun => ??? @@ -244,14 +243,14 @@ class Objects { Result(value2, errors) } - def call(meth: Symbol, superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = + def call(meth: Symbol, args: List[Value], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = value match { - case Hot => - Result(Hot, Errors.empty) + case Bottom => + Result(Bottom, Errors.empty) case Cold => val error = CallCold(meth, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Bottom, error :: Nil) case addr: Addr => val target = @@ -261,20 +260,21 @@ class Objects { resolveSuper(addr.klass, superType, meth) else resolve(addr.klass, meth) - if target.isOneOf(Flags.Method | Flags.Lazy) then + if target.isOneOf(Flags.Method) then if target.hasSource then val cls = target.owner.enclosingClass.asClass + val ddef = target.defTree.asInstanceOf[DefDef] + given Env = Env(ddef, args) if target.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, addr, cls, cacheResult = true)(using ctx, trace.add(tpl), promoted) + eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(tpl)) else - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, addr, cls, cacheResult = true) + eval(ddef.rhs, addr, cls, cacheResult = true) else if addr.canIgnoreMethodCall(target) then - Result(Hot, Nil) + Result(Bottom, Nil) else val error = CallUnknown(target, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Bottom, error :: Nil) else val obj = heap(addr) if obj.fields.contains(target) then @@ -288,40 +288,47 @@ class Objects { else eval(body, thisV, klass, cacheResult = true) case RefSet(refs) => - val resList = refs.map(_.call(meth, superType, source)) + val resList = refs.map(_.call(meth, args, superType, source)) val value2 = resList.map(_.value).join val errors = resList.flatMap(_.errors) Result(value2, errors) } /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ - def instantiate(klass: ClassSymbol, ctor: Symbol, source: Tree): Contextual[Result] = + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value match { - case Hot => - Result(Hot, Errors.empty) + case Bottom => + Result(Bottom, Errors.empty) case Cold => val error = CallCold(ctor, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Bottom, error :: Nil) case addr: Addr => - // widen the outer to finitize addresses - val outer = addr match - case Warm(_, _: Warm) => Cold - case _ => addr - - val value = Warm(klass, outer) - if !heap.contains(value) then - val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) - heap.update(value, obj) - val res = value.call(ctor, superType = NoType, source) - Result(value, res.errors) + // widen the outer and args to finitize addresses + extension (value: Value) + def widen: Value = value match + case RefSet(refs) => refs.map(_.widen).join + case Instance(_, outer, _, args) => + val nested = (outer :: args).exists(_.isInstanceOf[Instance]) + if nested then Cold + else value + case _ => value + + val outerWidened = value.widen + val argsWidened = args.map(_.widen) + val inst = Instance(klass, outerWidened, ctor, argsWidened) + if !heap.contains(inst) then + val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outerWidened)) + heap.update(inst, obj) + val res = inst.call(ctor, argsWidened, superType = NoType, source) + Result(inst, res.errors) case Fun(body, thisV, klass) => ??? // impossible case RefSet(refs) => - val resList = refs.map(_.instantiate(klass, ctor, source)) + val resList = refs.map(_.instantiate(klass, ctor, args, source)) val value2 = resList.map(_.value).join val errors = resList.flatMap(_.errors) Result(value2, errors) From c2ce64e27dfe8d86421e3b451f6c04f8e89ec002 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 27 May 2021 11:21:31 +0200 Subject: [PATCH 05/33] Fix semantics --- .../tools/dotc/transform/init/Objects.scala | 118 +++++++++++------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 2fef6357d46d..c3e86ed3254b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -53,7 +53,7 @@ class Objects { case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr /** A function value */ - case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value + case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol, env: Env) extends Value /** A value which represents a set of addresses * @@ -86,6 +86,20 @@ class Objects { extension (env: Env) def lookup(sym: Symbol): Value = env(sym) + + /** Widen the environment for closures */ + def widenForClosure: Env = + env.map { (k, v) => + k -> + v.match + case RefSet(refs) => refs.map(_.widen).join + case Instance(_, outer, _, args) => + val nested = (outer :: args).exists(_.isInstanceOf[Instance]) + if nested then Cold + else value + case _: Fun => Cold + case _ => value + } } type Env = Env.Env @@ -162,6 +176,8 @@ class Objects { /** The state that threads through the interpreter */ type Contextual[T] = (Env, Context, Trace) ?=> T + inline def use[T, R](v: T)(inline op: T ?=> R): R = op(using v) + // ----- Error Handling ----------------------------------- object Trace { @@ -313,6 +329,7 @@ class Objects { val nested = (outer :: args).exists(_.isInstanceOf[Instance]) if nested then Cold else value + case _: Fun => Cold case _ => value val outerWidened = value.widen @@ -372,7 +389,7 @@ class Objects { // 1. the result is decided by `cfg` for a legal program // (heap change is irrelevant thanks to monotonicity) // 2. errors will have been reported for an illegal program - innerMap(expr) = Hot + innerMap(expr) = Bottom val res = cases(expr, thisV, klass) if cacheResult then innerMap(expr) = res.value else innerMap.remove(expr) res @@ -384,18 +401,15 @@ class Objects { exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ - def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = - val ress = args.map { arg => + def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + args.map { arg => val res = if arg.isByName then val fun = Fun(arg.tree, thisV, klass) Result(fun, Nil) else eval(arg.tree, thisV, klass) - - res.ensureHot("May only use initialized value as arguments", arg.tree) } - ress.flatMap(_.errors) /** Handles the evaluation of different expressions * @@ -405,7 +419,7 @@ class Objects { expr match { case Ident(nme.WILDCARD) => // TODO: disallow `var x: T = _` - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case id @ Ident(name) if !id.symbol.is(Flags.Method) => assert(name.isTermName, "type trees should not reach here") @@ -413,19 +427,21 @@ class Objects { case NewExpr(tref, New(tpt), ctor, argss) => // check args - val errors = evalArgs(argss.flatten, thisV, klass) + val resArgs = evalArgs(argss.flatten, thisV, klass) + val argsValues = resArgs.map(_.value) + val argsErrors = resArgs.flatMap(_.errors) val cls = tref.classSymbol.asClass val res = outerValue(tref, thisV, klass, tpt) - val trace2 = trace.add(expr) - locally { - given Trace = trace2 - (res ++ errors).instantiate(cls, ctor, expr) + use(trace.add(expr)) { + (res ++ argsErrors).instantiate(cls, ctor, argsValues, expr) } case Call(ref, argss) => // check args - val errors = evalArgs(argss.flatten, thisV, klass) + val resArgs = evalArgs(argss.flatten, thisV, klass) + val argsValues = resArgs.map(_.value) + val argsErrors = resArgs.flatMap(_.errors) val trace2: Trace = trace.add(expr) @@ -433,11 +449,15 @@ class Objects { case Select(supert: Super, _) => val SuperType(thisTp, superTp) = supert.tpe val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) - Result(thisValue2, errors).call(ref.symbol, superTp, expr)(using ctx, trace2) + use(trace2) { + Result(thisValue2, argsErrors).call(ref.symbol, argsValues, superTp, expr) + } case Select(qual, _) => - val res = eval(qual, thisV, klass) ++ errors - res.call(ref.symbol, superType = NoType, source = expr)(using ctx, trace2) + val res = eval(qual, thisV, klass) ++ argsErrors + use(trace2) { + res.call(ref.symbol, argsValues, superType = NoType, source = expr) + } case id: Ident => id.tpe match @@ -446,10 +466,12 @@ class Objects { val enclosingClass = id.symbol.owner.enclosingClass.asClass val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) // local methods are not a member, but we can reuse the method `call` - thisValue2.call(id.symbol, superType = NoType, expr, needResolve = false) + Result(thisValue2, argsErrors).call(id.symbol, argsValues, superType = NoType, expr, needResolve = false) case TermRef(prefix, _) => - val res = cases(prefix, thisV, klass, id) ++ errors - res.call(id.symbol, superType = NoType, source = expr)(using ctx, trace2) + val res = cases(prefix, thisV, klass, id) ++ argsErrors + use(trace2) { + res.call(id.symbol, argsValues, superType = NoType, source = expr) + } case Select(qualifier, name) => eval(qualifier, thisV, klass).select(expr.symbol, expr) @@ -458,10 +480,10 @@ class Objects { cases(expr.tpe, thisV, klass, expr) case Literal(_) => - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case Typed(expr, tpt) => - if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Hot, Errors.empty) + if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Bottom, Errors.empty) else eval(expr, thisV, klass) case NamedArg(name, arg) => @@ -471,16 +493,16 @@ class Objects { lhs match case Select(qual, _) => val res = eval(qual, thisV, klass) - eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) ++ res.errors + eval(rhs, thisV, klass) ++ res.errors case id: Ident => - eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) + eval(rhs, thisV, klass) case closureDef(ddef) => - val value = Fun(ddef.rhs, thisV, klass) + val value = Fun(ddef.rhs, thisV, klass, env.widenForClosure) Result(value, Nil) case PolyFun(body) => - val value = Fun(body, thisV, klass) + val value = Fun(body, thisV, klass, env.widenForClosure) Result(value, Nil) case Block(stats, expr) => @@ -494,7 +516,7 @@ class Objects { Result(value, errors) case Annotated(arg, annot) => - if (expr.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Hot, Errors.empty) + if (expr.tpe.hasAnnotation(defn.UncheckedAnnot)) Result(Bottom, Errors.empty) else eval(arg, thisV, klass) case Match(selector, cases) => @@ -509,7 +531,7 @@ class Objects { case WhileDo(cond, body) => val ress = eval(cond :: body :: Nil, thisV, klass) - Result(Hot, ress.flatMap(_.errors)) + Result(Bottom, ress.flatMap(_.errors)) case Labeled(_, expr) => eval(expr, thisV, klass) @@ -529,7 +551,7 @@ class Objects { val ress = elems.map { elem => eval(elem, thisV, klass).ensureHot("May only use initialized value as method arguments", elem) } - Result(Hot, ress.flatMap(_.errors)) + Result(Bottom, ress.flatMap(_.errors)) case Inlined(call, bindings, expansion) => val ress = eval(bindings, thisV, klass) @@ -537,26 +559,25 @@ class Objects { case Thicket(List()) => // possible in try/catch/finally, see tests/crash/i6914.scala - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) 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 - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case tdef: TypeDef => // local type definition - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case tpl: Template => init(tpl, thisV, klass) case _: Import | _: Export => - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case _ => throw new Exception("unexpected tree: " + expr.show) @@ -566,23 +587,31 @@ class Objects { def cases(tp: Type, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { tp match { case _: ConstantType => - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case tmref: TermRef if tmref.prefix == NoPrefix => - Result(Hot, Errors.empty) + // - only var definitions are cold, it means they are not inspected + // - look up parameters from environment + // - evaluate the rhs of the local definition for val definitions: they are already cached + if tmref.is(Flags.Param) then env.lookup(tmref.symbol) + else if tmref.is(Flags.Mutable) then Cold + else { + val rhs = tmref.symbol.defTree.asInstanceOf[ValDef].rhs + eval(rhs, thisV, klass, cacheResult = true) + } case tmref: TermRef => cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) case tp @ ThisType(tref) => - if tref.symbol.is(Flags.Package) then Result(Hot, Errors.empty) + if tref.symbol.is(Flags.Package) then Result(Bottom, Errors.empty) else val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) Result(value, Errors.empty) case _: TermParamRef | _: RecThis => // possible from checking effects of types - Result(Hot, Errors.empty) + Result(Bottom, Errors.empty) case _ => throw new Exception("unexpected type: " + tp) @@ -595,9 +624,9 @@ class Objects { else if target.is(Flags.Package) || target.isStaticOwner then Hot else thisV match - case Hot | _: ThisRef => Hot - case warm: Warm => - val obj = heap(warm) + case Bottom => Bottom + case addr: Addr => + val obj = heap(addr) val outerCls = klass.owner.enclosingClass.asClass resolveThis(target, obj.outers(klass), outerCls, source) case RefSet(refs) => @@ -617,7 +646,7 @@ class Objects { val outerV = resolveThis(enclosing, thisV, klass, source) Result(outerV, Errors.empty) else - if cls.isAllOf(Flags.JavaInterface) then Result(Hot, Nil) + if cls.isAllOf(Flags.JavaInterface) then Result(Bottom, Nil) else cases(tref.prefix, thisV, klass, source) /** Initialize part of an abstract object in `klass` of the inheritance chain */ @@ -693,7 +722,8 @@ class Objects { case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) => val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors - thisV.updateField(vdef.symbol, res.value) + val fieldV = if vdef.symbol.is(Flags.Mutable) then Cold else res.value + thisV.updateField(vdef.symbol, fieldV) case _: MemberDef => From 74d80fa177879738180b84b09d45fc1a8be84814 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 10:36:24 +0200 Subject: [PATCH 06/33] Handle constructor parameters --- .../tools/dotc/transform/init/Objects.scala | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index c3e86ed3254b..89ec23545221 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -80,7 +80,7 @@ class Objects { def apply(bindings: Map[Symbol, Value]): Env = bindings def apply(ddef: DefDef, args: List[Value])(using Context): Env = - val params = ddef.paramss.collect(l => ValDefs.unapply(l)).asInstanceOf[List[ValDef]].map(_.symbol) + val params = ddef.termParamss.flatten.map(_.symbol) assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size) params.zip(args).toMap @@ -89,17 +89,16 @@ class Objects { /** Widen the environment for closures */ def widenForClosure: Env = - env.map { (k, v) => - k -> - v.match - case RefSet(refs) => refs.map(_.widen).join + def widen(v: Value) = v.match + case RefSet(refs) => refs.map(widen(_)).join case Instance(_, outer, _, args) => val nested = (outer :: args).exists(_.isInstanceOf[Instance]) if nested then Cold else value case _: Fun => Cold case _ => value - } + + env.map { (k, v) => k -> widen(v) } } type Env = Env.Env @@ -653,15 +652,15 @@ class Objects { def init(tpl: Template, thisV: Addr, klass: ClassSymbol): Contextual[Result] = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) { val errorBuffer = new mutable.ArrayBuffer[Error] + val paramsMap = tpl.constr.termParamss.flatten.map(vdef => vdef.name -> vdef.symbol).toMap + // init param fields - klass.paramAccessors.foreach { acc => - if (!acc.is(Flags.Method)) { - printer.println(acc.show + " initialized") - thisV.updateField(acc, Hot) - } + klass.paramGetters.foreach { acc => + printer.println(acc.show + " initialized") + thisV.updateField(acc, env.lookup(paramsMap(acc.name.toTermName))) } - def superCall(tref: TypeRef, ctor: Symbol, source: Tree): Unit = + def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) @@ -670,23 +669,33 @@ class Objects { // follow constructor if cls.hasSource then - val res2 = thisV.call(ctor, superType = NoType, source)(using ctx, trace.add(source)) - errorBuffer ++= res2.errors + use(trace.add(source)) { + val res2 = thisV.call(ctor, args, superType = NoType, source) + errorBuffer ++= res2.errors + } // parents def initParent(parent: Tree) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } - errorBuffer ++= evalArgs(argss.flatten, thisV, klass) - superCall(tref, ctor, tree) + val resArgs = evalArgs(argss.flatten, thisV, klass) + val argsValues = resArgs.map(_.value) + val argsErrors = resArgs.flatMap(_.errors) + + errorBuffer ++= argsErrors + superCall(tref, ctor, argsValues, tree) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) - errorBuffer ++= evalArgs(argss.flatten, thisV, klass) - superCall(tref, ctor, tree) + val resArgs = evalArgs(argss.flatten, thisV, klass) + val argsValues = resArgs.map(_.value) + val argsErrors = resArgs.flatMap(_.errors) + + errorBuffer ++= argsErrors + superCall(tref, ctor, argsValues, tree) case _ => // extends A or extends A[T] val tref = typeRefOf(parent.tpe) - superCall(tref, tref.classSymbol.primaryConstructor, parent) + superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent) } // see spec 5.1 about "Template Evaluation". @@ -713,7 +722,7 @@ class Objects { // term arguments to B. That can only be done in a concrete class. val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor) val ctor = tref.classSymbol.primaryConstructor - if ctor.exists then superCall(tref, ctor, superParent) + if ctor.exists then superCall(tref, ctor, Nil, superParent) } From 167e51ffbe692f15b0cda1ab10a0cc4b57990cdb Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 10:52:15 +0200 Subject: [PATCH 07/33] Fix semantic: compiles --- .../tools/dotc/transform/init/Objects.scala | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 89ec23545221..93725b436fa7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -87,16 +87,18 @@ class Objects { extension (env: Env) def lookup(sym: Symbol): Value = env(sym) + def union(other: Env): Env = env ++ other + /** Widen the environment for closures */ def widenForClosure: Env = - def widen(v: Value) = v.match + def widen(v: Value): Value = v.match case RefSet(refs) => refs.map(widen(_)).join case Instance(_, outer, _, args) => val nested = (outer :: args).exists(_.isInstanceOf[Instance]) if nested then Cold - else value + else v case _: Fun => Cold - case _ => value + case _ => v env.map { (k, v) => k -> widen(v) } } @@ -168,7 +170,7 @@ class Objects { def call(meth: Symbol, args: List[Value], superType: Type, source: Tree): Contextual[Result] = value.call(meth, args, superType, source) ++ errors - def instantiate(klass: ClassSymbol, args: List[Value], ctor: Symbol, source: Tree): Contextual[Result] = + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors } @@ -297,10 +299,13 @@ class Objects { else value.select(target, source, needResolve = false) - case Fun(body, thisV, klass) => + case Fun(body, thisV, klass, env2) => // meth == NoSymbol for poly functions if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` - else eval(body, thisV, klass, cacheResult = true) + else + use(env.union(env2)) { + eval(body, thisV, klass, cacheResult = true) + } case RefSet(refs) => val resList = refs.map(_.call(meth, args, superType, source)) @@ -340,7 +345,7 @@ class Objects { val res = inst.call(ctor, argsWidened, superType = NoType, source) Result(inst, res.errors) - case Fun(body, thisV, klass) => + case Fun(body, thisV, klass, _) => ??? // impossible case RefSet(refs) => @@ -402,12 +407,11 @@ class Objects { /** Evaluate arguments of methods */ def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = args.map { arg => - val res = - if arg.isByName then - val fun = Fun(arg.tree, thisV, klass) - Result(fun, Nil) - else - eval(arg.tree, thisV, klass) + if arg.isByName then + val fun = Fun(arg.tree, thisV, klass, env.widenForClosure) + Result(fun, Nil) + else + eval(arg.tree, thisV, klass) } /** Handles the evaluation of different expressions @@ -465,7 +469,7 @@ class Objects { val enclosingClass = id.symbol.owner.enclosingClass.asClass val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) // local methods are not a member, but we can reuse the method `call` - Result(thisValue2, argsErrors).call(id.symbol, argsValues, superType = NoType, expr, needResolve = false) + Result(thisValue2, argsErrors).call(id.symbol, argsValues, superType = NoType, expr) case TermRef(prefix, _) => val res = cases(prefix, thisV, klass, id) ++ argsErrors use(trace2) { @@ -519,14 +523,16 @@ class Objects { else eval(arg, thisV, klass) case Match(selector, cases) => - val res1 = eval(selector, thisV, klass).ensureHot("The value to be matched needs to be fully initialized", selector) + // TODO: handle extractors + val res1 = eval(selector, thisV, klass) val ress = eval(cases.map(_.body), thisV, klass) val value = ress.map(_.value).join val errors = res1.errors ++ ress.flatMap(_.errors) Result(value, errors) case Return(expr, from) => - eval(expr, thisV, klass).ensureHot("return expression may only be initialized value", expr) + // TODO: handle return by writing the interpreter in CPS + eval(expr, thisV, klass) case WhileDo(cond, body) => val ress = eval(cond :: body :: Nil, thisV, klass) @@ -548,9 +554,9 @@ class Objects { case SeqLiteral(elems, elemtpt) => val ress = elems.map { elem => - eval(elem, thisV, klass).ensureHot("May only use initialized value as method arguments", elem) + eval(elem, thisV, klass) } - Result(Bottom, ress.flatMap(_.errors)) + Result(Cold, ress.flatMap(_.errors)) case Inlined(call, bindings, expansion) => val ress = eval(bindings, thisV, klass) @@ -592,10 +598,11 @@ class Objects { // - only var definitions are cold, it means they are not inspected // - look up parameters from environment // - evaluate the rhs of the local definition for val definitions: they are already cached - if tmref.is(Flags.Param) then env.lookup(tmref.symbol) - else if tmref.is(Flags.Mutable) then Cold + val sym = tmref.symbol + if sym.is(Flags.Param) then Result(env.lookup(sym), Nil) + else if sym.is(Flags.Mutable) then Result(Cold, Nil) else { - val rhs = tmref.symbol.defTree.asInstanceOf[ValDef].rhs + val rhs = sym.defTree.asInstanceOf[ValDef].rhs eval(rhs, thisV, klass, cacheResult = true) } @@ -620,7 +627,7 @@ class Objects { /** Resolve C.this that appear in `klass` */ def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Value] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Value].show) { if target == klass then thisV - else if target.is(Flags.Package) || target.isStaticOwner then Hot + else if target.is(Flags.Package) || target.isStaticOwner then Bottom else thisV match case Bottom => Bottom From 4607c91223dc6872b7eb6a2899b722b8c7dbdfa3 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 11:59:47 +0200 Subject: [PATCH 08/33] Handle global objects in semantics --- .../tools/dotc/transform/init/Objects.scala | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 93725b436fa7..46122569b4e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -356,7 +356,7 @@ class Objects { } end extension -// ----- Policies ------------------------------------------------------ +// ----- Policies ------------------------------------------ extension (value: Addr) /** Can the method call on `value` be ignored? * @@ -368,7 +368,7 @@ class Objects { cls == defn.AnyValClass || cls == defn.ObjectClass -// ----- Semantic definition -------------------------------- +// ----- Semantic definition ------------------------------- /** Evaluate an expression with the given value for `this` in a given class `klass` * @@ -607,10 +607,17 @@ class Objects { } case tmref: TermRef => - cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) + val sym = tmref.symbol + if sym.isStaticObjectRef then + Result(ObjectRef(sym.moduleClass.asClass), Nil) + else + cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) case tp @ ThisType(tref) => - if tref.symbol.is(Flags.Package) then Result(Bottom, Errors.empty) + val sym = tref.symbol + if sym.is(Flags.Package) then Result(Bottom, Errors.empty) + else if sym.isStaticObjectRef && sym != klass then + Result(ObjectRef(sym.moduleClass.asClass), Nil) else val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) Result(value, Errors.empty) @@ -749,4 +756,10 @@ class Objects { Result(thisV, errorBuffer.toList) } + +// ----- Utility methods ------------------------------------ + + extension (sym: Symbol) + def isStaticObjectRef(using Context) = + sym.isTerm && !sym.is(Flags.Package) && sym.is(Flags.Module) && sym.isStatic } From 6883b5541e11d9f0515dfa44a6d9f48780b01ec8 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 12:49:25 +0200 Subject: [PATCH 09/33] Check cycles --- .../tools/dotc/transform/init/Errors.scala | 17 ++++++++ .../tools/dotc/transform/init/Objects.scala | 42 +++++++++++++++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index 57f860561d4a..5e7f50716f67 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -105,4 +105,21 @@ object Errors { }.mkString) } } + + case class CyclicObjectInit(objs: Seq[Symbol], trace: Seq[Tree]) extends Error { + def source: Tree = trace.last + def show(using Context): String = + "Cyclic object initialization for " + objs.map(_.show).mkString(", ") + "." + + override def issue(using Context): Unit = + report.warning(show + stacktrace, objs.head.srcPos) + } + + case class ObjectLeakDuringInit(obj: Symbol, trace: Seq[Tree]) extends Error { + def source: Tree = trace.last + def show(using Context): String = obj.show + " leaked during its initialization " + "." + + override def issue(using Context): Unit = + report.warning(show + stacktrace, obj.srcPos) + } } \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 46122569b4e7..c40700a4ab53 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -175,7 +175,7 @@ class Objects { } /** The state that threads through the interpreter */ - type Contextual[T] = (Env, Context, Trace) ?=> T + type Contextual[T] = (Env, Context, Trace, Path) ?=> T inline def use[T, R](v: T)(inline op: T ?=> R): R = op(using v) @@ -196,6 +196,21 @@ class Objects { import Trace._ def trace(using t: Trace): Trace = t + object Path { + opaque type Path = Vector[ClassSymbol] + + val empty: Path = Vector.empty + + extension (path: Path) + def add(node: ClassSymbol): Path = path :+ node + def from(node: ClassSymbol): Vector[ClassSymbol] = path.dropWhile(_ != node) + } + + type Path = Path.Path + + import Path._ + def path(using p: Path): Path = p + // ----- Operations on domains ----------------------------- extension (a: Value) def join(b: Value): Value = @@ -317,14 +332,11 @@ class Objects { /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value match { - case Bottom => - Result(Bottom, Errors.empty) - case Cold => val error = CallCold(ctor, source, trace.toVector) Result(Bottom, error :: Nil) - case addr: Addr => + case _: Addr | Bottom => // widen the outer and args to finitize addresses extension (value: Value) def widen: Value = value match @@ -356,6 +368,22 @@ class Objects { } end extension + extension (obj: ObjectRef) + def access(source: Tree): Contextual[Result] = + val cycle = path.from(obj.klass) + if cycle.nonEmpty then + var trace1 = trace.toVector :+ source + val warning = + if cycle.size > 1 then + CyclicObjectInit(cycle, trace1) + else + ObjectLeakDuringInit(obj.klass, trace1) + Result(obj, warning :: Nil) + else + given Path = path.add(obj.klass) + given Trace = trace.add(source) + Bottom.instantiate(obj.klass, obj.klass.primaryConstructor, Nil, source) + // ----- Policies ------------------------------------------ extension (value: Addr) /** Can the method call on `value` be ignored? @@ -609,7 +637,7 @@ class Objects { case tmref: TermRef => val sym = tmref.symbol if sym.isStaticObjectRef then - Result(ObjectRef(sym.moduleClass.asClass), Nil) + ObjectRef(sym.moduleClass.asClass).access(source) else cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -617,7 +645,7 @@ class Objects { val sym = tref.symbol if sym.is(Flags.Package) then Result(Bottom, Errors.empty) else if sym.isStaticObjectRef && sym != klass then - Result(ObjectRef(sym.moduleClass.asClass), Nil) + ObjectRef(sym.moduleClass.asClass).access(source) else val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) Result(value, Errors.empty) From c780ea7c1bede1a896553815fb320e5133ccb29b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 12:58:54 +0200 Subject: [PATCH 10/33] Enable checking of objects --- .../tools/dotc/transform/init/Checker.scala | 4 ++++ .../tools/dotc/transform/init/Objects.scala | 21 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 458a55199cb8..7abc11b0d4e3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -24,6 +24,7 @@ class Checker extends MiniPhase { val phaseName = "initChecker" private val semantic = new Semantic + private val objects = new Objects override val runsAfter = Set(Pickler.name) @@ -55,6 +56,9 @@ class Checker extends MiniPhase { heap.update(thisRef, obj) val res = eval(tpl, thisRef, cls) res.errors.foreach(_.issue) + + if objects.isStaticObjectRef(cls) then + objects.check(cls, tpl) } tree diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index c40700a4ab53..e699162db725 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -77,6 +77,9 @@ class Objects { /** The environment for method parameters */ object Env { opaque type Env = Map[Symbol, Value] + + val empty: Env = Map.empty + def apply(bindings: Map[Symbol, Value]): Env = bindings def apply(ddef: DefDef, args: List[Value])(using Context): Env = @@ -395,6 +398,22 @@ class Objects { cls == defn.AnyClass || cls == defn.AnyValClass || cls == defn.ObjectClass + end extension + + /** Check a static objet + * + * @param cls the module class of the static object + */ + def check(cls: ClassSymbol, tpl: Template)(using Context): Unit = { + val objRef = ObjectRef(cls) + val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map.empty) + given Path = Path.empty.add(cls) + given Trace = Trace.empty + given Env = Env.empty + heap.update(objRef, obj) + val res = eval(tpl, objRef, cls) + res.errors.foreach(_.issue) + } // ----- Semantic definition ------------------------------- @@ -789,5 +808,5 @@ class Objects { extension (sym: Symbol) def isStaticObjectRef(using Context) = - sym.isTerm && !sym.is(Flags.Package) && sym.is(Flags.Module) && sym.isStatic + !sym.is(Flags.Package) && sym.is(Flags.Module) && sym.isStatic } From d2cd2c22444c99a7dd5a62b7e89c0a94cb22a9d9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 13:02:30 +0200 Subject: [PATCH 11/33] Avoid cycles for givens --- .../src/dotty/tools/dotc/transform/init/Objects.scala | 8 +++++--- tests/init/neg/t9261.scala | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 tests/init/neg/t9261.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index e699162db725..136f309bacae 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -383,9 +383,11 @@ class Objects { ObjectLeakDuringInit(obj.klass, trace1) Result(obj, warning :: Nil) else - given Path = path.add(obj.klass) - given Trace = trace.add(source) - Bottom.instantiate(obj.klass, obj.klass.primaryConstructor, Nil, source) + use(path.add(obj.klass)) { + use(trace.add(source)) { + Bottom.instantiate(obj.klass, obj.klass.primaryConstructor, Nil, source) + } + } // ----- Policies ------------------------------------------ extension (value: Addr) diff --git a/tests/init/neg/t9261.scala b/tests/init/neg/t9261.scala new file mode 100644 index 000000000000..1e23bedb9b6a --- /dev/null +++ b/tests/init/neg/t9261.scala @@ -0,0 +1,3 @@ +sealed abstract class OrderType(val reverse: OrderType) +case object Buy extends OrderType(Sell) // error +case object Sell extends OrderType(Buy) From f8aa860df920ea35dfcf6fe24c104f97f1002fb9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 14:02:52 +0200 Subject: [PATCH 12/33] First test works --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 3 ++- tests/init/neg/t9261.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 136f309bacae..b3afb6376c06 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -161,7 +161,7 @@ class Objects { /** Result of abstract interpretation */ case class Result(value: Value, errors: Seq[Error]) { - def show(using Context) = value.show + ", errors = " + errors.map(_.toString) + def show(using Context) = value.show + ", errors = " + errors.map(_.getClass.getName) def ++(errors: Seq[Error]): Result = this.copy(errors = this.errors ++ errors) @@ -650,6 +650,7 @@ class Objects { val sym = tmref.symbol if sym.is(Flags.Param) then Result(env.lookup(sym), Nil) else if sym.is(Flags.Mutable) then Result(Cold, Nil) + else if sym.is(Flags.Package) then Result(Bottom, Nil) else { val rhs = sym.defTree.asInstanceOf[ValDef].rhs eval(rhs, thisV, klass, cacheResult = true) diff --git a/tests/init/neg/t9261.scala b/tests/init/neg/t9261.scala index 1e23bedb9b6a..378dad51b3a6 100644 --- a/tests/init/neg/t9261.scala +++ b/tests/init/neg/t9261.scala @@ -1,3 +1,3 @@ sealed abstract class OrderType(val reverse: OrderType) case object Buy extends OrderType(Sell) // error -case object Sell extends OrderType(Buy) +case object Sell extends OrderType(Buy) // error From 6e4327bafd380232eb8e6564b43156530c60c1c7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 14:20:49 +0200 Subject: [PATCH 13/33] Ensure object exists in heap --- .../tools/dotc/transform/init/Objects.scala | 14 +++++++---- tests/init/neg/t9360.scala | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/init/neg/t9360.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index b3afb6376c06..9362695162a5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -353,12 +353,16 @@ class Objects { val outerWidened = value.widen val argsWidened = args.map(_.widen) - val inst = Instance(klass, outerWidened, ctor, argsWidened) - if !heap.contains(inst) then + val addr = + if klass.isStaticObjectRef then + ObjectRef(klass) + else + Instance(klass, outerWidened, ctor, argsWidened) + if !heap.contains(addr) then val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outerWidened)) - heap.update(inst, obj) - val res = inst.call(ctor, argsWidened, superType = NoType, source) - Result(inst, res.errors) + heap.update(addr, obj) + val res = addr.call(ctor, argsWidened, superType = NoType, source) + Result(addr, res.errors) case Fun(body, thisV, klass, _) => ??? // impossible diff --git a/tests/init/neg/t9360.scala b/tests/init/neg/t9360.scala new file mode 100644 index 000000000000..71f0af4c0fa1 --- /dev/null +++ b/tests/init/neg/t9360.scala @@ -0,0 +1,25 @@ +class BaseClass(s: String) { + def print: Unit = () +} + +object Obj { // error + val s: String = "hello" + + object AObj extends BaseClass(s) + + object BObj extends BaseClass(s) + + val list = List(AObj, BObj) + + def print = { + println(list) + } +} + +object ObjectInit { + def main(args: Array[String]) = { + Obj.AObj.print + Obj.BObj.print + Obj.print + } +} From 35d2d45625044343117d2980b34c929a8a078c66 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 14:32:38 +0200 Subject: [PATCH 14/33] Handle fake Java objects --- .../tools/dotc/transform/init/Objects.scala | 3 +++ tests/init/neg/t9312.scala | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/init/neg/t9312.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 9362695162a5..3e42735fa13a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -386,6 +386,9 @@ class Objects { else ObjectLeakDuringInit(obj.klass, trace1) Result(obj, warning :: Nil) + else if obj.klass.is(Flags.JavaDefined) then + // Errors will be reported for method calls on it + Result(Bottom, Nil) else use(path.add(obj.klass)) { use(trace.add(source)) { diff --git a/tests/init/neg/t9312.scala b/tests/init/neg/t9312.scala new file mode 100644 index 000000000000..d4eed8d9e137 --- /dev/null +++ b/tests/init/neg/t9312.scala @@ -0,0 +1,23 @@ +object DeadLockTest { + def main(args: Array[String]): Unit = { + def run(block: => Unit): Unit = + new Thread(new Runnable {def run(): Unit = block}).start() + + run {println(Parent.Child1)} + run {println(Parent.Child2)} + + } + + object Parent { // error + trait Child { + Thread.sleep(2000) // ensure concurrent behavior + val parent = Parent + def siblings = parent.children - this + } + + object Child1 extends Child + object Child2 extends Child + + final val children = Set(Child1, Child2) + } +} From e6172a2622ca4cffc107273b9cab0343f7997454 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 14:36:26 +0200 Subject: [PATCH 15/33] Add tests --- tests/init/full/neg/global-cycle3.scala | 7 ------- tests/init/full/neg/global-cycle4.scala | 19 ----------------- tests/init/neg/global-cycle1.scala | 10 +++++++++ tests/init/neg/global-cycle10.scala | 17 +++++++++++++++ tests/init/neg/global-cycle11.scala | 22 +++++++++++++++++++ tests/init/neg/global-cycle12.scala | 24 +++++++++++++++++++++ tests/init/neg/global-cycle14.scala | 14 +++++++++++++ tests/init/neg/global-cycle6.scala | 25 ++++++++++++++++++++++ tests/init/neg/global-cycle7.scala | 18 ++++++++++++++++ tests/init/neg/global-cycle8.scala | 21 +++++++++++++++++++ tests/init/neg/global-cycle9.scala | 28 +++++++++++++++++++++++++ tests/init/{full => }/neg/i9176.scala | 2 +- tests/init/neg/t5366.scala | 15 +++++++++++++ tests/init/neg/t9115.scala | 8 +++++++ tests/init/neg/t9261.scala | 2 +- tests/init/neg/t9312.scala | 2 +- tests/init/neg/t9360.scala | 2 +- 17 files changed, 206 insertions(+), 30 deletions(-) delete mode 100644 tests/init/full/neg/global-cycle3.scala delete mode 100644 tests/init/full/neg/global-cycle4.scala create mode 100644 tests/init/neg/global-cycle1.scala create mode 100644 tests/init/neg/global-cycle10.scala create mode 100644 tests/init/neg/global-cycle11.scala create mode 100644 tests/init/neg/global-cycle12.scala create mode 100644 tests/init/neg/global-cycle14.scala create mode 100644 tests/init/neg/global-cycle6.scala create mode 100644 tests/init/neg/global-cycle7.scala create mode 100644 tests/init/neg/global-cycle8.scala create mode 100644 tests/init/neg/global-cycle9.scala rename tests/init/{full => }/neg/i9176.scala (82%) create mode 100644 tests/init/neg/t5366.scala create mode 100644 tests/init/neg/t9115.scala diff --git a/tests/init/full/neg/global-cycle3.scala b/tests/init/full/neg/global-cycle3.scala deleted file mode 100644 index 7fae20dbe894..000000000000 --- a/tests/init/full/neg/global-cycle3.scala +++ /dev/null @@ -1,7 +0,0 @@ -class A(x: Int) { - def foo(): Int = B.a + 10 -} - -object B { - val a: Int = A(4).foo() // error -} diff --git a/tests/init/full/neg/global-cycle4.scala b/tests/init/full/neg/global-cycle4.scala deleted file mode 100644 index 3de0533cb521..000000000000 --- a/tests/init/full/neg/global-cycle4.scala +++ /dev/null @@ -1,19 +0,0 @@ -trait A { - def foo(): Int -} - -class B extends A { - def foo(): Int = 10 -} - -class C extends A { - def foo(): Int = O.a + 10 -} - -class D(x: Int) { - def bar(): A = if x > 0 then new B else new C -} - -object O { - val a: Int = D(5).bar().foo() // error -} diff --git a/tests/init/neg/global-cycle1.scala b/tests/init/neg/global-cycle1.scala new file mode 100644 index 000000000000..35368191d9d0 --- /dev/null +++ b/tests/init/neg/global-cycle1.scala @@ -0,0 +1,10 @@ +object A { // error + val a: Int = B.b +} + +object B { + val b: Int = A.a +} + +@main +def Test = print(A.a) \ No newline at end of file diff --git a/tests/init/neg/global-cycle10.scala b/tests/init/neg/global-cycle10.scala new file mode 100644 index 000000000000..5ca76d1e70fd --- /dev/null +++ b/tests/init/neg/global-cycle10.scala @@ -0,0 +1,17 @@ +abstract class Base { + def foo(): Unit + foo() +} + +object O extends Base { // error + val msg: String = "hello" + + class Inner { + println(msg) + } + + def foo() = new Inner +} + +@main +def Test = O \ No newline at end of file diff --git a/tests/init/neg/global-cycle11.scala b/tests/init/neg/global-cycle11.scala new file mode 100644 index 000000000000..303dcdb9c8f7 --- /dev/null +++ b/tests/init/neg/global-cycle11.scala @@ -0,0 +1,22 @@ +import scala.collection.mutable + +object NameKinds { + private val qualifiedNameKinds = mutable.HashMap[Int, QualifiedNameKind]() + + val QualifiedName: QualifiedNameKind = new QualifiedNameKind(2, ".") + + abstract class NameKind(val tag: Int) + + class QualifiedNameKind(tag: Int, val separator: String) + extends NameKind(tag) { + qualifiedNameKinds(tag) = this + } +} + +object A { // error + val n: Int = B.m +} + +object B { + val m: Int = A.n +} diff --git a/tests/init/neg/global-cycle12.scala b/tests/init/neg/global-cycle12.scala new file mode 100644 index 000000000000..fc7558c02b69 --- /dev/null +++ b/tests/init/neg/global-cycle12.scala @@ -0,0 +1,24 @@ +// from Scala.js +object Names { + private final val ConstructorSimpleEncodedName: String = + "" + + final class SimpleMethodName(encoded: String) + + object SimpleMethodName { + def apply(name: String): SimpleMethodName = + val res = name == ConstructorSimpleEncodedName + new SimpleMethodName(name) + } + + val ConstructorSimpleName: SimpleMethodName = + SimpleMethodName(ConstructorSimpleEncodedName) +} + +object A { // error + val n: Int = B.m +} + +object B { + val m: Int = A.n +} diff --git a/tests/init/neg/global-cycle14.scala b/tests/init/neg/global-cycle14.scala new file mode 100644 index 000000000000..ab416424478b --- /dev/null +++ b/tests/init/neg/global-cycle14.scala @@ -0,0 +1,14 @@ +object O { + case class Data(x: Int) extends (Int => Int) { + def apply(x: Int) = x * x + } + val d = Data(3) +} + +object A { // error + val n: Int = B.m +} + +object B { + val m: Int = A.n +} diff --git a/tests/init/neg/global-cycle6.scala b/tests/init/neg/global-cycle6.scala new file mode 100644 index 000000000000..2d4f23c25187 --- /dev/null +++ b/tests/init/neg/global-cycle6.scala @@ -0,0 +1,25 @@ +object A { // error + val n: Int = B.m + class Inner { + println(n) + } +} + +object B { + val a = new A.Inner + val m: Int = 10 +} + +object O { + object A { + val n: Int = B.m + class Inner { + val x: Int = 4 + } + } + + object B { + val a = new A.Inner + val m: Int = 10 + } +} \ No newline at end of file diff --git a/tests/init/neg/global-cycle7.scala b/tests/init/neg/global-cycle7.scala new file mode 100644 index 000000000000..32fdc4eb7b10 --- /dev/null +++ b/tests/init/neg/global-cycle7.scala @@ -0,0 +1,18 @@ +object A { // error + val n: Int = B.m +} + +object B { + val m: Int = A.n +} + +abstract class TokensCommon { + def maxToken: Int + + val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) +} + +object JavaTokens extends TokensCommon { + final def maxToken: Int = DOUBLE + final val DOUBLE = 188 // error: but constant folding avoided the issue +} \ No newline at end of file diff --git a/tests/init/neg/global-cycle8.scala b/tests/init/neg/global-cycle8.scala new file mode 100644 index 000000000000..f1b258e7c3ce --- /dev/null +++ b/tests/init/neg/global-cycle8.scala @@ -0,0 +1,21 @@ +class A { + def foo() = println(O.n) +} + +class B { + val a = new A +} + +object O { // error + val n: Int = 10 + println(P.m) +} + +object P { + val m = Q.bar(new B) +} + +object Q { + def bar(b: B) = b.a.foo() +} + diff --git a/tests/init/neg/global-cycle9.scala b/tests/init/neg/global-cycle9.scala new file mode 100644 index 000000000000..c2d047b97022 --- /dev/null +++ b/tests/init/neg/global-cycle9.scala @@ -0,0 +1,28 @@ +object Names { + abstract class Name + + abstract class TermName extends Name: + def toTypeName: TypeName = ??? + + final class SimpleName(val start: Int, val length: Int) extends TermName + final class TypeName(val toTermName: TermName) extends Name + + class NameTable: + def add(index: Int, name: Name): Unit = () + add(0, EmptyTermName) + + var chrs: Array[Char] = new Array[Char](1024) + + val EmptyTermName: SimpleName = SimpleName(-1, 0) + val EmptyTypeName: TypeName = EmptyTermName.toTypeName + + val nameTable = NameTable() +} + +object A { // error + val n: Int = B.m +} + +object B { + val m: Int = A.n +} diff --git a/tests/init/full/neg/i9176.scala b/tests/init/neg/i9176.scala similarity index 82% rename from tests/init/full/neg/i9176.scala rename to tests/init/neg/i9176.scala index abb8a6394dd2..74c762dcb32f 100644 --- a/tests/init/full/neg/i9176.scala +++ b/tests/init/neg/i9176.scala @@ -1,6 +1,6 @@ class Foo(val opposite: Foo) case object A extends Foo(B) // error -case object B extends Foo(A) // error +case object B extends Foo(A) object Test { def main(args: Array[String]): Unit = { println(A.opposite) diff --git a/tests/init/neg/t5366.scala b/tests/init/neg/t5366.scala new file mode 100644 index 000000000000..854bdfe0544b --- /dev/null +++ b/tests/init/neg/t5366.scala @@ -0,0 +1,15 @@ +class IdAndMsg(val id: Int, val msg: String = "") + +case object ObjA extends IdAndMsg(1) // error +case object ObjB extends IdAndMsg(2) + +object IdAndMsg { // error + val values = List(ObjA , ObjB) +} + +object Test { + def main(args: Array[String]): Unit = { + ObjA + println(IdAndMsg.values) + } +} \ No newline at end of file diff --git a/tests/init/neg/t9115.scala b/tests/init/neg/t9115.scala new file mode 100644 index 000000000000..4ccb7dc5efa6 --- /dev/null +++ b/tests/init/neg/t9115.scala @@ -0,0 +1,8 @@ +object D { // error + def aaa = 1 //that’s the reason + class Z (depends: Any) + case object D1 extends Z(aaa) // 'null' when calling D.D1 first time // error + case object D2 extends Z(aaa) // 'null' when calling D.D2 first time + println(D1) + println(D2) +} \ No newline at end of file diff --git a/tests/init/neg/t9261.scala b/tests/init/neg/t9261.scala index 378dad51b3a6..1e23bedb9b6a 100644 --- a/tests/init/neg/t9261.scala +++ b/tests/init/neg/t9261.scala @@ -1,3 +1,3 @@ sealed abstract class OrderType(val reverse: OrderType) case object Buy extends OrderType(Sell) // error -case object Sell extends OrderType(Buy) // error +case object Sell extends OrderType(Buy) diff --git a/tests/init/neg/t9312.scala b/tests/init/neg/t9312.scala index d4eed8d9e137..31bb3e5547c0 100644 --- a/tests/init/neg/t9312.scala +++ b/tests/init/neg/t9312.scala @@ -15,7 +15,7 @@ object DeadLockTest { def siblings = parent.children - this } - object Child1 extends Child + object Child1 extends Child // error object Child2 extends Child final val children = Set(Child1, Child2) diff --git a/tests/init/neg/t9360.scala b/tests/init/neg/t9360.scala index 71f0af4c0fa1..2ec0c740d739 100644 --- a/tests/init/neg/t9360.scala +++ b/tests/init/neg/t9360.scala @@ -5,7 +5,7 @@ class BaseClass(s: String) { object Obj { // error val s: String = "hello" - object AObj extends BaseClass(s) + object AObj extends BaseClass(s) // error object BObj extends BaseClass(s) From 9175c128db35a2f727febc1008eef5389e57cf50 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 15:19:04 +0200 Subject: [PATCH 16/33] Refine traces --- .../tools/dotc/transform/init/Errors.scala | 2 +- .../tools/dotc/transform/init/Objects.scala | 40 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index 5e7f50716f67..ed06ca9397e9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -84,7 +84,7 @@ object Errors { case class CallUnknown(meth: Symbol, source: Tree, trace: Seq[Tree]) extends Error { def show(using Context): String = - val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field" + val prefix = if meth.is(Flags.Method) then "Calling the external " else "Accessing the external " prefix + meth.show + " may cause initialization errors" + "." } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 3e42735fa13a..4ac87b548dba 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -248,10 +248,13 @@ class Objects { case addr: Addr => val target = if needResolve then resolve(addr.klass, field) else field + val trace1 = trace.add(source) if target.is(Flags.Lazy) then + given Trace = trace1 val rhs = target.defTree.asInstanceOf[ValDef].rhs eval(rhs, addr, target.owner.asClass, cacheResult = true) else + given Trace = trace1 val obj = heap(addr) if obj.fields.contains(target) then Result(obj.fields(target), Nil) @@ -295,14 +298,17 @@ class Objects { resolveSuper(addr.klass, superType, meth) else resolve(addr.klass, meth) + + val trace1 = trace.add(source) if target.isOneOf(Flags.Method) then if target.hasSource then + given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] given Env = Env(ddef, args) if target.isPrimaryConstructor then val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(tpl)) + eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(cls.defTree)) else eval(ddef.rhs, addr, cls, cacheResult = true) else if addr.canIgnoreMethodCall(target) then @@ -334,8 +340,10 @@ class Objects { /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = + val trace1 = trace.add(source) value match { case Cold => + given Trace = trace1 val error = CallCold(ctor, source, trace.toVector) Result(Bottom, error :: Nil) @@ -351,6 +359,7 @@ class Objects { case _: Fun => Cold case _ => value + given Trace = trace1 val outerWidened = value.widen val argsWidened = args.map(_.widen) val addr = @@ -379,7 +388,8 @@ class Objects { def access(source: Tree): Contextual[Result] = val cycle = path.from(obj.klass) if cycle.nonEmpty then - var trace1 = trace.toVector :+ source + val classDef = obj.klass.defTree + var trace1 = trace.toVector.dropWhile(_ != classDef) :+ source val warning = if cycle.size > 1 then CyclicObjectInit(cycle, trace1) @@ -391,9 +401,7 @@ class Objects { Result(Bottom, Nil) else use(path.add(obj.klass)) { - use(trace.add(source)) { - Bottom.instantiate(obj.klass, obj.klass.primaryConstructor, Nil, source) - } + Bottom.instantiate(obj.klass, obj.klass.primaryConstructor, Nil, source) } // ----- Policies ------------------------------------------ @@ -416,11 +424,11 @@ class Objects { def check(cls: ClassSymbol, tpl: Template)(using Context): Unit = { val objRef = ObjectRef(cls) val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map.empty) - given Path = Path.empty.add(cls) + given Path = Path.empty given Trace = Trace.empty given Env = Env.empty heap.update(objRef, obj) - val res = eval(tpl, objRef, cls) + val res = objRef.access(tpl) res.errors.foreach(_.issue) } @@ -492,9 +500,7 @@ class Objects { val cls = tref.classSymbol.asClass val res = outerValue(tref, thisV, klass, tpt) - use(trace.add(expr)) { - (res ++ argsErrors).instantiate(cls, ctor, argsValues, expr) - } + (res ++ argsErrors).instantiate(cls, ctor, argsValues, expr) case Call(ref, argss) => // check args @@ -502,21 +508,15 @@ class Objects { val argsValues = resArgs.map(_.value) val argsErrors = resArgs.flatMap(_.errors) - val trace2: Trace = trace.add(expr) - ref match case Select(supert: Super, _) => val SuperType(thisTp, superTp) = supert.tpe val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) - use(trace2) { - Result(thisValue2, argsErrors).call(ref.symbol, argsValues, superTp, expr) - } + Result(thisValue2, argsErrors).call(ref.symbol, argsValues, superTp, expr) case Select(qual, _) => val res = eval(qual, thisV, klass) ++ argsErrors - use(trace2) { - res.call(ref.symbol, argsValues, superType = NoType, source = expr) - } + res.call(ref.symbol, argsValues, superType = NoType, source = expr) case id: Ident => id.tpe match @@ -528,9 +528,7 @@ class Objects { Result(thisValue2, argsErrors).call(id.symbol, argsValues, superType = NoType, expr) case TermRef(prefix, _) => val res = cases(prefix, thisV, klass, id) ++ argsErrors - use(trace2) { - res.call(id.symbol, argsValues, superType = NoType, source = expr) - } + res.call(id.symbol, argsValues, superType = NoType, source = expr) case Select(qualifier, name) => eval(qualifier, thisV, klass).select(expr.symbol, expr) From 39101498c7235002a047bd0ef7bbfca413aba673 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 15:50:23 +0200 Subject: [PATCH 17/33] Hanlde exception in bootstrapping --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 4ac87b548dba..d2205ccf7a9d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -656,10 +656,11 @@ class Objects { if sym.is(Flags.Param) then Result(env.lookup(sym), Nil) else if sym.is(Flags.Mutable) then Result(Cold, Nil) else if sym.is(Flags.Package) then Result(Bottom, Nil) - else { + else if sym.hasSource then val rhs = sym.defTree.asInstanceOf[ValDef].rhs eval(rhs, thisV, klass, cacheResult = true) - } + else + Result(Bottom, CallUnknown(sym, source, trace.toVector) :: Nil) case tmref: TermRef => val sym = tmref.symbol From 154bac415918e630dd1f394d1fe1c641eb94da1b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 28 May 2021 16:01:38 +0200 Subject: [PATCH 18/33] Use type abstraction for parameters --- .../tools/dotc/transform/init/Objects.scala | 146 ++++++++++-------- 1 file changed, 83 insertions(+), 63 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index d2205ccf7a9d..bb6509100411 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -24,7 +24,7 @@ class Objects { /** Abstract values * - * Value = Cold | ObjectRef | Instance | Fun | RefSet + * Value = TypeAbs | ObjectRef | Instance | Fun | RefSet * * `RefSet` represents a set of values which could be `ObjectRef`, * `Instance` or `Fun`. The following ordering applies for RefSet: @@ -38,8 +38,8 @@ class Objects { def show: String = this.toString() } - /** An unknown object */ - case object Cold extends Value + /** An object abstract by its type */ + case class TypeAbs(tp: Type) extends Value sealed abstract class Addr extends Value { def klass: ClassSymbol @@ -50,10 +50,10 @@ class Objects { case class ObjectRef(klass: ClassSymbol) extends Addr /** An instance of class */ - case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr + case class Instance(klass: ClassSymbol, outer: Value) extends Addr /** A function value */ - case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol, env: Env) extends Value + case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol) extends Value /** A value which represents a set of addresses * @@ -88,22 +88,15 @@ class Objects { params.zip(args).toMap extension (env: Env) - def lookup(sym: Symbol): Value = env(sym) + def lookup(sym: Symbol)(using Context): Value = + if sym.info <:< defn.StringType + || sym.info.classSymbol.isPrimitiveValueClass + then + Bottom + else + TypeAbs(sym.info) def union(other: Env): Env = env ++ other - - /** Widen the environment for closures */ - def widenForClosure: Env = - def widen(v: Value): Value = v.match - case RefSet(refs) => refs.map(widen(_)).join - case Instance(_, outer, _, args) => - val nested = (outer :: args).exists(_.isInstanceOf[Instance]) - if nested then Cold - else v - case _: Fun => Cold - case _ => v - - env.map { (k, v) => k -> widen(v) } } type Env = Env.Env @@ -175,6 +168,11 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors + + def ensureAccess(source: Tree): Contextual[Result] = + value match + case obj: ObjectRef => obj.access(source) ++ errors + case _ => this } /** The state that threads through the interpreter */ @@ -221,8 +219,8 @@ class Objects { case (Bottom, _) => b case (_, Bottom) => a - case (Cold, _) => Cold - case (_, Cold) => Cold + case (_: TypeAbs, _) => a + case (_, _: TypeAbs) => b case (a: (Fun | Addr), b: (Fun | Addr)) => RefSet(a :: b :: Nil) @@ -242,9 +240,19 @@ class Objects { case Bottom => Result(Bottom, Errors.empty) - case Cold => - val error = AccessCold(field, source, trace.toVector) - Result(Bottom, error :: Nil) + case TypeAbs(tp) => + if field.isEffectivelyFinal then + if field.hasSource then + val vdef = field.defTree.asInstanceOf[ValDef] + eval(vdef.rhs, value, field.owner.enclosingClass.asClass, cacheResult = true) + else if value.canIgnoreMethodCall(field) then + Result(Bottom, Nil) + else + val error = CallUnknown(field, source, trace.toVector) + Result(Bottom, error :: Nil) + else + val error = AccessCold(field, source, trace.toVector) + Result(Bottom, error :: Nil) case addr: Addr => val target = if needResolve then resolve(addr.klass, field) else field @@ -286,9 +294,20 @@ class Objects { case Bottom => Result(Bottom, Errors.empty) - case Cold => - val error = CallCold(meth, source, trace.toVector) - Result(Bottom, error :: Nil) + case TypeAbs(tp) => + if meth.isEffectivelyFinal then + if meth.hasSource then + val ddef = meth.defTree.asInstanceOf[DefDef] + given Env = Env(ddef, args) + eval(ddef.rhs, value, meth.owner.enclosingClass.asClass, cacheResult = true) + else if value.canIgnoreMethodCall(meth) then + Result(Bottom, Nil) + else + val error = CallUnknown(meth, source, trace.toVector) + Result(Bottom, error :: Nil) + else + val error = CallCold(meth, source, trace.toVector) + Result(Bottom, error :: Nil) case addr: Addr => val target = @@ -323,13 +342,10 @@ class Objects { else value.select(target, source, needResolve = false) - case Fun(body, thisV, klass, env2) => + case Fun(body, thisV, klass) => // meth == NoSymbol for poly functions if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` - else - use(env.union(env2)) { - eval(body, thisV, klass, cacheResult = true) - } + else eval(body, thisV, klass, cacheResult = true) case RefSet(refs) => val resList = refs.map(_.call(meth, args, superType, source)) @@ -342,7 +358,8 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = val trace1 = trace.add(source) value match { - case Cold => + case TypeAbs(tp) => + // TODO: can do better here given Trace = trace1 val error = CallCold(ctor, source, trace.toVector) Result(Bottom, error :: Nil) @@ -352,28 +369,27 @@ class Objects { extension (value: Value) def widen: Value = value match case RefSet(refs) => refs.map(_.widen).join - case Instance(_, outer, _, args) => - val nested = (outer :: args).exists(_.isInstanceOf[Instance]) - if nested then Cold + case Instance(klass, outer) => + val nested = outer.isInstanceOf[Instance] + if nested then TypeAbs(klass.typeRef) else value - case _: Fun => Cold + case _: Fun => TypeAbs(defn.AnyType) case _ => value given Trace = trace1 val outerWidened = value.widen - val argsWidened = args.map(_.widen) val addr = if klass.isStaticObjectRef then ObjectRef(klass) else - Instance(klass, outerWidened, ctor, argsWidened) + Instance(klass, outerWidened) if !heap.contains(addr) then val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outerWidened)) heap.update(addr, obj) - val res = addr.call(ctor, argsWidened, superType = NoType, source) + val res = addr.call(ctor, args, superType = NoType, source) Result(addr, res.errors) - case Fun(body, thisV, klass, _) => + case Fun(body, thisV, klass) => ??? // impossible case RefSet(refs) => @@ -405,7 +421,7 @@ class Objects { } // ----- Policies ------------------------------------------ - extension (value: Addr) + extension (value: Value) /** Can the method call on `value` be ignored? * * Note: assume overriding resolution has been performed. @@ -429,7 +445,7 @@ class Objects { given Env = Env.empty heap.update(objRef, obj) val res = objRef.access(tpl) - res.errors.foreach(_.issue) + res.errors.filterNot(_.isInstanceOf[CallUnknown]).foreach(_.issue) } // ----- Semantic definition ------------------------------- @@ -449,7 +465,7 @@ class Objects { * * This method only handles cache logic and delegates the work to `cases`. */ - def eval(expr: Tree, thisV: Addr, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Result] = log("evaluating " + expr.show + ", this = " + thisV.show, printer, res => res.asInstanceOf[Result].show) { + def eval(expr: Tree, thisV: Value, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Result] = log("evaluating " + expr.show + ", this = " + thisV.show, printer, res => res.asInstanceOf[Result].show) { val innerMap = cache.getOrElseUpdate(thisV, new EqHashMap[Tree, Value]) if (innerMap.contains(expr)) Result(innerMap(expr), Errors.empty) else { @@ -465,24 +481,24 @@ class Objects { } /** Evaluate a list of expressions */ - def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + def eval(exprs: List[Tree], thisV: Value, klass: ClassSymbol): Contextual[List[Result]] = exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ - def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] = + def evalArgs(args: List[Arg], thisV: Value, klass: ClassSymbol): Contextual[List[Result]] = args.map { arg => if arg.isByName then - val fun = Fun(arg.tree, thisV, klass, env.widenForClosure) + val fun = Fun(arg.tree, thisV, klass) Result(fun, Nil) else - eval(arg.tree, thisV, klass) + eval(arg.tree, thisV, klass).ensureAccess(arg.tree) } /** Handles the evaluation of different expressions * * Note: Recursive call should go to `eval` instead of `cases`. */ - def cases(expr: Tree, thisV: Addr, klass: ClassSymbol): Contextual[Result] = + def cases(expr: Tree, thisV: Value, klass: ClassSymbol): Contextual[Result] = expr match { case Ident(nme.WILDCARD) => // TODO: disallow `var x: T = _` @@ -555,11 +571,11 @@ class Objects { eval(rhs, thisV, klass) case closureDef(ddef) => - val value = Fun(ddef.rhs, thisV, klass, env.widenForClosure) + val value = Fun(ddef.rhs, thisV, klass) Result(value, Nil) case PolyFun(body) => - val value = Fun(body, thisV, klass, env.widenForClosure) + val value = Fun(body, thisV, klass) Result(value, Nil) case Block(stats, expr) => @@ -610,7 +626,7 @@ class Objects { val ress = elems.map { elem => eval(elem, thisV, klass) } - Result(Cold, ress.flatMap(_.errors)) + Result(TypeAbs(expr.tpe), ress.flatMap(_.errors)) case Inlined(call, bindings, expansion) => val ress = eval(bindings, thisV, klass) @@ -633,7 +649,7 @@ class Objects { Result(Bottom, Errors.empty) case tpl: Template => - init(tpl, thisV, klass) + init(tpl, thisV.asInstanceOf[Addr], klass) case _: Import | _: Export => Result(Bottom, Errors.empty) @@ -643,7 +659,7 @@ class Objects { } /** Handle semantics of leaf nodes */ - def cases(tp: Type, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { + def cases(tp: Type, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { tp match { case _: ConstantType => Result(Bottom, Errors.empty) @@ -654,7 +670,8 @@ class Objects { // - evaluate the rhs of the local definition for val definitions: they are already cached val sym = tmref.symbol if sym.is(Flags.Param) then Result(env.lookup(sym), Nil) - else if sym.is(Flags.Mutable) then Result(Cold, Nil) + else if sym.info <:< defn.StringType || sym.info.classSymbol.isPrimitiveValueClass then Result(Bottom, Nil) + else if sym.is(Flags.Mutable) then Result(TypeAbs(sym.info), Nil) else if sym.is(Flags.Package) then Result(Bottom, Nil) else if sym.hasSource then val rhs = sym.defTree.asInstanceOf[ValDef].rhs @@ -665,7 +682,7 @@ class Objects { case tmref: TermRef => val sym = tmref.symbol if sym.isStaticObjectRef then - ObjectRef(sym.moduleClass.asClass).access(source) + Result(ObjectRef(sym.moduleClass.asClass), Nil) else cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -673,7 +690,7 @@ class Objects { val sym = tref.symbol if sym.is(Flags.Package) then Result(Bottom, Errors.empty) else if sym.isStaticObjectRef && sym != klass then - ObjectRef(sym.moduleClass.asClass).access(source) + Result(ObjectRef(sym.moduleClass.asClass), Nil) else val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) Result(value, Errors.empty) @@ -702,13 +719,12 @@ class Objects { refs.map(ref => resolveThis(target, ref, klass, source)).join case fun: Fun => report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, source.srcPos) - Cold - case Cold => Cold - + TypeAbs(defn.AnyType) + case TypeAbs(tp) => TypeAbs(target.info) } /** Compute the outer value that correspond to `tref.prefix` */ - def outerValue(tref: TypeRef, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] = + def outerValue(tref: TypeRef, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Result] = val cls = tref.classSymbol.asClass if tref.prefix == NoPrefix then val enclosing = cls.owner.lexicallyEnclosingClass.asClass @@ -801,8 +817,12 @@ class Objects { case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) => val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors - val fieldV = if vdef.symbol.is(Flags.Mutable) then Cold else res.value - thisV.updateField(vdef.symbol, fieldV) + val sym = vdef.symbol + val fieldV = + if sym.info <:< defn.StringType || sym.info.classSymbol.isPrimitiveValueClass then Bottom + else if sym.is(Flags.Mutable) then TypeAbs(sym.info) + else res.value + thisV.updateField(sym, fieldV) case _: MemberDef => From 18a4fc870aa53075950c791caf1a52aebf082836 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 31 May 2021 18:15:27 +0200 Subject: [PATCH 19/33] Add ClassAbs definition --- .../tools/dotc/transform/init/Objects.scala | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index bb6509100411..41e47019b498 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -41,6 +41,12 @@ class Objects { /** An object abstract by its type */ case class TypeAbs(tp: Type) extends Value + /** An object abstract by its class + * + * This is used for arguments + */ + case class ClassAbs(klass: ClassSymbol) extends Value + sealed abstract class Addr extends Value { def klass: ClassSymbol } @@ -50,10 +56,10 @@ class Objects { case class ObjectRef(klass: ClassSymbol) extends Addr /** An instance of class */ - case class Instance(klass: ClassSymbol, outer: Value) extends Addr + case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr /** A function value */ - case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol) extends Value + case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol, env: Env) extends Value /** A value which represents a set of addresses * @@ -94,7 +100,7 @@ class Objects { then Bottom else - TypeAbs(sym.info) + env(sym) def union(other: Env): Env = env ++ other } @@ -219,21 +225,25 @@ class Objects { case (Bottom, _) => b case (_, Bottom) => a - case (_: TypeAbs, _) => a - case (_, _: TypeAbs) => b - - case (a: (Fun | Addr), b: (Fun | Addr)) => RefSet(a :: b :: Nil) + case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) - case (a: (Fun | Addr), RefSet(refs)) => RefSet(a :: refs) - case (RefSet(refs), b: (Fun | Addr)) => RefSet(b :: refs) + case (a, RefSet(refs)) => RefSet(a :: refs) + case (RefSet(refs), b) => RefSet(b :: refs) + case (a, b) => RefSet(a :: b :: Nil) - case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) + def widen: Value = v.match + case RefSet(refs) => refs.map(widen(_)).join + case inst: Instance => ClassAbs(inst.klass) + case Fun(e, thisV, klass, env) => Fun(e, thisV.widen, klass, env) + case _ => v extension (values: Seq[Value]) def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) } + def widen: List[Value] = values.map(_.widen).toList + extension (value: Value) def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = value match { @@ -263,7 +273,7 @@ class Objects { eval(rhs, addr, target.owner.asClass, cacheResult = true) else given Trace = trace1 - val obj = heap(addr) + val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty, mutable.Map.empty) if obj.fields.contains(target) then Result(obj.fields(target), Nil) else if target.is(Flags.ParamAccessor) then @@ -336,11 +346,7 @@ class Objects { val error = CallUnknown(target, source, trace.toVector) Result(Bottom, error :: Nil) else - val obj = heap(addr) - if obj.fields.contains(target) then - Result(obj.fields(target), Nil) - else - value.select(target, source, needResolve = false) + value.select(target, source, needResolve = false) case Fun(body, thisV, klass) => // meth == NoSymbol for poly functions From cb36c571cb2c18378d34e40d045868469b016314 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 Jun 2021 10:49:09 +0200 Subject: [PATCH 20/33] Fix compilation errors --- .../tools/dotc/transform/init/Objects.scala | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 41e47019b498..6013423c9ba3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -59,13 +59,13 @@ class Objects { case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr /** A function value */ - case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol, env: Env) extends Value + case class Fun(expr: Tree, params: List[Symbol], thisV: Value, klass: ClassSymbol, env: Env) extends Value - /** A value which represents a set of addresses + /** A value which represents a set of values * * It comes from `if` expressions. */ - case class RefSet(refs: List[Fun | Addr]) extends Value + case class RefSet(refs: List[Value]) extends Value val Bottom: Value = RefSet(Nil) @@ -231,11 +231,11 @@ class Objects { case (RefSet(refs), b) => RefSet(b :: refs) case (a, b) => RefSet(a :: b :: Nil) - def widen: Value = v.match - case RefSet(refs) => refs.map(widen(_)).join + def widen: Value = a.match + case RefSet(refs) => refs.map(_.widen).join case inst: Instance => ClassAbs(inst.klass) - case Fun(e, thisV, klass, env) => Fun(e, thisV.widen, klass, env) - case _ => v + case Fun(e, params, thisV, klass, env) => Fun(e, params, thisV.widen, klass, env) + case _ => a extension (values: Seq[Value]) def join: Value = @@ -261,8 +261,19 @@ class Objects { val error = CallUnknown(field, source, trace.toVector) Result(Bottom, error :: Nil) else - val error = AccessCold(field, source, trace.toVector) - Result(Bottom, error :: Nil) + val fieldType = tp.memberInfo(field) + Result(TypeAbs(fieldType), Nil) + + case ClassAbs(klass) => + val target = if needResolve then resolve(klass, field) else field + val trace1 = trace.add(source) + if target.is(Flags.Lazy) then + given Trace = trace1 + val rhs = target.defTree.asInstanceOf[ValDef].rhs + eval(rhs, value, target.owner.asClass, cacheResult = true) + else + val fieldType = klass.typeRef.memberInfo(field) + Result(TypeAbs(fieldType), Nil) case addr: Addr => val target = if needResolve then resolve(addr.klass, field) else field @@ -319,6 +330,31 @@ class Objects { val error = CallCold(meth, source, trace.toVector) Result(Bottom, error :: Nil) + case ClassAbs(klass) => + val target = + if !needResolve then + meth + else if superType.exists then + resolveSuper(klass, superType, meth) + else + resolve(klass, meth) + + val trace1 = trace.add(source) + if target.isOneOf(Flags.Method) then + if target.hasSource then + given Trace = trace1 + val cls = target.owner.enclosingClass.asClass + val ddef = target.defTree.asInstanceOf[DefDef] + given Env = Env(ddef, args) + eval(ddef.rhs, value, cls, cacheResult = true) + else if value.canIgnoreMethodCall(target) then + Result(Bottom, Nil) + else + val error = CallUnknown(target, source, trace.toVector) + Result(Bottom, error :: Nil) + else + value.select(target, source, needResolve = false) + case addr: Addr => val target = if !needResolve then @@ -348,10 +384,14 @@ class Objects { else value.select(target, source, needResolve = false) - case Fun(body, thisV, klass) => + case Fun(body, params, thisV, klass, env2) => // meth == NoSymbol for poly functions if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` - else eval(body, thisV, klass, cacheResult = true) + else + val env3 = Env(params.zip(args.widen).toMap).union(env2) + use(env3) { + eval(body, thisV, klass, cacheResult = true) + } case RefSet(refs) => val resList = refs.map(_.call(meth, args, superType, source)) @@ -364,38 +404,27 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = val trace1 = trace.add(source) value match { - case TypeAbs(tp) => + case _: TypeAbs | _: ClassAbs => // TODO: can do better here given Trace = trace1 val error = CallCold(ctor, source, trace.toVector) Result(Bottom, error :: Nil) case _: Addr | Bottom => - // widen the outer and args to finitize addresses - extension (value: Value) - def widen: Value = value match - case RefSet(refs) => refs.map(_.widen).join - case Instance(klass, outer) => - val nested = outer.isInstanceOf[Instance] - if nested then TypeAbs(klass.typeRef) - else value - case _: Fun => TypeAbs(defn.AnyType) - case _ => value - given Trace = trace1 val outerWidened = value.widen val addr = if klass.isStaticObjectRef then ObjectRef(klass) else - Instance(klass, outerWidened) + Instance(klass, outerWidened, ctor, args.widen) if !heap.contains(addr) then val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outerWidened)) heap.update(addr, obj) val res = addr.call(ctor, args, superType = NoType, source) Result(addr, res.errors) - case Fun(body, thisV, klass) => + case _: Fun => ??? // impossible case RefSet(refs) => @@ -494,7 +523,7 @@ class Objects { def evalArgs(args: List[Arg], thisV: Value, klass: ClassSymbol): Contextual[List[Result]] = args.map { arg => if arg.isByName then - val fun = Fun(arg.tree, thisV, klass) + val fun = Fun(arg.tree, Nil, thisV, klass, env) Result(fun, Nil) else eval(arg.tree, thisV, klass).ensureAccess(arg.tree) @@ -577,11 +606,12 @@ class Objects { eval(rhs, thisV, klass) case closureDef(ddef) => - val value = Fun(ddef.rhs, thisV, klass) + val params = ddef.termParamss.head.map(_.symbol) + val value = Fun(ddef.rhs, params, thisV, klass, env) Result(value, Nil) case PolyFun(body) => - val value = Fun(body, thisV, klass) + val value = Fun(body, Nil, thisV, klass, env) Result(value, Nil) case Block(stats, expr) => @@ -717,6 +747,7 @@ class Objects { else thisV match case Bottom => Bottom + case addr: Addr => val obj = heap(addr) val outerCls = klass.owner.enclosingClass.asClass @@ -727,6 +758,7 @@ class Objects { report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, source.srcPos) TypeAbs(defn.AnyType) case TypeAbs(tp) => TypeAbs(target.info) + case ClassAbs(tp) => TypeAbs(target.typeRef) } /** Compute the outer value that correspond to `tref.prefix` */ From 5a51a2ab5efe997ca84e978e6f05873d336c6bff Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 Jun 2021 11:53:37 +0200 Subject: [PATCH 21/33] Temporary fix for local methods --- .../dotty/tools/dotc/transform/init/Objects.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 6013423c9ba3..f9d801e9a9f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -43,7 +43,7 @@ class Objects { /** An object abstract by its class * - * This is used for arguments + * This abstraction is used for arguments to methods and constructors */ case class ClassAbs(klass: ClassSymbol) extends Value @@ -345,8 +345,9 @@ class Objects { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - given Env = Env(ddef, args) - eval(ddef.rhs, value, cls, cacheResult = true) + use(Env(ddef, args).union(env)) { + eval(ddef.rhs, value, cls, cacheResult = true) + } else if value.canIgnoreMethodCall(target) then Result(Bottom, Nil) else @@ -370,12 +371,15 @@ class Objects { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - given Env = Env(ddef, args) + val env2 = Env(ddef, args) if target.isPrimaryConstructor then + given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(cls.defTree)) else - eval(ddef.rhs, addr, cls, cacheResult = true) + use(env2.union(env)) { + eval(ddef.rhs, addr, cls, cacheResult = true) + } else if addr.canIgnoreMethodCall(target) then Result(Bottom, Nil) else From b2944e367061576ad6922975bfaecf55b9bae11e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 Jun 2021 13:24:46 +0200 Subject: [PATCH 22/33] Fix stacktrace --- compiler/src/dotty/tools/dotc/transform/init/Checker.scala | 2 +- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 7abc11b0d4e3..ca8be790f1b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -58,7 +58,7 @@ class Checker extends MiniPhase { res.errors.foreach(_.issue) if objects.isStaticObjectRef(cls) then - objects.check(cls, tpl) + objects.check(cls) } tree diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index f9d801e9a9f2..d78d41e22eac 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -476,14 +476,12 @@ class Objects { * * @param cls the module class of the static object */ - def check(cls: ClassSymbol, tpl: Template)(using Context): Unit = { + def check(cls: ClassSymbol)(using Context): Unit = { val objRef = ObjectRef(cls) - val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map.empty) given Path = Path.empty given Trace = Trace.empty given Env = Env.empty - heap.update(objRef, obj) - val res = objRef.access(tpl) + val res = objRef.access(cls.defTree) res.errors.filterNot(_.isInstanceOf[CallUnknown]).foreach(_.issue) } From 90afb95b7d685a736eb2fca12f5084b10e26502a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 Jun 2021 17:44:32 +0200 Subject: [PATCH 23/33] Fix semantics for object access --- .../tools/dotc/transform/init/Objects.scala | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index d78d41e22eac..1ba105553bd4 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -175,9 +175,11 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors - def ensureAccess(source: Tree): Contextual[Result] = + def ensureAccess(klass: ClassSymbol, source: Tree): Contextual[Result] = value match - case obj: ObjectRef => obj.access(source) ++ errors + case obj: ObjectRef => + if obj.klass == klass then this + else obj.access(source) ++ errors case _ => this } @@ -528,7 +530,7 @@ class Objects { val fun = Fun(arg.tree, Nil, thisV, klass, env) Result(fun, Nil) else - eval(arg.tree, thisV, klass).ensureAccess(arg.tree) + eval(arg.tree, thisV, klass) } /** Handles the evaluation of different expressions @@ -584,7 +586,9 @@ class Objects { res.call(id.symbol, argsValues, superType = NoType, source = expr) case Select(qualifier, name) => - eval(qualifier, thisV, klass).select(expr.symbol, expr) + val sym = expr.symbol + if sym.isStaticObjectRef then Result(ObjectRef(sym.moduleClass.asClass), Nil).ensureAccess(klass, expr) + else eval(qualifier, thisV, klass).select(expr.symbol, expr) case _: This => cases(expr.tpe, thisV, klass, expr) @@ -696,8 +700,13 @@ class Objects { throw new Exception("unexpected tree: " + expr.show) } - /** Handle semantics of leaf nodes */ - def cases(tp: Type, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { + /** Handle semantics of leaf nodes + * + * @param elideObjectAccess Whether object access should be omitted + * + * It happens when the object access is used as a prefix in `new o.C` + */ + def cases(tp: Type, thisV: Value, klass: ClassSymbol, source: Tree, elideObjectAccess: Boolean = false): Contextual[Result] = log("evaluating " + tp.show, printer, res => res.asInstanceOf[Result].show) { tp match { case _: ConstantType => Result(Bottom, Errors.empty) @@ -720,7 +729,8 @@ class Objects { case tmref: TermRef => val sym = tmref.symbol if sym.isStaticObjectRef then - Result(ObjectRef(sym.moduleClass.asClass), Nil) + val res = Result(ObjectRef(sym.moduleClass.asClass), Nil) + if elideObjectAccess then res else res.ensureAccess(klass, source) else cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) @@ -728,7 +738,8 @@ class Objects { val sym = tref.symbol if sym.is(Flags.Package) then Result(Bottom, Errors.empty) else if sym.isStaticObjectRef && sym != klass then - Result(ObjectRef(sym.moduleClass.asClass), Nil) + val res = Result(ObjectRef(sym.moduleClass.asClass), Nil) + if elideObjectAccess then res else res.ensureAccess(klass, source) else val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) Result(value, Errors.empty) @@ -772,7 +783,8 @@ class Objects { Result(outerV, Errors.empty) else if cls.isAllOf(Flags.JavaInterface) then Result(Bottom, Nil) - else cases(tref.prefix, thisV, klass, source) + else + cases(tref.prefix, thisV, klass, source, elideObjectAccess = cls.isStatic) /** Initialize part of an abstract object in `klass` of the inheritance chain */ def init(tpl: Template, thisV: Addr, klass: ClassSymbol): Contextual[Result] = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) { From e5742aa8650678758f5a3517babf4d68c6fed8a6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 2 Jun 2021 18:26:13 +0200 Subject: [PATCH 24/33] Replace object leak with object non-init error --- .../dotty/tools/dotc/transform/init/Errors.scala | 4 ++-- .../dotty/tools/dotc/transform/init/Objects.scala | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index ed06ca9397e9..66e081c6ef38 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -115,9 +115,9 @@ object Errors { report.warning(show + stacktrace, objs.head.srcPos) } - case class ObjectLeakDuringInit(obj: Symbol, trace: Seq[Tree]) extends Error { + case class ObjectNotInit(obj: Symbol, trace: Seq[Tree]) extends Error { def source: Tree = trace.last - def show(using Context): String = obj.show + " leaked during its initialization " + "." + def show(using Context): String = obj.show + " not yet initialized " + "." override def issue(using Context): Unit = report.warning(show + stacktrace, obj.srcPos) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 1ba105553bd4..2df20b5be46c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -447,12 +447,14 @@ class Objects { if cycle.nonEmpty then val classDef = obj.klass.defTree var trace1 = trace.toVector.dropWhile(_ != classDef) :+ source - val warning = + val warnings = if cycle.size > 1 then - CyclicObjectInit(cycle, trace1) + CyclicObjectInit(cycle, trace1) :: Nil else - ObjectLeakDuringInit(obj.klass, trace1) - Result(obj, warning :: Nil) + val o = heap(obj) + if o.fields.contains(obj.klass) then Nil + else ObjectNotInit(obj.klass, trace1) :: Nil + Result(obj, warnings) else if obj.klass.is(Flags.JavaDefined) then // Errors will be reported for method calls on it Result(Bottom, Nil) @@ -845,6 +847,10 @@ class Objects { val superCls = superParent.tpe.classSymbol.asClass initParent(superParent) + // Access to the object possible after this point + if klass.isStaticOwner then + thisV.updateField(klass, thisV) + val parents = tpl.parents.tail val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) mixins.reverse.foreach { mixin => From 1dce9fd9dc0b721c4bbc1d1bef5c70df400ea444 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 3 Jun 2021 14:38:20 +0200 Subject: [PATCH 25/33] Suppress warnings about unkown external calls --- .../tools/dotc/transform/init/Objects.scala | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 2df20b5be46c..4a5d6c4eb7d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -260,8 +260,7 @@ class Objects { else if value.canIgnoreMethodCall(field) then Result(Bottom, Nil) else - val error = CallUnknown(field, source, trace.toVector) - Result(Bottom, error :: Nil) + Result(Bottom, Nil) else val fieldType = tp.memberInfo(field) Result(TypeAbs(fieldType), Nil) @@ -299,8 +298,7 @@ class Objects { val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs eval(rhs, addr, target.owner.asClass, cacheResult = true) else - val error = CallUnknown(field, source, trace.toVector) - Result(Bottom, error :: Nil) + Result(Bottom, Nil) case _: Fun => ??? @@ -326,8 +324,7 @@ class Objects { else if value.canIgnoreMethodCall(meth) then Result(Bottom, Nil) else - val error = CallUnknown(meth, source, trace.toVector) - Result(Bottom, error :: Nil) + Result(Bottom, Nil) else val error = CallCold(meth, source, trace.toVector) Result(Bottom, error :: Nil) @@ -353,8 +350,7 @@ class Objects { else if value.canIgnoreMethodCall(target) then Result(Bottom, Nil) else - val error = CallUnknown(target, source, trace.toVector) - Result(Bottom, error :: Nil) + Result(Bottom, Nil) else value.select(target, source, needResolve = false) @@ -385,8 +381,7 @@ class Objects { else if addr.canIgnoreMethodCall(target) then Result(Bottom, Nil) else - val error = CallUnknown(target, source, trace.toVector) - Result(Bottom, error :: Nil) + Result(Bottom, Nil) else value.select(target, source, needResolve = false) @@ -486,7 +481,7 @@ class Objects { given Trace = Trace.empty given Env = Env.empty val res = objRef.access(cls.defTree) - res.errors.filterNot(_.isInstanceOf[CallUnknown]).foreach(_.issue) + res.errors.foreach(_.issue) } // ----- Semantic definition ------------------------------- @@ -726,7 +721,7 @@ class Objects { val rhs = sym.defTree.asInstanceOf[ValDef].rhs eval(rhs, thisV, klass, cacheResult = true) else - Result(Bottom, CallUnknown(sym, source, trace.toVector) :: Nil) + Result(Bottom, Nil) case tmref: TermRef => val sym = tmref.symbol From a0933a254b9e409ed2d4d2a35f2268e6cdf68c4f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 3 Jun 2021 14:56:10 +0200 Subject: [PATCH 26/33] Reorganize tests --- tests/init/{pos => crash}/assignments.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/init/{pos => crash}/assignments.scala (100%) diff --git a/tests/init/pos/assignments.scala b/tests/init/crash/assignments.scala similarity index 100% rename from tests/init/pos/assignments.scala rename to tests/init/crash/assignments.scala From 3dfcf51806260a5ee22259aeeeb958326c7f1f80 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 3 Jun 2021 19:16:46 +0200 Subject: [PATCH 27/33] Use type abstraction for all parameters This goes back to coarse-grained abstraction for parameters, which is trade-off between performance and expressiveness. --- .../tools/dotc/transform/init/Objects.scala | 237 +++++------------- tests/init/crash/by-name.scala | 11 + tests/init/neg/global-cycle8.scala | 4 +- tests/init/neg/i11262.scala | 2 + tests/init/{pos => neg}/i12544.scala | 2 +- 5 files changed, 76 insertions(+), 180 deletions(-) create mode 100644 tests/init/crash/by-name.scala create mode 100644 tests/init/neg/i11262.scala rename tests/init/{pos => neg}/i12544.scala (81%) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 4a5d6c4eb7d0..2b9ce81293b2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -24,10 +24,10 @@ class Objects { /** Abstract values * - * Value = TypeAbs | ObjectRef | Instance | Fun | RefSet + * Value = TypeAbs | ObjectRef | ClassAbs | Fun | RefSet * - * `RefSet` represents a set of values which could be `ObjectRef`, - * `Instance` or `Fun`. The following ordering applies for RefSet: + * RefSet represents a set of values which may contain values other than + * RefSet. The following ordering applies for RefSet: * * R_a ⊑ R_b if R_a ⊆ R_b * @@ -41,29 +41,22 @@ class Objects { /** An object abstract by its type */ case class TypeAbs(tp: Type) extends Value - /** An object abstract by its class - * - * This abstraction is used for arguments to methods and constructors - */ - case class ClassAbs(klass: ClassSymbol) extends Value - sealed abstract class Addr extends Value { def klass: ClassSymbol } - /** A reference to a static object - */ + /** A reference to a static object */ case class ObjectRef(klass: ClassSymbol) extends Addr - /** An instance of class */ - case class Instance(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr + /** An ClassAbs of class */ + case class ClassAbs(klass: ClassSymbol) extends Addr /** A function value */ - case class Fun(expr: Tree, params: List[Symbol], thisV: Value, klass: ClassSymbol, env: Env) extends Value + case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol) extends Value /** A value which represents a set of values * - * It comes from `if` expressions. + * It comes from `if` expressions. */ case class RefSet(refs: List[Value]) extends Value @@ -71,44 +64,11 @@ class Objects { // end of value definition - /** The abstract object which stores value about its fields and immediate outers. - * - * Semantically it suffices to store the outer for `klass`. We cache other outers - * for performance reasons. + /** The abstract object which stores value about its fields. * * Note: Object is NOT a value. */ - case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value], outers: mutable.Map[ClassSymbol, Value]) - - /** The environment for method parameters */ - object Env { - opaque type Env = Map[Symbol, Value] - - val empty: Env = Map.empty - - def apply(bindings: Map[Symbol, Value]): Env = bindings - - def apply(ddef: DefDef, args: List[Value])(using Context): Env = - val params = ddef.termParamss.flatten.map(_.symbol) - assert(args.size == params.size, "arguments = " + args.size + ", params = " + params.size) - params.zip(args).toMap - - extension (env: Env) - def lookup(sym: Symbol)(using Context): Value = - if sym.info <:< defn.StringType - || sym.info.classSymbol.isPrimitiveValueClass - then - Bottom - else - env(sym) - - def union(other: Env): Env = env ++ other - } - - type Env = Env.Env - def env(using env: Env) = env - - import Env._ + case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value]) /** Abstract heap stores abstract objects * @@ -133,8 +93,7 @@ class Objects { def updateField(field: Symbol, value: Value): Contextual[Unit] = heap(ref).fields(field) = value - def updateOuter(klass: ClassSymbol, value: Value): Contextual[Unit] = - heap(ref).outers(klass) = value + def updateOuter(klass: ClassSymbol, value: Value): Contextual[Unit] = () end extension } type Heap = Heap.Heap @@ -184,7 +143,7 @@ class Objects { } /** The state that threads through the interpreter */ - type Contextual[T] = (Env, Context, Trace, Path) ?=> T + type Contextual[T] = (Context, Trace, Path) ?=> T inline def use[T, R](v: T)(inline op: T ?=> R): R = op(using v) @@ -233,19 +192,11 @@ class Objects { case (RefSet(refs), b) => RefSet(b :: refs) case (a, b) => RefSet(a :: b :: Nil) - def widen: Value = a.match - case RefSet(refs) => refs.map(_.widen).join - case inst: Instance => ClassAbs(inst.klass) - case Fun(e, params, thisV, klass, env) => Fun(e, params, thisV.widen, klass, env) - case _ => a - extension (values: Seq[Value]) def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) } - def widen: List[Value] = values.map(_.widen).toList - extension (value: Value) def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = value match { @@ -265,19 +216,10 @@ class Objects { val fieldType = tp.memberInfo(field) Result(TypeAbs(fieldType), Nil) - case ClassAbs(klass) => - val target = if needResolve then resolve(klass, field) else field - val trace1 = trace.add(source) - if target.is(Flags.Lazy) then - given Trace = trace1 - val rhs = target.defTree.asInstanceOf[ValDef].rhs - eval(rhs, value, target.owner.asClass, cacheResult = true) - else - val fieldType = klass.typeRef.memberInfo(field) - Result(TypeAbs(fieldType), Nil) - case addr: Addr => val target = if needResolve then resolve(addr.klass, field) else field + if !target.hasSource then return Result(Bottom, Nil) + val trace1 = trace.add(source) if target.is(Flags.Lazy) then given Trace = trace1 @@ -285,23 +227,18 @@ class Objects { eval(rhs, addr, target.owner.asClass, cacheResult = true) else given Trace = trace1 - val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty, mutable.Map.empty) + val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty) if obj.fields.contains(target) then Result(obj.fields(target), Nil) else if target.is(Flags.ParamAccessor) then - // possible for trait parameters - // see tests/init/neg/trait2.scala - // - // return `Bottom` here, errors are reported in checking `ThisRef` - Result(Bottom, Nil) - else if target.hasSource then + Result(TypeAbs(target.info), Nil) + else val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs eval(rhs, addr, target.owner.asClass, cacheResult = true) - else - Result(Bottom, Nil) - case _: Fun => - ??? + case fun: Fun => + report.error("unexpected tree in select a function, fun = " + fun.expr.show, source) + Result(Bottom, Nil) case RefSet(refs) => val resList = refs.map(_.select(field, source)) @@ -316,10 +253,10 @@ class Objects { Result(Bottom, Errors.empty) case TypeAbs(tp) => - if meth.isEffectivelyFinal then + if meth.exists && meth.isEffectivelyFinal then if meth.hasSource then + val isLocal = meth.owner.isClass val ddef = meth.defTree.asInstanceOf[DefDef] - given Env = Env(ddef, args) eval(ddef.rhs, value, meth.owner.enclosingClass.asClass, cacheResult = true) else if value.canIgnoreMethodCall(meth) then Result(Bottom, Nil) @@ -329,32 +266,8 @@ class Objects { val error = CallCold(meth, source, trace.toVector) Result(Bottom, error :: Nil) - case ClassAbs(klass) => - val target = - if !needResolve then - meth - else if superType.exists then - resolveSuper(klass, superType, meth) - else - resolve(klass, meth) - - val trace1 = trace.add(source) - if target.isOneOf(Flags.Method) then - if target.hasSource then - given Trace = trace1 - val cls = target.owner.enclosingClass.asClass - val ddef = target.defTree.asInstanceOf[DefDef] - use(Env(ddef, args).union(env)) { - eval(ddef.rhs, value, cls, cacheResult = true) - } - else if value.canIgnoreMethodCall(target) then - Result(Bottom, Nil) - else - Result(Bottom, Nil) - else - value.select(target, source, needResolve = false) - case addr: Addr => + val isLocal = meth.owner.isClass val target = if !needResolve then meth @@ -369,15 +282,13 @@ class Objects { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - val env2 = Env(ddef, args) if target.isPrimaryConstructor then - given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(cls.defTree)) - else - use(env2.union(env)) { - eval(ddef.rhs, addr, cls, cacheResult = true) + use(trace.add(cls.defTree)) { + eval(tpl, addr, cls, cacheResult = true) } + else + eval(ddef.rhs, addr, cls, cacheResult = true) else if addr.canIgnoreMethodCall(target) then Result(Bottom, Nil) else @@ -385,14 +296,10 @@ class Objects { else value.select(target, source, needResolve = false) - case Fun(body, params, thisV, klass, env2) => + case Fun(body, thisV, klass) => // meth == NoSymbol for poly functions if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` - else - val env3 = Env(params.zip(args.widen).toMap).union(env2) - use(env3) { - eval(body, thisV, klass, cacheResult = true) - } + else eval(body, thisV, klass, cacheResult = true) case RefSet(refs) => val resList = refs.map(_.call(meth, args, superType, source)) @@ -405,29 +312,25 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = val trace1 = trace.add(source) value match { - case _: TypeAbs | _: ClassAbs => - // TODO: can do better here - given Trace = trace1 - val error = CallCold(ctor, source, trace.toVector) - Result(Bottom, error :: Nil) + case fun: Fun => + report.error("unexpected tree in instantiate a function, fun = " + fun.expr.show, source) + Result(Bottom, Nil) - case _: Addr | Bottom => + case Bottom | _: TypeAbs | _: ClassAbs | _: ObjectRef => given Trace = trace1 - val outerWidened = value.widen val addr = if klass.isStaticObjectRef then ObjectRef(klass) else - Instance(klass, outerWidened, ctor, args.widen) + ClassAbs(klass) + if !heap.contains(addr) then - val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outerWidened)) + val obj = Objekt(klass, fields = mutable.Map.empty) heap.update(addr, obj) + val res = addr.call(ctor, args, superType = NoType, source) Result(addr, res.errors) - case _: Fun => - ??? // impossible - case RefSet(refs) => val resList = refs.map(_.instantiate(klass, ctor, args, source)) val value2 = resList.map(_.value).join @@ -476,10 +379,10 @@ class Objects { * @param cls the module class of the static object */ def check(cls: ClassSymbol)(using Context): Unit = { + printer.println("checking " + cls.show) val objRef = ObjectRef(cls) given Path = Path.empty given Trace = Trace.empty - given Env = Env.empty val res = objRef.access(cls.defTree) res.errors.foreach(_.issue) } @@ -524,7 +427,7 @@ class Objects { def evalArgs(args: List[Arg], thisV: Value, klass: ClassSymbol): Contextual[List[Result]] = args.map { arg => if arg.isByName then - val fun = Fun(arg.tree, Nil, thisV, klass, env) + val fun = Fun(arg.tree, thisV, klass) Result(fun, Nil) else eval(arg.tree, thisV, klass) @@ -563,8 +466,8 @@ class Objects { ref match case Select(supert: Super, _) => val SuperType(thisTp, superTp) = supert.tpe - val thisValue2 = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) - Result(thisValue2, argsErrors).call(ref.symbol, argsValues, superTp, expr) + val res = resolveThis(thisTp.classSymbol.asClass, thisV, klass, ref) + (res ++ argsErrors).call(ref.symbol, argsValues, superTp, expr) case Select(qual, _) => val res = eval(qual, thisV, klass) ++ argsErrors @@ -575,9 +478,9 @@ class Objects { case TermRef(NoPrefix, _) => // resolve this for the local method val enclosingClass = id.symbol.owner.enclosingClass.asClass - val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) + val res = resolveThis(enclosingClass, thisV, klass, id) // local methods are not a member, but we can reuse the method `call` - Result(thisValue2, argsErrors).call(id.symbol, argsValues, superType = NoType, expr) + (res ++ argsErrors).call(id.symbol, argsValues, superType = NoType, expr) case TermRef(prefix, _) => val res = cases(prefix, thisV, klass, id) ++ argsErrors res.call(id.symbol, argsValues, superType = NoType, source = expr) @@ -610,11 +513,11 @@ class Objects { case closureDef(ddef) => val params = ddef.termParamss.head.map(_.symbol) - val value = Fun(ddef.rhs, params, thisV, klass, env) + val value = Fun(ddef.rhs, thisV, klass) Result(value, Nil) case PolyFun(body) => - val value = Fun(body, Nil, thisV, klass, env) + val value = Fun(body, thisV, klass) Result(value, Nil) case Block(stats, expr) => @@ -709,19 +612,17 @@ class Objects { Result(Bottom, Errors.empty) case tmref: TermRef if tmref.prefix == NoPrefix => - // - only var definitions are cold, it means they are not inspected - // - look up parameters from environment + // - params and var definitions are abstract by its type // - evaluate the rhs of the local definition for val definitions: they are already cached val sym = tmref.symbol - if sym.is(Flags.Param) then Result(env.lookup(sym), Nil) - else if sym.info <:< defn.StringType || sym.info.classSymbol.isPrimitiveValueClass then Result(Bottom, Nil) - else if sym.is(Flags.Mutable) then Result(TypeAbs(sym.info), Nil) + if sym.isOneOf(Flags.Param | Flags.Mutable) then Result(TypeAbs(sym.info), Nil) else if sym.is(Flags.Package) then Result(Bottom, Nil) else if sym.hasSource then val rhs = sym.defTree.asInstanceOf[ValDef].rhs eval(rhs, thisV, klass, cacheResult = true) else - Result(Bottom, Nil) + // pattern-bound variables + Result(TypeAbs(sym.info), Nil) case tmref: TermRef => val sym = tmref.symbol @@ -738,8 +639,7 @@ class Objects { val res = Result(ObjectRef(sym.moduleClass.asClass), Nil) if elideObjectAccess then res else res.ensureAccess(klass, source) else - val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) - Result(value, Errors.empty) + resolveThis(tref.classSymbol.asClass, thisV, klass, source) case _: TermParamRef | _: RecThis => // possible from checking effects of types @@ -751,24 +651,14 @@ class Objects { } /** Resolve C.this that appear in `klass` */ - def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Value] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Value].show) { - if target == klass then thisV - else if target.is(Flags.Package) || target.isStaticOwner then Bottom - else - thisV match - case Bottom => Bottom - - case addr: Addr => - val obj = heap(addr) - val outerCls = klass.owner.enclosingClass.asClass - resolveThis(target, obj.outers(klass), outerCls, source) - case RefSet(refs) => - refs.map(ref => resolveThis(target, ref, klass, source)).join - case fun: Fun => - report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, source.srcPos) - TypeAbs(defn.AnyType) - case TypeAbs(tp) => TypeAbs(target.info) - case ClassAbs(tp) => TypeAbs(target.typeRef) + def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree, elideObjectAccess: Boolean = false): Contextual[Result] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Value].show) { + if target == klass then Result(thisV, Nil) + else if target.is(Flags.Package) then Result(Bottom, Nil) + else if target.isStaticObjectRef then + val res = Result(ObjectRef(target.moduleClass.asClass), Nil) + if target == klass || elideObjectAccess then res + else res.ensureAccess(klass, source) + else Result(TypeAbs(target.typeRef), Nil) } /** Compute the outer value that correspond to `tref.prefix` */ @@ -776,8 +666,7 @@ class Objects { val cls = tref.classSymbol.asClass if tref.prefix == NoPrefix then val enclosing = cls.owner.lexicallyEnclosingClass.asClass - val outerV = resolveThis(enclosing, thisV, klass, source) - Result(outerV, Errors.empty) + resolveThis(enclosing, thisV, klass, source, elideObjectAccess = cls.isStatic) else if cls.isAllOf(Flags.JavaInterface) then Result(Bottom, Nil) else @@ -789,12 +678,6 @@ class Objects { val paramsMap = tpl.constr.termParamss.flatten.map(vdef => vdef.name -> vdef.symbol).toMap - // init param fields - klass.paramGetters.foreach { acc => - printer.println(acc.show + " initialized") - thisV.updateField(acc, env.lookup(paramsMap(acc.name.toTermName))) - } - def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree): Unit = val cls = tref.classSymbol.asClass // update outer for super class @@ -890,5 +773,5 @@ class Objects { extension (sym: Symbol) def isStaticObjectRef(using Context) = - !sym.is(Flags.Package) && sym.is(Flags.Module) && sym.isStatic + sym.is(Flags.Module, butNot = Flags.Package) && sym.isStatic } diff --git a/tests/init/crash/by-name.scala b/tests/init/crash/by-name.scala new file mode 100644 index 000000000000..623d7af8335c --- /dev/null +++ b/tests/init/crash/by-name.scala @@ -0,0 +1,11 @@ +def time[A](f: => A): A = + val start = System.nanoTime + val res = f + val elapsed = (System.nanoTime - start) + res + +case class Foo(data: Int) + +object o: + val foo = time(Foo(3)) + println(foo.data) diff --git a/tests/init/neg/global-cycle8.scala b/tests/init/neg/global-cycle8.scala index f1b258e7c3ce..ff614d46c393 100644 --- a/tests/init/neg/global-cycle8.scala +++ b/tests/init/neg/global-cycle8.scala @@ -6,7 +6,7 @@ class B { val a = new A } -object O { // error +object O { val n: Int = 10 println(P.m) } @@ -16,6 +16,6 @@ object P { } object Q { - def bar(b: B) = b.a.foo() + def bar(b: B) = b.a.foo() // error } diff --git a/tests/init/neg/i11262.scala b/tests/init/neg/i11262.scala new file mode 100644 index 000000000000..5703dbe3394a --- /dev/null +++ b/tests/init/neg/i11262.scala @@ -0,0 +1,2 @@ +object A { val x: String = B.y } // error +object B { val y: String = A.x } diff --git a/tests/init/pos/i12544.scala b/tests/init/neg/i12544.scala similarity index 81% rename from tests/init/pos/i12544.scala rename to tests/init/neg/i12544.scala index e5e3c9a2ade2..ca969fe68e66 100644 --- a/tests/init/pos/i12544.scala +++ b/tests/init/neg/i12544.scala @@ -2,7 +2,7 @@ enum Enum: case Case object Enum: - object nested: + object nested: // error val a: Enum = Case val b: Enum = f(nested.a) From 344738fca372cd8b1c07a9908a78dad7bbfdbe34 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 13:40:10 +0200 Subject: [PATCH 28/33] Add back outer-sensitivity This helps reduce more than 20 errors: [error] -- Error: /Users/fliu/Documents/dotty/compiler/src/dotty/tools/dotc/core/StdNames.scala:665:34 [error] 665 | final val MINUS_STAR : N = "-*" [error] | ^^^^ [error] |Call method dotty.tools.dotc.core.StdNames.ScalaNames.this.fromString("-*") on a value with an unknown initialization. Calling trace: [error] | -> package object printing { [ package.scala:6 ] [error] | -> val AndTypePrec: Int = parsing.precedence(tpnme.raw.AMP) [ package.scala:11 ] [error] | -> object raw { [ StdNames.scala:649 ] --- .../tools/dotc/transform/init/Objects.scala | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 2b9ce81293b2..cd49cb48cece 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -49,7 +49,7 @@ class Objects { case class ObjectRef(klass: ClassSymbol) extends Addr /** An ClassAbs of class */ - case class ClassAbs(klass: ClassSymbol) extends Addr + case class ClassAbs(klass: ClassSymbol, outer: Value) extends Addr /** A function value */ case class Fun(expr: Tree, thisV: Value, klass: ClassSymbol) extends Value @@ -68,7 +68,7 @@ class Objects { * * Note: Object is NOT a value. */ - case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value]) + case class Objekt(klass: ClassSymbol, fields: mutable.Map[Symbol, Value], outers: mutable.Map[Symbol, Value]) /** Abstract heap stores abstract objects * @@ -93,7 +93,8 @@ class Objects { def updateField(field: Symbol, value: Value): Contextual[Unit] = heap(ref).fields(field) = value - def updateOuter(klass: ClassSymbol, value: Value): Contextual[Unit] = () + def updateOuter(klass: ClassSymbol, value: Value): Contextual[Unit] = + heap(ref).outers(klass) = value end extension } type Heap = Heap.Heap @@ -192,6 +193,11 @@ class Objects { case (RefSet(refs), b) => RefSet(b :: refs) case (a, b) => RefSet(a :: b :: Nil) + def widen(using Context): Value = a.match + case RefSet(refs) => refs.map(_.widen).join + case ClassAbs(klass, outer: ClassAbs) => ClassAbs(klass, TypeAbs(outer.klass.typeRef)) + case _ => a + extension (values: Seq[Value]) def join: Value = if values.isEmpty then Bottom @@ -227,7 +233,7 @@ class Objects { eval(rhs, addr, target.owner.asClass, cacheResult = true) else given Trace = trace1 - val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty) + val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty, mutable.Map.empty) if obj.fields.contains(target) then Result(obj.fields(target), Nil) else if target.is(Flags.ParamAccessor) then @@ -318,14 +324,15 @@ class Objects { case Bottom | _: TypeAbs | _: ClassAbs | _: ObjectRef => given Trace = trace1 + val outer = value.widen val addr = if klass.isStaticObjectRef then ObjectRef(klass) else - ClassAbs(klass) + ClassAbs(klass, outer) if !heap.contains(addr) then - val obj = Objekt(klass, fields = mutable.Map.empty) + val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) heap.update(addr, obj) val res = addr.call(ctor, args, superType = NoType, source) @@ -658,7 +665,26 @@ class Objects { val res = Result(ObjectRef(target.moduleClass.asClass), Nil) if target == klass || elideObjectAccess then res else res.ensureAccess(klass, source) - else Result(TypeAbs(target.typeRef), Nil) + else + thisV match + case Bottom => Result(Bottom, Nil) + case addr: Addr => + val obj = heap(addr) + val outerCls = klass.owner.enclosingClass.asClass + if !obj.outers.contains(klass) then + val error = PromoteError("outer not yet initialized, target = " + target + ", klass = " + klass, source, trace.toVector) + report.error(error.show + error.stacktrace, source) + Result(Bottom, Nil) + else + resolveThis(target, obj.outers(klass), outerCls, source) + case RefSet(refs) => + val ress = refs.map(ref => resolveThis(target, ref, klass, source)) + Result(ress.map(_.value).join, ress.flatMap(_.errors)) + case fun: Fun => + report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, source.srcPos) + Result(TypeAbs(defn.AnyType), Nil) + case TypeAbs(tp) => + Result(TypeAbs(target.info), Nil) } /** Compute the outer value that correspond to `tref.prefix` */ From 32df0d1a91d6761ebddbd6350ee6b83e5fc75896 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 13:57:32 +0200 Subject: [PATCH 29/33] Handle outers of trait as the same in concrete semantics For traits, its outers will be proxy methods of the class that extends the trait. As the prefix is stable and is a valid value before any super constructor calls. Therefore, we may think the outers for traits are immediately set following the class parameters. Also, when trying promotion of warm values, we never try warm values whose fields are not fully filled -- which corresponds to promote ThisRef with non-initialized fields, and errors will be reported when the class is checked separately. --- .../tools/dotc/transform/init/Objects.scala | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index cd49cb48cece..500648431b74 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -135,12 +135,13 @@ class Objects { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors - def ensureAccess(klass: ClassSymbol, source: Tree): Contextual[Result] = + def ensureAccess(klass: ClassSymbol, source: Tree): Contextual[Result] = log("ensure access " + value.show, printer) { value match case obj: ObjectRef => if obj.klass == klass then this else obj.access(source) ++ errors case _ => this + } } /** The state that threads through the interpreter */ @@ -233,7 +234,7 @@ class Objects { eval(rhs, addr, target.owner.asClass, cacheResult = true) else given Trace = trace1 - val obj = if heap.contains(addr) then heap(addr) else Objekt(addr.klass, mutable.Map.empty, mutable.Map.empty) + val obj = heap(addr) if obj.fields.contains(target) then Result(obj.fields(target), Nil) else if target.is(Flags.ParamAccessor) then @@ -658,7 +659,7 @@ class Objects { } /** Resolve C.this that appear in `klass` */ - def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree, elideObjectAccess: Boolean = false): Contextual[Result] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Value].show) { + def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree, elideObjectAccess: Boolean = false): Contextual[Result] = log("resolving " + target.show + ", this = " + thisV.show + " in " + klass.show, printer, res => res.asInstanceOf[Result].show) { if target == klass then Result(thisV, Nil) else if target.is(Flags.Package) then Result(Bottom, Nil) else if target.isStaticObjectRef then @@ -704,7 +705,8 @@ class Objects { val paramsMap = tpl.constr.termParamss.flatten.map(vdef => vdef.name -> vdef.symbol).toMap - def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree): Unit = + type Handler = (() => Unit) => Unit + def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree, handler: Handler): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) @@ -713,13 +715,17 @@ class Objects { // follow constructor if cls.hasSource then - use(trace.add(source)) { - val res2 = thisV.call(ctor, args, superType = NoType, source) - errorBuffer ++= res2.errors + handler { () => + use(trace.add(source)) { + val res2 = thisV.call(ctor, args, superType = NoType, source) + errorBuffer ++= res2.errors + } } + else + handler { () => () } // parents - def initParent(parent: Tree) = parent match { + def initParent(parent: Tree, handler: Handler) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } val resArgs = evalArgs(argss.flatten, thisV, klass) @@ -727,7 +733,7 @@ class Objects { val argsErrors = resArgs.flatMap(_.errors) errorBuffer ++= argsErrors - superCall(tref, ctor, argsValues, tree) + superCall(tref, ctor, argsValues, tree, handler) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) val resArgs = evalArgs(argss.flatten, thisV, klass) @@ -735,31 +741,31 @@ class Objects { val argsErrors = resArgs.flatMap(_.errors) errorBuffer ++= argsErrors - superCall(tref, ctor, argsValues, tree) + superCall(tref, ctor, argsValues, tree, handler) case _ => // extends A or extends A[T] val tref = typeRefOf(parent.tpe) - superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent) + superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent, handler) } // see spec 5.1 about "Template Evaluation". // https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html if !klass.is(Flags.Trait) then + // outers are set first + val tasks = new mutable.ArrayBuffer[() => Unit] + val handler: Handler = task => tasks.append(task) + // 1. first init parent class recursively // 2. initialize traits according to linearization order val superParent = tpl.parents.head val superCls = superParent.tpe.classSymbol.asClass - initParent(superParent) - - // Access to the object possible after this point - if klass.isStaticOwner then - thisV.updateField(klass, thisV) + initParent(superParent, handler) val parents = tpl.parents.tail val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) mixins.reverse.foreach { mixin => parents.find(_.tpe.classSymbol == mixin) match - case Some(parent) => initParent(parent) + case Some(parent) => initParent(parent, handler) case None => // According to the language spec, if the mixin trait requires // arguments, then the class must provide arguments to it explicitly @@ -770,9 +776,21 @@ class Objects { // term arguments to B. That can only be done in a concrete class. val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor) val ctor = tref.classSymbol.primaryConstructor - if ctor.exists then superCall(tref, ctor, Nil, superParent) + if ctor.exists then superCall(tref, ctor, Nil, superParent, handler) } + // initialize super classes after outers are set + // 1. first call super class constructor + // 2. make the object accessible + // 3. call mixin initializations + tasks.head() + + // Access to the object possible after this point + if klass.isStaticOwner then + thisV.updateField(klass, thisV) + + tasks.tail.foreach(task => task()) + // class body tpl.body.foreach { From bfeaf4e70f17ac8e6d1a8e9f6ed63fae7308d290 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 15:23:17 +0200 Subject: [PATCH 30/33] Refactor (thanks @michelou) --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 500648431b74..531ed8801da9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -671,12 +671,12 @@ class Objects { case Bottom => Result(Bottom, Nil) case addr: Addr => val obj = heap(addr) - val outerCls = klass.owner.enclosingClass.asClass if !obj.outers.contains(klass) then val error = PromoteError("outer not yet initialized, target = " + target + ", klass = " + klass, source, trace.toVector) report.error(error.show + error.stacktrace, source) Result(Bottom, Nil) else + val outerCls = klass.owner.enclosingClass.asClass resolveThis(target, obj.outers(klass), outerCls, source) case RefSet(refs) => val ress = refs.map(ref => resolveThis(target, ref, klass, source)) From 725bc3884609ebc0fcd9b21c3ba4be347433f846 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 16:18:36 +0200 Subject: [PATCH 31/33] Avoid widening to an address which may not exist --- .../tools/dotc/transform/init/Objects.scala | 2 +- tests/init/crash/t6888.scala | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/init/crash/t6888.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 531ed8801da9..bd9312bfcfca 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -196,7 +196,7 @@ class Objects { def widen(using Context): Value = a.match case RefSet(refs) => refs.map(_.widen).join - case ClassAbs(klass, outer: ClassAbs) => ClassAbs(klass, TypeAbs(outer.klass.typeRef)) + case ClassAbs(klass, _: ClassAbs) => TypeAbs(klass.typeRef) case _ => a extension (values: Seq[Value]) diff --git a/tests/init/crash/t6888.scala b/tests/init/crash/t6888.scala new file mode 100644 index 000000000000..d339f840c86f --- /dev/null +++ b/tests/init/crash/t6888.scala @@ -0,0 +1,19 @@ +class C { + val x = 1 + object `$` { + val y = x + x + class abc$ { + def xy = x + y + } + object abc$ { + def xy = x + y + } + } +} + +object Test extends App { + val c = new C() + println(c.`$`.y) + println(c.`$`.abc$.xy) + println(new c.`$`.abc$().xy) +} From 6b2d25890a2e85296a7d0dcf43d92d88f168d752 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 17:05:24 +0200 Subject: [PATCH 32/33] Avoid forcing symbol loaded from libraries in -Ycheck This is safe because those symbols are already checked when they are compiled. This avoids cycles in reading tasty. --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 8972a1e12ddd..a9dae3edba11 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -71,7 +71,7 @@ class TreeChecker extends Phase with SymTransformer { if (sym.isClass && !sym.isAbsent()) { val validSuperclass = sym.isPrimitiveValueClass || defn.syntheticCoreClasses.contains(sym) || - (sym eq defn.ObjectClass) || sym.isOneOf(NoSuperClassFlags) || (sym.asClass.superClass.exists) || + (sym eq defn.ObjectClass) || sym.isOneOf(NoSuperClassFlags) || (!sym.isCompleted || sym.asClass.superClass.exists) || sym.isRefinementClass assert(validSuperclass, i"$sym has no superclass set") From 97cfc55b753fff2e7881e14627cf4b65405d8c79 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 22:17:42 +0200 Subject: [PATCH 33/33] Code cleanup --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index bd9312bfcfca..437880d026c6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -798,10 +798,7 @@ class Objects { val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors val sym = vdef.symbol - val fieldV = - if sym.info <:< defn.StringType || sym.info.classSymbol.isPrimitiveValueClass then Bottom - else if sym.is(Flags.Mutable) then TypeAbs(sym.info) - else res.value + val fieldV = if sym.is(Flags.Mutable) then TypeAbs(sym.info) else res.value thisV.updateField(sym, fieldV) case _: MemberDef =>