Skip to content

Fix #15883: Interpret inner static object access as this access #15984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/init/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,35 +79,35 @@ object Errors:
override def toString() = this.getClass.getName.nn

/** Access non-initialized field */
case class AccessNonInit(field: Symbol, trace: Seq[Tree]) extends Error:
case class AccessNonInit(field: Symbol)(val trace: Seq[Tree]) extends Error:
def source: Tree = trace.last
def show(using Context): String =
"Access non-initialized " + field.show + "." + stacktrace

override def pos(using Context): SourcePosition = field.sourcePos

/** Promote a value under initialization to fully-initialized */
case class PromoteError(msg: String, trace: Seq[Tree]) extends Error:
case class PromoteError(msg: String)(val trace: Seq[Tree]) extends Error:
def show(using Context): String = msg + stacktrace

case class AccessCold(field: Symbol, trace: Seq[Tree]) extends Error:
case class AccessCold(field: Symbol)(val trace: Seq[Tree]) extends Error:
def show(using Context): String =
"Access field " + field.show + " on a cold object." + stacktrace

case class CallCold(meth: Symbol, trace: Seq[Tree]) extends Error:
case class CallCold(meth: Symbol)(val trace: Seq[Tree]) extends Error:
def show(using Context): String =
"Call method " + meth.show + " on a cold object." + stacktrace

case class CallUnknown(meth: Symbol, trace: Seq[Tree]) extends Error:
case class CallUnknown(meth: Symbol)(val trace: Seq[Tree]) extends Error:
def show(using Context): String =
val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field"
prefix + meth.show + " may cause initialization errors." + stacktrace

/** Promote a value under initialization to fully-initialized */
case class UnsafePromotion(msg: String, trace: Seq[Tree], error: Error) extends Error:
case class UnsafePromotion(msg: String, error: Error)(val trace: Seq[Tree]) extends Error:
def show(using Context): String =
msg + stacktrace + "\n" +
"Promoting the value to hot failed due to the following problem:\n" + {
"Promoting the value to hot (transitively initialized) failed due to the following problem:\n" + {
val ctx2 = ctx.withProperty(IsFromPromotion, Some(true))
error.show(using ctx2)
}
Expand All @@ -116,7 +116,7 @@ object Errors:
*
* Invariant: argsIndices.nonEmpty
*/
case class UnsafeLeaking(trace: Seq[Tree], error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int]) extends Error:
case class UnsafeLeaking(error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int])(val trace: Seq[Tree]) extends Error:
def show(using Context): String =
"Problematic object instantiation: " + argumentInfo() + stacktrace + "\n" +
"It leads to the following error during object initialization:\n" +
Expand All @@ -141,5 +141,5 @@ object Errors:
acc + text2
}
val verb = if multiple then " are " else " is "
val adjective = "not hot."
val adjective = "not hot (transitively initialized)."
subject + verb + adjective
61 changes: 39 additions & 22 deletions compiler/src/dotty/tools/dotc/transform/init/Semantic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ object Semantic:
def hasField(f: Symbol) = fields.contains(f)

object Promoted:
class PromotionInfo:
class PromotionInfo(val entryClass: ClassSymbol):
var isCurrentObjectPromoted: Boolean = false
val values = mutable.Set.empty[Value]
override def toString(): String = values.toString()
Expand All @@ -165,14 +165,15 @@ object Semantic:
opaque type Promoted = PromotionInfo

/** Note: don't use `val` to avoid incorrect sharing */
def empty: Promoted = new PromotionInfo
def empty(entryClass: ClassSymbol): Promoted = new PromotionInfo(entryClass)

extension (promoted: Promoted)
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
def entryClass: ClassSymbol = promoted.entryClass
end extension
end Promoted
type Promoted = Promoted.Promoted
Expand Down Expand Up @@ -658,12 +659,12 @@ object Semantic:

