Skip to content

Commit

Permalink
Handle outers of trait as concrete semantics
Browse files Browse the repository at this point in the history
For traits, its outers will be fields of the class that extends the
trait. As the prefix is stable and is a valid value before any super
class. Therefore, we may think the outers for are immediately set
after 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 empty fields, and errors will be reported when the class
is checked separately.
  • Loading branch information
liufengyun committed Jun 4, 2021
1 parent f052bcb commit e07bb78
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 25 deletions.
67 changes: 42 additions & 25 deletions compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -950,44 +957,51 @@ 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".
// 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

// 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
Expand All @@ -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
Expand Down
20 changes: 20 additions & 0 deletions tests/init/neg/early-promote4.scala
Original file line number Diff line number Diff line change
@@ -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
}
25 changes: 25 additions & 0 deletions tests/init/neg/early-promote5.scala
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit e07bb78

Please sign in to comment.