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 ee40cca32..f91025f4c 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 @@ -2,7 +2,6 @@ package monocle.internal.focus.features import monocle.internal.focus.FocusBase import monocle.internal.focus.features.selectfield.SelectFieldGenerator -import monocle.internal.focus.features.selectonlyfield.SelectOnlyFieldGenerator import monocle.internal.focus.features.some.SomeGenerator import monocle.internal.focus.features.as.AsGenerator import monocle.internal.focus.features.each.EachGenerator @@ -15,7 +14,6 @@ import scala.quoted.Type private[focus] trait AllFeatureGenerators extends FocusBase with SelectFieldGenerator - with SelectOnlyFieldGenerator with SomeGenerator with AsGenerator with EachGenerator diff --git a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/ParserLoop.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/ParserLoop.scala index 3c6313c96..175f85207 100644 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/ParserLoop.scala +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/ParserLoop.scala @@ -3,7 +3,6 @@ package monocle.internal.focus.features import scala.quoted.Type import monocle.internal.focus.FocusBase import monocle.internal.focus.features.selectfield.SelectFieldParser -import monocle.internal.focus.features.selectonlyfield.SelectOnlyFieldParser import monocle.internal.focus.features.some.SomeParser import monocle.internal.focus.features.as.AsParser import monocle.internal.focus.features.each.EachParser @@ -16,7 +15,6 @@ private[focus] trait AllFeatureParsers with SelectParserBase with KeywordParserBase with SelectFieldParser - with SelectOnlyFieldParser with SomeParser with AsParser with EachParser @@ -53,9 +51,6 @@ private[focus] trait ParserLoop { case KeywordWithDefault(Right(remainingCode, action)) => loop(remainingCode, action :: listSoFar) case KeywordWithDefault(Left(error)) => Left(error) - case SelectOnlyField(Right(remainingCode, action)) => loop(remainingCode, action :: listSoFar) - case SelectOnlyField(Left(error)) => Left(error) - case SelectField(Right(remainingCode, action)) => loop(remainingCode, action :: listSoFar) case SelectField(Left(error)) => Left(error) 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 92ee089c7..951973112 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,6 +1,7 @@ package monocle.internal.focus.features.selectfield import monocle.internal.focus.FocusBase +import monocle.Iso import monocle.Lens private[focus] trait SelectFieldGenerator { @@ -39,4 +40,33 @@ private[focus] trait SelectFieldGenerator { }.asTerm } } + + def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = { + import action.{fieldName, fromType, fromTypeArgs, fromCompanion, toType} + + def generateReverseGet(to: Term): Term = + Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)) // Companion.apply(value) + + (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] } + ) + }.asTerm + } + } + + def generateSelectOnlyFieldWithImplicits(action: FocusAction.SelectOnlyFieldWithImplicits): Term = { + 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] })( + ${ reverseGet.asExprOf[t => 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 a3176d4b8..85d46ccfc 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 @@ -13,14 +13,19 @@ private[focus] trait SelectFieldParser { def unapply(term: Term): Option[FocusResult[(RemainingCode, FocusAction)]] = term match { case Select(CaseClass(remainingCode, classSymbol), fieldName) => - val fromType = getType(remainingCode) - val action = if (hasOnlyOneParameterList(classSymbol)) { - getFieldAction(fromType, fieldName) + if (isCaseField(classSymbol, fieldName)) { + val fromType = getType(remainingCode) + val action = (hasOnlyOneParameterList(classSymbol), hasOnlyOneField(classSymbol)) match { + case (true, false) => getSelectFieldAction(fromType, fieldName) + case (false, false) => getSelectFieldActionWithImplicits(fromType, fieldName) + case (true, true) => getSelectOnlyFieldAction(fromType, classSymbol, fieldName) + case (false, true) => getSelectOnlyFieldActionWithImplicits(fromType, classSymbol, fieldName) + } + val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) + Some(remainingCodeWithAction) } else { - getFieldActionWithImplicits(fromType, fieldName) + Some(FocusError.NotACaseField(remainingCode.tpe.show, fieldName).asResult) } - val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) - Some(remainingCodeWithAction) case Select(remainingCode, fieldName) => Some(FocusError.NotACaseClass(remainingCode.tpe.show, fieldName).asResult) @@ -29,6 +34,12 @@ private[focus] trait SelectFieldParser { } } + private def isCaseField(classSymbol: Symbol, fieldName: String): Boolean = + classSymbol.caseFields.exists(_.name == fieldName) + + private def hasOnlyOneField(classSymbol: Symbol): Boolean = + classSymbol.caseFields.length == 1 + private def hasOnlyOneParameterList(classSymbol: Symbol): Boolean = classSymbol.primaryConstructor.paramSymss match { case _ :: Nil => true @@ -36,12 +47,15 @@ private[focus] trait SelectFieldParser { case _ => false } - private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = + private def getCompanionObject(classSymbol: Symbol): Term = + Ref(classSymbol.companionModule) + + private def getSelectFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = getFieldType(fromType, fieldName).flatMap { toType => Right(FocusAction.SelectField(fieldName, fromType, getSuppliedTypeArgs(fromType), toType)) } - private def getFieldActionWithImplicits(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = + private def getSelectFieldActionWithImplicits(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = getFieldType(fromType, fieldName).flatMap { toType => val typeArgs = getSuppliedTypeArgs(fromType) constructSetter(fieldName, fromType, toType, typeArgs).map { setter => @@ -49,27 +63,71 @@ private[focus] trait SelectFieldParser { } } + private def getSelectOnlyFieldAction( + fromType: TypeRepr, + fromClassSymbol: Symbol, + fieldName: String + ): FocusResult[FocusAction] = + for { + toType <- getFieldType(fromType, fieldName) + companion = getCompanionObject(fromClassSymbol) + supplied = getSuppliedTypeArgs(fromType) + } yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType) + + private def getSelectOnlyFieldActionWithImplicits( + fromType: TypeRepr, + fromClassSymbol: Symbol, + fieldName: String + ): FocusResult[FocusAction] = + for { + toType <- getFieldType(fromType, fieldName) + companion = getCompanionObject(fromClassSymbol) + supplied = getSuppliedTypeArgs(fromType) + reverseGet <- constructReverseGet(companion, fromType, toType, supplied) + } yield FocusAction.SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) + private case class LiftException(error: FocusError) extends Exception + private def liftEtaExpansionResult(term: => Term): FocusResult[Term] = + scala.util.Try(term) match { + case scala.util.Success(term) => Right(term) + case scala.util.Failure(LiftException(error)) => Left(error) + case scala.util.Failure(other) => Left(FocusError.ExpansionFailed(other.toString)) + } + private def constructSetter( fieldName: String, fromType: TypeRepr, toType: TypeRepr, fromTypeArgs: List[TypeRepr] ): FocusResult[Term] = - // Companion.copy(value)(implicits)* + // from.copy(value)(implicits)+ (fromType.asType, toType.asType) match { case ('[f], '[t]) => - scala.util.Try('{ (to: t) => (from: f) => + liftEtaExpansionResult('{ (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 scala.util.Success(term) => Right(term) - case scala.util.Failure(LiftException(error)) => Left(error) - case scala.util.Failure(other) => Left(FocusError.ExpansionFailed(other.toString)) - } + }.asTerm) + } + + 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]) => + liftEtaExpansionResult('{ (to: t) => + ${ + etaExpandIfNecessary( + Select.overloaded(companion, "apply", fromTypeArgs, List('{ to }.asTerm)) + ).fold(error => throw new LiftException(error), _.asExprOf[f]) + } + }.asTerm) } } 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 deleted file mode 100644 index 19d2de92e..000000000 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala +++ /dev/null @@ -1,42 +0,0 @@ -package monocle.internal.focus.features.selectonlyfield - -import monocle.internal.focus.FocusBase -import monocle.Iso - -private[focus] trait SelectOnlyFieldGenerator { - this: FocusBase => - - import macroContext.reflect._ - - private def generateGetter(from: Term, fieldName: String): Term = - Select.unique(from, fieldName) // o.field - - def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = { - import action.{fieldName, fromType, fromTypeArgs, fromCompanion, toType} - - def generateReverseGet(to: Term): Term = - Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)) // Companion.apply(value) - - (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] } - ) - }.asTerm - } - } - - def generateSelectOnlyFieldWithImplicits(action: FocusAction.SelectOnlyFieldWithImplicits): Term = { - 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] })( - ${ 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 deleted file mode 100644 index 92a649422..000000000 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala +++ /dev/null @@ -1,94 +0,0 @@ -package monocle.internal.focus.features.selectonlyfield - -import monocle.internal.focus.FocusBase -import monocle.internal.focus.features.SelectParserBase - -private[focus] trait SelectOnlyFieldParser { - this: FocusBase with SelectParserBase => - - import this.macroContext.reflect._ - - object SelectOnlyField extends FocusParser { - - def unapply(term: Term): Option[FocusResult[(RemainingCode, FocusAction)]] = term match { - - case Select(CaseClass(remainingCode, classSymbol), fieldName) if notACaseField(classSymbol, fieldName) => - Some(FocusError.NotACaseField(remainingCode.tpe.show, fieldName).asResult) - - case Select(CaseClass(remainingCode, classSymbol), fieldName) if hasOnlyOneField(classSymbol) => - val fromType = getType(remainingCode) - val action = if (hasOnlyOneParameterList(classSymbol)) { - getFieldAction(fromType, classSymbol, fieldName) - } else { - getFieldActionWithImplicits(fromType, classSymbol, fieldName) - } - val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) - Some(remainingCodeWithAction) - - case _ => None - } - } - - private def getFieldAction( - fromType: TypeRepr, - fromClassSymbol: Symbol, - fieldName: String - ): FocusResult[FocusAction] = - for { - toType <- getFieldType(fromType, fieldName) - companion = getCompanionObject(fromClassSymbol) - supplied = getSuppliedTypeArgs(fromType) - } yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType) - - private def getFieldActionWithImplicits( - fromType: TypeRepr, - fromClassSymbol: Symbol, - fieldName: String - ): FocusResult[FocusAction] = - for { - toType <- getFieldType(fromType, fieldName) - companion = getCompanionObject(fromClassSymbol) - supplied = getSuppliedTypeArgs(fromType) - reverseGet <- constructReverseGet(companion, fromType, toType, supplied) - } yield FocusAction.SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) - - private def hasOnlyOneField(classSymbol: Symbol): Boolean = - classSymbol.caseFields.length == 1 - - private def notACaseField(classSymbol: Symbol, fieldName: String): Boolean = - classSymbol.caseFields.forall(_.name != fieldName) - - private def hasOnlyOneParameterList(classSymbol: Symbol): Boolean = - classSymbol.primaryConstructor.paramSymss match { - case _ :: Nil => true - case (head :: _) :: _ :: Nil if head.isTypeParam => true - case _ => false - } - - private def getCompanionObject(classSymbol: Symbol): Term = - Ref(classSymbol.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]) => - scala.util.Try('{ (to: t) => - ${ - etaExpandIfNecessary( - Select.overloaded(companion, "apply", fromTypeArgs, List('{ to }.asTerm)) - ).fold(error => throw new LiftException(error), _.asExprOf[f]) - } - }.asTerm) match { - case scala.util.Success(term) => Right(term) - case scala.util.Failure(LiftException(error)) => Left(error) - case scala.util.Failure(other) => Left(FocusError.ExpansionFailed(other.toString)) - } - } -}