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

Resolve Issue #26 #27

Merged
merged 3 commits into from
Jan 7, 2023
Merged

Resolve Issue #26 #27

merged 3 commits into from
Jan 7, 2023

Conversation

arainko
Copy link
Owner

@arainko arainko commented Jan 7, 2023

The culprit of this seems to be a weird interaction of the following:

  • quote matches that seem innocent on Transformer[A, B] that really is a Transformer.Identity[A, B >: A] (note the type bounds) underneath,
  • the placement of a one of the field case classes in an object and not referring to it with its full path

I also managed to create a reproduction for this issue which probably points to a bug in the metaprogramming facilities of Scala 3 (that I'll make sure to report):

import scala.quoted.*
import scala.deriving.Mirror

@FunctionalInterface
trait ReproTransformer[A, B] {
  def transform(from: A): B
}

object ReproTransformer {
  final class Identity[A, B >: A] extends ReproTransformer[A, B] {
    def transform(from: A): B = from
  }

  given identity[A, B >: A]: Identity[A, B] = Identity[A, B]

  inline given derived[A <: Product, B <: Product](using Mirror.ProductOf[A], Mirror.ProductOf[B]): ReproTransformer[A, B] = 
    ${ deriveProductTransformerMacro[A, B] }

  def deriveProductTransformerMacro[A: Type, B: Type](using Quotes): Expr[ReproTransformer[A, B]] =
    '{ value => ${ transformProductMacro[A, B]('value) } }

  def transformProductMacro[A: Type, B: Type](source: Expr[A])(using Quotes): Expr[B] = {
    import quotes.reflect.*

    val sourceTpe = TypeRepr.of[A]
    val destTpe = TypeRepr.of[B]

    def fields(tpe: TypeRepr) =
      tpe.typeSymbol.caseFields
        .map(sym => sym.name -> tpe.memberType(sym))
        .toMap

    def accessField(source: Expr[A], name: String) = Select.unique(source.asTerm, name)

    val sourceFields = fields(sourceTpe)
    val destFields = fields(destTpe)

    val fieldTransformations =
      sourceFields.map { (name, sourceTpe) =>
        val destTpe = destFields(name)
        (sourceTpe.asType -> destTpe.asType) match {
          case '[src] -> '[dest] =>
            val transformer = Expr.summon[ReproTransformer[src, dest]].getOrElse(report.errorAndAbort(s"Not found for $name"))
// -------------------- INTERESTING STUFF STARTS HERE
            transformer match {
              case '{ $transformer: ReproTransformer[a, b] } =>
                val field = accessField(source, name).asExprOf[a]
                NamedArg(name, '{ $transformer.transform($field) }.asTerm)
            }
// -------------------- INTERESTING STUFF ENDS HERE
        }
      }.toList

    val constructorSym = destTpe.typeSymbol.primaryConstructor
    New(Inferred(destTpe)).select(constructorSym).appliedToArgs(fieldTransformations).asExprOf[B]
  }
}

If we were to take our original example:

case class A(anotherCaseClass: A.AnotherCaseClass)

object A {
  case class AnotherCaseClass(name: String)

  // note how AnotherCaseClass is not referred to as A.AnotherCaseClass
  case class B(anotherCaseClass: AnotherCaseClass)
}

and call ReproTransformer.derived[A, A.B] we'd end up with the issue as in #26.

However if we were to alter the INTERESTING block from the example above to just this (note how I only got rid of the pattern match that shouldn't change the semantics):

// -------------------- INTERESTING STUFF STARTS HERE
            val field = accessField(source, name).asExprOf[src]
            NamedArg(name, '{ $transformer.transform($field) }.asTerm)
// -------------------- INTERESTING STUFF ENDS HERE

then ReproTransformer.derived[A, A.B] compiles fine.

The issue is resolved by special-casing quote matches on Tranformer.Identity in ProductTransformerMacros until this weird interaction is gone (?)

@arainko arainko force-pushed the issue-26 branch 4 times, most recently from a62d0ac to c92046c Compare January 7, 2023 00:26
@arainko arainko merged commit 4e4fa09 into master Jan 7, 2023
arainko added a commit that referenced this pull request Jan 8, 2023
* reproduce issue 26

* workaround by prefixing `AnotherCaseClass` with `A.`

* fix issue 26
@arainko arainko deleted the issue-26 branch January 8, 2023 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Type Mismatch Error for the same type
1 participant