Some Name
Some Name

Reputation: 9521

Specifying weaker Aux type bound does not cause compile error

Here is an example:

sealed trait Type
object Type {
    case object Type1 extends Type
    case object Type2 extends Type
}

sealed trait TypedTrait {
    type Tpe <: Type
}

object TypedTrait {
    type Aux[T <: Type] = TypedTrait{ type Tpe = T }
}

sealed trait Test {
    //Bounded with TypedTrait.Aux[Type.Type1.type]
    type TptTrait <: TypedTrait.Aux[Type.Type1.type]
}

object Test {
    //No TypedTrait.Aux[Type.Type1.type] type bound
    type Aux[T <: TypedTrait] = Test { type TptTrait = T } //compiles fine, but I expected error
}

The example compiles for both Scala 2 and Scala 3, but it does not seem to make sense. Why does it compile?

Upvotes: 2

Views: 46

Answers (1)

Mateusz Kubuszok
Mateusz Kubuszok

Reputation: 27535

While this compiles you cannot instantiate this type with a bad type:

class Wololo extends TypedTrait { type Tpe = Type.Type2.type } // bad type
val ax: Test.Aux[Wololo] = new Test { type TptTrait = Wololo }

This fails with

cmd1.sc:24: incompatible type in overriding
type TptTrait <: ammonite.$sess.cmd1.TypedTrait.Aux[ammonite.$sess.cmd1.Type.Type1.type] (defined in trait Test);
 found   : ammonite.$sess.cmd1.Wololo
 required:  <: ammonite.$sess.cmd1.TypedTrait.Aux[ammonite.$sess.cmd1.Type.Type1.type]
    (which expands to)   <: ammonite.$sess.cmd1.TypedTrait{type Tpe = ammonite.$sess.cmd1.Type.Type1.type}
val ax: Test.Aux[Wololo] = new Test { type TptTrait = Wololo }
                                           ^
Compilation Failed

Meanwhile

class Wololo extends TypedTrait { type Tpe = Type.Type1.type } // good type
val ax: Test.Aux[Wololo] = new Test { type TptTrait = Wololo }

succeeds with

ax: Test.Aux[Wololo] = ammonite.$sess.cmd1$$anon$1@78de58ea

So we can conclude that the bounds here combines, but aren't failing until you actually try to create an instance that would violate the constraints. Probably this isn't triggering any of cases where spec would let it fail immediately (after all you can create a valid instance!), so the check/proof is deferred until you try to create an actual instance.

(I evaluated it in Ammonite as a whole block because of sealed traits)

@ {
  sealed trait Type
  object Type {
      case object Type1 extends Type
      case object Type2 extends Type
  }

  sealed trait TypedTrait {
      type Tpe <: Type
  }

  object TypedTrait {
      type Aux[T <: Type] = TypedTrait{ type Tpe = T }
  }

  sealed trait Test {
      //Bounded with TypedTrait.Aux[Type.Type1.type]
      type TptTrait <: TypedTrait.Aux[Type.Type1.type]
  }

  object Test {
      type Aux[T <: TypedTrait] = Test { type TptTrait = T }
  }
  class Wololo extends TypedTrait { type Tpe = Type.Type1.type }
  val ax: Test.Aux[Wololo] = new Test { type TptTrait = Wololo }
  }
defined trait Type
defined object Type
defined trait TypedTrait
defined object TypedTrait
defined trait Test
defined object Test
defined class Wololo
ax: Test.Aux[Wololo] = ammonite.$sess.cmd1$$anon$1@78de58ea

Upvotes: 4

Related Questions