Scala Programming Language
  1. Scala Programming Language
  2. SI-5543

Using this as default argument to constructor does not refer to enclosing object

    Details

      Description

      In the following code, the default argument to Bar is object Bar instead of object Main.

      However, scalac requires that both Main and Bar conform to the parameter type.

      I'd expect Main.this to be in scope, even though the expression is evaluated by Bar$.

      Reproduce Code

      package trythis
      
      //object Main { // type mismatch
      object Main extends Function0[Int] {
      
        class Bar(x: Function0[Int] = this) {
          override def toString = "X "+ x.toString +"="+ x()
        }
        object Bar extends Function0[Int] {
          def apply() = 21
          override def toString = "My Bar module"
        }
        def main(args: Array[String]): Unit = {
          println(new Bar())
        }
        override def toString = "My Main"
        def apply() = 17
      }
      

      Expected result

      X My Main=17
      

      Actual Result

      X My Bar module=21
      

        Activity

        Hide
        Christopher Vogt added a comment -

        please re-assign, if you are not the right person

        Show
        Christopher Vogt added a comment - please re-assign, if you are not the right person
        Hide
        A. P. Marki added a comment -

        I took a look and submitted a pull request.

        https://github.com/scala/scala/pull/317

        The problem seems to be that when default-getters for constructors are added to the companion module, the expression has the module in scope.

        Adding constrTyperIf(flag) before entering typedDefDef seems to work.

        Since there is no flag for default-getter for ctor, it does a name compare. It might be preferable to add a flag or abuse an existing flag instead.

        This change includes renaming init$default$ to init$$default$ to avoid collisions with default-getters for an ordinary method on the module named init.

        That change is not trivial, so guidance is needed. Alternatively, one could live with the possibility of name collision and just allow that there are interactions that impose limits, similar to SI-5366.

        More example:

        object A {
          val v = 7
          class B(val x: Int = v)
          object B {
            val v = 5
            //def init(y: Int = v) = y  // names collide
          }
          def main(args: Array[String]) {
            println(new B().x) // 5
          }
        }
        
        
        Show
        A. P. Marki added a comment - I took a look and submitted a pull request. https://github.com/scala/scala/pull/317 The problem seems to be that when default-getters for constructors are added to the companion module, the expression has the module in scope. Adding constrTyperIf(flag) before entering typedDefDef seems to work. Since there is no flag for default-getter for ctor, it does a name compare. It might be preferable to add a flag or abuse an existing flag instead. This change includes renaming init$default$ to init$$default$ to avoid collisions with default-getters for an ordinary method on the module named init. That change is not trivial, so guidance is needed. Alternatively, one could live with the possibility of name collision and just allow that there are interactions that impose limits, similar to SI-5366 . More example: object A { val v = 7 class B(val x: Int = v) object B { val v = 5 //def init(y: Int = v) = y // names collide } def main(args: Array[String]) { println(new B().x) // 5 } }
        Hide
        A. P. Marki added a comment -

        77b577a5aeab782b64b39b3a812c35fdd8ab265a
        in
        https://github.com/scala/scala/pull/317

        Show
        A. P. Marki added a comment - 77b577a5aeab782b64b39b3a812c35fdd8ab265a in https://github.com/scala/scala/pull/317
        Hide
        Lukas Rytz added a comment -

        The fix goes too far because it also affects `this` references of auxiliary constructors.

        object T {
          override def toString = "T"
        
          // here, `this` should refer to T. That's what was fixed
          class C(val x: Any = this) {
            override def toString() = "C"
          }
        
          class D(val x: Any) {
            override def toString() = "D"
            // here, `this` should refer to the D instance, but after the fix it also
            // refers to T.
            def this(a: Int, b: Int, c: Any = this) { this(c) }
          }
        }
        
        println((new T.C()).x)     // prints "T", correct
        println((new T.D(0,0)).x)  // prints "T", should print "D"
        
        Show
        Lukas Rytz added a comment - The fix goes too far because it also affects `this` references of auxiliary constructors. object T { override def toString = "T" // here, `this` should refer to T. That's what was fixed class C(val x: Any = this) { override def toString() = "C" } class D(val x: Any) { override def toString() = "D" // here, `this` should refer to the D instance, but after the fix it also // refers to T. def this(a: Int, b: Int, c: Any = this) { this(c) } } } println((new T.C()).x) // prints "T", correct println((new T.D(0,0)).x) // prints "T", should print "D"
        Hide
        Lukas Rytz added a comment -

        actually i changed my mind, the fix / current behavior is correct. it doesn't make sense to have the class members in scope when typing (auxiliary or primary) constructor defaults (or types).

        class C { type T; def this(x: T) { this() } }                 // gives `not found: type T`
        class C(x: Int) { def this(a: Int, b: Int = x) { this(b) } }  // gives `not found: value x`
        class C { val x = 0; def this(a: Int = x) { this() } }        // also  `not found: value x`
        

        spec 5.3.1: The signature and the self constructor invocation of a constructor definition are type-checked and evaluated in the scope which is in effect at the point of the enclosing class definition, augmented by any type parameters of the enclosing class and by any early definitions of the enclosing template. The rest of the constructor expression is type-checked and evaluated as a function body in the current class.

        Show
        Lukas Rytz added a comment - actually i changed my mind, the fix / current behavior is correct. it doesn't make sense to have the class members in scope when typing (auxiliary or primary) constructor defaults (or types). class C { type T; def this(x: T) { this() } } // gives `not found: type T` class C(x: Int) { def this(a: Int, b: Int = x) { this(b) } } // gives `not found: value x` class C { val x = 0; def this(a: Int = x) { this() } } // also `not found: value x` spec 5.3.1: The signature and the self constructor invocation of a constructor definition are type-checked and evaluated in the scope which is in effect at the point of the enclosing class definition, augmented by any type parameters of the enclosing class and by any early definitions of the enclosing template. The rest of the constructor expression is type-checked and evaluated as a function body in the current class.
        Hide
        Jason Zaugg added a comment -

        If you haven't already, could you please add a test that demonstrates that behaviour?

        Show
        Jason Zaugg added a comment - If you haven't already, could you please add a test that demonstrates that behaviour?
        Hide
        Lukas Rytz added a comment -

        sure, will be part of the upcoming namer pull request

        Show
        Lukas Rytz added a comment - sure, will be part of the upcoming namer pull request

          People

          • Assignee:
            A. P. Marki
            Reporter:
            A. P. Marki
          • Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development