Skip to content

Commit

Permalink
Fix Splicer.isEscapedVariable (#16838)
Browse files Browse the repository at this point in the history
Consider that `val macro` expansion in the context can come from an
outer macro that is being expanded (i.e. this is a nested macro). Nested
macro expansion can occur when a macro summons an implicit macro.

Fixes partially #16835
  • Loading branch information
Kordyjan authored Feb 16, 2023
2 parents 9b8db9f + 5024353 commit 60f2b96
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 2 deletions.
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ object Splicer {
}
}

/** Checks that no symbol that whas generated within the macro expansion has an out of scope reference */
/** Checks that no symbol that was generated within the macro expansion has an out of scope reference */
def checkEscapedVariables(tree: Tree, expansionOwner: Symbol)(using Context): tree.type =
new TreeTraverser {
private[this] var locals = Set.empty[Symbol]
Expand Down Expand Up @@ -119,7 +119,10 @@ object Splicer {
sym.exists && !sym.is(Package)
&& sym.owner.ownersIterator.exists(x =>
x == expansionOwner || // symbol was generated within this macro expansion
isMacroOwner(x) // symbol was generated within another macro expansion
{ // symbol was generated within another macro expansion
isMacroOwner(x) &&
!ctx.owner.ownersIterator.contains(x)
}
)
&& !locals.contains(sym) // symbol is not in current scope
}.traverse(tree)
Expand Down
79 changes: 79 additions & 0 deletions tests/pos-macros/i16835/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import scala.quoted.*
import scala.deriving.Mirror

// derivation code is a slightly modified version of: https://github.com/lampepfl/dotty-macro-examples/blob/main/macroTypeClassDerivation/src/macro.scala
object Derivation {

// Typeclass instance gets constructed as part of a macro
inline given deriveFullyConstrucedByMacro[A](using Mirror.ProductOf[A]): Show[A] = Derivation.deriveShow[A]

// Typeclass instance is built inside as part of a method, only the 'show' impl is filled in by a macro
inline given derivePartiallyConstructedByMacro[A](using Mirror.ProductOf[A]): Show[A] =
new {
def show(value: A): String = Derivation.show(value)
}

inline def show[T](value: T): String = ${ showValue('value) }

inline def deriveShow[T]: Show[T] = ${ deriveCaseClassShow[T] }

private def deriveCaseClassShow[T](using quotes: Quotes, tpe: Type[T]): Expr[Show[T]] = {
import quotes.reflect.*
// Getting the case fields of the case class
val fields: List[Symbol] = TypeTree.of[T].symbol.caseFields

'{
new Show[T] {
override def show(t: T): String =
${ showValue('t) }
}
}
}

def showValue[T: Type](value: Expr[T])(using Quotes): Expr[String] = {
import quotes.reflect.*

val fields: List[Symbol] = TypeTree.of[T].symbol.caseFields

val vTerm: Term = value.asTerm
val valuesExprs: List[Expr[String]] = fields.map(showField(vTerm, _))
val exprOfList: Expr[List[String]] = Expr.ofList(valuesExprs)
'{ "{ " + $exprOfList.mkString(", ") + " }" }
}

/** Create a quoted String representation of a given field of the case class */
private def showField(using Quotes)(caseClassTerm: quotes.reflect.Term, field: quotes.reflect.Symbol): Expr[String] = {
import quotes.reflect.*

val fieldValDef: ValDef = field.tree.asInstanceOf[ValDef]
val fieldTpe: TypeRepr = fieldValDef.tpt.tpe
val fieldName: String = fieldValDef.name

val tcl: Term = lookupShowFor(fieldTpe) // Show[$fieldTpe]
val fieldValue: Term = Select(caseClassTerm, field) // v.field
val strRepr: Expr[String] = applyShow(tcl, fieldValue).asExprOf[String]
'{ ${ Expr(fieldName) } + ": " + $strRepr } // summon[Show[$fieldTpe]].show(v.field)
}

/** Look up the Show[$t] typeclass for a given type t */
private def lookupShowFor(using Quotes)(t: quotes.reflect.TypeRepr): quotes.reflect.Term = {
import quotes.reflect.*
t.asType match {
case '[tpe] =>
Implicits.search(TypeRepr.of[Show[tpe]]) match {
case res: ImplicitSearchSuccess => res.tree
case failure: DivergingImplicit => report.errorAndAbort(s"Diverving: ${failure.explanation}")
case failure: NoMatchingImplicits => report.errorAndAbort(s"NoMatching: ${failure.explanation}")
case failure: AmbiguousImplicits => report.errorAndAbort(s"Ambiguous: ${failure.explanation}")
case failure: ImplicitSearchFailure =>
report.errorAndAbort(s"catch all: ${failure.explanation}")
}
}
}

/** Composes the tree: $tcl.show($arg) */
private def applyShow(using Quotes)(tcl: quotes.reflect.Term, arg: quotes.reflect.Term): quotes.reflect.Term = {
import quotes.reflect.*
Apply(Select.unique(tcl, "show"), arg :: Nil)
}
}
11 changes: 11 additions & 0 deletions tests/pos-macros/i16835/Show_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
trait Show[A] {
def show(value: A): String
}

object Show {
given identity: Show[String] = a => a

given int: Show[Int] = _.toString()

given list[A](using A: Show[A]): Show[List[A]] = _.map(A.show).toString()
}
30 changes: 30 additions & 0 deletions tests/pos-macros/i16835/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import scala.deriving.*

object usage {
final case class Person(name: String, age: Int, otherNames: List[String], p2: Person2)

final case class Person2(name: String, age: Int, otherNames: List[String])

locally {
import Derivation.deriveFullyConstrucedByMacro
// works for case classes without other nested case classes inside
summon[Show[Person2]]

// also derives instances with nested case classes
summon[Show[Person]]
}

locally {
import Derivation.derivePartiallyConstructedByMacro

// works for case classes without other nested case classes inside
summon[Show[Person2]]

// fails for case classes with other nested case classes inside,
// note how that error is not a `NonMatching', `Diverging` or `Ambiguous` implicit search error but something else
/*
catch all: given instance deriveWithConstructionOutsideMacro in object Derivation does not match type io.github.arainko.ducktape.issue_repros.Show[Person2]
*/
summon[Show[Person]]
}
}

0 comments on commit 60f2b96

Please sign in to comment.