Open deusaquilus opened 3 years ago
Btw, I cannot create AnyVal encoders in Quill unless this is solved :anguished:
@deusaquilus a side question: can't you use opaque type Wrap = String
?
I can make an Mapped-Encoder for opaque types but whatever macros I use for that will probably have a similar issue as this one.
Minimized to
import scala.quoted.*
class Encoder
class EncoderContext:
implicit inline def anyClsEncoder: Encoder =
${ EncoderContext.applyImpl('this) }
object EncoderContext:
def applyImpl(ctx: Expr[EncoderContext])(using Quotes): Expr[Encoder] =
'{ $ctx; ??? }
object SummonAndEncode:
inline def apply(): Unit =
${ applyImpl }
private def applyImpl(using Quotes): Expr[Unit] =
Expr.summon[Encoder] match
case Some(value) => println("ENCODER FOUND: " + value.show)
case None => quotes.reflect.report.error("ENCODER NOT FOUND: " + Type.show[Encoder])
'{ () }
object Test:
def main(args: Array[String]): Unit = {
val ctx = new EncoderContext()
import ctx._
summon[Encoder] // ok
SummonAndEncode() // breaks
}
Possible workaround
class EncoderContext { self =>
def encode[Cls](cls: Cls) = List(cls.toString)
implicit inline def anyClsEncoder[Cls]: Encoder[Cls] =
- MappedEncoderMaker[Cls](self)
+ new Encoder[Cls] { def encode(m: Cls) = self.encode[Cls](m) }
}
Unfortunately, that workaround doesn't work for me. In this toy-example, I don't actually need the MappedEncoderMaker
to encode Cls
. In reality, however, Cls
is actually a AnyVal class that MappedEncoderMaker
finds a constructor for and then creates. If you are interested, here is a full example of what I need to do (also separated out in it's own repo).
https://github.com/deusaquilus/anyval_encoder_issue/tree/full_example
In reality, MappedEncoderMaker
looks like this:
object MappedEncoderMaker:
inline def apply[Encoder[_], Mapped <: AnyVal](inline ctx: AnyValEncoderContext[Encoder, Mapped]): Encoder[Mapped] = ${ applyImpl[Encoder, Mapped]('ctx) }
def applyImpl[Encoder[_]: Type, Mapped <: AnyVal: Type](ctx: Expr[AnyValEncoderContext[Encoder, Mapped]])(using qctx: Quotes): Expr[Encoder[Mapped]] =
import qctx.reflect._
val tpe = TypeRepr.of[Mapped]
val firstParam = tpe.typeSymbol.primaryConstructor.paramSymss(0)(0)
val firstParamField = tpe.typeSymbol.memberField(firstParam.name)
val firstParamType = tpe.memberType(firstParamField)
// Try to summon an encoder from the first param type
firstParamType.asType match
case '[tt] =>
Expr.summon[Encoder[tt]] match
case Some(enc) =>
val mappedEncoding = '{ MappedEncoding((v:Mapped) => ${ Select('v.asTerm, firstParamField).asExprOf[tt] }) }
val out = '{ $ctx.makeMappedEncoder[tt]($mappedEncoding, $enc) }
println(s"========== RETURNING Encoder ${tpe.show} => ${firstParamType.show} Consisting of: ${out.show} =========")
out
case None =>
report.throwError(s"Cannot find a regular encoder for the AnyVal type ${tpe.show} or a mapped-encoder for it's base type: ${firstParamType.show}")
... and it is invoked in the EncoderContext
like this:
implicit inline def anyValEncoder[Cls <: AnyVal]: Encoder[Cls] =
MappedEncoderMaker[Encoder, Cls](
new AnyValEncoderContext[Encoder, Cls] {
override def makeMappedEncoder[Base](mapped: MappedEncoding[Cls, Base], encoder: Encoder[Base]): Encoder[Cls] =
self.mappedEncoder(mapped, encoder)
}
)
So I need it to be able to construct the AnyVal instance.
One additional note:
The anyValEncoder
needs self.mappedEncoder
since in reality (i.e. in the full ProtoQuill), EncoderContext is actually a trait whose mappedEncoder
method will be implemented later by a child context that inherits from it.
(Some additions in edits in the comment above just now)
Not sure if it helps but if you make def encode[Cls]
then it works. Unfortunately not useful for me as a workaround because I need this method to be virtual.
I think I found a working workaround. If you make it return a function that takes the EncoderContext and pass the encoder context afterward it works. I.e. if you do this:
object MappedEncoderMaker:
inline def apply[T]: EncoderContext => Encoder[T] = ${ applyImpl[T] }
def applyImpl[T: Type](using Quotes): Expr[EncoderContext => Encoder[T]] =
import quotes.reflect._
println(s"===== Creating Instance for: ${Printer.TypeReprShortCode.show(TypeRepr.of[T])}")
'{ (ctx: EncoderContext) => new Encoder[T] { def encode(m: T) = ctx.encode[T](m) } }
... and then call it like this:
class EncoderContext { self =>
def encode[Cls](cls: Cls) = List(cls.toString)
implicit inline def anyClsEncoder[Cls]: Encoder[Cls] =
MappedEncoderMaker[Cls].apply(self)
}
Hope this info helps.
Compiler version
RC3
Minimized code
Create a simple class that does something which we want an implicit instance of e.g. an Encoder.
Then create the macro
MappedEncoderMaker
in which we will access this EncoderContext. I decided to also add a print statement to see if the macro is being invoked.Creating a summoning macro that will attempt to summon the encoder created by our macro:
Finally, create a class that will import the context and attempt to summon the Encoder.
Output
The result is that the summoning macro will not be able to find the Encoder. However, right before, I can see that the MappedEncoderMaker is actually working as intended.
Expectation
Then encoder for the
Wrap
object should be successfully summoned.Note that if you remove the
self
from theMappedEncoderMaker[Cls](self)
call i.e:Then the encoder will be summoned:
Repo
https://github.com/deusaquilus/anyval_encoder_issue