From 99a132158035de63e8c027459f250d30550573cb Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Wed, 5 Nov 2025 21:44:15 -0500 Subject: [PATCH 1/3] Warning when calling object methods before super constructor finishes --- .../dotty/tools/dotc/transform/init/Objects.scala | 15 +++++++++++++++ tests/init-global/pos/multiple-by-name.scala | 7 ------- tests/init-global/warn/call-before-super.scala | 7 +++++++ tests/init-global/warn/call-before-super2.scala | 10 ++++++++++ tests/init-global/warn/call-before-super3.scala | 9 +++++++++ tests/init-global/warn/call-before-super4.scala | 9 +++++++++ 6 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 tests/init-global/warn/call-before-super.scala create mode 100644 tests/init-global/warn/call-before-super2.scala create mode 100644 tests/init-global/warn/call-before-super3.scala create mode 100644 tests/init-global/warn/call-before-super4.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 474ec4de7962..4a089467aaf7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -144,6 +144,8 @@ class Objects(using Context @constructorOnly): def isObjectRef: Boolean = this.isInstanceOf[ObjectRef] + def asObjectRef: ObjectRef = this.asInstanceOf[ObjectRef] + def valValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym) def varValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym) @@ -178,6 +180,12 @@ class Objects(using Context @constructorOnly): /** A reference to a static object */ case class ObjectRef private (klass: ClassSymbol)(using Trace) extends Ref: + var afterSuperCall = false + + def isAfterSuperCall = afterSuperCall + + def setAfterSuperCall(): Unit = afterSuperCall = true + def owner = klass def show(using Context) = "ObjectRef(" + klass.show + ")" @@ -1058,6 +1066,9 @@ class Objects(using Context @constructorOnly): else if target.equals(defn.Predef_classOf) then // Predef.classOf is a stub method in tasty and is replaced in backend UnknownValue + else if ref.isInstanceOf[ObjectRef] && !ref.asObjectRef.isAfterSuperCall then + report.warning("Calling " + target + " of object " + ref.klass + " before the super constructor of the object finishes! " + Trace.show, Trace.position) + Bottom else if target.hasSource then val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] @@ -2112,6 +2123,10 @@ class Objects(using Context @constructorOnly): tasks.foreach(task => task()) end if + if thisV.isInstanceOf[ObjectRef] && klass == thisV.klass then + thisV.asObjectRef.setAfterSuperCall() + end if + // class body tpl.body.foreach { case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty => diff --git a/tests/init-global/pos/multiple-by-name.scala b/tests/init-global/pos/multiple-by-name.scala index 5697c7c1bc52..1cc0df80e670 100644 --- a/tests/init-global/pos/multiple-by-name.scala +++ b/tests/init-global/pos/multiple-by-name.scala @@ -18,10 +18,3 @@ object O { val c = foo2(new Y) val d = foo3(new Y) } - -/** - * Pass arg to by-name parameter: create a Fun where body is the argument expression - * Read value of by-name parameter: call 'apply' on every possible Fun value of the by-name parameter - * Solution: Add special EnvRefs for by-name params; - * differentiate these EnvRefs by the arg tree passed to the by-name param - */ \ No newline at end of file diff --git a/tests/init-global/warn/call-before-super.scala b/tests/init-global/warn/call-before-super.scala new file mode 100644 index 000000000000..89a24099fe5b --- /dev/null +++ b/tests/init-global/warn/call-before-super.scala @@ -0,0 +1,7 @@ +class C(i: Int = 42, j: Int = 27) + +object X extends C(j = X.foo()): // warn + def foo() = 5 + +@main def test = println: + X diff --git a/tests/init-global/warn/call-before-super2.scala b/tests/init-global/warn/call-before-super2.scala new file mode 100644 index 000000000000..1fe6c95aafea --- /dev/null +++ b/tests/init-global/warn/call-before-super2.scala @@ -0,0 +1,10 @@ +class C(j: Int) + +object A: + def foo = X.k // warn + +object X extends C(A.foo): + def k = 5 + +@main def test = println: + X \ No newline at end of file diff --git a/tests/init-global/warn/call-before-super3.scala b/tests/init-global/warn/call-before-super3.scala new file mode 100644 index 000000000000..ed57ad82f6a1 --- /dev/null +++ b/tests/init-global/warn/call-before-super3.scala @@ -0,0 +1,9 @@ +abstract class Foo[T](defaultValue: => T, arg1: Int = 1, arg2: Int = 2): + def getValue: T = defaultValue + +enum Baz: + case E1, E2 // warn + +object Baz extends Foo[Baz](Baz.E1, arg2 = 2) // warn + +@main def test = println(Baz.getValue) \ No newline at end of file diff --git a/tests/init-global/warn/call-before-super4.scala b/tests/init-global/warn/call-before-super4.scala new file mode 100644 index 000000000000..dab1cb9248ff --- /dev/null +++ b/tests/init-global/warn/call-before-super4.scala @@ -0,0 +1,9 @@ +class C(i: Int = 42, j: Int = 27) { + val f = X.foo() // warn +} + +object X extends C(j = 5): + def foo() = 5 + +@main def test = println: + X \ No newline at end of file From 3ddd193b13191d588b78e2c8863b5616837aad4c Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 6 Nov 2025 21:51:41 -0500 Subject: [PATCH 2/3] Only conduct check on objects with source --- .../dotty/tools/dotc/transform/init/Objects.scala | 4 +++- tests/init-global/warn/call-before-super3.scala | 14 +++++++------- tests/init-global/warn/call-before-super4.scala | 9 --------- tests/init-global/warn/global-cycle7.check | 10 ++++++++++ tests/init-global/warn/global-cycle7.scala | 2 +- 5 files changed, 21 insertions(+), 18 deletions(-) delete mode 100644 tests/init-global/warn/call-before-super4.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 4a089467aaf7..1aa6f0ff4298 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1460,7 +1460,9 @@ class Objects(using Context @constructorOnly): if classSym.hasSource then State.checkObjectAccess(classSym) else - ObjectRef(classSym) + val obj = ObjectRef(classSym) + obj.setAfterSuperCall() + obj } diff --git a/tests/init-global/warn/call-before-super3.scala b/tests/init-global/warn/call-before-super3.scala index ed57ad82f6a1..dab1cb9248ff 100644 --- a/tests/init-global/warn/call-before-super3.scala +++ b/tests/init-global/warn/call-before-super3.scala @@ -1,9 +1,9 @@ -abstract class Foo[T](defaultValue: => T, arg1: Int = 1, arg2: Int = 2): - def getValue: T = defaultValue +class C(i: Int = 42, j: Int = 27) { + val f = X.foo() // warn +} -enum Baz: - case E1, E2 // warn +object X extends C(j = 5): + def foo() = 5 -object Baz extends Foo[Baz](Baz.E1, arg2 = 2) // warn - -@main def test = println(Baz.getValue) \ No newline at end of file +@main def test = println: + X \ No newline at end of file diff --git a/tests/init-global/warn/call-before-super4.scala b/tests/init-global/warn/call-before-super4.scala deleted file mode 100644 index dab1cb9248ff..000000000000 --- a/tests/init-global/warn/call-before-super4.scala +++ /dev/null @@ -1,9 +0,0 @@ -class C(i: Int = 42, j: Int = 27) { - val f = X.foo() // warn -} - -object X extends C(j = 5): - def foo() = 5 - -@main def test = println: - X \ No newline at end of file diff --git a/tests/init-global/warn/global-cycle7.check b/tests/init-global/warn/global-cycle7.check index 366857885769..19865b479284 100644 --- a/tests/init-global/warn/global-cycle7.check +++ b/tests/init-global/warn/global-cycle7.check @@ -18,3 +18,13 @@ | │ ^ | └── val m: Int = A.n // warn [ global-cycle7.scala:6 ] | ^^^ +-- Warning: /Users/enzexing/IdeaProjects/dotty-staging/dotty/tests/init-global/warn/global-cycle7.scala:12:66 +12 | val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) + | ^^^^^^^^ + |Calling method maxToken of object module class JavaTokens$ before the super constructor of the object finishes! Calling trace: + |├── object JavaTokens extends TokensCommon { [ global-cycle7.scala:15 ] + |│ ^ + |├── abstract class TokensCommon { [ global-cycle7.scala:9 ] + |│ ^ + |└── val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) [ global-cycle7.scala:12 ] + | ^^^^^^^^ diff --git a/tests/init-global/warn/global-cycle7.scala b/tests/init-global/warn/global-cycle7.scala index 30b74ceb95c4..f7690b029ef9 100644 --- a/tests/init-global/warn/global-cycle7.scala +++ b/tests/init-global/warn/global-cycle7.scala @@ -9,7 +9,7 @@ object B { abstract class TokensCommon { def maxToken: Int - val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) + val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) // warn } object JavaTokens extends TokensCommon { From cdcf91aa1c9f6967e3c8cbbd7d75d010d84d07f2 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Tue, 11 Nov 2025 16:31:56 -0500 Subject: [PATCH 3/3] Address comments --- .../tools/dotc/transform/init/Objects.scala | 9 +++--- tests/init-global/warn/global-cycle10.check | 28 +++++++++++++++++++ .../{pos => warn}/global-cycle10.scala | 4 +-- tests/init-global/warn/global-cycle7.check | 10 ------- tests/init-global/warn/global-cycle7.scala | 2 +- tests/init-global/warn/i9176.check | 8 +++++- tests/init-global/warn/i9176.scala | 2 +- .../warn/resolve-parent-this.check | 10 +++++++ .../warn/resolve-parent-this.scala | 2 +- tests/init-global/warn/t9261.check | 8 +++++- tests/init-global/warn/t9261.scala | 2 +- 11 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 tests/init-global/warn/global-cycle10.check rename tests/init-global/{pos => warn}/global-cycle10.scala (74%) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 1aa6f0ff4298..b8a213718a19 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1066,9 +1066,6 @@ class Objects(using Context @constructorOnly): else if target.equals(defn.Predef_classOf) then // Predef.classOf is a stub method in tasty and is replaced in backend UnknownValue - else if ref.isInstanceOf[ObjectRef] && !ref.asObjectRef.isAfterSuperCall then - report.warning("Calling " + target + " of object " + ref.klass + " before the super constructor of the object finishes! " + Trace.show, Trace.position) - Bottom else if target.hasSource then val cls = target.owner.enclosingClass.asClass val ddef = target.defTree.asInstanceOf[DefDef] @@ -1458,7 +1455,11 @@ class Objects(using Context @constructorOnly): /** Check an individual object */ private def accessObject(classSym: ClassSymbol)(using Context, State.Data, Trace, Heap.MutableData, EnvMap.EnvMapMutableData): ObjectRef = log("accessing " + classSym.show, printer, (_: Value).show) { if classSym.hasSource then - State.checkObjectAccess(classSym) + val obj = State.checkObjectAccess(classSym) + if !obj.isAfterSuperCall then + report.warning("Accessing " + obj.klass + " before the super constructor of the object finishes! " + Trace.show, Trace.position) + end if + obj else val obj = ObjectRef(classSym) obj.setAfterSuperCall() diff --git a/tests/init-global/warn/global-cycle10.check b/tests/init-global/warn/global-cycle10.check new file mode 100644 index 000000000000..9d3d7bd2875c --- /dev/null +++ b/tests/init-global/warn/global-cycle10.check @@ -0,0 +1,28 @@ +-- Warning: tests/init-global/warn/global-cycle10.scala:13:14 ---------------------------------------------------------- +13 | def foo() = new Inner // warn + | ^^^^^^^^^ + | Accessing module class O$ before the super constructor of the object finishes! Calling trace: + | ├── object O extends Base { // error [ global-cycle10.scala:7 ] + | │ ^ + | ├── abstract class Base { [ global-cycle10.scala:1 ] + | │ ^ + | ├── foo() [ global-cycle10.scala:4 ] + | │ ^^^^^ + | └── def foo() = new Inner // warn [ global-cycle10.scala:13 ] + | ^^^^^^^^^ +-- Warning: tests/init-global/warn/global-cycle10.scala:10:12 ---------------------------------------------------------- +10 | println(msg) // warn + | ^^^ + | Accessing module class O$ before the super constructor of the object finishes! Calling trace: + | ├── object O extends Base { // error [ global-cycle10.scala:7 ] + | │ ^ + | ├── abstract class Base { [ global-cycle10.scala:1 ] + | │ ^ + | ├── foo() [ global-cycle10.scala:4 ] + | │ ^^^^^ + | ├── def foo() = new Inner // warn [ global-cycle10.scala:13 ] + | │ ^^^^^^^^^ + | ├── class Inner { [ global-cycle10.scala:9 ] + | │ ^ + | └── println(msg) // warn [ global-cycle10.scala:10 ] + | ^^^ diff --git a/tests/init-global/pos/global-cycle10.scala b/tests/init-global/warn/global-cycle10.scala similarity index 74% rename from tests/init-global/pos/global-cycle10.scala rename to tests/init-global/warn/global-cycle10.scala index 9d6200cd884d..0d4eaf97d0c2 100644 --- a/tests/init-global/pos/global-cycle10.scala +++ b/tests/init-global/warn/global-cycle10.scala @@ -7,10 +7,10 @@ abstract class Base { object O extends Base { // error class Inner { - println(msg) + println(msg) // warn } - def foo() = new Inner + def foo() = new Inner // warn } @main diff --git a/tests/init-global/warn/global-cycle7.check b/tests/init-global/warn/global-cycle7.check index 19865b479284..366857885769 100644 --- a/tests/init-global/warn/global-cycle7.check +++ b/tests/init-global/warn/global-cycle7.check @@ -18,13 +18,3 @@ | │ ^ | └── val m: Int = A.n // warn [ global-cycle7.scala:6 ] | ^^^ --- Warning: /Users/enzexing/IdeaProjects/dotty-staging/dotty/tests/init-global/warn/global-cycle7.scala:12:66 -12 | val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) - | ^^^^^^^^ - |Calling method maxToken of object module class JavaTokens$ before the super constructor of the object finishes! Calling trace: - |├── object JavaTokens extends TokensCommon { [ global-cycle7.scala:15 ] - |│ ^ - |├── abstract class TokensCommon { [ global-cycle7.scala:9 ] - |│ ^ - |└── val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) [ global-cycle7.scala:12 ] - | ^^^^^^^^ diff --git a/tests/init-global/warn/global-cycle7.scala b/tests/init-global/warn/global-cycle7.scala index f7690b029ef9..30b74ceb95c4 100644 --- a/tests/init-global/warn/global-cycle7.scala +++ b/tests/init-global/warn/global-cycle7.scala @@ -9,7 +9,7 @@ object B { abstract class TokensCommon { def maxToken: Int - val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) // warn + val tokenString, debugString: Array[String] = new Array[String](maxToken + 1) } object JavaTokens extends TokensCommon { diff --git a/tests/init-global/warn/i9176.check b/tests/init-global/warn/i9176.check index 05a208d1f8a1..72aafebacf64 100644 --- a/tests/init-global/warn/i9176.check +++ b/tests/init-global/warn/i9176.check @@ -4,5 +4,11 @@ | Cyclic initialization: object A -> object B -> object A. Calling trace: | ├── case object A extends Foo(B) // warn [ i9176.scala:2 ] | │ ^ - | └── case object B extends Foo(A) [ i9176.scala:3 ] + | └── case object B extends Foo(A) // warn [ i9176.scala:3 ] | ^ +-- Warning: tests/init-global/warn/i9176.scala:3:26 -------------------------------------------------------------------- +3 |case object B extends Foo(A) // warn + | ^ + | Accessing module class A$ before the super constructor of the object finishes! Calling trace: + | └── case object B extends Foo(A) // warn [ i9176.scala:3 ] + | ^ diff --git a/tests/init-global/warn/i9176.scala b/tests/init-global/warn/i9176.scala index 86b65f6d8c0f..106fc5c4bcf3 100644 --- a/tests/init-global/warn/i9176.scala +++ b/tests/init-global/warn/i9176.scala @@ -1,6 +1,6 @@ class Foo(val opposite: Foo) case object A extends Foo(B) // warn -case object B extends Foo(A) +case object B extends Foo(A) // warn object Test { def main(args: Array[String]): Unit = { println(A.opposite) diff --git a/tests/init-global/warn/resolve-parent-this.check b/tests/init-global/warn/resolve-parent-this.check index 58299cca167e..abb0cf715f9d 100644 --- a/tests/init-global/warn/resolve-parent-this.check +++ b/tests/init-global/warn/resolve-parent-this.check @@ -1,3 +1,13 @@ +-- Warning: tests/init-global/warn/resolve-parent-this.scala:3:18 ------------------------------------------------------ +3 | val f: O.type = O // warn + | ^ + | Accessing module class O$ before the super constructor of the object finishes! Calling trace: + | ├── object O extends Delegate { [ resolve-parent-this.scala:6 ] + | │ ^ + | ├── class Delegate { [ resolve-parent-this.scala:1 ] + | │ ^ + | └── val f: O.type = O // warn [ resolve-parent-this.scala:3 ] + | ^ -- Warning: tests/init-global/warn/resolve-parent-this.scala:7:21 ------------------------------------------------------ 7 | val a: Int = foo().a // warn | ^^^^^^^ diff --git a/tests/init-global/warn/resolve-parent-this.scala b/tests/init-global/warn/resolve-parent-this.scala index 02f7a6266437..84ac48fc296e 100644 --- a/tests/init-global/warn/resolve-parent-this.scala +++ b/tests/init-global/warn/resolve-parent-this.scala @@ -1,6 +1,6 @@ class Delegate { def foo() = f - val f: O.type = O + val f: O.type = O // warn } object O extends Delegate { diff --git a/tests/init-global/warn/t9261.check b/tests/init-global/warn/t9261.check index 1e8b05d1ffa2..43fe1db015cb 100644 --- a/tests/init-global/warn/t9261.check +++ b/tests/init-global/warn/t9261.check @@ -4,5 +4,11 @@ | Cyclic initialization: object Buy -> object Sell -> object Buy. Calling trace: | ├── case object Buy extends OrderType(Sell) // warn [ t9261.scala:2 ] | │ ^^^^ - | └── case object Sell extends OrderType(Buy) [ t9261.scala:3 ] + | └── case object Sell extends OrderType(Buy) // warn [ t9261.scala:3 ] | ^^^ +-- Warning: tests/init-global/warn/t9261.scala:3:35 -------------------------------------------------------------------- +3 |case object Sell extends OrderType(Buy) // warn + | ^^^ + | Accessing module class Buy$ before the super constructor of the object finishes! Calling trace: + | └── case object Sell extends OrderType(Buy) // warn [ t9261.scala:3 ] + | ^^^ diff --git a/tests/init-global/warn/t9261.scala b/tests/init-global/warn/t9261.scala index fcb2f3c62901..c8514891ff6c 100644 --- a/tests/init-global/warn/t9261.scala +++ b/tests/init-global/warn/t9261.scala @@ -1,3 +1,3 @@ sealed abstract class OrderType(val reverse: OrderType) case object Buy extends OrderType(Sell) // warn -case object Sell extends OrderType(Buy) +case object Sell extends OrderType(Buy) // warn