Skip to content

Path dependent types don't play well with macros and typeclasses #7048

Closed
@anatoliykmetyuk

Description

@anatoliykmetyuk

Consider the following:

import scala.quoted._


trait IsExpr[T] {
  type Underlying
  def toExpr(x: T): Expr[Underlying]
}

// object IsExpr { type Aux[T, U] = IsExpr[T] { type Underlying = U } }

// given [U] as IsExpr.Aux[Expr[U], U] = new IsExpr[Expr[U]] {
given [U] as IsExpr[Expr[U]] = new IsExpr[Expr[U]] {
  type Underlying = U
  def toExpr(x: Expr[U]): Expr[U] = x
}

def f(x: Any): String = x.toString

def g[T](x: T) given (e: IsExpr[T], tu: Type[e.Underlying]): given QuoteContext => Expr[String] = {
  val underlying: Expr[e.Underlying] = e.toExpr(x)
  '{f($underlying)}
}

// def g[T, U](x: T) given (e: IsExpr.Aux[T, U], tu: Type[U]): given QuoteContext => Expr[String] = {
//   val underlying: Expr[U] = e.toExpr(x)
//   '{f($underlying)}
// }

inline def mcr: Any = ${mcrImpl}
def mcrImpl given QuoteContext: Expr[Any] = {
  val x = '{1}
  g(x)
}

Will output:

-- Error: ../pg/Lib.scala:21:6 -------------------------------------------------
21 |  '{f($underlying)}
   |      ^^^^^^^^^^^
   |      access to value e from wrong staging level:
   |       - the definition is at level 0,
   |       - but the access is at level 1.

Sort of makes sense, since e is indeed defined at level 0. However, two facts make me think something can be done here. First, only the path-dependent type is accessed there (presumably, since the compiler needs to know the type of underlying), and we have it with the Type argument. Second, the following trick works:

import scala.quoted._


trait IsExpr[T] {
  type Underlying
  def toExpr(x: T): Expr[Underlying]
}

object IsExpr { type Aux[T, U] = IsExpr[T] { type Underlying = U } }

given [U] as IsExpr.Aux[Expr[U], U] = new IsExpr[Expr[U]] {
// given [U] as IsExpr[Expr[U]] = new IsExpr[Expr[U]] {
  type Underlying = U
  def toExpr(x: Expr[U]): Expr[U] = x
}

def f(x: Any): String = x.toString

// def g[T](x: T) given (e: IsExpr[T], tu: Type[e.Underlying]): given QuoteContext => Expr[String] = {
//   val underlying: Expr[e.Underlying] = e.toExpr(x)
//   '{f($underlying)}
// }

def g[T, U](x: T) given (e: IsExpr.Aux[T, U], tu: Type[U]): given QuoteContext => Expr[String] = {
  val underlying: Expr[U] = e.toExpr(x)
  '{f($underlying)}
}

inline def mcr: Any = ${mcrImpl}
def mcrImpl given QuoteContext: Expr[Any] = {
  val x = '{1}
  g(x)
}

Aux trick comes from Shapeless 2 and was used to bypass the limitations of Scala 2, where it was impossible to refer to a path-dependent type of a sibling argument from the same argument list.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions