Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Refer to Path Dependent Type inside of type-quote #8887

Closed
deusaquilus opened this issue May 6, 2020 · 14 comments · Fixed by #17209
Closed

Cannot Refer to Path Dependent Type inside of type-quote #8887

deusaquilus opened this issue May 6, 2020 · 14 comments · Fixed by #17209
Assignees
Labels
Milestone

Comments

@deusaquilus
Copy link
Contributor

Minimized code

Let's say we want to create a class with a PDT for example:

trait Context {
  type RowType
}

Then inside of a macro, I want to be able to use this PDT (i.e. RowType) inside of a type-quote

val tpe = '[$context.RowType]

I would want to do this for many reasons... for example to summon an object with it:

val decodeOpt = Expr.summon(using '[Decoder[$context.RowType, T]])

Only it doesn't work...

Output

The compiler fails with the following error:

[error] 34 |    val tpe = '[$context.RowType]
[error]    |                        ^
[error]    |                        ']' expected, but '.' found

Expectation

The code should compile and run properly. Statements like '[$foo.Bar] should be possible.

Code

A full code sample of this issue and a minimized version of my use-case is available here:
https://github.com/deusaquilus/pdt_typesummon

@deusaquilus
Copy link
Contributor Author

FYI, I was using this functionality to implement Quill's QueryMeta functionality in a type-safe way. Going to look for workarounds.

@LPTK
Copy link
Contributor

LPTK commented May 6, 2020

How would this work? context could be an arbitrary expression which is not a stable path, which would probably result in generating a program with an illegal type projection.

Maybe the macro API could provide a Stable[T] <: Expr[T] type for such use cases.

@nicolasstucki
Copy link
Contributor

@deusaquilus could you provide a small self contained example that show from where you get the context in those cases?

@deusaquilus
Copy link
Contributor Author

@nicolasstucki The attached code sums it up. I have removed everything extraneous.
https://github.com/deusaquilus/pdt_typesummon/blob/master/src/main/scala/bug/Macro.scala

The idea is:

  1. Create a object with a type member used in internal implicit defs:
trait Context {
  type RowType
  implicit def stringDecoder: Decoder[RowType, String]
  def serve[T](decoder: Decoder[RowType, T]): T
}
  1. Summon one of these implicit defs inside of a macro. Then pass it back into the object.
  val tpe = '[Decoder[$context.RowType, T]]
  val decoderExpr = 
    Expr.summon(using tpe) match {
      case Some(decoder) => decoder
      case None => qctx.throwError(s"Cannot find decoder for: ${tpe.show}")
    }
  
  '{ $context.serve[T]($decoderExpr) }

That's basically it!

@nicolasstucki
Copy link
Contributor

@deusaquilus it looks like this can be encoded as

import scala.quoted._
import scala.quoted.matching._

trait Decoder[RowType, T] {
  def decode(row:RowType): T
}

trait Context {
  type RowType
  implicit def stringDecoder: Decoder[RowType, String]
  def serve[T](decoder: Decoder[RowType, T]): T
}

object Macro {

  inline def serveDecoder[T](context: Context): T = ${ serveDecoderImpl('context) }
  def serveDecoderImpl[T: Type, U: Type, C <: Context { type RowType = U }: Type](context: Expr[C])(using qctx: QuoteContext): Expr[T] = {
    import qctx.tasty._

    val tpe = '[Decoder[U, T]]
    val decoderExpr =
      Expr.summon(using tpe) match {
        case Some(decoder) => decoder
        case None => qctx.throwError(s"Cannot find decoder for: ${tpe.show}")
      }

    '{ $context.serve[T]($decoderExpr) }
  }
}

I have not tried to expand the macro.

@deusaquilus
Copy link
Contributor Author

deusaquilus commented May 8, 2020

@nicolasstucki I'll give that a try. My context (in the real Quill) itself has parameters. Should I encode that like this?

def serveDecoderImpl[T: Type, U: Type, CN <: Naming, C <: Context[CN] { type RowType = U }: Type](context: Expr[C])(using qctx: QuoteContext): Expr[T]

Or like this?

def serveDecoderImpl[T: Type, U: Type, CN <: Naming, C <: Context[_] { type RowType = U }: Type](context: Expr[C[CN]])(using qctx: QuoteContext): Expr[T]

@nicolasstucki
Copy link
Contributor

The first one looks better

@deusaquilus
Copy link
Contributor Author

deusaquilus commented May 8, 2020

Edit: Removing my previous comments as they were incorrect.

@nicolasstucki Okay, so your example above does not actually work because you need the type T in order to know type of T to be able to summon a decoder.

[error] 20 |  println( serveDecoder[String](ctx) )
[error]    |           ^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |        Cannot find decoder for: bug.Decoder[ctx.RowType, scala.Nothing]
[error]    | This location contains code that was inlined from Test.scala:20

You can have a look at that here:
https://github.com/deusaquilus/pdt_typesummon/tree/decoder_type_is_nothing

@deusaquilus
Copy link
Contributor Author

However, if I add a parameter to specify T as a specific type (e.g. via Class[T]), it will work:
This works:

inline def serveDecoder[T](context: Context, cls: Class[T]): T = ${ serveDecoderImpl('context, 'cls) }
def serveDecoderImpl[T: Type, U: Type, C <: Context { type RowType = U }: Type](context: Expr[C], cls: Expr[Class[T]])(using qctx: QuoteContext): Expr[T] = {

You can see it in this branch:
https://github.com/deusaquilus/pdt_typesummon/tree/working_with_inferred_types

@deusaquilus
Copy link
Contributor Author

If I want to manually type the inline serveDecoder method how do I do that?
I tried this:

inline def serveDecoder[T](context: Context): T = ${ serveDecoderImpl[T, context.RowType, Context]('context) }

... and I get:

[error] -- [E057] Type Mismatch Error: /home/alexander/git/dotty/pdt_typesummon/src/main/scala/bug/Macro.scala:18:92 
[error] 18 |  inline def serveDecoder[T](context: Context): T = ${ serveDecoderImpl[T, context.RowType, Context]('context) }
[error]    |                                                                                            ^
[error]    |Type argument bug.Context does not conform to upper bound bug.Context{RowType = context.RowType}

I tried adding Context { type RowType = context.RowType } like this:

inline def serveDecoder[T](context: Context): T = ${ serveDecoderImpl[T, context.RowType, Context { type RowType = context.RowType }]('context) }

... but then this error happens:

[error] Cyclic macro dependencies in /home/alexander/git/dotty/pdt_typesummon/src/main/scala/bug/Test.scala.
[error] Compilation stopped since no further progress can be made.

Code here:
https://github.com/deusaquilus/pdt_typesummon/tree/manual_typing_not_working

@nicolasstucki
Copy link
Contributor

That seems to be another bug. I will investigate it.

The context type parameter should probably be context.type.

@deusaquilus
Copy link
Contributor Author

deusaquilus commented May 8, 2020

Just tried that. That causes this error:

[error] 21 |  inline def serveDecoder[T](context: Context): T = ${ serveDecoderImpl[T, context.RowType, context.type]('context) }
[error]    |                                                                                            ^^^^^^^
[error]    |                       access to value context from wrong staging level:
[error]    |                        - the definition is at level 0,
[error]    |                        - but the access is at level -1.

Just checked that into the following branch:
https://github.com/deusaquilus/pdt_typesummon/tree/using_context.type

@nicolasstucki
Copy link
Contributor

That should be allowed.

@nicolasstucki
Copy link
Contributor

Minimized to

import scala.quoted._
inline def foo(x: Any): Any = ${ expr[x.type] }
def expr[X](using Quotes): Expr[Any] = ???
3 |inline def foo(x: Any): Any = ${ expr[x.type] }
  |                                      ^
  |                           access to parameter x from wrong staging level:
  |                            - the definition is at level 0,
  |                            - but the access is at level -1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants