From b911eca400f9e767ea7462fe523e2ba8e1a0142f Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Thu, 3 Mar 2022 21:16:27 +0100 Subject: [PATCH] Move eta expansion / implicit search into parser --- .../internal/focus/ErrorHandling.scala | 4 ++ .../monocle/internal/focus/FocusBase.scala | 22 +++++---- .../focus/features/GeneratorLoop.scala | 1 - .../focus/features/SelectGeneratorBase.scala | 47 ------------------- .../focus/features/SelectParserBase.scala | 38 ++++++++++++++- .../selectfield/SelectFieldGenerator.scala | 13 ++--- .../selectfield/SelectFieldParser.scala | 32 ++++++++++++- .../SelectOnlyFieldGenerator.scala | 13 ++--- .../SelectOnlyFieldParser.scala | 30 +++++++++++- 9 files changed, 123 insertions(+), 77 deletions(-) delete mode 100644 core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala index bd7eaec07..9b1f0ffda 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala @@ -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" } } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala index 0917606c9..be08d7520 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala @@ -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) @@ -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}, ...)" @@ -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) } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/GeneratorLoop.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/GeneratorLoop.scala index 50422cba0..568a50eee 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/GeneratorLoop.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/GeneratorLoop.scala @@ -14,7 +14,6 @@ import scala.quoted.Type private[focus] trait AllFeatureGenerators extends FocusBase - with SelectGeneratorBase with SelectFieldGenerator with SelectOnlyFieldGenerator with SomeGenerator diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala deleted file mode 100644 index 9d99877bc..000000000 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala +++ /dev/null @@ -1,47 +0,0 @@ -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)) - } -} diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala index 3df1ffa99..93a117dde 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala @@ -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 => @@ -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 { @@ -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)) + } + } } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldGenerator.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldGenerator.scala index a75377e8b..7c8ea3b1c 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldGenerator.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldGenerator.scala @@ -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 } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldParser.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldParser.scala index a87a76873..f9195786c 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldParser.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectfield/SelectFieldParser.scala @@ -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 => @@ -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)) + } } } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala index 8134f969c..f2163a27f 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala @@ -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 } diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala index 17cb11036..a36da5556 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala @@ -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 => @@ -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)) + } + } }