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..c41b37c36 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 @@ -6,6 +6,10 @@ private[focus] trait ErrorHandling { def errorMessage(error: FocusError): String = error match { case FocusError.NotACaseClass(fromClass, fieldName) => s"Cannot generate Lens for field '$fieldName', because '$fromClass' is not a case class" + case FocusError.NotACaseField(caseClass, fieldName) => + s"Can only create lenses for case fields, but '$fieldName' is not a case field of '$caseClass'" + case FocusError.NonImplicitNonCaseParameter(caseClass, parameters) => + s"Case class '$caseClass' has non-implicit non-case parameters, which is not supported: ${parameters.map("'" + _ + "'").mkString(", ")}" case FocusError.NotAConcreteClass(fromClass) => s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass" case FocusError.NotASimpleLambdaFunction => @@ -17,5 +21,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, explanation) => + s"Could not find (unique) implicit value for '$implicitType' due to $explanation. Note: 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..0430ff830 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 @@ -7,20 +7,23 @@ private[focus] trait FocusBase { given Quotes = macroContext + type Symbol = macroContext.reflect.Symbol type Term = macroContext.reflect.Term type TypeRepr = macroContext.reflect.TypeRepr case class LambdaConfig(argName: String, lambdaBody: Term) enum FocusAction { - case SelectField(fieldName: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr) + case SelectField(caseFieldSymbol: Symbol, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr) + case SelectFieldWithImplicits(caseFieldSymbol: Symbol, fromType: TypeRepr, toType: TypeRepr, setter: Term) case SelectOnlyField( - fieldName: String, + caseFieldSymbol: Symbol, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], fromCompanion: Term, toType: TypeRepr ) + case SelectOnlyFieldWithImplicits(caseFieldSymbol: Symbol, fromType: TypeRepr, toType: TypeRepr, reverseGet: Term) case KeywordSome(toType: TypeRepr) case KeywordAs(fromType: TypeRepr, toType: TypeRepr) case KeywordEach(fromType: TypeRepr, toType: TypeRepr, eachInstance: Term) @@ -29,10 +32,14 @@ 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(caseFieldSymbol, fromType, fromTypeArgs, toType) => + s"SelectField(${caseFieldSymbol.name}, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ${toType.show})" + case SelectFieldWithImplicits(caseFieldSymbol, fromType, toType, setter) => + s"SelectFieldWithImplicits(${caseFieldSymbol.name}, ${fromType.show}, ${toType.show}, ...)" + case SelectOnlyField(caseFieldSymbol, fromType, fromTypeArgs, _, toType) => + s"SelectOnlyField(${caseFieldSymbol.name}, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})" + case SelectOnlyFieldWithImplicits(caseFieldSymbol, fromType, toType, reverseGet) => + s"SelectOnlyFieldWithImplicits(${caseFieldSymbol.name}, ${fromType.show}, ${toType.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}, ...)" @@ -44,6 +51,8 @@ private[focus] trait FocusBase { enum FocusError { case NotACaseClass(className: String, fieldName: String) + case NotACaseField(className: String, fieldName: String) + case NonImplicitNonCaseParameter(className: String, parameters: List[String]) case NotAConcreteClass(className: String) case DidNotDirectlyAccessArgument(argName: String) case NotASimpleLambdaFunction @@ -52,6 +61,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, explanation: 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 568a50eee..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 @@ -28,24 +26,27 @@ private[focus] trait GeneratorLoop { import macroContext.reflect._ - def generateCode[From: Type](actions: List[FocusAction]): FocusResult[Term] = { - val idOptic: FocusResult[Term] = Right('{ Iso.id[From] }.asTerm) - - actions.foldLeft(idOptic) { (resultSoFar, action) => - resultSoFar.flatMap(term => composeOptics(term, generateActionCode(action))) + def generateCode[From: Type](actions: List[FocusAction]): FocusResult[Term] = + actions match { + case Nil => Right('{ Iso.id[From] }.asTerm) + case head :: tail => + tail.foldLeft[FocusResult[Term]](Right(generateActionCode(head))) { (resultSoFar, action) => + resultSoFar.flatMap(term => composeOptics(term, generateActionCode(action))) + } } - } private def generateActionCode(action: FocusAction): Term = action match { - case a: FocusAction.SelectField => generateSelectField(a) - case a: FocusAction.SelectOnlyField => generateSelectOnlyField(a) - case a: FocusAction.KeywordSome => generateSome(a) - case a: FocusAction.KeywordAs => generateAs(a) - case a: FocusAction.KeywordEach => generateEach(a) - case a: FocusAction.KeywordAt => generateAt(a) - case a: FocusAction.KeywordIndex => generateIndex(a) - case a: FocusAction.KeywordWithDefault => generateWithDefault(a) + case a: FocusAction.SelectField => generateSelectField(a) + case a: FocusAction.SelectFieldWithImplicits => generateSelectFieldWithImplicits(a) + case a: FocusAction.SelectOnlyField => generateSelectOnlyField(a) + case a: FocusAction.SelectOnlyFieldWithImplicits => generateSelectOnlyFieldWithImplicits(a) + case a: FocusAction.KeywordSome => generateSome(a) + case a: FocusAction.KeywordAs => generateAs(a) + case a: FocusAction.KeywordEach => generateEach(a) + case a: FocusAction.KeywordAt => generateAt(a) + case a: FocusAction.KeywordIndex => generateIndex(a) + case a: FocusAction.KeywordWithDefault => generateWithDefault(a) } private def composeOptics(lens1: Term, lens2: Term): FocusResult[Term] = 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/SelectParserBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala index 3df1ffa99..f1edb9b57 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,52 +1,65 @@ 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 => import this.macroContext.reflect._ - // Match on a term that is an instance of a case class - object CaseClass { - def unapply(term: Term): Option[Term] = + case class CaseClass(typeRepr: TypeRepr, classSymbol: Symbol) { + val typeArgs: List[TypeRepr] = getSuppliedTypeArgs(typeRepr) + val companionObject: Term = Ref(classSymbol.companionModule) + + private val (typeParams, caseFieldParams :: otherParams) = + classSymbol.primaryConstructor.paramSymss.span(_.headOption.fold(false)(_.isTypeParam)) + val hasOnlyOneCaseField: Boolean = caseFieldParams.length == 1 + val hasOnlyOneParameterList: Boolean = otherParams.isEmpty + private val nonCaseNonImplicitParameters: List[Symbol] = + otherParams.flatten.filterNot(symbol => symbol.flags.is(Flags.Implicit) || symbol.flags.is(Flags.Given)) + val allOtherParametersAreImplicitResult: FocusResult[Unit] = nonCaseNonImplicitParameters match { + case Nil => Right(()) + case list => FocusError.NonImplicitNonCaseParameter(typeRepr.show, list.map(_.name)).asResult + } + + def getCaseFieldSymbol(fieldName: String): FocusResult[Symbol] = + classSymbol.caseFields.find(_.name == fieldName) match { + case Some(symbol) => Right(symbol) + case None => FocusError.NotACaseField(typeRepr.show, fieldName).asResult + } + def getCaseFieldType(caseFieldSymbol: Symbol): FocusResult[TypeRepr] = + caseFieldSymbol match { + case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(typeRepr, possiblyTypeArg)) + case _ => FocusError.CouldntFindFieldType(typeRepr.show, caseFieldSymbol.name).asResult + } + } + + object CaseClassExtractor { + def unapply(term: Term): Option[CaseClass] = term.tpe.classSymbol.flatMap { sym => - Option.when(sym.flags.is(Flags.Case))(term) + Option.when(sym.flags.is(Flags.Case))(CaseClass(getType(term), sym)) } } - def getSuppliedTypeArgs(fromType: TypeRepr): List[TypeRepr] = + private def getSuppliedTypeArgs(fromType: TypeRepr): List[TypeRepr] = fromType match { case AppliedType(_, argTypeReprs) => argTypeReprs case _ => Nil } - def getClassSymbol(tpe: TypeRepr): FocusResult[Symbol] = tpe.classSymbol match { - case Some(sym) => Right(sym) - case None => FocusError.NotAConcreteClass(tpe.show).asResult - } - - def getFieldType(fromType: TypeRepr, fieldName: String): FocusResult[TypeRepr] = { - // 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) - - getClassSymbol(fromType).flatMap { fromTypeSymbol => - getTrimmedFieldSymbol(fromTypeSymbol) match { - case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg)) - case _ => FocusError.CouldntFindFieldType(fromType.show, fieldName).asResult - } - } - } - private object FieldType { def unapply(fieldSymbol: Symbol): Option[TypeRepr] = fieldSymbol match { case sym if sym.isNoSymbol => None case sym => sym.tree match { case ValDef(_, typeTree, _) => Some(typeTree.tpe) - case _ => None + // Only needed for Tuples because `_1` is a DefDef while `_1 ` is the corresponding ValDef. + case DefDef(_, _, typeTree, _) => Some(typeTree.tpe) + case _ => None } } } @@ -69,4 +82,42 @@ 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, _)) => + def searchForImplicit(typeRepr: TypeRepr): FocusResult[Term] = + Implicits.search(typeRepr) match { + case success: ImplicitSearchSuccess => + Right(success.tree) + case failure: ImplicitSearchFailure => + FocusError.ImplicitNotFound(typeRepr.show, failure.explanation).asResult + } + + searchForImplicit(t.tpe) + .map(acc :+ _) + + 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 699efdef8..97055c247 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,30 +1,72 @@ package monocle.internal.focus.features.selectfield import monocle.internal.focus.FocusBase +import monocle.Iso import monocle.Lens -import scala.quoted.Quotes private[focus] trait SelectFieldGenerator { this: FocusBase => import macroContext.reflect._ - def generateSelectField(action: FocusAction.SelectField): Term = { - import action.{fieldName, fromType, fromTypeArgs, toType} + private def generateGetter(from: Term, caseFieldSymbol: Symbol): Term = + Select(from, caseFieldSymbol) // o.field - def generateGetter(from: Term): Term = - Select.unique(from, fieldName) // o.field + def generateSelectField(action: FocusAction.SelectField): Term = { + import action.{caseFieldSymbol, fromType, fromTypeArgs, toType} def generateSetter(from: Term, to: Term): Term = - Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil) // o.copy(field = value) + Select.overloaded(from, "copy", fromTypeArgs, NamedArg(caseFieldSymbol.name, to) :: Nil) // o.copy(field = value) (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, caseFieldSymbol).asExprOf[t] })((to: t) => (from: f) => ${ generateSetter('{ from }.asTerm, '{ to }.asTerm).asExprOf[f] } ) }.asTerm } } + + def generateSelectFieldWithImplicits(action: FocusAction.SelectFieldWithImplicits): Term = { + import action.{caseFieldSymbol, fromType, toType, setter} + + (fromType.asType, toType.asType) match { + case ('[f], '[t]) => + '{ + Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })( + ${ setter.asExprOf[t => f => f] } + ) + }.asTerm + } + } + + def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = { + import action.{caseFieldSymbol, 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, caseFieldSymbol).asExprOf[t] })((to: t) => + ${ generateReverseGet('{ to }.asTerm).asExprOf[f] } + ) + }.asTerm + } + } + + def generateSelectOnlyFieldWithImplicits(action: FocusAction.SelectOnlyFieldWithImplicits): Term = { + import action.{caseFieldSymbol, fromType, toType, reverseGet} + + (fromType.asType, toType.asType) match { + case ('[f], '[t]) => + '{ + Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).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 a87a76873..0e295d2d0 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 @@ -12,11 +12,19 @@ private[focus] trait SelectFieldParser { def unapply(term: Term): Option[FocusResult[(RemainingCode, FocusAction)]] = term match { - case Select(CaseClass(remainingCode), fieldName) => - val fromType = getType(remainingCode) - val action = getFieldAction(fromType, fieldName) - val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) - Some(remainingCodeWithAction) + case Select(remainingCode @ CaseClassExtractor(caseClass: CaseClass), fieldName) => + Some( + for { + _ <- caseClass.allOtherParametersAreImplicitResult + caseFieldSymbol <- caseClass.getCaseFieldSymbol(fieldName) + action <- (caseClass.hasOnlyOneParameterList, caseClass.hasOnlyOneCaseField) match { + case (true, false) => getSelectFieldAction(caseClass, caseFieldSymbol) + case (false, false) => getSelectFieldActionWithImplicits(caseClass, caseFieldSymbol) + case (true, true) => getSelectOnlyFieldAction(caseClass, caseFieldSymbol) + case (false, true) => getSelectOnlyFieldActionWithImplicits(caseClass, caseFieldSymbol) + } + } yield (RemainingCode(remainingCode), action) + ) case Select(remainingCode, fieldName) => Some(FocusError.NotACaseClass(remainingCode.tpe.show, fieldName).asResult) @@ -25,8 +33,103 @@ 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)) + private def getSelectFieldAction( + caseClass: CaseClass, + caseFieldSymbol: Symbol + ): FocusResult[FocusAction] = + for { + toType <- caseClass.getCaseFieldType(caseFieldSymbol) + } yield FocusAction.SelectField( + caseFieldSymbol, + caseClass.typeRepr, + caseClass.typeArgs, + toType + ) + + private def getSelectFieldActionWithImplicits( + caseClass: CaseClass, + caseFieldSymbol: Symbol + ): FocusResult[FocusAction] = + for { + toType <- caseClass.getCaseFieldType(caseFieldSymbol) + setter <- constructSetter(caseFieldSymbol.name, caseClass.typeRepr, toType, caseClass.typeArgs) + } yield FocusAction.SelectFieldWithImplicits( + caseFieldSymbol, + caseClass.typeRepr, + toType, + setter + ) + + private def getSelectOnlyFieldAction( + caseClass: CaseClass, + caseFieldSymbol: Symbol + ): FocusResult[FocusAction] = + for { + toType <- caseClass.getCaseFieldType(caseFieldSymbol) + } yield FocusAction.SelectOnlyField( + caseFieldSymbol, + caseClass.typeRepr, + caseClass.typeArgs, + caseClass.companionObject, + toType + ) + + private def getSelectOnlyFieldActionWithImplicits( + caseClass: CaseClass, + caseFieldSymbol: Symbol + ): FocusResult[FocusAction] = + for { + toType <- caseClass.getCaseFieldType(caseFieldSymbol) + reverseGet <- constructReverseGet(caseClass.companionObject, caseClass.typeRepr, toType, caseClass.typeArgs) + } yield FocusAction.SelectOnlyFieldWithImplicits( + caseFieldSymbol, + caseClass.typeRepr, + 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] = + // from.copy(value)(implicits)+ + (fromType.asType, toType.asType) match { + case ('[f], '[t]) => + 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) + } + + 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 0b8948e39..000000000 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala +++ /dev/null @@ -1,30 +0,0 @@ -package monocle.internal.focus.features.selectonlyfield - -import monocle.internal.focus.FocusBase -import monocle.Iso -import scala.quoted.Quotes - -private[focus] trait SelectOnlyFieldGenerator { - this: FocusBase => - - 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) - - (fromType.asType, toType.asType) match { - case ('[f], '[t]) => - '{ - Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm).asExprOf[t] })((to: t) => - ${ generateReverseGet('{ to }.asTerm).asExprOf[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 17cb11036..000000000 --- a/core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala +++ /dev/null @@ -1,37 +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), fieldName) if hasOnlyOneField(remainingCode) => - val fromType = getType(remainingCode) - val action = getFieldAction(fromType, fieldName) - val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) - Some(remainingCodeWithAction) - - case _ => None - } - } - - private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = - for { - toType <- getFieldType(fromType, fieldName) - companion <- getCompanionObject(fromType) - supplied = getSuppliedTypeArgs(fromType) - } yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType) - - 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)) -} diff --git a/macro/src/test/scala-2.x/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala b/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala similarity index 64% rename from macro/src/test/scala-2.x/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala rename to macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala index d0a1bf3f3..8066bce60 100644 --- a/macro/src/test/scala-2.x/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala +++ b/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala @@ -9,18 +9,18 @@ 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]) { + def bar(t: T): T = 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)