1

Considering the following example:

trait Supe {

  type Out <: Supe
  def out: Out
}

class Reif1 extends Supe {

  type Out = Reif1
  override def out: Out = this
}

class Reif2 extends Supe {

  type Out >: this.type <: Reif2
  override def out: Out = this
}

class Reif1 should obviously work. as type Out is reified and becomes a type alias

class Reif2 also works but seriously? type Out only has upper/lower bound defined and the bound was not even tight enough: this.type is a singleton type, and Reif2 is a class type. So what exactly would Out look like if Reif2 got instantiated? Is it going to be this.type? Or Reif2? But the bigger question should be: why scalac 2.12/2.13 allows it be compiled?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • 1
    The language allow abstract types in non abstract classes. It is actually a feature, since there are some advanced use cases that require the type to be abstract. - sadly, there is no way to force subclasses to implicit it, except putting a middle class / trait that uses a type parameter instead – Luis Miguel Mejía Suárez Sep 04 '20 at 17:47

1 Answers1

5

In Scala (or in DOT calculus 1 2) all types are intervals.

type Out = Reif1 is (or is supposed to be) type Out >: Reif1 <: Reif1.

Abstract type without bounds type Out is type Out >: Nothing <: Any.

So what exactly would Out look like if Reif2 got instantiated?

It will remain exactly type Out >: this.type <: Reif2

val r = new Reif2

import scala.reflect.runtime.universe._
typeOf[r.Out] // App.r.Out
showRaw(typeOf[r.Out]) // TypeRef(SingleType(ThisType(App), TermName("r")), TypeName("Out"), List())
typeOf[r.Out].typeSymbol.isAbstract // true
typeOf[r.Out].typeSymbol.typeSignature //  >: Reif2.this.type <: App.Reif2

If you replace type Out >: this.type <: Reif2 in Reif2 with type Out = this.type (or type Out = Reif2 or type Out = Supe) then isAbstract will return false.

Abstract type member of a singleton object (see application of abstract type)

What is the meaning of a type declaration without definition in an object? (see why it's not easy to check that type T >: L <: U is not abstract)

Concrete classes can have abstract type members #1753

SI-8217 allow abstract type members in objects #4024

Abstract type members are incorrectly forbidden in objects (unless inherited) #8217

Use of abstract type in a concrete class?

Concrete classes with abstract type members

What's different between "def apply[T](c:T)" and "type T;def apply(c:T)"

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Wow, if that is true it will open a lot of possibilities Once immediate observation is that TypeTag cannot be generated from such interval, I guess pre-dotty still had some obsolete paradigms that are hard to remove – tribbloid Sep 06 '20 at 19:25
  • 1
    @tribbloid *TypeTag cannot be generated from such interval* I'm afraid I don't understand what this means. `typeTag[r.Out]` compiles. – Dmytro Mitin Sep 06 '20 at 20:09
  • 1
    That's not what I mean, `typeTag[Reif1#Out]` compiles but `typeTag[Reif2#Out]` doesn't. I'm not sure about how it works internally, but I think in this case `r.Out` can be considered an existential type, not an interval any more – tribbloid Sep 08 '20 at 01:08
  • @tribbloid Yeah, for `Reif2#Out` `WeakTypeTag` should be used: `weakTypeTag[Reif2#Out]`. Existential is wider: `type Exist = r0.Out forSome {val r0: Reif2}` `implicitly[r.Out <:< Exist]` `implicitly[Exist <:< Reif2#Out]`. It's an interval but path-dependent: `typeOf[r.Out].typeSymbol.typeSignature =:= typeOf[r1.Out].typeSymbol.typeSignature` returns `true` while `typeOf[r.Out] =:= typeOf[r1.Out]` returns `false`. – Dmytro Mitin Sep 08 '20 at 08:05
  • @tribbloid `typeOf[r.Out].typeSymbol.typeSignature =:= internal.typeBounds(internal.thisType(typeOf[Reif2].typeSymbol), typeOf[Reif2])` returns `true`. – Dmytro Mitin Sep 08 '20 at 08:06