def select(field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value, printer, (_: Value).show) {
if promoted.isCurrentObjectPromoted then Hot
else value match {
else value match
case Hot =>
Hot

case Cold =>
val error = AccessCold(field, trace.toVector)
val error = AccessCold(field)(trace.toVector)
reporter.report(error)
Hot

Expand All @@ -688,11 +689,11 @@ object Semantic:
val rhs = target.defTree.asInstanceOf[ValOrDefDef].rhs
eval(rhs, ref, target.owner.asClass, cacheResult = true)
else
val error = CallUnknown(field, trace.toVector)
val error = CallUnknown(field)(trace.toVector)
reporter.report(error)
Hot
else
val error = AccessNonInit(target, trace.toVector)
val error = AccessNonInit(target)(trace.toVector)
reporter.report(error)
Hot
else
Expand All @@ -710,7 +711,6 @@ object Semantic:

case RefSet(refs) =>
refs.map(_.select(field, receiver)).join
}
}

def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
Expand Down Expand Up @@ -779,7 +779,7 @@ object Semantic:

case Cold =>
promoteArgs()
val error = CallCold(meth, trace.toVector)
val error = CallCold(meth)(trace.toVector)
reporter.report(error)
Hot

Expand Down Expand Up @@ -820,7 +820,7 @@ object Semantic:
// try promoting the receiver as last resort
val hasErrors = Reporter.hasErrors { ref.promote("try promote value to hot") }
if hasErrors then
val error = CallUnknown(target, trace.toVector)
val error = CallUnknown(target)(trace.toVector)
reporter.report(error)
Hot
else if target.exists then
Expand Down Expand Up @@ -899,7 +899,7 @@ object Semantic:
Hot
else
// no source code available
val error = CallUnknown(ctor, trace.toVector)
val error = CallUnknown(ctor)(trace.toVector)
reporter.report(error)
Hot
}
Expand All @@ -922,7 +922,7 @@ object Semantic:
yield
i + 1

val error = UnsafeLeaking(trace.toVector, errors.head, nonHotOuterClass, indices)
val error = UnsafeLeaking(errors.head, nonHotOuterClass, indices)(trace.toVector)
reporter.report(error)
Hot
else
Expand All @@ -947,7 +947,7 @@ object Semantic:
tryLeak(warm, NoSymbol, args2)

case Cold =>
val error = CallCold(ctor, trace.toVector)
val error = CallCold(ctor)(trace.toVector)
reporter.report(error)
Hot

Expand Down Expand Up @@ -1078,15 +1078,15 @@ object Semantic:
case Hot =>

case Cold =>
reporter.report(PromoteError(msg, trace.toVector))
reporter.report(PromoteError(msg)(trace.toVector))

case thisRef: ThisRef =>
val emptyFields = thisRef.nonInitFields()
if emptyFields.isEmpty then
promoted.promoteCurrent(thisRef)
else
val fields = "Non initialized field(s): " + emptyFields.map(_.show).mkString(", ") + "."
reporter.report(PromoteError(msg + "\n" + fields, trace.toVector))
reporter.report(PromoteError(msg + "\n" + fields)(trace.toVector))

case warm: Warm =>
if !promoted.contains(warm) then
Expand All @@ -1106,7 +1106,7 @@ object Semantic:
res.promote("The function return value is not hot. Found = " + res.show + ".")
}
if errors.nonEmpty then
reporter.report(UnsafePromotion(msg, trace.toVector, errors.head))
reporter.report(UnsafePromotion(msg, errors.head)(trace.toVector))
else
promoted.add(fun)

Expand Down Expand Up @@ -1156,7 +1156,7 @@ object Semantic:
if !isHotSegment then
for member <- klass.info.decls do
if member.isClass then
val error = PromoteError("Promotion cancelled as the value contains inner " + member.show + ".", Vector.empty)
val error = PromoteError("Promotion cancelled as the value contains inner " + member.show + ".")(Vector.empty)
reporter.report(error)
else if !member.isType && !member.isConstructor && !member.is(Flags.Deferred) then
given Trace = Trace.empty
Expand Down Expand Up @@ -1189,7 +1189,7 @@ object Semantic:
}

if errors.isEmpty then Nil
else UnsafePromotion(msg, trace.toVector, errors.head) :: Nil
else UnsafePromotion(msg, errors.head)(trace.toVector) :: Nil
}

end extension
Expand Down Expand Up @@ -1230,7 +1230,7 @@ object Semantic:

