Search code examples
scaladefault-valuetype-parameterfunction-literal

Default type-parametrized function literal class parameter


Is this an intended behavior or is it a bug? Consider the following trait (be it a class, doesn't matter):

trait P[T] {
    class Inner(val f: T => Unit = _ => println("nope"))
}

This is what I would have expected:

scala> val p = new P[Int] {
     |     val inner = new Inner
     | }
p: java.lang.Object with P[Int]{def inner: this.Inner} = $anon$1@12192a9

scala> p.inner.f(5)
nope

But this?

scala> val p = new P[Int] {
     |     val inner = new Inner() {
     |         println("some primary constructor code in here")
     |     }
     | }
<console>:6: error: type mismatch;
 found   : (T) => Unit
 required: (Int) => Unit
           val inner = new Inner() {
                           ^

Solution

  • That appears to be a bug, albeit at a rather obscure intersection between nested classes, abstract types, and default parameters. You could raise a ticket in the Scala bug tracker -- I couldn't find an existing ticket describing this.

    Here's how it looks after the typer phase:

     ~: scala -nocompdaemon -Xprint:typer -e 'trait P[T] { class Inner(val f: T = null.asInstanceOf[T]) }; new P[Int] { new Inner(){} }'
    !!!
    discarding <script preamble>
    (fragment of scalacmd162105603941759154.scala):1: error: type mismatch;
     found   : T
     required: Int
    trait P[T] { class Inner(val f: T = null.asInstanceOf[T]) }; new P[Int] { new Inner(){} }
                                                                                   ^
    [[syntax trees at end of typer]]// Scala source: (virtual file)
    package <empty> {
      final object Main extends java.lang.Object with ScalaObject {
        def this(): object Main = {
          Main.super.this();
          ()
        };
        def main(argv: Array[String]): Unit = {
          val args: Array[String] = argv;
          {
            final class $anon extends scala.AnyRef {
              def this(): anonymous class $anon = {
                $anon.super.this();
                ()
              };
              abstract trait P[T >: Nothing <: Any] extends java.lang.Object with ScalaObject {
                def /*P*/$init$(): Unit = {
                  ()
                };
                class Inner extends java.lang.Object with ScalaObject {
                  <paramaccessor> private[this] val f: T = _;
                  <stable> <accessor> <paramaccessor> def f: T = Inner.this.f;
                  def this(f: T = null.asInstanceOf[T]): P.this.Inner = {
                    Inner.super.this();
                    ()
                  }
                };
                final <synthetic> object Inner extends java.lang.Object with ScalaObject {
                  <synthetic> def init$default$1: T @scala.annotation.unchecked.uncheckedVariance = null.asInstanceOf[T];
                  def this(): object P.this.Inner = {
                    Inner.super.this();
                    ()
                  }
                }
              };
              {
                final class $anon extends java.lang.Object with this.P[Int] {
                  def this(): anonymous class $anon = {
                    $anon.super.this();
                    ()
                  };
                  {
                    final class $anon extends $anon.this.Inner {
                      def this(): anonymous class $anon = {
                        $anon.super.this(P.this.Inner.<error: method init$default$1>);
                        ()
                      };
                      <empty>
                    };
                    new $anon()
                  }
                };
                new $anon()
              }
            };
            {
              new $anon();
              ()
            }
          }
        }
      }
    }
    

    And the working version, without the anonymous inner class extending Inner.

     ~: scala -nocompdaemon -Xprint:typer -e 'trait P[T] { class Inner(val f: T = null.asInstanceOf[T]) }; new P[Int] { new Inner() }'
    [[syntax trees at end of typer]]// Scala source: (virtual file)
    package <empty> {
      final object Main extends java.lang.Object with ScalaObject {
        def this(): object Main = {
          Main.super.this();
          ()
        };
        def main(argv: Array[String]): Unit = {
          val args: Array[String] = argv;
          {
            final class $anon extends scala.AnyRef {
              def this(): anonymous class $anon = {
                $anon.super.this();
                ()
              };
              abstract trait P[T >: Nothing <: Any] extends java.lang.Object with ScalaObject {
                def /*P*/$init$(): Unit = {
                  ()
                };
                class Inner extends java.lang.Object with ScalaObject {
                  <paramaccessor> private[this] val f: T = _;
                  <stable> <accessor> <paramaccessor> def f: T = Inner.this.f;
                  def this(f: T = null.asInstanceOf[T]): P.this.Inner = {
                    Inner.super.this();
                    ()
                  }
                };
                final <synthetic> object Inner extends java.lang.Object with ScalaObject {
                  <synthetic> def init$default$1: T @scala.annotation.unchecked.uncheckedVariance = null.asInstanceOf[T];
                  def this(): object P.this.Inner = {
                    Inner.super.this();
                    ()
                  }
                }
              };
              {
                final class $anon extends java.lang.Object with this.P[Int] {
                  def this(): anonymous class $anon = {
                    $anon.super.this();
                    ()
                  };
                  new $anon.this.Inner($anon.this.Inner.init$default$1)
                };
                new $anon()
              }
            };
            {
              new $anon();
              ()
            }
          }
        }
      }
    }