From 8cdb1ed60960bb646bfb5b6c638eb4bd9608c980 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 00:20:31 +0200 Subject: [PATCH 01/36] Add definition --- .../tools/dotc/transform/init/Semantic.scala | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 28228f845590..e44912a6ed16 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -77,10 +77,10 @@ class Semantic { * * We need to restrict nesting levels of `outer` to finitize the domain. */ - case class Warm(klass: ClassSymbol, outer: Value) extends Addr + case class Warm(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, params: List[Symbol], thisV: Addr, klass: ClassSymbol, env: Env) extends Value /** A value which represents a set of addresses * @@ -138,6 +138,32 @@ class Semantic { import Heap._ val heap: Heap = Heap.empty + /** 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 = env(sym) + + def getOrElse(sym: Symbol, default: Value)(using Context): Value = env.getOrElse(sym, default) + + def union(other: Env): Env = env ++ other + } + + type Env = Env.Env + def env(using env: Env) = env + + import Env._ + object Promoted { /** Values that have been safely promoted */ opaque type Promoted = mutable.Set[Value] @@ -251,11 +277,21 @@ class Semantic { case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) + def widen: Value = + a match + case RefSet(refs) => refs.map(_.widen).join + case _: Addr => Cold + 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 Hot 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 { From a4319557183a901322c23c3b8b644c83cb5ca982 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 00:46:54 +0200 Subject: [PATCH 02/36] Add doc for env --- compiler/src/dotty/tools/dotc/transform/init/Semantic.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index e44912a6ed16..e4f0a480587c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -138,7 +138,10 @@ class Semantic { import Heap._ val heap: Heap = Heap.empty - /** The environment for method parameters */ + /** The environment for method parameters + * + * For performance and usability, we restrict parameters to be either `Cold` or `Hot`. + */ object Env { opaque type Env = Map[Symbol, Value] From 2a47d89511db032604e4c2dd58e53683030fe691 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 00:57:17 +0200 Subject: [PATCH 03/36] Fix compilation --- .../tools/dotc/transform/init/Checker.scala | 1 + .../tools/dotc/transform/init/Semantic.scala | 102 +++++++++--------- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 458a55199cb8..b4117e63a655 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -52,6 +52,7 @@ class Checker extends MiniPhase { val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map(cls -> Hot)) given Promoted = Promoted.empty given Trace = Trace.empty + given Env = Env.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 e4f0a480587c..56ae5a7a4c96 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -233,15 +233,15 @@ class Semantic { 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, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = + value.instantiate(klass, ctor, args, source) ++ errors } /** The state that threads through the interpreter */ - type Contextual[T] = (Context, Trace, Promoted) ?=> T + type Contextual[T] = (Env, Context, Trace, Promoted) ?=> T // ----- Error Handling ----------------------------------- @@ -282,9 +282,8 @@ class Semantic { def widen: Value = a match + case _: Addr | _: Fun => Cold case RefSet(refs) => refs.map(_.widen).join - case _: Addr => Cold - case Fun(e, params, thisV, klass, env) => Fun(e, params, thisV.widen, klass, env) case _ => a @@ -308,7 +307,7 @@ class Semantic { 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) + value.call(target, Nil, superType = NoType, source, needResolve = false) else val obj = heap(addr) if obj.fields.contains(target) then @@ -330,8 +329,9 @@ class Semantic { val error = AccessNonInit(target, trace.add(source).toVector) Result(Hot, error :: Nil) - case _: Fun => - ??? + case fun: Fun => + report.error("unexpected tree in selecting a function, fun = " + fun.expr.show, source) + Result(Hot, Nil) case RefSet(refs) => val resList = refs.map(_.select(field, source)) @@ -340,7 +340,7 @@ class Semantic { 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) @@ -362,7 +362,7 @@ class Semantic { 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(cls.defTree), promoted) + eval(tpl, addr, cls, cacheResult = true)(using env, ctx, trace.add(cls.defTree), promoted) else val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs eval(rhs, addr, cls, cacheResult = true) @@ -378,20 +378,20 @@ class Semantic { else value.select(target, source, needResolve = false) - case Fun(body, thisV, klass) => + case Fun(body, params, thisV, klass, env) => // 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 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) @@ -403,21 +403,22 @@ class Semantic { case addr: Addr => // widen the outer to finitize addresses val outer = addr match - case Warm(_, _: Warm) => Cold + case Warm(_, _: Warm, _, _) => Cold case _ => addr - val value = Warm(klass, outer) + val value = Warm(klass, outer, ctor, args.widen) 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) + val res = value.call(ctor, args, superType = NoType, source) Result(value, res.errors) - case Fun(body, thisV, klass) => - ??? // impossible + case Fun(body, params, thisV, klass, env) => + report.error("unexpected tree in instantiating a function, fun = " + body.show, source) + Result(Hot, Nil) 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) @@ -490,7 +491,7 @@ class Semantic { errors } - case fun @ Fun(body, thisV, klass) => + case fun @ Fun(body, params, thisV, klass, env) => if promoted.contains(fun) then Nil else val res = eval(body, thisV, klass) @@ -550,7 +551,8 @@ class Semantic { val trace2 = trace.add(m.defTree) locally { given Trace = trace2 - val res = warm.call(m, superType = NoType, source = source) + val args = m.info.paramInfoss.flatten.map(_ => Hot) + val res = warm.call(m, args, superType = NoType, source = source) buffer ++= res.ensureHot(msg, source).errors } buffer.nonEmpty @@ -611,18 +613,15 @@ class Semantic { exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ - def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[List[Error]] = + def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[(List[Error], List[Value])] = 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) + if arg.isByName then + val fun = Fun(arg.tree, Nil, thisV, klass, env) + Result(fun, Nil) + else + eval(arg.tree, thisV, klass) } - ress.flatMap(_.errors) + (ress.flatMap(_.errors), ress.map(_.value)) /** Handles the evaluation of different expressions * @@ -640,19 +639,19 @@ class Semantic { case NewExpr(tref, New(tpt), ctor, argss) => // check args - val errors = evalArgs(argss.flatten, thisV, klass) + val (errors, args) = 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) + (res ++ errors).instantiate(cls, ctor, args, expr) } case Call(ref, argss) => // check args - val errors = evalArgs(argss.flatten, thisV, klass) + val (errors, args) = evalArgs(argss.flatten, thisV, klass) val trace2: Trace = trace.add(expr) @@ -660,11 +659,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 ctx, trace2) + Result(thisValue2, errors).call(ref.symbol, args, superTp, expr)(using env, ctx, trace2) case Select(qual, _) => val res = eval(qual, thisV, klass) ++ errors - res.call(ref.symbol, superType = NoType, source = expr)(using ctx, trace2) + res.call(ref.symbol, args, superType = NoType, source = expr)(using env, ctx, trace2) case id: Ident => id.tpe match @@ -673,10 +672,10 @@ class Semantic { 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) + thisValue2.call(id.symbol, args, 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) + res.call(id.symbol, args, superType = NoType, source = expr)(using env, ctx, trace2) case Select(qualifier, name) => eval(qualifier, thisV, klass).select(expr.symbol, expr) @@ -703,11 +702,12 @@ class Semantic { eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) 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) => @@ -860,7 +860,7 @@ class Semantic { } } - 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) @@ -870,23 +870,25 @@ class Semantic { // follow constructor if cls.hasSource then printer.println("init super class " + cls.show) - val res2 = thisV.call(ctor, superType = NoType, source)(using ctx, trace.add(source)) + val res2 = thisV.call(ctor, args, superType = NoType, source)(using env, 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) + val (erros, args) = evalArgs(argss.flatten, thisV, klass) + errorBuffer ++= erros + superCall(tref, ctor, args, tree) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) - errorBuffer ++= evalArgs(argss.flatten, thisV, klass) - superCall(tref, ctor, tree) + val (erros, args) = evalArgs(argss.flatten, thisV, klass) + errorBuffer ++= erros + superCall(tref, ctor, args, 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". @@ -913,7 +915,7 @@ class Semantic { // 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 5a82985d50f5287e72c442d724bb436014bc5db2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 10:08:57 +0200 Subject: [PATCH 04/36] Refactor trace --- .../tools/dotc/transform/init/Semantic.scala | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 56ae5a7a4c96..e946d8e7fa3d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -306,8 +306,11 @@ class Semantic { 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 - value.call(target, Nil, superType = NoType, source, needResolve = false) + given Trace = trace1 + 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 @@ -357,12 +360,15 @@ class Semantic { resolveSuper(addr.klass, superType, meth) else resolve(addr.klass, meth) - if target.isOneOf(Flags.Method | Flags.Lazy) then + + if target.isOneOf(Flags.Method) then + val trace1 = trace.add(source) if target.hasSource then + given Trace = trace1 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 env, ctx, trace.add(cls.defTree), promoted) + eval(tpl, addr, cls, cacheResult = true) else val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs eval(rhs, addr, cls, cacheResult = true) @@ -392,15 +398,18 @@ class Semantic { /** 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 Hot => Result(Hot, Errors.empty) case Cold => - val error = CallCold(ctor, source, trace.toVector) + val error = CallCold(ctor, source, trace1.toVector) Result(Hot, error :: Nil) case addr: Addr => + given Trace = trace1 + // widen the outer to finitize addresses val outer = addr match case Warm(_, _: Warm, _, _) => Cold @@ -653,17 +662,15 @@ class Semantic { // check args val (errors, args) = 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, args, superTp, expr)(using env, ctx, trace2) + Result(thisValue2, errors).call(ref.symbol, args, superTp, expr) case Select(qual, _) => val res = eval(qual, thisV, klass) ++ errors - res.call(ref.symbol, args, superType = NoType, source = expr)(using env, ctx, trace2) + res.call(ref.symbol, args, superType = NoType, source = expr) case id: Ident => id.tpe match @@ -675,7 +682,7 @@ class Semantic { thisValue2.call(id.symbol, args, superType = NoType, expr, needResolve = false) case TermRef(prefix, _) => val res = cases(prefix, thisV, klass, id) ++ errors - res.call(id.symbol, args, superType = NoType, source = expr)(using env, ctx, trace2) + res.call(id.symbol, args, superType = NoType, source = expr) case Select(qualifier, name) => eval(qualifier, thisV, klass).select(expr.symbol, expr) @@ -870,7 +877,7 @@ class Semantic { // follow constructor if cls.hasSource then printer.println("init super class " + cls.show) - val res2 = thisV.call(ctor, args, superType = NoType, source)(using env, ctx, trace.add(source)) + val res2 = thisV.call(ctor, args, superType = NoType, source) errorBuffer ++= res2.errors // parents @@ -957,13 +964,14 @@ class Semantic { object Semantic { // ----- Utility methods and extractors -------------------------------- + inline def use[T, R](v: T)(inline op: T ?=> R): R = op(using v) 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 + type Arg = Tree | ByNameArg case class ByNameArg(tree: Tree) extension (arg: Arg) From 08f040c5f0a318094a66bf205781e3f4a9a74bca Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 10:58:14 +0200 Subject: [PATCH 05/36] Support Value.source for friendly error message --- .../tools/dotc/transform/init/Semantic.scala | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index e946d8e7fa3d..77b80ad71582 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -55,8 +55,29 @@ class Semantic { * V ⊑ R if V ∈ R * */ - sealed abstract class Value { + sealed abstract class Value extends Cloneable { def show: String = this.toString() + + /** Source code where the value originates from + * + * It is used for displaying friendly messages + */ + private var mySource: Tree = EmptyTree + + def source: Tree = mySource + + def attachSource(source: Tree): this.type = + assert(mySource.isEmpty, "Update existing source of value " + this) + mySource = source + this + + def withSource(source: Tree): Value = + if mySource.isEmpty then attachSource(source) + else { + val value2 = this.clone.asInstanceOf[Value] + value2.mySource = source + value2 + } } /** A transitively initialized object */ @@ -238,6 +259,8 @@ class Semantic { def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors + + def withSource(source: Tree): Result = Result(value.withSource(source), errors) } /** The state that threads through the interpreter */ @@ -626,9 +649,9 @@ class Semantic { val ress = args.map { arg => if arg.isByName then val fun = Fun(arg.tree, Nil, thisV, klass, env) - Result(fun, Nil) + Result(fun, Nil).withSource(arg.tree) else - eval(arg.tree, thisV, klass) + eval(arg.tree, thisV, klass).withSource(arg.tree) } (ress.flatMap(_.errors), ress.map(_.value)) From 632b7d34fa1d65f3462f9dd21b83c2d87edcae68 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 11:15:43 +0200 Subject: [PATCH 06/36] Disallow non-hot arguments to non-constructors --- .../dotty/tools/dotc/transform/init/Semantic.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 77b80ad71582..cbf3e7ac309c 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -376,6 +376,7 @@ class Semantic { Result(Hot, error :: Nil) case addr: Addr => + val isLocal = meth.owner.isClass val target = if !needResolve then meth @@ -389,12 +390,20 @@ class Semantic { if target.hasSource then given Trace = trace1 val cls = target.owner.enclosingClass.asClass + val ddef = target.defTree.asInstanceOf[DefDef] + val env2 = Env(ddef, args.widen) if target.isPrimaryConstructor then + given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] eval(tpl, addr, cls, cacheResult = true) + else if target.isConstructor then + given Env = env2 + eval(ddef.rhs, addr, cls, cacheResult = true) else - val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs - eval(rhs, addr, cls, cacheResult = true) + val errors = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } + use(Env.empty) { + eval(ddef.rhs, addr, cls, cacheResult = true) + } else if addr.canIgnoreMethodCall(target) then Result(Hot, Nil) else From 0faa404849ba483301ba20cfe50f4af7426faf86 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 11:48:59 +0200 Subject: [PATCH 07/36] Handle access of parameters in constructors --- .../tools/dotc/transform/init/Semantic.scala | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index cbf3e7ac309c..9da8905c60e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -181,6 +181,8 @@ class Semantic { def getOrElse(sym: Symbol, default: Value)(using Context): Value = env.getOrElse(sym, default) def union(other: Env): Env = env ++ other + + def isHot: Boolean = env.values.exists(_ != Hot) } type Env = Env.Env @@ -401,7 +403,7 @@ class Semantic { eval(ddef.rhs, addr, cls, cacheResult = true) else val errors = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } - use(Env.empty) { + use(if isLocal then env else Env.empty) { eval(ddef.rhs, addr, cls, cacheResult = true) } else if addr.canIgnoreMethodCall(target) then @@ -452,7 +454,11 @@ class Semantic { val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) heap.update(value, obj) val res = value.call(ctor, args, superType = NoType, source) - Result(value, res.errors) + + // Abstract instances of local classes inside 2nd constructor as Cold + // This way, we avoid complicating the domain for Warm unnecessarily + if !env.isHot then Result(Cold, res.errors) + else Result(value, res.errors) case Fun(body, params, thisV, klass, env) => report.error("unexpected tree in instantiating a function, fun = " + body.show, source) @@ -836,16 +842,29 @@ class Semantic { Result(Hot, Errors.empty) case tmref: TermRef if tmref.prefix == NoPrefix => - Result(Hot, Errors.empty) + val sym = tmref.symbol + + def default() = Result(Hot, Nil) + + if sym.is(Flags.Param) then + if sym.owner.isConstructor then + // instances of local classes inside secondary constructors cannot + // reach here, as those values are abstracted by Cold instead of Warm. + // This enables us to simplify the domain without sacrificing + // expressiveness nor soundess, as local classes inside secondary + // constructors are uncommon. + Result(env.lookup(sym), Nil) + else + default() + else + default() 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) + val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) + Result(value, Errors.empty) case _: TermParamRef | _: RecThis => // possible from checking effects of types @@ -891,15 +910,18 @@ class Semantic { 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 -> env.lookup(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 => + val value = paramsMap(acc.name.toTermName) + thisV.updateField(acc, value) + printer.println(acc.show + " initialized with " + value) } - def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree): Unit = + def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree)(using Env): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) @@ -913,7 +935,7 @@ class Semantic { errorBuffer ++= res2.errors // parents - def initParent(parent: Tree) = parent match { + def initParent(parent: Tree)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } val (erros, args) = evalArgs(argss.flatten, thisV, klass) @@ -933,6 +955,8 @@ class Semantic { // 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 + given Env = Env.empty + // 1. first init parent class recursively // 2. initialize traits according to linearization order val superParent = tpl.parents.head @@ -961,6 +985,7 @@ class Semantic { // class body tpl.body.foreach { case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) => + given Env = Env.empty val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors thisV.updateField(vdef.symbol, res.value) @@ -968,6 +993,7 @@ class Semantic { case _: MemberDef => case tree => + given Env = Env.empty errorBuffer ++= eval(tree, thisV, klass).errors } From 0af900a7ba4c04396dd282e3627338426ea9d4df Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 12:07:51 +0200 Subject: [PATCH 08/36] Handle exception --- .../src/dotty/tools/dotc/transform/init/Checker.scala | 9 +++++++-- .../src/dotty/tools/dotc/transform/init/Semantic.scala | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index b4117e63a655..998881e16d72 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -49,11 +49,16 @@ class Checker extends MiniPhase { import semantic._ val tpl = tree.rhs.asInstanceOf[Template] val thisRef = ThisRef(cls) + val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map(cls -> Hot)) + heap.update(thisRef, obj) + + val paramValues = tpl.constr.termParamss.flatten.map(param => param.symbol -> Hot).toMap + given Promoted = Promoted.empty given Trace = Trace.empty - given Env = Env.empty - heap.update(thisRef, obj) + given Env = Env(paramValues) + 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 9da8905c60e1..bb6620e278dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -73,6 +73,7 @@ class Semantic { def withSource(source: Tree): Value = if mySource.isEmpty then attachSource(source) + else if this == Hot || this == Cold then this else { val value2 = this.clone.asInstanceOf[Value] value2.mySource = source From f00b0c992dcb1c321b5844c123b41d3358b691c9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 12:13:30 +0200 Subject: [PATCH 09/36] No exceptions --- .../src/dotty/tools/dotc/transform/init/Semantic.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index bb6620e278dd..b640ac66ab8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -419,10 +419,14 @@ class Semantic { else value.select(target, source, needResolve = false) - case Fun(body, params, thisV, klass, env) => + 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)) From 746373bb3674f3ce0aa7c013268ac1437cbdbd1b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 12:18:11 +0200 Subject: [PATCH 10/36] Fix Env.isHot --- compiler/src/dotty/tools/dotc/transform/init/Semantic.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index b640ac66ab8d..8671ddc08ad6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -183,7 +183,7 @@ class Semantic { def union(other: Env): Env = env ++ other - def isHot: Boolean = env.values.exists(_ != Hot) + def isHot: Boolean = env.values.forall(_ == Hot) } type Env = Env.Env From 748edc15dc80832cc9ea905a7de6f7bf56f6d06f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 12:21:38 +0200 Subject: [PATCH 11/36] Add missing checks for arguments --- .../dotty/tools/dotc/transform/init/Semantic.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 8671ddc08ad6..afafb32d5eec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -370,13 +370,15 @@ class Semantic { } def call(meth: Symbol, args: List[Value], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = + def checkArgs = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } + value match { case Hot => - Result(Hot, Errors.empty) + Result(Hot, checkArgs) case Cold => val error = CallCold(meth, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Hot, error :: checkArgs) case addr: Addr => val isLocal = meth.owner.isClass @@ -403,15 +405,14 @@ class Semantic { given Env = env2 eval(ddef.rhs, addr, cls, cacheResult = true) else - val errors = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } use(if isLocal then env else Env.empty) { - eval(ddef.rhs, addr, cls, cacheResult = true) + eval(ddef.rhs, addr, cls, cacheResult = true) ++ checkArgs } else if addr.canIgnoreMethodCall(target) then Result(Hot, Nil) else val error = CallUnknown(target, source, trace.toVector) - Result(Hot, error :: Nil) + Result(Hot, error :: checkArgs) else val obj = heap(addr) if obj.fields.contains(target) then From bfaef00542b74f7d98256d61f4ba0870563d5881 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 13:07:36 +0200 Subject: [PATCH 12/36] Handle new expression with only cold args --- .../tools/dotc/transform/init/Semantic.scala | 25 ++++++++++++++++--- tests/init/neg/soundness6.scala | 4 +-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index afafb32d5eec..c7c1699d0d39 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -145,7 +145,9 @@ class Semantic { * Invariant: fields are immutable and only set once from `init` */ def updateField(field: Symbol, value: Value): Contextual[Unit] = - heap(ref).fields(field) = value + val fields = heap(ref).fields + assert(!fields.contains(field), field.show + " already init, new = " + value + ", ref =" + ref) + fields(field) = value /** Update the immediate outer of the given `klass` of the abstract object * @@ -400,7 +402,8 @@ class Semantic { if target.isPrimaryConstructor then given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - eval(tpl, addr, cls, cacheResult = true) + val res = eval(tpl, addr, cls, cacheResult = true) + Result(addr, res.errors) else if target.isConstructor then given Env = env2 eval(ddef.rhs, addr, cls, cacheResult = true) @@ -441,7 +444,21 @@ class Semantic { val trace1 = trace.add(source) value match { case Hot => - Result(Hot, Errors.empty) + val buffer = new mutable.ArrayBuffer[Error] + val args2 = args.map { arg => + val errors = arg.promote("May only use initialized value as arguments", arg.source) + buffer ++= errors + if errors.isEmpty then Hot + else arg.widen + } + if buffer.isEmpty then Result(Hot, Errors.empty) + else + val value = Warm(klass, Hot, ctor, args2) + if !heap.contains(value) then + val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> Hot)) + heap.update(value, obj) + val res = value.call(ctor, args, superType = NoType, source) + Result(res.value, res.errors) case Cold => val error = CallCold(ctor, source, trace1.toVector) @@ -990,7 +1007,7 @@ class Semantic { // class body tpl.body.foreach { - case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) => + case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty => given Env = Env.empty val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors diff --git a/tests/init/neg/soundness6.scala b/tests/init/neg/soundness6.scala index 09d55dba292c..a3f80df11d1b 100644 --- a/tests/init/neg/soundness6.scala +++ b/tests/init/neg/soundness6.scala @@ -1,5 +1,5 @@ class C(c: C) { - println(c.n) - val c2 = new C(this) // error + println(c.n) // error + val c2 = new C(this) val n = 10 } From c5475b42057d4d18a999a6795f6afd35b5dee12e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 13:11:44 +0200 Subject: [PATCH 13/36] Fix soundness test --- tests/init/neg/soundness1.scala | 26 ++++++++++++++++++++++++-- tests/init/neg/soundness2.scala | 3 ++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/init/neg/soundness1.scala b/tests/init/neg/soundness1.scala index 8e048a8f72d8..ac4cb2721ef5 100644 --- a/tests/init/neg/soundness1.scala +++ b/tests/init/neg/soundness1.scala @@ -1,7 +1,29 @@ class A(b: B) { - val b2 = new B(this) // error + val b2 = new B(this) } class B(a: A) { - val a2 = new A(this) // error + val a2 = new A(this) } + +object Test2: + class A(b: B) { + val b2 = new B(this) + val c = b2.a2 + } + + class B(a: A) { + val a2 = new A(this) + val c = a2.b2 + } + +object Test3: + class A(b: B) { + println(b.a2) // error + val b2 = new B(this) + } + + class B(a: A) { + println(a.b2) // error + val a2 = new A(this) + } diff --git a/tests/init/neg/soundness2.scala b/tests/init/neg/soundness2.scala index 9d460849c6b0..34d225e953de 100644 --- a/tests/init/neg/soundness2.scala +++ b/tests/init/neg/soundness2.scala @@ -1,3 +1,4 @@ class C(c: C) { - val c2 = new C(this) // error + val d = c.c2 // error + val c2 = new C(this) } From 1e98f5c22df2d9bc972eb22e54e6419a333cc14c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 13:30:27 +0200 Subject: [PATCH 14/36] Reorganize tests --- tests/init/{neg => pos}/features-doublelist.scala | 2 +- tests/init/{neg => pos}/lazylist1.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/init/{neg => pos}/features-doublelist.scala (58%) rename tests/init/{neg => pos}/lazylist1.scala (92%) diff --git a/tests/init/neg/features-doublelist.scala b/tests/init/pos/features-doublelist.scala similarity index 58% rename from tests/init/neg/features-doublelist.scala rename to tests/init/pos/features-doublelist.scala index a18b9ada9f10..77b4999bf4a6 100644 --- a/tests/init/neg/features-doublelist.scala +++ b/tests/init/pos/features-doublelist.scala @@ -1,6 +1,6 @@ class DoubleList { class Node(var prev: Node, var next: Node, data: Int) - object sentinel extends Node(sentinel, sentinel, 0) // error // error + object sentinel extends Node(sentinel, sentinel, 0) def insert(x: Int) = ??? } diff --git a/tests/init/neg/lazylist1.scala b/tests/init/pos/lazylist1.scala similarity index 92% rename from tests/init/neg/lazylist1.scala rename to tests/init/pos/lazylist1.scala index 75556d73aa74..3ff0f9bc449d 100644 --- a/tests/init/neg/lazylist1.scala +++ b/tests/init/pos/lazylist1.scala @@ -2,7 +2,7 @@ class LazyList[A] object LazyList { inline implicit def toDeferred[A](l: LazyList[A]): Deferred[A] = - new Deferred(l) // error + new Deferred(l) final class Deferred[A](l: => LazyList[A]) { def #:: [B >: A](elem: => B): LazyList[B] = ??? From 11d4325d3b45c3168066097d6c078910665b184f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 13:31:40 +0200 Subject: [PATCH 15/36] Add test --- tests/init/neg/cycle.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/init/neg/cycle.scala diff --git a/tests/init/neg/cycle.scala b/tests/init/neg/cycle.scala new file mode 100644 index 000000000000..55b7c28acbef --- /dev/null +++ b/tests/init/neg/cycle.scala @@ -0,0 +1,11 @@ +class A(x: B) { + println(x.b) // error + val a = new B(this) + val d = a.b +} + +class B(x: A) { + println(x.a) // error + val b = new A(this) + val d = b.a +} \ No newline at end of file From 74fa3a3a7aedc7bbf2d63a1ec8156d67c1bf37ff Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 14:02:29 +0200 Subject: [PATCH 16/36] Add test for secondary constructor --- .../tools/dotc/transform/init/Semantic.scala | 11 ++++++----- tests/init/neg/secondary-ctor.scala | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 tests/init/neg/secondary-ctor.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index c7c1699d0d39..1e721378c4fe 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -505,13 +505,13 @@ class Semantic { * * This is a fast track for early promotion of values. */ - def canPromoteExtrinsic: Contextual[Boolean] = + def canPromoteExtrinsic: Contextual[Boolean] = log("canPromoteExtrinsic " + value + ", promoted = " + promoted, printer) { value match case Hot => true case Cold => false case warm: Warm => - warm.outer.canPromoteExtrinsic && { + (warm.outer :: warm.args).forall(_.canPromoteExtrinsic) && { promoted.add(warm) true } @@ -537,10 +537,10 @@ class Semantic { case RefSet(refs) => refs.forall(_.canPromoteExtrinsic) - end canPromoteExtrinsic + } /** Promotion of values to hot */ - def promote(msg: String, source: Tree): Contextual[List[Error]] = + def promote(msg: String, source: Tree): Contextual[List[Error]] = log("promoting " + value + ", promoted = " + promoted, printer) { value match case Hot => Nil @@ -574,6 +574,7 @@ class Semantic { case RefSet(refs) => refs.flatMap(_.promote(msg, source)) + } end extension extension (warm: Warm) @@ -594,7 +595,7 @@ class Semantic { * 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) { + def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show + ", promoted = " + promoted, printer) { val classRef = warm.klass.appliedRef if classRef.memberClasses.nonEmpty then return PromoteError(msg, source, trace.toVector) :: Nil diff --git a/tests/init/neg/secondary-ctor.scala b/tests/init/neg/secondary-ctor.scala new file mode 100644 index 000000000000..22eabcd57438 --- /dev/null +++ b/tests/init/neg/secondary-ctor.scala @@ -0,0 +1,19 @@ +class A(b: B, x: Int) { + def this(b: B) = { + this(b, 5) + println(b.n) // error + } +} + +class B(val d: D) { + val n: Int = 10 +} + +class C(b: B) extends A(b) { + def this(b: B, x: Int) = this(b) +} + +class D { + val b = new B(this) + val c = new C(b, 5) +} From c96531563c465ec89d8c267e68a5270e26f1eba6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 14:19:21 +0200 Subject: [PATCH 17/36] Refactor promotion --- .../tools/dotc/transform/init/Semantic.scala | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 1e721378c4fe..280b0e1645d2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -194,16 +194,22 @@ class Semantic { import Env._ object Promoted { + class PromotionInfo { + var isCurrentObjectPromoted: Boolean = false + val values = mutable.Set.empty[Value] + } /** Values that have been safely promoted */ - opaque type Promoted = mutable.Set[Value] + opaque type Promoted = PromotionInfo /** Note: don't use `val` to avoid incorrect sharing */ - def empty: Promoted = mutable.Set.empty + def empty: Promoted = new PromotionInfo 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 + def isCurrentObjectPromoted: Boolean = promoted.isCurrentObjectPromoted + def promoteCurrent(thisRef: ThisRef): Unit = promoted.isCurrentObjectPromoted = true + def contains(value: Value): Boolean = promoted.values.contains(value) + def add(value: Value): Unit = promoted.values += value + def remove(value: Value): Unit = promoted.values -= value end extension } type Promoted = Promoted.Promoted @@ -374,6 +380,9 @@ class Semantic { def call(meth: Symbol, args: List[Value], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = def checkArgs = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } + // fast track if the current object is already initialized + if promoted.isCurrentObjectPromoted then return Result(Hot, Nil) + value match { case Hot => Result(Hot, checkArgs) @@ -402,7 +411,7 @@ class Semantic { if target.isPrimaryConstructor then given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - val res = eval(tpl, addr, cls, cacheResult = true) + val res = use(trace.add(cls.defTree)) { eval(tpl, addr, cls, cacheResult = true) } Result(addr, res.errors) else if target.isConstructor then given Env = env2 @@ -451,6 +460,7 @@ class Semantic { if errors.isEmpty then Hot else arg.widen } + if buffer.isEmpty then Result(Hot, Errors.empty) else val value = Warm(klass, Hot, ctor, args2) @@ -496,64 +506,37 @@ 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] = log("canPromoteExtrinsic " + value + ", promoted = " + promoted, printer) { - value match - case Hot => true - case Cold => false - - case warm: Warm => - (warm.outer :: warm.args).forall(_.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 + extension (thisRef: ThisRef) + def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) { + promoted.isCurrentObjectPromoted || { + 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) } - - case RefSet(refs) => - refs.forall(_.canPromoteExtrinsic) - + if allFieldsInitialized then promoted.promoteCurrent(thisRef) + allFieldsInitialized + } } + extension (value: Value) /** Promotion of values to hot */ def promote(msg: String, source: Tree): Contextual[List[Error]] = log("promoting " + value + ", promoted = " + promoted, printer) { - value match + if promoted.isCurrentObjectPromoted then Nil else + + 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 if thisRef.tryPromoteCurrentObject 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) @@ -902,7 +885,7 @@ 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 then Hot + else if target.is(Flags.Package) then Hot else thisV match case Hot => Hot @@ -1005,6 +988,7 @@ class Semantic { if ctor.exists then superCall(tref, ctor, Nil, superParent) } + var fieldsChanged = true // class body tpl.body.foreach { @@ -1013,10 +997,17 @@ class Semantic { val res = eval(vdef.rhs, thisV, klass, cacheResult = true) errorBuffer ++= res.errors thisV.updateField(vdef.symbol, res.value) + fieldsChanged = true case _: MemberDef => case tree => + thisV match + case thisRef: ThisRef => + if fieldsChanged then thisRef.tryPromoteCurrentObject + fieldsChanged = false + case _ => + given Env = Env.empty errorBuffer ++= eval(tree, thisV, klass).errors } From 629f57c9efb2baeab88459f9776bbafbe3910aeb Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 14:43:06 +0200 Subject: [PATCH 18/36] Add test --- tests/init/neg/i12544.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/init/neg/i12544.scala diff --git a/tests/init/neg/i12544.scala b/tests/init/neg/i12544.scala new file mode 100644 index 000000000000..2692c27134e0 --- /dev/null +++ b/tests/init/neg/i12544.scala @@ -0,0 +1,19 @@ +enum Enum: + case Case + case Case2(x: Int) + +def g(b: Enum.B): Int = b.foo() + +object Enum: + object nested: + val a: Enum = Case + + val b: Enum = f(nested.a) + + def f(e: Enum): Enum = e + + class B() { def foo() = n + 1 } + g(new B()) // error + val n: Int = 10 + +@main def main(): Unit = println(Enum.b) From 6edc40a14b5c0c2e15e095191d70d140212399d6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 14:48:34 +0200 Subject: [PATCH 19/36] More logging --- .../tools/dotc/transform/init/Semantic.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 280b0e1645d2..6d551f0f66f3 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -329,8 +329,9 @@ class Semantic { def widen: List[Value] = values.map(_.widen).toList extension (value: Value) - def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = - value match { + def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("select " + field.show, printer, res => res.asInstanceOf[Result].show) { + if promoted.isCurrentObjectPromoted then Result(Hot, Nil) + else value match { case Hot => Result(Hot, Errors.empty) @@ -376,14 +377,14 @@ class Semantic { val errors = resList.flatMap(_.errors) Result(value2, errors) } + } - def call(meth: Symbol, args: List[Value], 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] = log("call " + meth.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { def checkArgs = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } // fast track if the current object is already initialized - if promoted.isCurrentObjectPromoted then return Result(Hot, Nil) - - value match { + if promoted.isCurrentObjectPromoted then Result(Hot, Nil) + else value match { case Hot => Result(Hot, checkArgs) @@ -447,11 +448,13 @@ class Semantic { 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, args: List[Value], source: Tree): Contextual[Result] = + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = log("instantiating " + klass.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { val trace1 = trace.add(source) - value match { + if promoted.isCurrentObjectPromoted then Result(Hot, Nil) + else value match { case Hot => val buffer = new mutable.ArrayBuffer[Error] val args2 = args.map { arg => @@ -503,6 +506,7 @@ class Semantic { val errors = resList.flatMap(_.errors) Result(value2, errors) } + } end extension // ----- Promotion ---------------------------------------------------- From 9842479e83673dea7dc8d704aca5262dc522bac3 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 15:07:21 +0200 Subject: [PATCH 20/36] Fix bug with promotion of this The previous code does not work, because private fields are not included. --- .../tools/dotc/transform/init/Semantic.scala | 17 ++++++++++++----- tests/init/neg/early-promote2.scala | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/init/neg/early-promote2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 6d551f0f66f3..4d3f3f8077e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -515,10 +515,18 @@ class Semantic { promoted.isCurrentObjectPromoted || { 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) + val allFieldsInitialized = thisRef.klass.baseClasses.forall { klass => + if klass.hasSource then + val nonInits = klass.info.decls.filter { member => + !member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred) + && !member.isType + && !obj.fields.contains(member) + } + nonInits.isEmpty + else + true } + if allFieldsInitialized then promoted.promoteCurrent(thisRef) allFieldsInitialized } @@ -535,8 +543,7 @@ class Semantic { case Cold => PromoteError(msg, source, trace.toVector) :: Nil case thisRef: ThisRef => - if promoted.contains(thisRef) then Nil - else if thisRef.tryPromoteCurrentObject then Nil + if thisRef.tryPromoteCurrentObject then Nil else PromoteError(msg, source, trace.toVector) :: Nil case warm: Warm => diff --git a/tests/init/neg/early-promote2.scala b/tests/init/neg/early-promote2.scala new file mode 100644 index 000000000000..514aed36a8ed --- /dev/null +++ b/tests/init/neg/early-promote2.scala @@ -0,0 +1,6 @@ +class M { + println(this) // error + foo() + private val a = 5 // error + def foo() = a +} From 436609ef5e6f45b764e7f07fe8dc976882f4b20e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 15:54:52 +0200 Subject: [PATCH 21/36] Refactor code: introduce ArgInfo instead of Value.source --- .../tools/dotc/transform/init/Semantic.scala | 75 ++++++++----------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 4d3f3f8077e2..494794a9c381 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -57,28 +57,6 @@ class Semantic { */ sealed abstract class Value extends Cloneable { def show: String = this.toString() - - /** Source code where the value originates from - * - * It is used for displaying friendly messages - */ - private var mySource: Tree = EmptyTree - - def source: Tree = mySource - - def attachSource(source: Tree): this.type = - assert(mySource.isEmpty, "Update existing source of value " + this) - mySource = source - this - - def withSource(source: Tree): Value = - if mySource.isEmpty then attachSource(source) - else if this == Hot || this == Cold then this - else { - val value2 = this.clone.asInstanceOf[Value] - value2.mySource = source - value2 - } } /** A transitively initialized object */ @@ -265,13 +243,11 @@ class Semantic { def select(f: Symbol, source: Tree): Contextual[Result] = value.select(f, source) ++ errors - def call(meth: Symbol, args: List[Value], superType: Type, source: Tree): Contextual[Result] = + def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree): Contextual[Result] = value.call(meth, args, superType, source) ++ errors - def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[Value], source: Tree): Contextual[Result] = + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = value.instantiate(klass, ctor, args, source) ++ errors - - def withSource(source: Tree): Result = Result(value.withSource(source), errors) } /** The state that threads through the interpreter */ @@ -379,8 +355,8 @@ class Semantic { } } - def call(meth: Symbol, args: List[Value], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("call " + meth.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { - def checkArgs = args.flatMap { arg => arg.promote("May only use initialized value as arguments", arg.source) } + def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("call " + meth.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { + def checkArgs = args.flatMap(_.promote) // fast track if the current object is already initialized if promoted.isCurrentObjectPromoted then Result(Hot, Nil) @@ -408,7 +384,7 @@ class Semantic { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - val env2 = Env(ddef, args.widen) + val env2 = Env(ddef, args.map(_.value).widen) if target.isPrimaryConstructor then given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] @@ -437,7 +413,7 @@ class Semantic { // 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) + val env3 = Env(params.zip(args.map(_.value).widen).toMap).union(env2) use(env3) { eval(body, thisV, klass, cacheResult = true) } @@ -451,17 +427,17 @@ class Semantic { } /** 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] = log("instantiating " + klass.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = log("instantiating " + klass.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { val trace1 = trace.add(source) if promoted.isCurrentObjectPromoted then Result(Hot, Nil) else value match { case Hot => val buffer = new mutable.ArrayBuffer[Error] val args2 = args.map { arg => - val errors = arg.promote("May only use initialized value as arguments", arg.source) + val errors = arg.promote buffer ++= errors if errors.isEmpty then Hot - else arg.widen + else arg.value.widen } if buffer.isEmpty then Result(Hot, Errors.empty) @@ -485,7 +461,7 @@ class Semantic { case Warm(_, _: Warm, _, _) => Cold case _ => addr - val value = Warm(klass, outer, ctor, args.widen) + val value = Warm(klass, outer, ctor, args.map(_.value).widen) if !heap.contains(value) then val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) heap.update(value, obj) @@ -616,7 +592,7 @@ class Semantic { val trace2 = trace.add(m.defTree) locally { given Trace = trace2 - val args = m.info.paramInfoss.flatten.map(_ => Hot) + val args = m.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, EmptyTree)) val res = warm.call(m, args, superType = NoType, source = source) buffer ++= res.ensureHot(msg, source).errors } @@ -643,6 +619,11 @@ class Semantic { // ----- Semantic definition -------------------------------- + /** Utility definition used for better error-reporting of argument errors */ + case class ArgInfo(value: Value, source: Tree) { + def promote: Contextual[List[Error]] = value.promote("May only use initialized value as arguments", source) + } + /** 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`. @@ -678,15 +659,21 @@ class Semantic { exprs.map { expr => eval(expr, thisV, klass) } /** Evaluate arguments of methods */ - def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[(List[Error], List[Value])] = - val ress = args.map { arg => - if arg.isByName then - val fun = Fun(arg.tree, Nil, thisV, klass, env) - Result(fun, Nil).withSource(arg.tree) - else - eval(arg.tree, thisV, klass).withSource(arg.tree) + def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): Contextual[(List[Error], List[ArgInfo])] = + val errors = new mutable.ArrayBuffer[Error] + val argInfos = new mutable.ArrayBuffer[ArgInfo] + args.foreach { arg => + val res = + if arg.isByName then + val fun = Fun(arg.tree, Nil, thisV, klass, env) + Result(fun, Nil) + else + eval(arg.tree, thisV, klass) + + errors ++= res.errors + argInfos += ArgInfo(res.value, arg.tree) } - (ress.flatMap(_.errors), ress.map(_.value)) + (errors.toList, argInfos.toList) /** Handles the evaluation of different expressions * @@ -939,7 +926,7 @@ class Semantic { printer.println(acc.show + " initialized with " + value) } - def superCall(tref: TypeRef, ctor: Symbol, args: List[Value], source: Tree)(using Env): Unit = + def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree)(using Env): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) From 3ce2532ad1b820643b2dfa043322612a4ff80c4c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 16:28:18 +0200 Subject: [PATCH 22/36] Handle O.this outside of it's definition --- .../dotty/tools/dotc/transform/init/Semantic.scala | 14 +++++++++++--- tests/init/pos/Parsers.scala | 10 ++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/init/pos/Parsers.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 494794a9c381..3c3d5bd83b76 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -868,8 +868,11 @@ class Semantic { cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source) case tp @ ThisType(tref) => - val value = resolveThis(tref.classSymbol.asClass, thisV, klass, source) - Result(value, Errors.empty) + val cls = tref.classSymbol.asClass + if cls.isStaticOwner && !klass.isContainedIn(cls) then Result(Hot, Nil) + else + val value = resolveThis(cls, thisV, klass, source) + Result(value, Errors.empty) case _: TermParamRef | _: RecThis => // possible from checking effects of types @@ -890,7 +893,12 @@ class Semantic { case addr: Addr => val obj = heap(addr) val outerCls = klass.owner.enclosingClass.asClass - resolveThis(target, obj.outers(klass), outerCls, source) + 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) + Hot + else + resolveThis(target, obj.outers(klass), outerCls, source) case RefSet(refs) => refs.map(ref => resolveThis(target, ref, klass, source)).join case fun: Fun => diff --git a/tests/init/pos/Parsers.scala b/tests/init/pos/Parsers.scala new file mode 100644 index 000000000000..2fa3f16839ba --- /dev/null +++ b/tests/init/pos/Parsers.scala @@ -0,0 +1,10 @@ +object Parsers { + enum Location(val inParens: Boolean, val inPattern: Boolean, val inArgs: Boolean): + case InParens extends Location(true, false, false) + case InArgs extends Location(true, false, true) + case InPattern extends Location(false, true, false) + case InGuard extends Location(false, false, false) + case InPatternArgs extends Location(false, true, true) // InParens not true, since it might be an alternative + case InBlock extends Location(false, false, false) + case ElseWhere extends Location(false, false, false) +} \ No newline at end of file From 2804eecc08553be46123d9ef74aa95028cdafdb1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 17:58:51 +0200 Subject: [PATCH 23/36] Fix owner of values with static flag With the flag , the enclosing class will be skipped. See the documentation `SymDenotation.enclosingClass` --- compiler/src/dotty/tools/dotc/transform/init/Errors.scala | 2 +- compiler/src/dotty/tools/dotc/transform/init/Semantic.scala | 2 +- tests/init/neg/promotion-loop.check | 2 +- tests/init/pos/enums.scala | 6 ++++++ 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 tests/init/pos/enums.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index 57f860561d4a..db16c4e81f08 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -69,7 +69,7 @@ object Errors { /** Promote `this` under initialization to fully-initialized */ case class PromoteError(msg: String, source: Tree, trace: Seq[Tree]) extends Error { - def show(using Context): String = "Promote the value under initialization to fully-initialized. " + msg + def show(using Context): String = "Promote the value under initialization to fully-initialized. " + msg + "." } case class AccessCold(field: Symbol, source: Tree, trace: Seq[Tree]) extends Error { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 3c3d5bd83b76..fb717643c1c6 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -892,7 +892,7 @@ class Semantic { case Hot => Hot case addr: Addr => val obj = heap(addr) - val outerCls = klass.owner.enclosingClass.asClass + val outerCls = klass.owner.lexicallyEnclosingClass.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) diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index beba9a3d3197..7d3738429760 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -5,5 +5,5 @@ | |The unsafe promotion may cause the following problem(s): | - |1. Promote the value under initialization to fully-initialized. May only use initialized value as arguments Calling trace: + |1. Promote the value under initialization to fully-initialized. May only use initialized value as arguments. Calling trace: | -> val outer = test [ promotion-loop.scala:12 ] diff --git a/tests/init/pos/enums.scala b/tests/init/pos/enums.scala new file mode 100644 index 000000000000..76722e084ab1 --- /dev/null +++ b/tests/init/pos/enums.scala @@ -0,0 +1,6 @@ +enum Color(val x: Int) { + case Green extends Color(3) + // case Red extends Color(2) + case Violet extends Color(Green.x + 1) + // case RGB(xx: Int) extends Color(xx) +} From a6d96ef056204edc38976fc7fc50d210b9dba3c9 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 18:12:14 +0200 Subject: [PATCH 24/36] Add documentation about caching --- .../dotty/tools/dotc/transform/init/Semantic.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index fb717643c1c6..aac1f47658df 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -55,7 +55,7 @@ class Semantic { * V ⊑ R if V ∈ R * */ - sealed abstract class Value extends Cloneable { + sealed abstract class Value { def show: String = this.toString() } @@ -142,7 +142,14 @@ class Semantic { /** The environment for method parameters * - * For performance and usability, we restrict parameters to be either `Cold` or `Hot`. + * For performance and usability, we restrict parameters to be either `Cold` + * or `Hot`. + * + * Despite that we have environment for evaluating expressions in secondary + * constructors (currently we restrict method arguments to be hot), we don't + * need to put environment as the cache key. The reason is that constructor + * parameters are determined by the value of `this` --- it suffices to make + * the value of `this` as part of the cache key. */ object Env { opaque type Env = Map[Symbol, Value] From bc0cce3e29f1cdc92b9c9ddee0c82a3a86421f36 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 18:21:06 +0200 Subject: [PATCH 25/36] Add test --- tests/init/neg/secondary-ctor2.scala | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/init/neg/secondary-ctor2.scala diff --git a/tests/init/neg/secondary-ctor2.scala b/tests/init/neg/secondary-ctor2.scala new file mode 100644 index 000000000000..2bccc4bab6b8 --- /dev/null +++ b/tests/init/neg/secondary-ctor2.scala @@ -0,0 +1,25 @@ +class A(b: B, x: Int) { + def this(b: B) = { + this(b, 5) + class Inner() { + def foo() = println(b.n) + } + Inner().foo() // error: calling method on cold + + val f = () => new A(b, 3) + f() // ok + } +} + +class B(val d: D) { + val n: Int = 10 +} + +class C(b: B) extends A(b) { + def this(b: B, x: Int) = this(b) +} + +class D { + val b = new B(this) + val c = new C(b, 5) +} From c033c9e707b38e0e22291e8fd758d79b6ccd279b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 21:59:14 +0200 Subject: [PATCH 26/36] Add another neg test for promotion of ThisRef --- tests/init/neg/early-promote3.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/init/neg/early-promote3.scala diff --git a/tests/init/neg/early-promote3.scala b/tests/init/neg/early-promote3.scala new file mode 100644 index 000000000000..ecb5bbedca69 --- /dev/null +++ b/tests/init/neg/early-promote3.scala @@ -0,0 +1,11 @@ +abstract class A { + bar() + private val a = 5 + def foo() = a + def bar(): Unit +} + +class M extends A { + def bar() = promote(this) // error + def promote(m: M) = m.foo() +} From 218f56bbf25bee8cd9da685855b2d23913713e69 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 4 Jun 2021 23:46:26 +0200 Subject: [PATCH 27/36] 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/Semantic.scala | 67 ++++++++++++------- tests/init/neg/early-promote4.scala | 20 ++++++ tests/init/neg/early-promote5.scala | 25 +++++++ 3 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 tests/init/neg/early-promote4.scala create mode 100644 tests/init/neg/early-promote5.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index aac1f47658df..ed9eb005d423 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -493,25 +493,31 @@ class Semantic { end extension // ----- Promotion ---------------------------------------------------- + extension (addr: Addr) + def isFullyInitialized: Contextual[Boolean] = log("isFullyInitialized " + addr, printer) { + val obj = heap(addr) + addr.klass.baseClasses.forall { klass => + !klass.hasSource || { + val nonInits = klass.info.decls.filter { member => + !member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred) + && !member.isType + && !obj.fields.contains(member) + } + printer.println("nonInits = " + nonInits) + nonInits.isEmpty + } + } + } + extension (thisRef: ThisRef) def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) { promoted.isCurrentObjectPromoted || { val obj = heap(thisRef) // If we have all fields initialized, then we can promote This to hot. - val allFieldsInitialized = thisRef.klass.baseClasses.forall { klass => - if klass.hasSource then - val nonInits = klass.info.decls.filter { member => - !member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred) - && !member.isType - && !obj.fields.contains(member) - } - nonInits.isEmpty - else - true + thisRef.isFullyInitialized && { + promoted.promoteCurrent(thisRef) + true } - - if allFieldsInitialized then promoted.promoteCurrent(thisRef) - allFieldsInitialized } } @@ -574,7 +580,7 @@ class Semantic { */ def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show + ", promoted = " + promoted, printer) { val classRef = warm.klass.appliedRef - if classRef.memberClasses.nonEmpty then + if classRef.memberClasses.nonEmpty || !warm.isFullyInitialized then return PromoteError(msg, source, trace.toVector) :: Nil val fields = classRef.fields @@ -941,7 +947,8 @@ class Semantic { printer.println(acc.show + " initialized with " + value) } - def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree)(using Env): Unit = + type Handler = (() => Unit) => Unit + def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree, handler: Handler)(using Env): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) @@ -950,26 +957,28 @@ class Semantic { // follow constructor if cls.hasSource then - printer.println("init super class " + cls.show) - val res2 = thisV.call(ctor, args, superType = NoType, source) - errorBuffer ++= res2.errors + handler { () => + printer.println("init super class " + cls.show) + val res2 = thisV.call(ctor, args, superType = NoType, source) + errorBuffer ++= res2.errors + } // parents - def initParent(parent: Tree)(using Env) = parent match { + def initParent(parent: Tree, handler: Handler)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } val (erros, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= erros - superCall(tref, ctor, args, tree) + superCall(tref, ctor, args, tree, handler) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) val (erros, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= erros - superCall(tref, ctor, args, tree) + superCall(tref, ctor, args, 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". @@ -977,17 +986,22 @@ class Semantic { if !klass.is(Flags.Trait) then given Env = Env.empty + // 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) + 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 @@ -998,9 +1012,12 @@ class Semantic { // 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 + tasks.foreach(task => task()) + var fieldsChanged = true // class body diff --git a/tests/init/neg/early-promote4.scala b/tests/init/neg/early-promote4.scala new file mode 100644 index 000000000000..65f917553974 --- /dev/null +++ b/tests/init/neg/early-promote4.scala @@ -0,0 +1,20 @@ +abstract class A { + bar() + def bar(): Unit +} + +class Outer { + val a: Int = 5 + trait B { + def bar() = assert(a == 5) + } +} + +class M(val o: Outer) extends A with o.B { + val n: Int = 10 +} + +class Dummy { + val m: Int = n + 4 + val n: Int = 10 // error +} \ No newline at end of file diff --git a/tests/init/neg/early-promote5.scala b/tests/init/neg/early-promote5.scala new file mode 100644 index 000000000000..b5e015732def --- /dev/null +++ b/tests/init/neg/early-promote5.scala @@ -0,0 +1,25 @@ +abstract class A { + bar(this) + def bar(x: A): Unit +} + +class Outer { + val a: Int = 4 + trait B { + def bar(x: A) = println(a) + } +} + +class M(val o: Outer, c: Container) extends A with o.B + +class Container { + val o = new Outer + val m = new M(o, this) + val s = "hello" +} + +class Dummy { + val m: Int = n + 4 + val n: Int = 10 // error +} + From a4b504a0b5648197c99732bd70e9f8628d8ac5af Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 5 Jun 2021 11:28:53 +0200 Subject: [PATCH 28/36] Address review (thanks @michelou) --- .../tools/dotc/transform/init/Semantic.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index ed9eb005d423..6707de379e9a 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -494,6 +494,18 @@ class Semantic { // ----- Promotion ---------------------------------------------------- extension (addr: Addr) + /** Whether the object is fully initialized + * + * It means all fields and outers are set. For performance, we don't check + * outers here, because Scala semantics ensure that they are always set + * before any user code in the constructor. + * + * The interesting case is the outers for traits. The compiler synthesizes + * proxy accessors for the outers in the class that extends the trait. As + * those outers must be stable values, they are initialized immediately + * following class parameters and before super constructor calls and user + * code in the class body. + */ def isFullyInitialized: Contextual[Boolean] = log("isFullyInitialized " + addr, printer) { val obj = heap(addr) addr.klass.baseClasses.forall { klass => @@ -512,7 +524,6 @@ class Semantic { extension (thisRef: ThisRef) def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) { promoted.isCurrentObjectPromoted || { - val obj = heap(thisRef) // If we have all fields initialized, then we can promote This to hot. thisRef.isFullyInitialized && { promoted.promoteCurrent(thisRef) @@ -999,6 +1010,11 @@ class Semantic { val parents = tpl.parents.tail val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) + // The interesting case is the outers for traits. The compiler + // synthesizes proxy accessors for the outers in the class that extends + // the trait. As those outers must be stable values, they are initialized + // immediately following class parameters and before super constructor + // calls and user code in the class body. mixins.reverse.foreach { mixin => parents.find(_.tpe.classSymbol == mixin) match case Some(parent) => initParent(parent, handler) From 03f0cdec86a42eb732ad42c199f794a2d566d018 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 5 Jun 2021 11:37:11 +0200 Subject: [PATCH 29/36] Rename isFullyInitialized to isFullyFilled to avoid confusion --- .../tools/dotc/transform/init/Semantic.scala | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 6707de379e9a..3b3d7ef5abdf 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -494,19 +494,17 @@ class Semantic { // ----- Promotion ---------------------------------------------------- extension (addr: Addr) - /** Whether the object is fully initialized + /** Whether the object is fully assigned * * It means all fields and outers are set. For performance, we don't check * outers here, because Scala semantics ensure that they are always set * before any user code in the constructor. * - * The interesting case is the outers for traits. The compiler synthesizes - * proxy accessors for the outers in the class that extends the trait. As - * those outers must be stable values, they are initialized immediately - * following class parameters and before super constructor calls and user - * code in the class body. + * Note that `isFullyFilled = true` does not mean we can use the + * object freely, as its fields or outers may still reach uninitialized + * objects. */ - def isFullyInitialized: Contextual[Boolean] = log("isFullyInitialized " + addr, printer) { + def isFullyFilled: Contextual[Boolean] = log("isFullyFilled " + addr, printer) { val obj = heap(addr) addr.klass.baseClasses.forall { klass => !klass.hasSource || { @@ -525,7 +523,7 @@ class Semantic { def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) { promoted.isCurrentObjectPromoted || { // If we have all fields initialized, then we can promote This to hot. - thisRef.isFullyInitialized && { + thisRef.isFullyFilled && { promoted.promoteCurrent(thisRef) true } @@ -591,7 +589,7 @@ class Semantic { */ def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show + ", promoted = " + promoted, printer) { val classRef = warm.klass.appliedRef - if classRef.memberClasses.nonEmpty || !warm.isFullyInitialized then + if classRef.memberClasses.nonEmpty || !warm.isFullyFilled then return PromoteError(msg, source, trace.toVector) :: Nil val fields = classRef.fields From 0214051ba32e132624b7ecce123032deb1e33f39 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 5 Jun 2021 12:49:55 +0200 Subject: [PATCH 30/36] Restrict function arguments to be hot This aligns with the design of restricting method arguments to be hot. We only allow non-hot to constructors. --- .../tools/dotc/transform/init/Semantic.scala | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 3b3d7ef5abdf..cefddcc777a9 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -80,7 +80,7 @@ class Semantic { case class Warm(klass: ClassSymbol, outer: Value, ctor: Symbol, args: List[Value]) extends Addr /** A function value */ - case class Fun(expr: Tree, params: List[Symbol], thisV: Addr, klass: ClassSymbol, env: Env) extends Value + case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol, env: Env) extends Value /** A value which represents a set of addresses * @@ -416,13 +416,12 @@ class Semantic { else value.select(target, source, needResolve = false) - case Fun(body, params, thisV, klass, env2) => + case Fun(body, thisV, klass, env) => // 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.map(_.value).widen).toMap).union(env2) - use(env3) { - eval(body, thisV, klass, cacheResult = true) + use(env) { + eval(body, thisV, klass, cacheResult = true) ++ checkArgs } case RefSet(refs) => @@ -479,7 +478,7 @@ class Semantic { if !env.isHot then Result(Cold, res.errors) else Result(value, res.errors) - case Fun(body, params, thisV, klass, env) => + case Fun(body, thisV, klass, env) => report.error("unexpected tree in instantiating a function, fun = " + body.show, source) Result(Hot, Nil) @@ -553,7 +552,7 @@ class Semantic { errors } - case fun @ Fun(body, params, thisV, klass, env) => + case fun @ Fun(body, thisV, klass, env) => if promoted.contains(fun) then Nil else val res = eval(body, thisV, klass) @@ -687,7 +686,7 @@ class Semantic { args.foreach { arg => val res = if arg.isByName then - val fun = Fun(arg.tree, Nil, thisV, klass, env) + val fun = Fun(arg.tree, thisV, klass, env) Result(fun, Nil) else eval(arg.tree, thisV, klass) @@ -774,12 +773,11 @@ class Semantic { eval(rhs, thisV, klass).ensureHot("May only assign fully initialized value", rhs) 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, env) Result(value, Nil) case PolyFun(body) => - val value = Fun(body, Nil, thisV, klass, env) + val value = Fun(body, thisV, klass, env) Result(value, Nil) case Block(stats, expr) => From 29cc8442ae26823c4926629f77c13e30c32f5284 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 5 Jun 2021 13:13:57 +0200 Subject: [PATCH 31/36] Add documentation --- .../tools/dotc/transform/init/Semantic.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index cefddcc777a9..87d393d7212b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -146,10 +146,18 @@ class Semantic { * or `Hot`. * * Despite that we have environment for evaluating expressions in secondary - * constructors (currently we restrict method arguments to be hot), we don't - * need to put environment as the cache key. The reason is that constructor - * parameters are determined by the value of `this` --- it suffices to make - * the value of `this` as part of the cache key. + * constructors, we don't need to put environment as the cache key. The + * reason is that constructor parameters are determined by the value of + * `this` --- it suffices to make the value of `this` as part of the cache + * key. + * + * This crucially depends on the fact that in the initialization process + * there can be exactly one call to a specific constructor for a given + * receiver. However, once we relax the design to allow non-hot values to + * methods and functions, we have to put the environment as part of the cache + * key. The reason is that given the same receiver, a method or function may + * be called with different arguments -- they are not decided by the receiver + * anymore. */ object Env { opaque type Env = Map[Symbol, Value] @@ -453,7 +461,7 @@ class Semantic { val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> Hot)) heap.update(value, obj) val res = value.call(ctor, args, superType = NoType, source) - Result(res.value, res.errors) + Result(value, res.errors) case Cold => val error = CallCold(ctor, source, trace1.toVector) From cea9f26786b696d9c5184773479658f860acdf3c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 20:38:39 +0200 Subject: [PATCH 32/36] Address review --- .../tools/dotc/transform/init/Checker.scala | 5 +- .../tools/dotc/transform/init/Semantic.scala | 114 ++++++++++-------- tests/init/neg/secondary-ctor3.scala | 39 ++++++ 3 files changed, 105 insertions(+), 53 deletions(-) create mode 100644 tests/init/neg/secondary-ctor3.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala index 998881e16d72..5d82e4d8b7e5 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Checker.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Checker.scala @@ -48,10 +48,7 @@ class Checker extends MiniPhase { if (instantiable && cls.enclosingPackageClass != defn.StdLibPatchesPackage.moduleClass) { import semantic._ val tpl = tree.rhs.asInstanceOf[Template] - val thisRef = ThisRef(cls) - - val obj = Objekt(cls, fields = mutable.Map.empty, outers = mutable.Map(cls -> Hot)) - heap.update(thisRef, obj) + val thisRef = ThisRef(cls).ensureExists val paramValues = tpl.constr.termParamss.flatten.map(param => param.symbol -> Hot).toMap diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index 87d393d7212b..ae725bba8e4b 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -67,11 +67,14 @@ class Semantic { sealed abstract class Addr extends Value { def klass: ClassSymbol + def outer: Value } /** A reference to the object under initialization pointed by `this` */ - case class ThisRef(klass: ClassSymbol) extends Addr + case class ThisRef(klass: ClassSymbol) extends Addr { + val outer = Hot + } /** An object with all fields initialized but reaches objects under initialization * @@ -183,6 +186,7 @@ class Semantic { type Env = Env.Env def env(using env: Env) = env + inline def withEnv[T](env: Env)(op: Env ?=> T): T = op(using env) import Env._ @@ -261,8 +265,8 @@ class Semantic { def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree): Contextual[Result] = value.call(meth, args, superType, source) ++ errors - def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = - value.instantiate(klass, ctor, args, source) ++ errors + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree, inside: ClassSymbol): Contextual[Result] = + value.instantiate(klass, ctor, args, source, inside) ++ errors } /** The state that threads through the interpreter */ @@ -284,6 +288,7 @@ class Semantic { import Trace._ def trace(using t: Trace): Trace = t + inline def withTrace[T](t: Trace)(op: Trace ?=> T): T = op(using t) // ----- Operations on domains ----------------------------- extension (a: Value) @@ -305,10 +310,11 @@ class Semantic { case (RefSet(refs1), RefSet(refs2)) => RefSet(refs1 ++ refs2) - def widen: Value = + /** Conservatively approximate the value with `Cold` or `Hot` */ + def widenArg: Value = a match case _: Addr | _: Fun => Cold - case RefSet(refs) => refs.map(_.widen).join + case RefSet(refs) => refs.map(_.widenArg).join case _ => a @@ -317,7 +323,7 @@ class Semantic { if values.isEmpty then Hot else values.reduce { (v1, v2) => v1.join(v2) } - def widen: List[Value] = values.map(_.widen).toList + def widenArgs: List[Value] = values.map(_.widenArg).toList extension (value: Value) def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("select " + field.show, printer, res => res.asInstanceOf[Result].show) { @@ -384,7 +390,7 @@ class Semantic { Result(Hot, error :: checkArgs) case addr: Addr => - val isLocal = meth.owner.isClass + val isLocal = !meth.owner.isClass val target = if !needResolve then meth @@ -399,25 +405,28 @@ class Semantic { given Trace = trace1 val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] - val env2 = Env(ddef, args.map(_.value).widen) + val env2 = Env(ddef, args.map(_.value).widenArgs) if target.isPrimaryConstructor then given Env = env2 val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template] - val res = use(trace.add(cls.defTree)) { eval(tpl, addr, cls, cacheResult = true) } + val res = withTrace(trace.add(cls.defTree)) { eval(tpl, addr, cls, cacheResult = true) } Result(addr, res.errors) else if target.isConstructor then given Env = env2 eval(ddef.rhs, addr, cls, cacheResult = true) else - use(if isLocal then env else Env.empty) { + // normal method call + withEnv(if isLocal then env else Env.empty) { eval(ddef.rhs, addr, cls, cacheResult = true) ++ checkArgs } else if addr.canIgnoreMethodCall(target) then Result(Hot, Nil) else + // no source code available val error = CallUnknown(target, source, trace.toVector) Result(Hot, error :: checkArgs) else + // method call resolves to a field val obj = heap(addr) if obj.fields.contains(target) then Result(obj.fields(target), Nil) @@ -428,7 +437,7 @@ class Semantic { // meth == NoSymbol for poly functions if meth.name.toString == "tupled" then Result(value, Nil) // a call like `fun.tupled` else - use(env) { + withEnv(env) { eval(body, thisV, klass, cacheResult = true) ++ checkArgs } @@ -441,7 +450,7 @@ class Semantic { } /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ - def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = log("instantiating " + klass.show + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree, inside: ClassSymbol): Contextual[Result] = log("instantiating " + klass.show + ", value = " + value + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { val trace1 = trace.add(source) if promoted.isCurrentObjectPromoted then Result(Hot, Nil) else value match { @@ -451,15 +460,12 @@ class Semantic { val errors = arg.promote buffer ++= errors if errors.isEmpty then Hot - else arg.value.widen + else arg.value.widenArg } if buffer.isEmpty then Result(Hot, Errors.empty) else - val value = Warm(klass, Hot, ctor, args2) - if !heap.contains(value) then - val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> Hot)) - heap.update(value, obj) + val value = Warm(klass, Hot, ctor, args2).ensureExists val res = value.call(ctor, args, superType = NoType, source) Result(value, res.errors) @@ -475,15 +481,15 @@ class Semantic { case Warm(_, _: Warm, _, _) => Cold case _ => addr - val value = Warm(klass, outer, ctor, args.map(_.value).widen) - if !heap.contains(value) then - val obj = Objekt(klass, fields = mutable.Map.empty, outers = mutable.Map(klass -> outer)) - heap.update(value, obj) + val value = Warm(klass, outer, ctor, args.map(_.value).widenArgs).ensureExists val res = value.call(ctor, args, superType = NoType, source) - // Abstract instances of local classes inside 2nd constructor as Cold + def inSecondaryConstructor(sym: Symbol): Boolean = + !sym.isClass && (sym.isConstructor || inSecondaryConstructor(sym.owner)) + + // Approximate instances of local classes inside secondary constructor as Cold. // This way, we avoid complicating the domain for Warm unnecessarily - if !env.isHot then Result(Cold, res.errors) + if klass.isContainedIn(inside) && inSecondaryConstructor(klass.owner) then Result(Cold, res.errors) else Result(value, res.errors) case Fun(body, thisV, klass, env) => @@ -491,7 +497,7 @@ class Semantic { Result(Hot, Nil) case RefSet(refs) => - val resList = refs.map(_.instantiate(klass, ctor, args, source)) + val resList = refs.map(_.instantiate(klass, ctor, args, source, inside)) val value2 = resList.map(_.value).join val errors = resList.flatMap(_.errors) Result(value2, errors) @@ -526,15 +532,25 @@ class Semantic { } } + /** Ensure the corresponding object exists in the heap */ + def ensureExists: addr.type = + if !heap.contains(addr) then + val obj = Objekt(addr.klass, fields = mutable.Map.empty, outers = mutable.Map(addr.klass -> addr.outer)) + heap.update(addr, obj) + addr + + end extension + extension (thisRef: ThisRef) def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) { - promoted.isCurrentObjectPromoted || { + if promoted.isCurrentObjectPromoted then + true + else if thisRef.isFullyFilled then // If we have all fields initialized, then we can promote This to hot. - thisRef.isFullyFilled && { - promoted.promoteCurrent(thisRef) - true - } - } + promoted.promoteCurrent(thisRef) + true + else + false } extension (value: Value) @@ -563,7 +579,7 @@ class Semantic { case fun @ Fun(body, thisV, klass, env) => if promoted.contains(fun) then Nil else - val res = eval(body, thisV, klass) + val res = withEnv(env) { 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 @@ -727,7 +743,7 @@ class Semantic { val trace2 = trace.add(expr) locally { given Trace = trace2 - (res ++ errors).instantiate(cls, ctor, args, expr) + (res ++ errors).instantiate(cls, ctor, args, source = expr, inside = klass) } case Call(ref, argss) => @@ -879,16 +895,13 @@ class Semantic { def default() = Result(Hot, Nil) - if sym.is(Flags.Param) then - if sym.owner.isConstructor then - // instances of local classes inside secondary constructors cannot - // reach here, as those values are abstracted by Cold instead of Warm. - // This enables us to simplify the domain without sacrificing - // expressiveness nor soundess, as local classes inside secondary - // constructors are uncommon. - Result(env.lookup(sym), Nil) - else - default() + if sym.is(Flags.Param) && sym.owner.isConstructor then + // instances of local classes inside secondary constructors cannot + // reach here, as those values are abstracted by Cold instead of Warm. + // This enables us to simplify the domain without sacrificing + // expressiveness nor soundess, as local classes inside secondary + // constructors are uncommon. + Result(env.lookup(sym), Nil) else default() @@ -897,7 +910,9 @@ class Semantic { case tp @ ThisType(tref) => val cls = tref.classSymbol.asClass - if cls.isStaticOwner && !klass.isContainedIn(cls) then Result(Hot, Nil) + if cls.isStaticOwner && !klass.isContainedIn(cls) then + // O.this outside the body of the object O + Result(Hot, Nil) else val value = resolveThis(cls, thisV, klass, source) Result(value, Errors.empty) @@ -962,6 +977,8 @@ class Semantic { printer.println(acc.show + " initialized with " + value) } + // Handler is used to schedule super constructor calls. + // Super constructor calls are delayed until all outers are set. type Handler = (() => Unit) => Unit def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree, handler: Handler)(using Env): Unit = val cls = tref.classSymbol.asClass @@ -982,13 +999,13 @@ class Semantic { def initParent(parent: Tree, handler: Handler)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } - val (erros, args) = evalArgs(argss.flatten, thisV, klass) - errorBuffer ++= erros + val (errors, args) = evalArgs(argss.flatten, thisV, klass) + errorBuffer ++= errors superCall(tref, ctor, args, tree, handler) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) - val (erros, args) = evalArgs(argss.flatten, thisV, klass) - errorBuffer ++= erros + val (errors, args) = evalArgs(argss.flatten, thisV, klass) + errorBuffer ++= errors superCall(tref, ctor, args, tree, handler) case _ => // extends A or extends A[T] @@ -1087,14 +1104,13 @@ class Semantic { object Semantic { // ----- Utility methods and extractors -------------------------------- - inline def use[T, R](v: T)(inline op: T ?=> R): R = op(using v) def typeRefOf(tp: Type)(using Context): TypeRef = tp.dealias.typeConstructor match { case tref: TypeRef => tref case hklambda: HKTypeLambda => typeRefOf(hklambda.resType) } - type Arg = Tree | ByNameArg + opaque type Arg = Tree | ByNameArg case class ByNameArg(tree: Tree) extension (arg: Arg) diff --git a/tests/init/neg/secondary-ctor3.scala b/tests/init/neg/secondary-ctor3.scala new file mode 100644 index 000000000000..46e830fe03f8 --- /dev/null +++ b/tests/init/neg/secondary-ctor3.scala @@ -0,0 +1,39 @@ +def foo() = + class L1(x: Int) { val n: Int = 5 } + + class A(b: B, x: Int) { + class L2(x: Int) { val n: Int = 5 } + + def this(b: B) = { + this(b, 5) + class Inner() { + def foo() = println(b.n) + } + Inner().foo() // error: calling method on cold + + val l1 = new L1(3) + println(l1.n) + + val l2 = new L2(3) + println(l2.n) + + (() => new A(b, 3))() // ok + } + } + + class B(val d: D) { + val n: Int = 10 + } + + trait T { + val m: Int = 10 + } + + class C(b: B) extends A(b) with T { + def this(b: B, x: Int) = this(b) + } + + class D { + val b = new B(this) + val c = new C(b, 5) + } From ec1de2fc5424f94e9050706d5a62efd1edfd7e25 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 7 Jun 2021 22:44:58 +0200 Subject: [PATCH 33/36] Simplify logic for checking local classes inside secondary constructors --- .../dotty/tools/dotc/transform/init/Semantic.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index ae725bba8e4b..cb2c1025f320 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -265,8 +265,8 @@ class Semantic { def call(meth: Symbol, args: List[ArgInfo], superType: Type, source: Tree): Contextual[Result] = value.call(meth, args, superType, source) ++ errors - def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree, inside: ClassSymbol): Contextual[Result] = - value.instantiate(klass, ctor, args, source, inside) ++ errors + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = + value.instantiate(klass, ctor, args, source) ++ errors } /** The state that threads through the interpreter */ @@ -450,7 +450,7 @@ class Semantic { } /** Handle a new expression `new p.C` where `p` is abstracted by `value` */ - def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree, inside: ClassSymbol): Contextual[Result] = log("instantiating " + klass.show + ", value = " + value + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { + def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], source: Tree): Contextual[Result] = log("instantiating " + klass.show + ", value = " + value + ", args = " + args, printer, res => res.asInstanceOf[Result].show) { val trace1 = trace.add(source) if promoted.isCurrentObjectPromoted then Result(Hot, Nil) else value match { @@ -489,7 +489,7 @@ class Semantic { // Approximate instances of local classes inside secondary constructor as Cold. // This way, we avoid complicating the domain for Warm unnecessarily - if klass.isContainedIn(inside) && inSecondaryConstructor(klass.owner) then Result(Cold, res.errors) + if inSecondaryConstructor(klass.owner) then Result(Cold, res.errors) else Result(value, res.errors) case Fun(body, thisV, klass, env) => @@ -497,7 +497,7 @@ class Semantic { Result(Hot, Nil) case RefSet(refs) => - val resList = refs.map(_.instantiate(klass, ctor, args, source, inside)) + val resList = refs.map(_.instantiate(klass, ctor, args, source)) val value2 = resList.map(_.value).join val errors = resList.flatMap(_.errors) Result(value2, errors) @@ -743,7 +743,7 @@ class Semantic { val trace2 = trace.add(expr) locally { given Trace = trace2 - (res ++ errors).instantiate(cls, ctor, args, source = expr, inside = klass) + (res ++ errors).instantiate(cls, ctor, args, source = expr) } case Call(ref, argss) => From d9892f955594e01b4945a4609e4a56aa67158f56 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 8 Jun 2021 22:13:48 +0200 Subject: [PATCH 34/36] Address review: simplify logic --- .../tools/dotc/transform/init/Semantic.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index cb2c1025f320..fa52c2c21043 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -977,10 +977,10 @@ class Semantic { printer.println(acc.show + " initialized with " + value) } - // Handler is used to schedule super constructor calls. + // Tasks is used to schedule super constructor calls. // Super constructor calls are delayed until all outers are set. - type Handler = (() => Unit) => Unit - def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree, handler: Handler)(using Env): Unit = + type Tasks = mutable.ArrayBuffer[() => Unit] + def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree, tasks: Tasks)(using Env): Unit = val cls = tref.classSymbol.asClass // update outer for super class val res = outerValue(tref, thisV, klass, source) @@ -989,28 +989,29 @@ class Semantic { // follow constructor if cls.hasSource then - handler { () => + tasks.append { () => printer.println("init super class " + cls.show) val res2 = thisV.call(ctor, args, superType = NoType, source) errorBuffer ++= res2.errors + () } // parents - def initParent(parent: Tree, handler: Handler)(using Env) = parent match { + def initParent(parent: Tree, tasks: Tasks)(using Env) = parent match { case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors } val (errors, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= errors - superCall(tref, ctor, args, tree, handler) + superCall(tref, ctor, args, tree, tasks) case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args) val (errors, args) = evalArgs(argss.flatten, thisV, klass) errorBuffer ++= errors - superCall(tref, ctor, args, tree, handler) + superCall(tref, ctor, args, tree, tasks) case _ => // extends A or extends A[T] val tref = typeRefOf(parent.tpe) - superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent, handler) + superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent, tasks) } // see spec 5.1 about "Template Evaluation". @@ -1020,13 +1021,12 @@ class Semantic { // 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, handler) + initParent(superParent, tasks) val parents = tpl.parents.tail val mixins = klass.baseClasses.tail.takeWhile(_ != superCls) @@ -1038,7 +1038,7 @@ class Semantic { // calls and user code in the class body. mixins.reverse.foreach { mixin => parents.find(_.tpe.classSymbol == mixin) match - case Some(parent) => initParent(parent, handler) + case Some(parent) => initParent(parent, tasks) case None => // According to the language spec, if the mixin trait requires // arguments, then the class must provide arguments to it explicitly @@ -1049,7 +1049,7 @@ class Semantic { // 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, handler) + if ctor.exists then superCall(tref, ctor, Nil, superParent, tasks) } // initialize super classes after outers are set From ae566b48908724c13489573640d1221c773314dd Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 8 Jun 2021 22:40:15 +0200 Subject: [PATCH 35/36] Abstract parameters of non-local constructor parameters as cold This is a simple fix which should have no impact on expressiveness and usability, as local classes inside secondary constructors are extremely rare. --- .../tools/dotc/transform/init/Errors.scala | 2 +- .../tools/dotc/transform/init/Semantic.scala | 17 +++--- tests/init/neg/secondary-ctor2.scala | 4 +- tests/init/neg/secondary-ctor3.scala | 4 +- tests/init/neg/secondary-ctor4.scala | 61 +++++++++++++++++++ 5 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 tests/init/neg/secondary-ctor4.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala index db16c4e81f08..09116226fa1d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Errors.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Errors.scala @@ -74,7 +74,7 @@ object Errors { case class AccessCold(field: Symbol, source: Tree, trace: Seq[Tree]) extends Error { def show(using Context): String = - "Access field " + source.show + " on a value with an unknown initialization status" + "." + "Access field " + source.show + " on a value with an unknown initialization status." } case class CallCold(meth: Symbol, source: Tree, trace: Seq[Tree]) extends Error { diff --git a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala index fa52c2c21043..a2e0351f1393 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala @@ -475,7 +475,6 @@ class Semantic { case addr: Addr => given Trace = trace1 - // widen the outer to finitize addresses val outer = addr match case Warm(_, _: Warm, _, _) => Cold @@ -483,14 +482,7 @@ class Semantic { val value = Warm(klass, outer, ctor, args.map(_.value).widenArgs).ensureExists val res = value.call(ctor, args, superType = NoType, source) - - def inSecondaryConstructor(sym: Symbol): Boolean = - !sym.isClass && (sym.isConstructor || inSecondaryConstructor(sym.owner)) - - // Approximate instances of local classes inside secondary constructor as Cold. - // This way, we avoid complicating the domain for Warm unnecessarily - if inSecondaryConstructor(klass.owner) then Result(Cold, res.errors) - else Result(value, res.errors) + Result(value, res.errors) case Fun(body, thisV, klass, env) => report.error("unexpected tree in instantiating a function, fun = " + body.show, source) @@ -901,7 +893,12 @@ class Semantic { // This enables us to simplify the domain without sacrificing // expressiveness nor soundess, as local classes inside secondary // constructors are uncommon. - Result(env.lookup(sym), Nil) + if sym.isContainedIn(klass) then + Result(env.lookup(sym), Nil) + else + // We don't know much about secondary constructor parameters in outer scope. + // It's always safe to approximate them with `Cold`. + Result(Cold, Nil) else default() diff --git a/tests/init/neg/secondary-ctor2.scala b/tests/init/neg/secondary-ctor2.scala index 2bccc4bab6b8..ef4fa674aaca 100644 --- a/tests/init/neg/secondary-ctor2.scala +++ b/tests/init/neg/secondary-ctor2.scala @@ -2,9 +2,9 @@ class A(b: B, x: Int) { def this(b: B) = { this(b, 5) class Inner() { - def foo() = println(b.n) + def foo() = println(b.n) // error: calling method on cold } - Inner().foo() // error: calling method on cold + Inner().foo() val f = () => new A(b, 3) f() // ok diff --git a/tests/init/neg/secondary-ctor3.scala b/tests/init/neg/secondary-ctor3.scala index 46e830fe03f8..0001e74fc6b7 100644 --- a/tests/init/neg/secondary-ctor3.scala +++ b/tests/init/neg/secondary-ctor3.scala @@ -7,9 +7,9 @@ def foo() = def this(b: B) = { this(b, 5) class Inner() { - def foo() = println(b.n) + def foo() = println(b.n) // error } - Inner().foo() // error: calling method on cold + Inner().foo() val l1 = new L1(3) println(l1.n) diff --git a/tests/init/neg/secondary-ctor4.scala b/tests/init/neg/secondary-ctor4.scala new file mode 100644 index 000000000000..e43c063541ed --- /dev/null +++ b/tests/init/neg/secondary-ctor4.scala @@ -0,0 +1,61 @@ +trait D { + val n: Int = 10 +} + +class M(x: Int) { + + def this(d: D) = { + this(d.n) + + class L1(x: Int) { val n: Int = 5 } + + class A(b: B, x: Int) { + println(d.n) // error + + class L2(x: Int) { val n: Int = 5 } + + def this(b: B) = { + this(b, 5) + println(d.n) // error + + class Inner() { + println(d.n) // error + println(b.n) // error + def foo() = println(b.n) // error + } + Inner().foo() + + val l1 = new L1(3) + println(l1.n) + + val l2 = new L2(3) + println(l2.n) + + (() => new A(b, 3))() // ok + } + } + + class B(val d: D) { + val n: Int = 10 + } + + new A(new B(new D)) + + trait T { + val m: Int = 10 + } + + class C(b: B) extends A(b) with T { + def this(b: B, x: Int) = this(b) + } + + class D { + val b = new B(this) + val c = new C(b, 5) + } + } +} + +class N(d: D) extends M(d) { + val msg = "Scala" +} From 7fde75e3a11ae2b87b992a2b3d32f550636525f1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 9 Jun 2021 10:57:20 +0200 Subject: [PATCH 36/36] Update docs --- .../other-new-features/safe-initialization.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/other-new-features/safe-initialization.md b/docs/docs/reference/other-new-features/safe-initialization.md index 8a8ed938f868..8983baa023da 100644 --- a/docs/docs/reference/other-new-features/safe-initialization.md +++ b/docs/docs/reference/other-new-features/safe-initialization.md @@ -185,13 +185,13 @@ With the established principles and design goals, following rules are imposed: initialization in order to enforce different rules. Scala has different syntax for them, it thus is not an issue. -2. References to objects under initialization may not be passed as arguments to method calls or constructors. +2. Objects under initialization may not be passed as arguments to method calls. - Escape of `this` in the constructor is commonly regarded as an - anti-pattern, and it's rarely used in practice. This rule is simple - for the programmer to reason about initialization and it simplifies - implementation. The theory supports safe escape of `this` with the help of - annotations, we delay the extension until there is a strong need. + Escape of `this` in the constructor is commonly regarded as an anti-pattern. + However, escape of `this` as constructor arguments are allowed, to support + creation of cyclic data structures. The checker will ensure that the escaped + non-initialized object is not used, i.e. calling methods or accessing fields + on the escaped object is not allowed. 3. Local definitions may only refer to transitively initialized objects.