diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 2c92f446047f..458a55199cb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -49,8 +49,11 @@ class Checker extends MiniPhase { import semantic._ val tpl = tree.rhs.asInstanceOf[Template] val thisRef = ThisRef(cls) - val heap = Objekt(cls, fields = mutable.Map.empty) - val res = eval(tpl, thisRef, cls)(using heap, ctx, Vector.empty) + val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map(cls -> Hot)) + given Promoted = Promoted.empty + given Trace = Trace.empty + heap.update(thisRef, obj) + val res = eval(tpl, thisRef, cls) res.errors.foreach(_.issue) } diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index ccbdac812342..630d10703dff 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -65,43 +65,96 @@ class Semantic { /** 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 Value + 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 Value + case class Warm(klass: ClassSymbol, outer: Value) extends Addr /** A function value */ - case class Fun(expr: Tree, thisV: ThisRef | Warm, klass: ClassSymbol) extends 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[Warm | Fun | ThisRef]) extends Value + case class RefSet(refs: List[Fun | Addr]) extends Value // end of value definition - /** The current object under initialization + /** 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, val fields: mutable.Map[Symbol, Value]) { - val promotedValues = mutable.Set.empty[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`. */ - type Heap = Objekt - def heap(using h: Heap): Heap = h + 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) + /** Update field value of the abstract object + * + * Invariant: fields are immutable and only set once from `init` + */ + def updateField(field: Symbol, value: Value): Contextual[Unit] = + heap(ref).fields(field) = value + + /** Update the immediate outer of the given `klass` of the abstract object + * + * Invariant: outers are immutable and only set once from `init` + */ + 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 * @@ -159,14 +212,24 @@ class Semantic { } /** The state that threads through the interpreter */ - type Contextual[T] = (Heap, Context, Trace) ?=> T + type Contextual[T] = (Context, Trace, Promoted) ?=> T // ----- Error Handling ----------------------------------- - type Trace = Vector[Tree] - def trace(using t: Trace): Trace = t - extension (trace: Trace) - def add(node: Tree): Trace = trace :+ node + 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) @@ -194,32 +257,39 @@ class Semantic { else values.reduce { (v1, v2) => v1.join(v2) } extension (value: Value) - def select(field: Symbol, source: Tree): Contextual[Result] = + 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) + val error = AccessCold(field, source, trace.toVector) Result(Hot, error :: Nil) - case thisRef: ThisRef => - val target = resolve(thisRef.klass, field) - if target.is(Flags.Lazy) then value.call(target, superType = NoType, source) - else if heap.fields.contains(target) then - Result(heap.fields(target), Nil) - else - val error = AccessNonInit(target, trace.add(source)) - Result(Hot, error :: Nil) - - case warm: Warm => - val target = resolve(warm.klass, field) - if target.hasSource then - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, warm, target.owner.asClass, cacheResult = true) + 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 error = CallUnknown(field, source, trace) - Result(Hot, error :: Nil) + 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 => ??? @@ -231,62 +301,43 @@ class Semantic { Result(value2, errors) } - def call(meth: Symbol, superType: Type, source: Tree): Contextual[Result] = + 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) + val error = CallCold(meth, source, trace.toVector) Result(Hot, error :: Nil) - case thisRef: ThisRef => + case addr: Addr => val target = - if superType.exists then - resolveSuper(thisRef.klass, superType, meth) + if !needResolve then + meth + else if superType.exists then + resolveSuper(addr.klass, superType, meth) else - resolve(thisRef.klass, meth) + 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 cls = target.owner.asClass val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, thisRef, cls, cacheResult = true)(using heap, ctx, trace.add(tpl)) + eval(tpl, addr, cls, cacheResult = true)(using ctx, trace.add(cls.defTree), promoted) else val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, thisRef, target.owner.asClass, cacheResult = true) - else if thisRef.canIgnoreMethodCall(target) then + eval(rhs, addr, cls, cacheResult = true) + else if addr.canIgnoreMethodCall(target) then Result(Hot, Nil) else - val error = CallUnknown(target, source, trace) + val error = CallUnknown(target, source, trace.toVector) Result(Hot, error :: Nil) - else if heap.fields.contains(target) then - Result(heap.fields(target), Nil) else - val error = AccessNonInit(target, trace.add(source)) - Result(Hot, error :: Nil) - - case warm: Warm => - val target = - if superType.exists then - resolveSuper(warm.klass, superType, meth) - else - resolve(warm.klass, meth) - if target.is(Flags.Param) then - Result(Hot, Nil) - else if target.hasSource then - if target.isPrimaryConstructor then - val cls = target.owner.asClass - val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, warm, cls, cacheResult = true) + val obj = heap(addr) + if obj.fields.contains(target) then + Result(obj.fields(target), Nil) else - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, warm, target.owner.asClass, cacheResult = true) - else if warm.canIgnoreMethodCall(target) then - Result(Hot, Nil) - else - val error = CallUnknown(target, source, trace) - Result(Hot, error :: Nil) + value.select(target, source, needResolve = false) case Fun(body, thisV, klass) => // meth == NoSymbol for poly functions @@ -307,18 +358,19 @@ class Semantic { Result(Hot, Errors.empty) case Cold => - val error = CallCold(ctor, source, trace) + val error = CallCold(ctor, source, trace.toVector) Result(Hot, error :: Nil) - case thisRef: ThisRef => - val value = Warm(klass, outer = thisRef) - val res = value.call(ctor, superType = NoType, source) - Result(value, res.errors) - - case warm: Warm => + case addr: Addr => // widen the outer to finitize addresses - val outer = if warm.outer.isInstanceOf[Warm] then warm.copy(outer = Cold) else warm + 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) @@ -333,13 +385,6 @@ class Semantic { } end extension - extension (ref: ThisRef | Warm) - def updateField(field: Symbol, value: Value): Contextual[Unit] = - ref match - case thisRef: ThisRef => heap.fields(field) = value - case warm: Warm => // ignore - end extension - // ----- Promotion ---------------------------------------------------- extension (value: Value) @@ -350,31 +395,32 @@ class Semantic { * * This is a fast track for early promotion of values. */ - def canPromoteExtrinsic(using Heap, Context): Boolean = + def canPromoteExtrinsic: Contextual[Boolean] = value match case Hot => true case Cold => false case warm: Warm => warm.outer.canPromoteExtrinsic && { - heap.promotedValues += warm + promoted.add(warm) true } case thisRef: ThisRef => - heap.promotedValues.contains(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) || heap.fields.contains(sym) + sym.isOneOf(Flags.Lazy | Flags.Deferred) || obj.fields.contains(sym) } - if allFieldsInitialized then heap.promotedValues += thisRef + if allFieldsInitialized then promoted.add(thisRef) allFieldsInitialized } case fun: Fun => fun.thisV.canPromoteExtrinsic && { - heap.promotedValues += fun + promoted.add(fun) true } @@ -388,32 +434,32 @@ class Semantic { value match case Hot => Nil - case Cold => PromoteError(msg, source, trace) :: Nil + case Cold => PromoteError(msg, source, trace.toVector) :: Nil case thisRef: ThisRef => - if heap.promotedValues.contains(thisRef) then Nil + if promoted.contains(thisRef) then Nil else if thisRef.canPromoteExtrinsic then Nil - else PromoteError(msg, source, trace) :: Nil + else PromoteError(msg, source, trace.toVector) :: Nil case warm: Warm => - if heap.promotedValues.contains(warm) then Nil + if promoted.contains(warm) then Nil else if warm.canPromoteExtrinsic then Nil else { - heap.promotedValues += warm + promoted.add(warm) val errors = warm.tryPromote(msg, source) - if errors.nonEmpty then heap.promotedValues -= warm + if errors.nonEmpty then promoted.remove(warm) errors } case fun @ Fun(body, thisV, klass) => - if heap.promotedValues.contains(fun) then Nil + 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, res.errors ++ errors2) :: Nil + UnsafePromotion(msg, source, trace.toVector, res.errors ++ errors2) :: Nil else - heap.promotedValues += fun + promoted.add(fun) Nil case RefSet(refs) => @@ -441,7 +487,7 @@ class Semantic { 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) :: Nil + return PromoteError(msg, source, trace.toVector) :: Nil val fields = classRef.fields val methods = classRef.membersBasedOnFlags(Flags.Method, Flags.Deferred | Flags.Accessor) @@ -450,31 +496,39 @@ class Semantic { 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) - buffer ++= res.ensureHot(msg, source)(using heap, ctx, trace.add(f.defTree)).errors + 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 res = warm.call(m, superType = NoType, source = source)(using heap, ctx, trace.add(m.defTree)) - buffer ++= res.ensureHot(msg, source)(using heap, ctx, trace.add(m.defTree)).errors + 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, buffer.toList) :: Nil + else UnsafePromotion(msg, source, trace.toVector, buffer.toList) :: Nil } end extension // ----- Policies ------------------------------------------------------ - extension (value: Warm | ThisRef) + extension (value: Addr) /** Can the method call on `value` be ignored? - * - * Note: assume overriding resolution has been performed. - */ + * + * Note: assume overriding resolution has been performed. + */ def canIgnoreMethodCall(meth: Symbol)(using Context): Boolean = val cls = meth.owner cls == defn.AnyClass || @@ -498,7 +552,7 @@ class Semantic { * * This method only handles cache logic and delegates the work to `cases`. */ - 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) { + 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 { @@ -514,19 +568,16 @@ class Semantic { } /** Evaluate a list of expressions */ - def eval(exprs: List[Tree], thisV: Value, klass: ClassSymbol): Contextual[List[Result]] = + 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: Value, klass: ClassSymbol): Contextual[List[Error]] = + def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = val ress = args.map { arg => val res = if arg.isByName then - thisV match - case obj: (ThisRef | Warm) => - val value = Fun(arg.tree, obj, klass) - Result(value, Nil) - case _ => ??? // impossible + val fun = Fun(arg.tree, thisV, klass) + Result(fun, Nil) else eval(arg.tree, thisV, klass) @@ -538,7 +589,7 @@ class Semantic { * * Note: Recursive call should go to `eval` instead of `cases`. */ - def cases(expr: Tree, thisV: Value, klass: ClassSymbol): Contextual[Result] = + def cases(expr: Tree, thisV: Addr, klass: ClassSymbol): Contextual[Result] = expr match { case Ident(nme.WILDCARD) => // TODO: disallow `var x: T = _` @@ -554,7 +605,11 @@ class Semantic { val cls = tref.classSymbol.asClass val res = outerValue(tref, thisV, klass, tpt) - (res ++ errors).instantiate(cls, ctor, expr)(using heap, ctx, trace.add(expr)) + val trace2 = trace.add(expr) + locally { + given Trace = trace2 + (res ++ errors).instantiate(cls, ctor, expr) + } case Call(ref, argss) => // check args @@ -566,11 +621,11 @@ class Semantic { 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 heap, ctx, trace2) + 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 heap, ctx, trace2) + res.call(ref.symbol, superType = NoType, source = expr)(using ctx, trace2) case id: Ident => id.tpe match @@ -578,14 +633,11 @@ class Semantic { // resolve this for the local method val enclosingClass = id.symbol.owner.enclosingClass.asClass val thisValue2 = resolveThis(enclosingClass, thisV, klass, id) - thisValue2 match - case Hot => Result(Hot, errors) - case _ => - val rhs = id.symbol.defTree.asInstanceOf[DefDef].rhs - eval(rhs, thisValue2, enclosingClass, cacheResult = true)(using heap, ctx, trace2) + // 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 heap, ctx, trace2) + res.call(id.symbol, superType = NoType, source = expr)(using ctx, trace2) case Select(qualifier, name) => eval(qualifier, thisV, klass).select(expr.symbol, expr) @@ -612,25 +664,12 @@ class Semantic { eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) case closureDef(ddef) => - thisV match - case obj: (ThisRef | Warm) => - val value = Fun(ddef.rhs, obj, klass) - Result(value, Nil) - case _ => - // The reason is that we never evaluate an expression if `thisV` is - // Cold. And `thisV` can never be `Fun`. - report.warning("Unexpected branch reached. this = " + thisV.show, expr.srcPos) - Result(Hot, Nil) + val value = Fun(ddef.rhs, thisV, klass) + Result(value, Nil) case PolyFun(body) => - thisV match - case obj: (ThisRef | Warm) => - val value = Fun(body, obj, klass) - Result(value, Nil) - case _ => - // See the comment for the case above - report.warning("Unexpected branch reached. this = " + thisV.show, expr.srcPos) - Result(Hot, Nil) + val value = Fun(body, thisV, klass) + Result(value, Nil) case Block(stats, expr) => val ress = eval(stats, thisV, klass) @@ -703,9 +742,7 @@ class Semantic { else Result(Hot, checkTermUsage(tdef.rhs, thisV, klass)) case tpl: Template => - thisV match - case value: (ThisRef | Warm) => init(tpl, value, klass) - case _ => ??? // impossible + init(tpl, thisV, klass) case _: Import | _: Export => Result(Hot, Errors.empty) @@ -715,7 +752,7 @@ class Semantic { } /** 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) { + 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) @@ -744,40 +781,36 @@ class Semantic { /** 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 && target != heap.klass then Hot + else if target.is(Flags.Package) || target.isStaticOwner then Hot else thisV match - case Hot | _: ThisRef => Hot - case warm: Warm => - // use existing type information as a shortcut - val tref = typeRefOf(warm.klass.typeRef.baseType(klass)) - if tref.prefix == NoPrefix then - // Current class is local, in the enclosing scope of `warm.klass` - val outerCls = warm.klass.owner.enclosingClass.asClass - resolveThis(target, warm.outer, outerCls, source) - else - val outerCls = klass.owner.enclosingClass.asClass - val warmOuterCls = warm.klass.owner.enclosingClass.asClass - val res = cases(tref.prefix, warm.outer, warmOuterCls, source) - assert(res.errors.isEmpty, "unexpected error " + res) - resolveThis(target, res.value, outerCls, source) - case _ => - // report.error("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, source.srcPos) + case Hot => Hot + 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) Cold + case Cold => Cold + } /** Compute the outer value that correspond to `tref.prefix` */ - def outerValue(tref: TypeRef, thisV: Value, klass: ClassSymbol, source: Tree): Contextual[Result] = + 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 - cases(tref.prefix, thisV, klass, source) + 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: ThisRef | Warm, klass: ClassSymbol): Contextual[Result] = log("init " + klass.show, printer, res => res.asInstanceOf[Result].show) { + 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 @@ -791,11 +824,13 @@ class Semantic { def superCall(tref: TypeRef, ctor: Symbol, source: Tree): Unit = val cls = tref.classSymbol.asClass // update outer for super class - // ignored as they are all hot + 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 heap, ctx, trace.add(source)) + val res2 = thisV.call(ctor, superType = NoType, source)(using ctx, trace.add(source)) errorBuffer ++= res2.errors // parents @@ -862,7 +897,7 @@ class Semantic { * * This is intended to avoid type soundness issues in Dotty. */ - def checkTermUsage(tpt: Tree, thisV: Value, klass: ClassSymbol): Contextual[List[Error]] = + 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 { diff --git a/tests/init/neg/inner-loop.scala b/tests/init/neg/inner-loop.scala index c6d5c615580c..d2b6a1fae8e0 100644 --- a/tests/init/neg/inner-loop.scala +++ b/tests/init/neg/inner-loop.scala @@ -1,6 +1,6 @@ class Outer { outer => class Inner extends Outer { - val x = 5 + outer.n + val x = 5 + outer.n // error } val inner = new Inner val n = 6 // error diff --git a/tests/init/neg/java1.scala b/tests/init/neg/java1.scala new file mode 100644 index 000000000000..1eff09cb6ff1 --- /dev/null +++ b/tests/init/neg/java1.scala @@ -0,0 +1,11 @@ +import java.util.Spliterator +import java.util.function.Consumer + +class A extends Spliterator.OfDouble: + def characteristics() = 10 + def estimateSize() = 10 + def trySplit() = ??? + def tryAdvance(x$0: java.util.function.DoubleConsumer): Boolean = false + + val m = n + 1 + val n = 10 // error diff --git a/tests/init/neg/trait1.scala b/tests/init/neg/trait1.scala new file mode 100644 index 000000000000..48eae248fee7 --- /dev/null +++ b/tests/init/neg/trait1.scala @@ -0,0 +1,10 @@ +abstract class A(x: Int) { + def foo(): Unit + foo() +} + +trait B(val y: Int) // error + +class C extends A(10) with B(20) { + def foo(): Unit = println(y) +} \ No newline at end of file diff --git a/tests/init/neg/trait2.scala b/tests/init/neg/trait2.scala new file mode 100644 index 000000000000..6011c259b73f --- /dev/null +++ b/tests/init/neg/trait2.scala @@ -0,0 +1,14 @@ +abstract class A(x: Int) { + def foo(): Unit + foo() +} + +trait B(val y: Int) // error + +class D { + class C extends A(10) with B(20) { + def foo(): Unit = println(y) + } + + val c = new C +} \ No newline at end of file