Skip to content

Commit

Permalink
Fix compilation of lenses with context bounds in Scala 3
Browse files Browse the repository at this point in the history
  • Loading branch information
NTPape committed Feb 25, 2022
1 parent 21a37a0 commit d37be02
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import scala.quoted.Type

private[focus] trait AllFeatureGenerators
extends FocusBase
with SelectGeneratorBase
with SelectFieldGenerator
with SelectOnlyFieldGenerator
with SomeGenerator
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package monocle.internal.focus.features

import monocle.internal.focus.FocusBase
import scala.annotation.tailrec

private[focus] trait SelectGeneratorBase {
this: FocusBase =>

import this.macroContext.reflect._

def generateGetter(from: Term, fieldName: String): Term =
Select.unique(from, fieldName) // o.field

@tailrec
final def etaExpandIfNecessary(term: Term): Term =
if (term.isExpr) {
term
} else {
val expanded: Term = term.etaExpand(Symbol.spliceOwner)

val implicits: List[Term] = expanded match {
case Block(List(DefDef(_, List(params), _, _)), _) =>
params.params.map {
case ValDef(_, t, _) =>
val typeRepr: TypeRepr = t.tpe.dealias
Implicits.search(typeRepr) match {
case success: ImplicitSearchSuccess => success.tree
case _ =>
report.errorAndAbort(
s"Couldn't find assumed implicit for ${typeRepr.show}. Neither " +
s"multiple (non-implicit) parameter sets nor default arguments for implicits are supported."
)
}
case other =>
report.errorAndAbort(
s"Expected a value definition as parameter but found $other."
)
}
case other =>
report.errorAndAbort(
s"Expected code block with eta expanded function but found $other."
)
}

etaExpandIfNecessary(Apply(term, implicits))
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package monocle.internal.focus.features.selectfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectGeneratorBase
import monocle.Lens
import scala.quoted.Quotes

private[focus] trait SelectFieldGenerator {
this: FocusBase =>
this: FocusBase with SelectGeneratorBase =>

import macroContext.reflect._

def generateSelectField(action: FocusAction.SelectField): Term = {
import action.{fieldName, fromType, fromTypeArgs, toType}

def generateGetter(from: Term): Term =
Select.unique(from, fieldName) // o.field

def generateSetter(from: Term, to: Term): Term =
Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil) // o.copy(field = value)
// o.copy(field = value)(implicits)*
etaExpandIfNecessary(Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil))

(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
'{
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm).asExprOf[t] })((to: t) =>
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
(from: f) => ${ generateSetter('{ from }.asTerm, '{ to }.asTerm).asExprOf[f] }
)
}.asTerm
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package monocle.internal.focus.features.selectonlyfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectGeneratorBase
import monocle.Iso
import scala.quoted.Quotes

private[focus] trait SelectOnlyFieldGenerator {
this: FocusBase =>
this: FocusBase with SelectGeneratorBase =>

import macroContext.reflect._

def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = {
import action.{fieldName, fromType, fromTypeArgs, fromCompanion, toType}

def generateGetter(from: Term): Term =
Select.unique(from, fieldName) // o.field

def generateReverseGet(to: Term): Term =
Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)) // Companion.apply(value)
// Companion.apply(value)(implicits)*
etaExpandIfNecessary(Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)))

(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
'{
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm).asExprOf[t] })((to: t) =>
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
${ generateReverseGet('{ to }.asTerm).asExprOf[f] }
)
}.asTerm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ class ContextBoundCompilationIssueSpec extends DisciplineSuite {
private trait Foo[T]
private trait Bar[T]

private case class A[T: Foo](s: A.S[T]) {
val lens: Lens[A.S[T], Bar[T]] = GenLens[A.S[T]](_.bar)
private case class A[T: Foo](s: S[T]) {
val lens: Lens[S[T], Bar[T]] = GenLens[S[T]](_.bar)
}

private object A {
case class S[T: Foo](bar: Bar[T])
}
private case class S[T: Foo](bar: Bar[T])

private case object FooImpl extends Foo[Unit]
private case object BarImpl extends Bar[Unit]

private val a: A[Unit] = A(A.S(BarImpl)(FooImpl))(FooImpl)
private val a: A[Unit] = A(S(BarImpl)(FooImpl))(FooImpl)

test("context.bound.compilation") {
assertEquals(a.lens.get(a.s), BarImpl)
Expand Down

0 comments on commit d37be02

Please sign in to comment.