Skip to content

Commit 9e9c4a5

Browse files
committed
Handle outers of trait as the same in concrete semantics
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 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.
1 parent f052bcb commit 9e9c4a5

File tree

3 files changed

+87
-25
lines changed

3 files changed

+87
-25
lines changed

compiler/src/dotty/tools/dotc/transform/init/Semantic.scala

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -493,25 +493,31 @@ class Semantic {
493493
end extension
494494

495495
// ----- Promotion ----------------------------------------------------
496+
extension (addr: Addr)
497+
def isFullyInitialized: Contextual[Boolean] = log("isFullyInitialized " + addr, printer) {
498+
val obj = heap(addr)
499+
addr.klass.baseClasses.forall { klass =>
500+
!klass.hasSource || {
501+
val nonInits = klass.info.decls.filter { member =>
502+
!member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred)
503+
&& !member.isType
504+
&& !obj.fields.contains(member)
505+
}
506+
printer.println("nonInits = " + nonInits)
507+
nonInits.isEmpty
508+
}
509+
}
510+
}
511+
496512
extension (thisRef: ThisRef)
497513
def tryPromoteCurrentObject: Contextual[Boolean] = log("tryPromoteCurrentObject ", printer) {
498514
promoted.isCurrentObjectPromoted || {
499515
val obj = heap(thisRef)
500516
// If we have all fields initialized, then we can promote This to hot.
501-
val allFieldsInitialized = thisRef.klass.baseClasses.forall { klass =>
502-
if klass.hasSource then
503-
val nonInits = klass.info.decls.filter { member =>
504-
!member.isOneOf(Flags.Method | Flags.Lazy | Flags.Deferred)
505-
&& !member.isType
506-
&& !obj.fields.contains(member)
507-
}
508-
nonInits.isEmpty
509-
else
510-
true
517+
thisRef.isFullyInitialized && {
518+
promoted.promoteCurrent(thisRef)
519+
true
511520
}
512-
513-
if allFieldsInitialized then promoted.promoteCurrent(thisRef)
514-
allFieldsInitialized
515521
}
516522
}
517523

@@ -574,7 +580,7 @@ class Semantic {
574580
*/
575581
def tryPromote(msg: String, source: Tree): Contextual[List[Error]] = log("promote " + warm.show + ", promoted = " + promoted, printer) {
576582
val classRef = warm.klass.appliedRef
577-
if classRef.memberClasses.nonEmpty then
583+
if classRef.memberClasses.nonEmpty || !warm.isFullyInitialized then
578584
return PromoteError(msg, source, trace.toVector) :: Nil
579585

580586
val fields = classRef.fields
@@ -941,7 +947,8 @@ class Semantic {
941947
printer.println(acc.show + " initialized with " + value)
942948
}
943949

944-
def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree)(using Env): Unit =
950+
type Handler = (() => Unit) => Unit
951+
def superCall(tref: TypeRef, ctor: Symbol, args: List[ArgInfo], source: Tree, handler: Handler)(using Env): Unit =
945952
val cls = tref.classSymbol.asClass
946953
// update outer for super class
947954
val res = outerValue(tref, thisV, klass, source)
@@ -950,44 +957,51 @@ class Semantic {
950957

951958
// follow constructor
952959
if cls.hasSource then
953-
printer.println("init super class " + cls.show)
954-
val res2 = thisV.call(ctor, args, superType = NoType, source)
955-
errorBuffer ++= res2.errors
960+
handler { () =>
961+
printer.println("init super class " + cls.show)
962+
val res2 = thisV.call(ctor, args, superType = NoType, source)
963+
errorBuffer ++= res2.errors
964+
}
956965

957966
// parents
958-
def initParent(parent: Tree)(using Env) = parent match {
967+
def initParent(parent: Tree, handler: Handler)(using Env) = parent match {
959968
case tree @ Block(stats, NewExpr(tref, New(tpt), ctor, argss)) => // can happen
960969
eval(stats, thisV, klass).foreach { res => errorBuffer ++= res.errors }
961970
val (erros, args) = evalArgs(argss.flatten, thisV, klass)
962971
errorBuffer ++= erros
963-
superCall(tref, ctor, args, tree)
972+
superCall(tref, ctor, args, tree, handler)
964973

965974
case tree @ NewExpr(tref, New(tpt), ctor, argss) => // extends A(args)
966975
val (erros, args) = evalArgs(argss.flatten, thisV, klass)
967976
errorBuffer ++= erros
968-
superCall(tref, ctor, args, tree)
977+
superCall(tref, ctor, args, tree, handler)
969978

970979
case _ => // extends A or extends A[T]
971980
val tref = typeRefOf(parent.tpe)
972-
superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent)
981+
superCall(tref, tref.classSymbol.primaryConstructor, Nil, parent, handler)
973982
}
974983

975984
// see spec 5.1 about "Template Evaluation".
976985
// https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
977986
if !klass.is(Flags.Trait) then
978987
given Env = Env.empty
979988

989+
// outers are set first
990+
val tasks = new mutable.ArrayBuffer[() => Unit]
991+
val handler: Handler = task => tasks.append(task)
992+
980993
// 1. first init parent class recursively
981994
// 2. initialize traits according to linearization order
982995
val superParent = tpl.parents.head
983996
val superCls = superParent.tpe.classSymbol.asClass
984-
initParent(superParent)
997+
initParent(superParent, handler)
985998

986999
val parents = tpl.parents.tail
9871000
val mixins = klass.baseClasses.tail.takeWhile(_ != superCls)
1001+
9881002
mixins.reverse.foreach { mixin =>
9891003
parents.find(_.tpe.classSymbol == mixin) match
990-
case Some(parent) => initParent(parent)
1004+
case Some(parent) => initParent(parent, handler)
9911005
case None =>
9921006
// According to the language spec, if the mixin trait requires
9931007
// arguments, then the class must provide arguments to it explicitly
@@ -998,9 +1012,12 @@ class Semantic {
9981012
// term arguments to B. That can only be done in a concrete class.
9991013
val tref = typeRefOf(klass.typeRef.baseType(mixin).typeConstructor)
10001014
val ctor = tref.classSymbol.primaryConstructor
1001-
if ctor.exists then superCall(tref, ctor, Nil, superParent)
1015+
if ctor.exists then superCall(tref, ctor, Nil, superParent, handler)
10021016
}
10031017

1018+
// initialize super classes after outers are set
1019+
tasks.foreach(task => task())
1020+
10041021
var fieldsChanged = true
10051022

10061023
// class body

tests/init/neg/early-promote4.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
abstract class A {
2+
bar()
3+
def bar(): Unit
4+
}
5+
6+
class Outer {
7+
val a: Int = 5
8+
trait B {
9+
def bar() = assert(a == 5)
10+
}
11+
}
12+
13+
class M(val o: Outer) extends A with o.B {
14+
val n: Int = 10
15+
}
16+
17+
class Dummy {
18+
val m: Int = n + 4
19+
val n: Int = 10 // error
20+
}

tests/init/neg/early-promote5.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
abstract class A {
2+
bar(this)
3+
def bar(x: A): Unit
4+
}
5+
6+
class Outer {
7+
val a: Int = 4
8+
trait B {
9+
def bar(x: A) = println(a)
10+
}
11+
}
12+
13+
class M(val o: Outer, c: Container) extends A with o.B
14+
15+
class Container {
16+
val o = new Outer
17+
val m = new M(o, this)
18+
val s = "hello"
19+
}
20+
21+
class Dummy {
22+
val m: Int = n + 4
23+
val n: Int = 10 // error
24+
}
25+

0 commit comments

Comments
 (0)