Dotty: Compat Scala 2: Abstract override

Created on 6 Jul 2018  Â·  13Comments  Â·  Source: lampepfl/dotty

class Seq

trait IterableOnceOps {
  def toSeq: Seq = new Seq
}

trait IterableOps extends IterableOnceOps

trait SeqOps extends IterableOps {
  def toSeq: Seq
}

class Foo extends SeqOps

The code snippet above compiles with Scala2 but not Dotty:

-- Error: tests/allan/Test.scala:53:6 ------------------------------------------
53 |class Foo extends SeqOps
   |      ^
   | class Foo needs to be abstract, since def toSeq: => Seq is not defined 
one error found

This pattern is used in the 2.13 collection library

refchecks scala2 bug question needs spec

All 13 comments

cc/ @julienrf

Followup, If I do decide to implement toSeq in Foo, should I be required to add the override modifier:

class Foo extends SeqOps {
  def toSeq: Seq = new Seq
}
tests/allan/Test.scala:54: error: overriding method toSeq in trait IterableOnceOps of type => Seq;
 method toSeq needs `override` modifier
  def toSeq: Seq = new Seq
      ^
one error found
  1. This seems to be specified as allowed (tho I'd clarify the spec), so I'm removing the Scala2 label — see member h in this example from https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#overriding:
trait A { def f: Int }
trait B extends A { def f: Int = 1 ; def g: Int = 2 ; def h: Int = 3 }
trait C extends A { override def f: Int = 4 ; def g: Int }
trait D extends B with C { def h: Int }

Also, the text which defines overriding never forbids this scenario:

This definition also determines the overriding relationships between matching members of a class C and its parents. First, a concrete definition always overrides an abstract definition. Second, for definitions M and M' which are both concrete or both abstract, M overrides M′ if M appears in a class that precedes (in the linearization of C) the class in which M′ is defined.

  1. Foo.toSeq overrides an existing implementation, so it seems override should be required as usual to ensure that the override is desired — luckily, that's even clear in the spec (https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#override).

https://www.scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html#overriding

Apparently this is an intended change and matches Java semantics (though Martin didn't go into detail), so the spec should be updated and the compat:Scala2 label stays.

We should also see if we can make a rewrite rule for this.

Apparently this is an intended change and matches Java semantics (though Martin didn't go into detail), so the spec should be updated and the compat:Scala2 label stays.

This came up in the context of https://github.com/scala/bug/issues/12224 (ty @Jasper-M!). Could you go more into detail on why this was changed? In https://github.com/lampepfl/dotty/issues/2072 it seemed like it was understood to be an issue in Dotty, but then it just fizzled out...

I remember learning from this SO answer:

https://stackoverflow.com/questions/10213395/cake-pattern-with-overriding-abstract-type-dont-work-with-upper-type-bounds/

which now compiles on Scala 3, as promised in 2012:

trait A {
  def ping = println("ping")
}

trait Cake {
  type T
}

trait S { this: Cake =>  // S with Cake
  //type T = A   // concrete overrides abstract
  type T <: A    // but abstract was overridden by abstract in linearization order; loses the bound in Scala 2
  def t: T
  def f = t.ping  // was: error: value ping is not a member of S.this.T
}

Just to document this related change here.

ping @odersky when you get a second, do you remember why this is an intended change in Scala 3?

Because it's what Java does, as well as what the JVM does for default methods IIRC.

Any ideas why Java does it that way? Is there a reason why this is bad? For instance, in the n years that this has worked this way in Scala 2, has caused an issue? Or perhaps there's implementation reasons behind this?

I have no idea.

Only discussion related to this that I can remember is https://contributors.scala-lang.org/t/ability-to-make-a-parents-concrete-method-abstract/1255

It's also pointed out in that thread that Scala 2 already does the "Java thing" when it comes to abstract classes.

scala> class Baz { def foo: String = "string" }
class Baz

scala> trait Foo extends Baz{
     |   override def foo: String
     | }
trait Foo

scala> new Foo{}
val res5: Baz with Foo = $anon$1@4374e0

scala> abstract class Bar extends Baz{
     |   override def foo: String
     | }
class Bar

scala> new Bar{}
           ^
       error: object creation impossible. No implementation found in a subclass for deferred declaration
       override def foo: String (defined in class Bar)

In case that's news to anyone.

Scala 2 allows widening the type in an abstract override, but that seems a pretty obscure feature

scala> trait T { def f: String = "" }; trait U extends T { override def f: Object }
trait T
trait U
Was this page helpful?
0 / 5 - 0 ratings

Related issues

neko-kai picture neko-kai  Â·  39Comments

odersky picture odersky  Â·  95Comments

odersky picture odersky  Â·  28Comments

odersky picture odersky  Â·  45Comments

odersky picture odersky  Â·  60Comments