Skip to content

Commit

Permalink
Move eta expansion / implicit search into parser
Browse files Browse the repository at this point in the history
  • Loading branch information
NTPape committed Mar 3, 2022
1 parent d37be02 commit b911eca
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ private[focus] trait ErrorHandling {
case FocusError.UnexpectedCodeStructure(code) => s"Unexpected code structure: $code"
case FocusError.CouldntFindFieldType(fromType, fieldName) => s"Couldn't find type for $fromType.$fieldName"
case FocusError.InvalidDowncast(fromType, toType) => s"Type '$fromType' could not be cast to '$toType'"
case FocusError.ImplicitNotFound(implicitType) =>
s"Could not find implicit for '$implicitType'. Note: multiple non-implicit parameter sets or implicits with default values are not supported."
case FocusError.ExpansionFailed(reason) =>
s"Case class with multiple parameter sets could not be expanded because of: $reason"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ private[focus] trait FocusBase {
case class LambdaConfig(argName: String, lambdaBody: Term)

enum FocusAction {
case SelectField(fieldName: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr)
case SelectField(
fieldName: String,
fromType: TypeRepr,
toType: TypeRepr,
setter: Term
)
case SelectOnlyField(
fieldName: String,
fromType: TypeRepr,
fromTypeArgs: List[TypeRepr],
fromCompanion: Term,
toType: TypeRepr
toType: TypeRepr,
reverseGet: Term
)
case KeywordSome(toType: TypeRepr)
case KeywordAs(fromType: TypeRepr, toType: TypeRepr)
Expand All @@ -29,10 +33,10 @@ private[focus] trait FocusBase {
case KeywordWithDefault(toType: TypeRepr, defaultValue: Term)

override def toString(): String = this match {
case SelectField(fieldName, fromType, fromTypeArgs, toType) =>
s"SelectField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ${toType.show})"
case SelectOnlyField(fieldName, fromType, fromTypeArgs, _, toType) =>
s"SelectOnlyField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})"
case SelectField(fieldName, fromType, toType, setter) =>
s"SelectField($fieldName, ${fromType.show}, ${toType.show}, ${setter.asExpr.show})"
case SelectOnlyField(fieldName, fromType, toType, reverseGet) =>
s"SelectOnlyField($fieldName, ${fromType.show}, ${toType.show}, ${reverseGet.asExpr.show})"
case KeywordSome(toType) => s"KeywordSome(${toType.show})"
case KeywordAs(fromType, toType) => s"KeywordAs(${fromType.show}, ${toType.show})"
case KeywordEach(fromType, toType, _) => s"KeywordEach(${fromType.show}, ${toType.show}, ...)"
Expand All @@ -52,6 +56,8 @@ private[focus] trait FocusBase {
case CouldntFindFieldType(fromType: String, fieldName: String)
case ComposeMismatch(type1: String, type2: String)
case InvalidDowncast(fromType: String, toType: String)
case ImplicitNotFound(implicitType: String)
case ExpansionFailed(reason: String)

def asResult: FocusResult[Nothing] = Left(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import scala.quoted.Type

private[focus] trait AllFeatureGenerators
extends FocusBase
with SelectGeneratorBase
with SelectFieldGenerator
with SelectOnlyFieldGenerator
with SomeGenerator
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package monocle.internal.focus.features

import monocle.internal.focus.FocusBase
import scala.annotation.tailrec
import scala.util.Failure
import scala.util.Success
import scala.util.Try

private[focus] trait SelectParserBase extends ParserBase {
this: FocusBase =>
Expand Down Expand Up @@ -30,7 +34,7 @@ private[focus] trait SelectParserBase extends ParserBase {
// We need to do this to support tuples, because even though they conform as case classes in other respects,
// for some reason their field names (_1, _2, etc) have a space at the end, ie `_1 `.
def getTrimmedFieldSymbol(fromTypeSymbol: Symbol): Symbol =
fromTypeSymbol.memberFields.find(_.name.trim == fieldName).getOrElse(Symbol.noSymbol)
fromTypeSymbol.fieldMembers.find(_.name.trim == fieldName).getOrElse(Symbol.noSymbol)

getClassSymbol(fromType).flatMap { fromTypeSymbol =>
getTrimmedFieldSymbol(fromTypeSymbol) match {
Expand Down Expand Up @@ -69,4 +73,36 @@ private[focus] trait SelectParserBase extends ParserBase {
case Some(typeParamList :: _) if typeParamList.exists(_.isTypeParam) => typeParamList
case _ => Nil
}

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

val implicitsResult: FocusResult[List[Term]] =
expanded match {
case Block(List(DefDef(_, List(params), _, _)), _) =>
params.params.foldLeft[FocusResult[List[Term]]](Right(List.empty[Term])) {
case (Right(acc), ValDef(_, t, _)) =>
val typeRepr: TypeRepr = t.tpe.dealias
Implicits.search(typeRepr) match {
case success: ImplicitSearchSuccess => Right(success.tree :: acc)
case _ => FocusError.ImplicitNotFound(typeRepr.show).asResult
}
case (Right(acc), other) =>
FocusError.ExpansionFailed(s"Expected value definition but found unexpected ${other.show}").asResult
case (left @ Left(_), _) =>
left
}
case other =>
FocusError.ExpansionFailed(s"Expected block of expanded term but found unexpected ${other.show}").asResult
}

implicitsResult match {
case Left(error) => Left(error)
case Right(implicits) => etaExpandIfNecessary(Apply(term, implicits))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
package monocle.internal.focus.features.selectfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectGeneratorBase
import monocle.Lens

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

import macroContext.reflect._

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

def generateSetter(from: Term, to: Term): Term =
// o.copy(field = value)(implicits)*
etaExpandIfNecessary(Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil))
import action.{fieldName, fromType, toType, setter}

(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
'{
Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
(from: f) => ${ generateSetter('{ from }.asTerm, '{ to }.asTerm).asExprOf[f] }
Lens.apply[f, t]((from: f) => ${ Select.unique('{ from }.asTerm, fieldName).asExprOf[t] })(
${ setter.asExprOf[t => f => f] }
)
}.asTerm
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package monocle.internal.focus.features.selectfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectParserBase
import scala.util.Failure
import scala.util.Success
import scala.util.Try

private[focus] trait SelectFieldParser {
this: FocusBase with SelectParserBase =>
Expand All @@ -27,6 +30,33 @@ private[focus] trait SelectFieldParser {

private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] =
getFieldType(fromType, fieldName).flatMap { toType =>
Right(FocusAction.SelectField(fieldName, fromType, getSuppliedTypeArgs(fromType), toType))
val typeArgs = getSuppliedTypeArgs(fromType)
constructSetter(fieldName, fromType, toType, typeArgs).map { setter =>
FocusAction.SelectField(fieldName, fromType, toType, setter)
}
}

private case class LiftException(error: FocusError) extends Exception

private def constructSetter(
fieldName: String,
fromType: TypeRepr,
toType: TypeRepr,
fromTypeArgs: List[TypeRepr]
): FocusResult[Term] =
// Companion.copy(value)(implicits)*
(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
Try('{ (to: t) => (from: f) =>
${
etaExpandIfNecessary(
Select.overloaded('{ from }.asTerm, "copy", fromTypeArgs, List(NamedArg(fieldName, '{ to }.asTerm)))
).fold(error => throw new LiftException(error), _.asExprOf[f])
}
}.asTerm) match {
case Success(term) => Right(term)
case Failure(LiftException(error)) => Left(error)
case Failure(other) => Left(FocusError.ExpansionFailed(other.toString))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
package monocle.internal.focus.features.selectonlyfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectGeneratorBase
import monocle.Iso

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

import macroContext.reflect._

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

def generateReverseGet(to: Term): Term =
// Companion.apply(value)(implicits)*
etaExpandIfNecessary(Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)))
import action.{fieldName, fromType, toType, reverseGet}

(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
'{
Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) =>
${ generateReverseGet('{ to }.asTerm).asExprOf[f] }
Iso.apply[f, t]((from: f) => ${ Select.unique('{ from }.asTerm, fieldName).asExprOf[t] })(
${ reverseGet.asExprOf[t => f] }
)
}.asTerm
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package monocle.internal.focus.features.selectonlyfield

import monocle.internal.focus.FocusBase
import monocle.internal.focus.features.SelectParserBase
import scala.util.Failure
import scala.util.Success
import scala.util.Try

private[focus] trait SelectOnlyFieldParser {
this: FocusBase with SelectParserBase =>
Expand All @@ -27,11 +30,36 @@ private[focus] trait SelectOnlyFieldParser {
toType <- getFieldType(fromType, fieldName)
companion <- getCompanionObject(fromType)
supplied = getSuppliedTypeArgs(fromType)
} yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType)
reverseGet <- constructReverseGet(companion, fromType, toType, supplied)
} yield FocusAction.SelectOnlyField(fieldName, fromType, toType, reverseGet)

private def hasOnlyOneField(fromCode: Term): Boolean =
getType(fromCode).classSymbol.exists(_.caseFields.length == 1)

private def getCompanionObject(fromType: TypeRepr): FocusResult[Term] =
getClassSymbol(fromType).map(sym => Ref(sym.companionModule))

private case class LiftException(error: FocusError) extends Exception

private def constructReverseGet(
companion: Term,
fromType: TypeRepr,
toType: TypeRepr,
fromTypeArgs: List[TypeRepr]
): FocusResult[Term] =
// Companion.apply(value)(implicits)*
(fromType.asType, toType.asType) match {
case ('[f], '[t]) =>
Try('{ (to: t) =>
${
etaExpandIfNecessary(
Select.overloaded(companion, "apply", fromTypeArgs, List('{ to }.asTerm))
).fold(error => throw new LiftException(error), _.asExprOf[f])
}
}.asTerm) match {
case Success(term) => Right(term)
case Failure(LiftException(error)) => Left(error)
case Failure(other) => Left(FocusError.ExpansionFailed(other.toString))
}
}
}

0 comments on commit b911eca

Please sign in to comment.