Skip to content

Macro changes semantics based on how you refer to a type located in an object after a seemingly innocent quote match #16636

Closed
@arainko

Description

@arainko

Compiler version

3.2.1, 3.3.0-RC1-bin-20230106-d49c2d9-NIGHTLY

Minimized code

// file: ReproTransformer.scala

import scala.quoted.*

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 def getTransformer[A, B]: ReproTransformer[A, B] = ${ getTransformerMacro[A, B] }

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

    val transformer = (A -> B) match {
      case '[a] -> '[b] => 
        val summoned = Expr.summon[ReproTransformer[a, b]].get
// ----------- INTERESTING STUFF STARTS HERE
        summoned match {
          case '{ $t: ReproTransformer[src, dest] } => t
        }
// ----------- INTERESTING STUFF ENDS HERE
    }
    transformer.asExprOf[ReproTransformer[A, B]]
  }
}
// file: usage.scala

object A {
  case class AnotherCaseClass(name: String)

  val errorsOut1 = ReproTransformer.getTransformer[A.AnotherCaseClass, AnotherCaseClass]
  val errorsOu2 = ReproTransformer.getTransformer[AnotherCaseClass, A.AnotherCaseClass]

  val works1 = ReproTransformer.getTransformer[A.AnotherCaseClass, A.AnotherCaseClass]
  val works2 = ReproTransformer.getTransformer[AnotherCaseClass, AnotherCaseClass]
}

Output

[error] -- Error: /home/aleksander/repos/ducktape/ducktape/src/main/scala/io/github/arainko/ducktape/usage.scala:8:50 
[error]  8 |  val errorsOut1 = ReproTransformer.getTransformer[A.AnotherCaseClass, AnotherCaseClass]
[error]    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |Exception occurred while executing macro expansion.
[error]    |java.lang.Exception: Expr cast exception: io.github.arainko.ducktape.ReproTransformer.identity[io.github.arainko.ducktape.A.AnotherCaseClass, B]
[error]    |of type: io.github.arainko.ducktape.ReproTransformer.Identity[io.github.arainko.ducktape.A.AnotherCaseClass, B]
[error]    |did not conform to type: io.github.arainko.ducktape.ReproTransformer[io.github.arainko.ducktape.A.AnotherCaseClass, io.github.arainko.ducktape.A.AnotherCaseClass]
[error]    |
[error]    |    at scala.quoted.runtime.impl.QuotesImpl.asExprOf(QuotesImpl.scala:73)
[error]    |    at io.github.arainko.ducktape.ReproTransformer$.getTransformerMacro(ReproTransformer.scala:30)
[error]    |
[error]    |----------------------------------------------------------------------------
[error]    |Inline stack trace
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from ReproTransformer.scala:16
[error] 16 |  inline def getTransformer[A, B]: ReproTransformer[A, B] = ${ getTransformerMacro[A, B] }
[error]    |                                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]     ----------------------------------------------------------------------------

Expectation

An additional (irrefutable?) quote match shouldn't change the semantics of the macro and the way you refer to a type located in an object shouldn't matter.

If you change the implementation of getTransformerMacro to not include the additional pattern match:

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

    val transformer = (A -> B) match {
      case '[a] -> '[b] => 
        val summoned = Expr.summon[ReproTransformer[a, b]].get
// ----------- INTERESTING STUFF STARTS HERE
        summoned // return the summoned Expr straight away without additional matches
// ----------- INTERESTING ends STARTS HERE
    }
    transformer.asExprOf[ReproTransformer[A, B]]
  }

both of the usages compile properly.

Context

This issue was brought up by one of the users of ducktape here: arainko/ducktape#26, which was subsequently fixed here: arainko/ducktape#27

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions