Implicit resolution quirk

Can anyone explain why the last example here fails to compile?
https://scastie.scala-lang.org/AX0uenhmQ6mIQ87xKNeQYg

Is this a 2.1x compiler bug?

It compiles under dotty just fine, apparently, but i can’t understand why the implicit referred to in example 2 isn’t found in example 3.

Any insight would be majorly appreciated.

I’m not really sure what the correct behavior here should be. But I think you should generally avoid things like _ <: FlatShapeLevel (existentials basically) in implicit parameters. Especially your tuple2Shape method looks very fishy to me with its 2 levels of <:, Level <: ShapeLevel and _ <: Level. That looks tough to infer correctly.

I could get it to work by refactoring some _ <: stuff: by introducing a type parameter L <: FlatShapeLevel in map and in pairItBad. So even without touching tuple2Shape :slight_smile:

Unfortunately I can’t share the working code snippet because for some reason scastie stole my save button…

Well this is a start, thanks for checking!

Alas i can’t control this part; all of this code (except the pair* functions) correspond to implementation in Slick, the Lightbend database project. It’s very much full of stuff like this, so i can’t really change the Levels or map or the definition of anything other than the pair* implementations in the actual source code.

Implicits with existentials are used all over the place (seemingly successfully) throughout slick without problems. I’m curious if anyone else can track down the exact reason for this one not working. I wonder what makes it different from all the other successful ones inside slick?

It also appears to work if you only change pairItBad like I described in my previous answer. I can’t really say why your version doesn’t work though, especially since it seems to work with dotty. The strangest part is that I just read somewhere that the kind of existentials that require importing scala.language.existentials will be removed in dotty, but they appear to work better in dotty in this piece of code.

Any other thoughts on this, anyone? It’s really causing some consternation over here :frowning:

In the original (non-example) code I tried Jasper’s suggestion for the pairItBad case, and even though it works in scastie in reduced example, it does not work in the real code, which when modified in a similar way as suggested has an implementation like:

object WithRights {
  def forGenericQueryWithIds[AA, BB](
      source: Query[AA, BB, Seq]) = new Object {
    def apply[LEVEL <: FlatShapeLevel](
        getId: (AA) => Rep[Id])(
        implicit queryShape: Shape[LEVEL, AA, BB, AA]) = {
      val mixedSource =
        source
          .joinLeft(otherTable)
          .on(getId(_) === _.Id)

      mixedSource
        .joinLeft(userRights)
        .on { case ((stuff, _), (ur, _)) =>
          ur.entityKey.asColumnOf[Id] === getId(stuff).?
        }
        // This map fails to find the correct Shape for the tuple.
        .map { case ((stuff, _), maybeUR) =>
          (stuff, maybeUR.map(_._2))
        }
    }
  }
}

Note the use of intermediate Object.apply is to make type inference for getId possible at the caller.