Skip to content

Commit e8e7e15

Browse files
committed
Refactoring accessLocal
1 parent 4887b89 commit e8e7e15

File tree

3 files changed

+120
-95
lines changed

3 files changed

+120
-95
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ class Checker extends Phase {
3737

3838
val traverser = new TreeTraverser {
3939
override def traverse(tree: Tree)(using Context): Unit =
40+
traverseChildren(tree)
4041
tree match {
4142
case tdef: MemberDef =>
4243
// self-type annotation ValDef has no symbol
4344
if tdef.name != nme.WILDCARD then
4445
tdef.symbol.defTree = tree
4546
case _ =>
46-
traverseChildren(tree)
4747
}
4848
}
4949

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ object Errors {
6969

7070
/** Promote `this` under initialization to fully-initialized */
7171
case class PromoteError(msg: String, source: Tree, trace: Seq[Tree]) extends Error {
72-
def show(using Context): String = "Promote the value under initialization to fully-initialized. " + msg + "."
72+
def show(using Context): String = "Cannot prove that the value is fully initialized. " + msg + "."
7373
}
7474

7575
case class AccessCold(field: Symbol, source: Tree, trace: Seq[Tree]) extends Error {
@@ -91,18 +91,14 @@ object Errors {
9191
/** Promote a value under initialization to fully-initialized */
9292
case class UnsafePromotion(msg: String, source: Tree, trace: Seq[Tree], errors: Errors) extends Error {
9393
assert(errors.nonEmpty)
94-
9594
override def issue(using Context): Unit =
9695
report.warning(show, source.srcPos)
9796

9897
def show(using Context): String = {
9998
var index = 0
100-
"Promoting the value to fully-initialized is unsafe. " + msg + "\n" + stacktrace +
101-
"\nThe unsafe promotion may cause the following problem(s):\n" +
102-
(errors.flatMap(_.flatten).map { error =>
103-
index += 1
104-
s"\n$index. " + error.show + error.stacktrace
105-
}.mkString)
99+
"Cannot prove that the value is fully-initialized. " + msg + ".\n" + stacktrace +
100+
"\nThe unsafe promotion may cause the following problem:\n" +
101+
errors.head.show + errors.head.stacktrace
106102
}
107103
}
108104
}

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

Lines changed: 115 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Contexts._
77
import Symbols._
88
import Types._
99
import StdNames._
10+
import NameKinds.OuterSelectName
1011

1112
import ast.tpd._
1213
import util.EqHashMap
@@ -27,24 +28,20 @@ class Semantic {
2728
* Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
2829
*
2930
* Cold
30-
* ┌──────► ▲ ──┐ ◄────┐
31-
* │ │ │ │
32-
* │ │ │ │
33-
* ThisRef(C) │ │
34-
* │ │ │
35-
* Warm(D) Fun RefSet
36-
* │ ▲ ▲ ▲
37-
* │ │ │ │
38-
* Warm(C) │ │
39-
* ▲ │ │ │
40-
* │ │ │ │
41-
* └─────────┴──────┴───────┘
31+
* ┌──────► ▲ ◄────┐ ◄────┐
32+
* │ │ │ │
33+
* │ │ │ │
34+
* | │ │ │
35+
* | │ │
36+
* ThisRef(C) Warm(D) Fun RefSet
37+
* │ ▲ ▲ ▲
38+
* │ │ │ │
39+
* | │ │
40+
* ▲ │ │ │
41+
* │ │ │ │
42+
* └─────────┴──────┴───────┘
4243
* Hot
4344
*
44-
* The most important ordering is the following:
45-
*
46-
* Hot ⊑ Warm(C) ⊑ ThisRef(C) ⊑ Cold
47-
*
4845
* The diagram above does not reflect relationship between `RefSet`
4946
* and other values. `RefSet` represents a set of values which could
5047
* be `ThisRef`, `Warm` or `Fun`. The following ordering applies for
@@ -301,9 +298,6 @@ class Semantic {
301298
case (Cold, _) => Cold
302299
case (_, Cold) => Cold
303300

304-
case (a: Warm, b: ThisRef) if a.klass == b.klass => b
305-
case (a: ThisRef, b: Warm) if a.klass == b.klass => a
306-
307301
case (a: (Fun | Warm | ThisRef), b: (Fun | Warm | ThisRef)) => RefSet(a :: b :: Nil)
308302

309303
case (a: (Fun | Warm | ThisRef), RefSet(refs)) => RefSet(a :: refs)
@@ -406,7 +400,6 @@ class Semantic {
406400
given Trace = trace1
407401
val cls = target.owner.enclosingClass.asClass
408402
val ddef = target.defTree.asInstanceOf[DefDef]
409-
// try early promotion here; if returns error, returns cold
410403
val env2 = Env(ddef, args.map(_.value).widenArgs)
411404
if target.isPrimaryConstructor then
412405
given Env = env2
@@ -497,6 +490,46 @@ class Semantic {
497490
Result(value2, errors)
498491
}
499492
}
493+
494+
def accessLocal(tmref: TermRef, klass: ClassSymbol, source: Tree): Contextual[Result] =
495+
val sym = tmref.symbol
496+
497+
def default() = Result(Hot, Nil)
498+
499+
if sym.is(Flags.Param) && sym.owner.isConstructor then
500+
// instances of local classes inside secondary constructors cannot
501+
// reach here, as those values are abstracted by Cold instead of Warm.
502+
// This enables us to simplify the domain without sacrificing
503+
// expressiveness nor soundess, as local classes inside secondary
504+
// constructors are uncommon.
505+
if sym.isContainedIn(klass) then
506+
Result(env.lookup(sym), Nil)
507+
else
508+
// We don't know much about secondary constructor parameters in outer scope.
509+
// It's always safe to approximate them with `Cold`.
510+
Result(Cold, Nil)
511+
else if sym.is(Flags.Param) then
512+
default()
513+
else
514+
sym.defTree match {
515+
case vdef: ValDef =>
516+
// resolve this for local variable
517+
val enclosingClass = sym.owner.enclosingClass.asClass
518+
val thisValue2 = resolveThis(enclosingClass, value, klass, source)
519+
thisValue2 match {
520+
case Hot => Result(Hot, Errors.empty)
521+
522+
case Cold => Result(Cold, Nil)
523+
524+
case addr: Addr => eval(vdef.rhs, addr, klass)
525+
526+
case _ =>
527+
report.error("unexpected defTree when accessing local variable, sym = " + sym.show + ", defTree = " + sym.defTree.show, source)
528+
default()
529+
}
530+
531+
case _ => default()
532+
}
500533
end extension
501534

502535
// ----- Promotion ----------------------------------------------------
@@ -609,33 +642,24 @@ class Semantic {
609642
if classRef.memberClasses.nonEmpty || !warm.isFullyFilled then
610643
return PromoteError(msg, source, trace.toVector) :: Nil
611644

612-
val fields = classRef.fields
613-
val methods = classRef.membersBasedOnFlags(Flags.Method, Flags.Deferred | Flags.Accessor)
614645
val buffer = new mutable.ArrayBuffer[Error]
615646

616-
fields.exists { denot =>
617-
val f = denot.symbol
618-
if !f.isOneOf(Flags.Deferred | Flags.Private | Flags.Protected) && f.hasSource then
619-
val trace2 = trace.add(f.defTree)
620-
val res = warm.select(f, source)
621-
locally {
622-
given Trace = trace2
623-
buffer ++= res.ensureHot(msg, source).errors
624-
}
625-
buffer.nonEmpty
626-
}
627-
628-
buffer.nonEmpty || methods.exists { denot =>
629-
val m = denot.symbol
630-
if !m.isConstructor && m.hasSource then
631-
val trace2 = trace.add(m.defTree)
632-
locally {
633-
given Trace = trace2
634-
val args = m.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, EmptyTree))
635-
val res = warm.call(m, args, superType = NoType, source = source)
636-
buffer ++= res.ensureHot(msg, source).errors
637-
}
638-
buffer.nonEmpty
647+
warm.klass.baseClasses.exists { klass =>
648+
klass.hasSource && klass.info.decls.exists { member =>
649+
if !member.isType && !member.isConstructor && member.hasSource && !member.is(Flags.Deferred) then
650+
if member.is(Flags.Method) then
651+
val trace2 = trace.add(source)
652+
locally {
653+
given Trace = trace2
654+
val args = member.info.paramInfoss.flatten.map(_ => ArgInfo(Hot, EmptyTree))
655+
val res = warm.call(member, args, superType = NoType, source = member.defTree)
656+
buffer ++= res.ensureHot(msg, source).errors
657+
}
658+
else
659+
val res = warm.select(member, source)
660+
buffer ++= res.ensureHot(msg, source).errors
661+
buffer.nonEmpty
662+
}
639663
}
640664

641665
if buffer.isEmpty then Nil
@@ -694,7 +718,7 @@ class Semantic {
694718
}
695719

696720
/** Evaluate a list of expressions */
697-
def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] =
721+
def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): Contextual[List[Result]] =
698722
exprs.map { expr => eval(expr, thisV, klass) }
699723

700724
/** Evaluate arguments of methods */
@@ -767,7 +791,15 @@ class Semantic {
767791
res.call(id.symbol, args, superType = NoType, source = expr)
768792

769793
case Select(qualifier, name) =>
770-
eval(qualifier, thisV, klass).select(expr.symbol, expr)
794+
val qualRes = eval(qualifier, thisV, klass)
795+
796+
name match
797+
case OuterSelectName(_, hops) =>
798+
val SkolemType(tp) = expr.tpe
799+
val outer = resolveOuterSelect(tp.classSymbol.asClass, qualRes.value, hops, source = expr)
800+
Result(outer, qualRes.errors)
801+
case _ =>
802+
qualRes.select(expr.symbol, expr)
771803

772804
case _: This =>
773805
cases(expr.tpe, thisV, klass, expr)
@@ -857,8 +889,7 @@ class Semantic {
857889
case vdef : ValDef =>
858890
// local val definition
859891
// TODO: support explicit @cold annotation for local definitions
860-
eval(vdef.rhs, thisV, klass, true)
861-
// .ensureHot("Local definitions may only hold initialized values", vdef)
892+
eval(vdef.rhs, thisV, klass, cacheResult = true)
862893

863894
case ddef : DefDef =>
864895
// local method
@@ -886,43 +917,7 @@ class Semantic {
886917
Result(Hot, Errors.empty)
887918

888919
case tmref: TermRef if tmref.prefix == NoPrefix =>
889-
val sym = tmref.symbol
890-
891-
def default() = Result(Hot, Nil)
892-
893-
if sym.is(Flags.Param) && sym.owner.isConstructor then
894-
// instances of local classes inside secondary constructors cannot
895-
// reach here, as those values are abstracted by Cold instead of Warm.
896-
// This enables us to simplify the domain without sacrificing
897-
// expressiveness nor soundess, as local classes inside secondary
898-
// constructors are uncommon.
899-
if sym.isContainedIn(klass) then
900-
Result(env.lookup(sym), Nil)
901-
else
902-
// We don't know much about secondary constructor parameters in outer scope.
903-
// It's always safe to approximate them with `Cold`.
904-
Result(Cold, Nil)
905-
else
906-
sym.defTree match {
907-
case vdef: ValDef => {
908-
// resolve this for local variable
909-
val enclosingClass = sym.owner.enclosingClass.asClass
910-
val thisValue2 = resolveThis(enclosingClass, thisV, klass, source)
911-
thisValue2 match {
912-
case Hot => Result(Hot, Errors.empty)
913-
case Cold => {
914-
val error = AccessCold(sym, source, trace.toVector)
915-
Result(Hot, error :: Nil)
916-
}
917-
case addr: Addr => {
918-
val res = eval(vdef.rhs, addr, klass)
919-
if res.value.promote("Try promote", source).isEmpty then Result(Hot, Errors.empty) else res
920-
}
921-
case _ => ???
922-
}
923-
}
924-
case _ => default()
925-
}
920+
thisV.accessLocal(tmref, klass, source)
926921

927922
case tmref: TermRef =>
928923
cases(tmref.prefix, thisV, klass, source).select(tmref.symbol, source)
@@ -970,6 +965,40 @@ class Semantic {
970965

971966
}
972967

968+
/** Resolve outer select introduced during inlining.
969+
*
970+
* See `tpd.outerSelect` and `ElimOuterSelect`.
971+
*/
972+
def resolveOuterSelect(target: ClassSymbol, thisV: Value, hops: Int, source: Tree): Contextual[Value] = log("resolving outer " + target.show + ", this = " + thisV.show + ", hops = " + hops, printer, res => res.asInstanceOf[Value].show) {
973+
// Is `target` reachable from `cls` with the given `hops`?
974+
def reachable(cls: ClassSymbol, hops: Int): Boolean =
975+
if hops == 0 then cls == target
976+
else reachable(cls.lexicallyEnclosingClass.asClass, hops - 1)
977+
978+
thisV match
979+
case Hot => Hot
980+
981+
case addr: Addr =>
982+
val obj = heap(addr)
983+
val curOpt = obj.klass.baseClasses.find(cls => reachable(cls, hops))
984+
curOpt match
985+
case Some(cur) =>
986+
resolveThis(target, thisV, cur, source)
987+
988+
case None =>
989+
report.warning("unexpected outerSelect, thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos)
990+
Cold
991+
992+
case RefSet(refs) =>
993+
refs.map(ref => resolveOuterSelect(target, ref, hops, source)).join
994+
995+
case fun: Fun =>
996+
report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, source.srcPos)
997+
Cold
998+
999+
case Cold => Cold
1000+
}
1001+
9731002
/** Compute the outer value that correspond to `tref.prefix` */
9741003
def outerValue(tref: TypeRef, thisV: Addr, klass: ClassSymbol, source: Tree): Contextual[Result] =
9751004
val cls = tref.classSymbol.asClass

0 commit comments

Comments
 (0)