Search code examples
scalatypesslickcompound-typeintersection-types

DBIOAction[_, NoStream, Read with Write with Read] in Slick


Let's say I have the following action which does at first a read operation and then updates the fields.

action1: DBIOAction[Any, NoStream, Read with Write]

Let action2 do another read operation on the same table. To do the three operation sequentially I do the following:

val action3 = action1 andThen action2

So I presume the output should be of the format.

action3: DBIOAction[Any, NoStream, Read with Write with Read]

But when I see that the output is of the form again :

DBIOAction[Any, NoStream, Read with Write]

Indeed in the method's signature I wrote DBIOAction[Any, NoStream, Read with Write with Read] but the IntelliJ does not complain. This does not seem correct. Am I doing a mistake ?


Solution

  • Read with Write with Read and Read with Write are mostly the same type:

    implicitly[Read with Write with Read =:= Read with Write] // compiles
    implicitly[Read with Write =:= Read with Write with Read] // compiles
    implicitly[Write with Read =:= Read with Write] // compiles
    implicitly[Read with Write =:= Write with Read] // compiles
    

    although according to the spec they seem to be not equivalent (≡):

    Two compound types are equivalent if the sequences of their component are pairwise equivalent, and occur in the same order, and their refinements are equivalent.

    Looking into compiler:

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

    RefinedType#normalize is responsible for flattening nested compound types to a flat representation.

    Types are normalized during =:= in search of a successful result.

    This means that ((A with B) with C) =:= (A with B with C).

    https://github.com/retronym/scala/blob/a9182fbeaf018c1aa0f88f0aee7b921383b746f2/src/reflect/scala/reflect/internal/Types.scala#L1601-L1619

    private def normalizeImpl = {
      // TODO see comments around def intersectionType and def merge
      // SI-8575 The dealias is needed here to keep subtyping transitive, example in run/t8575b.scala
      def flatten(tps: List[Type]): List[Type] = {
        def dealiasRefinement(tp: Type) = if (tp.dealias.isInstanceOf[RefinedType]) tp.dealias else tp
        tps map dealiasRefinement flatMap {
          case RefinedType(parents, ds) if ds.isEmpty => flatten(parents)
          case tp => List(tp)
        }
      }
      val flattened = flatten(parents).distinct
      if (decls.isEmpty && hasLength(flattened, 1)) {
        flattened.head
      } else if (flattened != parents) {
        refinedType(flattened, if (typeSymbol eq NoSymbol) NoSymbol else typeSymbol.owner, decls, NoPosition)
      } else if (isHigherKinded) {
        etaExpand
      } else super.normalize
    }
    

    Notice the usage of flatten(...) and .distinct.