From af62be64b521a68896689f4648f5e1e874804ba5 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Wed, 5 Nov 2025 21:44:15 -0500 Subject: [PATCH 1/2] 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 edf760c1eb292d0399bfcd860666e6b5a932c3dd Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 6 Nov 2025 21:51:41 -0500 Subject: [PATCH 2/2] 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 {