@tailrec
def iterate(): Unit = {
given Promoted = Promoted.empty
given Promoted = Promoted.empty(thisRef.klass)
given Trace = Trace.empty.add(thisRef.klass.defTree)
given reporter: Reporter.BufferedReporter = new Reporter.BufferedReporter

Expand Down Expand Up @@ -1513,16 +1513,24 @@ object Semantic:
thisV.accessLocal(tmref, klass)

case tmref: TermRef =>
cases(tmref.prefix, thisV, klass).select(tmref.symbol, receiver = tmref.prefix)
val cls = tmref.widenSingleton.classSymbol
if cls.exists && cls.isStaticOwner then
if klass.isContainedIn(cls) then
resolveThis(cls.asClass, thisV, klass)
else if cls.isContainedIn(promoted.entryClass) then
cases(tmref.prefix, thisV, klass).select(tmref.symbol, receiver = tmref.prefix)
else
Hot
else
cases(tmref.prefix, thisV, klass).select(tmref.symbol, receiver = tmref.prefix)

case tp @ ThisType(tref) =>
val cls = tref.classSymbol.asClass
if cls.isStaticOwner && !klass.isContainedIn(cls) then
// O.this outside the body of the object O
Hot
else
val value = resolveThis(cls, thisV, klass)
value
resolveThis(cls, thisV, klass)

case _: TermParamRef | _: RecThis =>
// possible from checking effects of types
Expand Down Expand Up @@ -1664,7 +1672,16 @@ object Semantic:
if thisV.isThisRef || !thisV.asInstanceOf[Warm].isPopulatingParams then tpl.body.foreach {
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty =>
val res = eval(vdef.rhs, thisV, klass)
thisV.updateField(vdef.symbol, res)
// TODO: Improve promotion to avoid handling enum initialization specially
//
// The failing case is tests/init/pos/i12544.scala due to promotion failure.
if vdef.symbol.name == nme.DOLLAR_VALUES
&& vdef.symbol.is(Flags.Synthetic)
&& vdef.symbol.owner.companionClass.is(Flags.Enum)
then
thisV.updateField(vdef.symbol, Hot)
else
thisV.updateField(vdef.symbol, res)
fieldsChanged = true

case _: MemberDef =>
Expand Down
2 changes: 1 addition & 1 deletion tests/init/neg/closureLeak.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| -> l.foreach(a => a.addX(this)) // error [ closureLeak.scala:11 ]
| ^^^^^^^^^^^^^^^^^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Cannot prove the method argument is hot. Only hot values are safe to leak.
| Found = ThisRef[class Outer].
| Non initialized field(s): value p. Promotion trace:
Expand Down
4 changes: 2 additions & 2 deletions tests/init/neg/cycle-structure.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Error: tests/init/neg/cycle-structure.scala:3:13 --------------------------------------------------------------------
3 | val x = B(this) // error
| ^^^^^^^
| Problematic object instantiation: arg 1 is not hot. Calling trace:
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
| -> case class A(b: B) { [ cycle-structure.scala:1 ]
| ^
| -> val x = B(this) // error [ cycle-structure.scala:3 ]
Expand All @@ -16,7 +16,7 @@
-- Error: tests/init/neg/cycle-structure.scala:9:13 --------------------------------------------------------------------
9 | val x = A(this) // error
| ^^^^^^^
| Problematic object instantiation: arg 1 is not hot. Calling trace:
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
| -> case class B(a: A) { [ cycle-structure.scala:7 ]
| ^
| -> val x = A(this) // error [ cycle-structure.scala:9 ]
Expand Down
2 changes: 1 addition & 1 deletion tests/init/neg/i15363.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Error: tests/init/neg/i15363.scala:3:10 -----------------------------------------------------------------------------
3 | val b = new B(this) // error
| ^^^^^^^^^^^
| Problematic object instantiation: arg 1 is not hot. Calling trace:
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
| -> class A: [ i15363.scala:1 ]
| ^
| -> val b = new B(this) // error [ i15363.scala:3 ]
Expand Down
2 changes: 2 additions & 0 deletions tests/init/neg/i15883.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
val a = b
val b = 1 // error
2 changes: 1 addition & 1 deletion tests/init/neg/inherit-non-hot.check
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| -> if b == null then b = new B(this) // error [ inherit-non-hot.scala:6 ]
| ^^^^^^^^^^^^^^^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Cannot prove that the field value a is hot. Found = Cold. Promotion trace:
| -> class B(a: A) { [ inherit-non-hot.scala:10 ]
| ^^^^
2 changes: 1 addition & 1 deletion tests/init/neg/promotion-loop.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| -> println(b) // error [ promotion-loop.scala:16 ]
| ^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Cannot prove that the field value outer is hot. Found = ThisRef[class Test].
| Non initialized field(s): value n. Promotion trace:
| -> val outer = test [ promotion-loop.scala:12 ]
Expand Down
2 changes: 1 addition & 1 deletion tests/init/neg/promotion-segment3.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
| -> bar(new B) // error [ promotion-segment3.scala:9 ]
| ^^^^^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Promotion cancelled as the value contains inner class C.
36 changes: 18 additions & 18 deletions tests/init/neg/secondary-ctor4.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Error: tests/init/neg/secondary-ctor4.scala:54:14 -------------------------------------------------------------------
54 | val c = new C(b, 5) // error
| ^^^^^^^^^^^
| Problematic object instantiation: arg 1 is not hot. Calling trace:
| Problematic object instantiation: arg 1 is not hot (transitively initialized). Calling trace:
| -> class D { [ secondary-ctor4.scala:52 ]
| ^
| -> val c = new C(b, 5) // error [ secondary-ctor4.scala:54 ]
Expand All @@ -24,21 +24,21 @@
-- Error: tests/init/neg/secondary-ctor4.scala:42:4 --------------------------------------------------------------------
42 | new A(new B(new D)) // error
| ^^^^^^^^^^^^^^^^^^^
| Problematic object instantiation: the outer M.this and arg 1 are not hot. Calling trace:
| -> class N(d: D) extends M(d) { [ secondary-ctor4.scala:59 ]
| ^
| -> def this(d: D) = { [ secondary-ctor4.scala:7 ]
| ^
| -> new A(new B(new D)) // error [ secondary-ctor4.scala:42 ]
| ^^^^^^^^^^^^^^^^^^^
|Problematic object instantiation: the outer M.this and arg 1 are not hot (transitively initialized). Calling trace:
|-> class N(d: D) extends M(d) { [ secondary-ctor4.scala:59 ]
| ^
|-> def this(d: D) = { [ secondary-ctor4.scala:7 ]
| ^
|-> new A(new B(new D)) // error [ secondary-ctor4.scala:42 ]
| ^^^^^^^^^^^^^^^^^^^
|
| It leads to the following error during object initialization:
| Access field value n on a cold object. Calling trace:
| -> def this(b: B) = { [ secondary-ctor4.scala:17 ]
| ^
| -> Inner().foo() [ secondary-ctor4.scala:26 ]
| ^^^^^^^
| -> class Inner() { [ secondary-ctor4.scala:21 ]
| ^
| -> println(b.n) [ secondary-ctor4.scala:23 ]
| ^^^
|It leads to the following error during object initialization:
|Access field value n on a cold object. Calling trace:
|-> def this(b: B) = { [ secondary-ctor4.scala:17 ]
| ^
|-> Inner().foo() [ secondary-ctor4.scala:26 ]
| ^^^^^^^
|-> class Inner() { [ secondary-ctor4.scala:21 ]
| ^
|-> println(b.n) [ secondary-ctor4.scala:23 ]
| ^^^
4 changes: 2 additions & 2 deletions tests/init/neg/t3273.check
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ]
| ^^^^^^^^^^^^^^^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Access non-initialized value num1. Promotion trace:
| -> val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error [ t3273.scala:4 ]
| ^^^^
Expand All @@ -22,7 +22,7 @@
| -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| Promoting the value to hot failed due to the following problem:
| Promoting the value to hot (transitively initialized) failed due to the following problem:
| Access non-initialized value num2. Promotion trace:
| -> val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error [ t3273.scala:5 ]
| ^^^^