From d37be02fb6100f89eeb21339d88bbee842a94b9c Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Sat, 26 Feb 2022 00:10:07 +0100 Subject: [PATCH 1/6] Fix compilation of lenses with context bounds in Scala 3 Fixes #1259 --- .../focus/features/GeneratorLoop.scala | 1 + .../focus/features/SelectGeneratorBase.scala | 47 +++++++++++++++++++ .../selectfield/SelectFieldGenerator.scala | 12 ++--- .../SelectOnlyFieldGenerator.scala | 12 ++--- .../ContextBoundCompilationIssueSpec.scala | 10 ++-- 5 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala rename macro/src/test/{scala-2.x => scala}/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala (64%) 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..50422cba0 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,6 +14,7 @@ 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 new file mode 100644 index 000000000..9d99877bc --- /dev/null +++ b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectGeneratorBase.scala @@ -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)) + } +} 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..a75377e8b 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,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 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 0b8948e39..8134f969c 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,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 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..0115c1484 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,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) From 83d4a7a7373b2c9b0516b887279f0faee1f7c92a 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 2/6] Move eta expansion / implicit search into parser --- .../internal/focus/ErrorHandling.scala | 4 ++ .../monocle/internal/focus/FocusBase.scala | 22 +++++---- .../focus/features/GeneratorLoop.scala | 14 +++--- .../focus/features/SelectGeneratorBase.scala | 47 ------------------- .../focus/features/SelectParserBase.scala | 38 ++++++++++++++- .../selectfield/SelectFieldGenerator.scala | 13 ++--- .../selectfield/SelectFieldParser.scala | 29 +++++++++++- .../SelectOnlyFieldGenerator.scala | 13 ++--- .../SelectOnlyFieldParser.scala | 27 ++++++++++- 9 files changed, 124 insertions(+), 83 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..b559f134a 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 @@ -29,13 +28,14 @@ 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 { 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..34ca2a8f9 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 @@ -27,6 +27,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]) => + scala.util.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 scala.util.Success(term) => Right(term) + case scala.util.Failure(LiftException(error)) => Left(error) + case scala.util.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..1d341b379 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 @@ -27,11 +27,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]) => + 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)) + } + } } From 7b6df5d30a75d2fb43e68473d6f8e0b3e8db7ca2 Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:28:47 +0100 Subject: [PATCH 3/6] Split Select(Only)Field(WithImplicits) --- .../internal/focus/ErrorHandling.scala | 2 + .../monocle/internal/focus/FocusBase.scala | 27 +++++---- .../focus/features/GeneratorLoop.scala | 18 +++--- .../focus/features/SelectParserBase.scala | 20 ++++--- .../selectfield/SelectFieldGenerator.scala | 21 ++++++- .../selectfield/SelectFieldParser.scala | 24 ++++++-- .../SelectOnlyFieldGenerator.scala | 21 ++++++- .../SelectOnlyFieldParser.scala | 56 +++++++++++++++---- 8 files changed, 144 insertions(+), 45 deletions(-) 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 9b1f0ffda..226f81365 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,8 @@ 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.NotAConcreteClass(fromClass) => s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass" case FocusError.NotASimpleLambdaFunction => 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 be08d7520..6a9ec2da2 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,18 +13,16 @@ private[focus] trait FocusBase { case class LambdaConfig(argName: String, lambdaBody: Term) enum FocusAction { - case SelectField( - fieldName: String, - fromType: TypeRepr, - toType: TypeRepr, - setter: Term - ) + case SelectField(fieldName: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr) + case SelectFieldWithImplicits(fieldName: String, fromType: TypeRepr, toType: TypeRepr, setter: Term) case SelectOnlyField( fieldName: String, fromType: TypeRepr, - toType: TypeRepr, - reverseGet: Term + fromTypeArgs: List[TypeRepr], + fromCompanion: Term, + toType: TypeRepr ) + case SelectOnlyFieldWithImplicits(fieldName: String, fromType: TypeRepr, toType: TypeRepr, reverseGet: Term) case KeywordSome(toType: TypeRepr) case KeywordAs(fromType: TypeRepr, toType: TypeRepr) case KeywordEach(fromType: TypeRepr, toType: TypeRepr, eachInstance: Term) @@ -33,10 +31,14 @@ private[focus] trait FocusBase { case KeywordWithDefault(toType: TypeRepr, defaultValue: Term) override def toString(): String = this match { - 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 SelectField(fieldName, fromType, fromTypeArgs, toType) => + s"SelectField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ${toType.show})" + case SelectFieldWithImplicits(fieldName, fromType, toType, setter) => + s"SelectFieldWithImplicits($fieldName, ${fromType.show}, ${toType.show}, ...)" + case SelectOnlyField(fieldName, fromType, fromTypeArgs, _, toType) => + s"SelectOnlyField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})" + case SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) => + s"SelectOnlyFieldWithImplicits($fieldName, ${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}, ...)" @@ -48,6 +50,7 @@ private[focus] trait FocusBase { enum FocusError { case NotACaseClass(className: String, fieldName: String) + case NotACaseField(className: String, fieldName: String) case NotAConcreteClass(className: String) case DidNotDirectlyAccessArgument(argName: String) case NotASimpleLambdaFunction 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 b559f134a..ee40cca32 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 @@ -39,14 +39,16 @@ private[focus] trait GeneratorLoop { 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/SelectParserBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala index 93a117dde..2720debf6 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 @@ -13,9 +13,9 @@ private[focus] trait SelectParserBase extends ParserBase { // Match on a term that is an instance of a case class object CaseClass { - def unapply(term: Term): Option[Term] = + def unapply(term: Term): Option[(Term, Symbol)] = term.tpe.classSymbol.flatMap { sym => - Option.when(sym.flags.is(Flags.Case))(term) + Option.when(sym.flags.is(Flags.Case))((term, sym)) } } @@ -86,11 +86,17 @@ private[focus] trait SelectParserBase extends ParserBase { 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 - } + def searchForImplicit(typeRepr: TypeRepr): FocusResult[Term] = + Implicits.search(typeRepr) match { + case success: ImplicitSearchSuccess => Right(success.tree) + case _ => FocusError.ImplicitNotFound(typeRepr.show).asResult + } + searchForImplicit(t.tpe) + .orElse(searchForImplicit(t.tpe.dealias)) + .orElse(searchForImplicit(t.tpe.widen)) + .orElse(searchForImplicit(t.tpe.widen.dealias)) + .map(acc :+ _) + case (Right(acc), other) => FocusError.ExpansionFailed(s"Expected value definition but found unexpected ${other.show}").asResult case (left @ Left(_), _) => 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 7c8ea3b1c..92ee089c7 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 @@ -8,13 +8,32 @@ private[focus] trait SelectFieldGenerator { import macroContext.reflect._ + private def generateGetter(from: Term, fieldName: String): Term = + Select.unique(from, fieldName) // o.field + def generateSelectField(action: FocusAction.SelectField): Term = { + import action.{fieldName, fromType, fromTypeArgs, toType} + + def generateSetter(from: Term, to: Term): Term = + Select.overloaded(from, "copy", fromTypeArgs, NamedArg(fieldName, to) :: Nil) // o.copy(field = value) + + (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] } + ) + }.asTerm + } + } + + def generateSelectFieldWithImplicits(action: FocusAction.SelectFieldWithImplicits): Term = { import action.{fieldName, fromType, toType, setter} (fromType.asType, toType.asType) match { case ('[f], '[t]) => '{ - Lens.apply[f, t]((from: f) => ${ Select.unique('{ from }.asTerm, fieldName).asExprOf[t] })( + Lens.apply[f, t]((from: f) => ${ generateGetter('{ 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 34ca2a8f9..a3176d4b8 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,9 +12,13 @@ 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) + case Select(CaseClass(remainingCode, classSymbol), fieldName) => + val fromType = getType(remainingCode) + val action = if (hasOnlyOneParameterList(classSymbol)) { + getFieldAction(fromType, fieldName) + } else { + getFieldActionWithImplicits(fromType, fieldName) + } val remainingCodeWithAction = action.map(a => (RemainingCode(remainingCode), a)) Some(remainingCodeWithAction) @@ -25,11 +29,23 @@ private[focus] trait SelectFieldParser { } } + private def hasOnlyOneParameterList(classSymbol: Symbol): Boolean = + classSymbol.primaryConstructor.paramSymss match { + case _ :: Nil => true + case (head :: _) :: _ :: Nil if head.isTypeParam => true + case _ => false + } + private def getFieldAction(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] = getFieldType(fromType, fieldName).flatMap { toType => val typeArgs = getSuppliedTypeArgs(fromType) constructSetter(fieldName, fromType, toType, typeArgs).map { setter => - FocusAction.SelectField(fieldName, fromType, toType, setter) + FocusAction.SelectFieldWithImplicits(fieldName, fromType, toType, setter) } } 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 f2163a27f..19d2de92e 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 @@ -8,13 +8,32 @@ private[focus] trait SelectOnlyFieldGenerator { 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) => ${ Select.unique('{ from }.asTerm, fieldName).asExprOf[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 index 1d341b379..92a649422 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 @@ -12,9 +12,16 @@ private[focus] trait SelectOnlyFieldParser { 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) + 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) @@ -22,19 +29,44 @@ private[focus] trait SelectOnlyFieldParser { } } - private def getFieldAction(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = + private def getFieldAction( + fromType: TypeRepr, + fromClassSymbol: Symbol, + fieldName: String + ): FocusResult[FocusAction] = for { - toType <- getFieldType(fromType, fieldName) - companion <- getCompanionObject(fromType) - supplied = getSuppliedTypeArgs(fromType) + 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.SelectOnlyField(fieldName, fromType, toType, reverseGet) + } yield FocusAction.SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) + + private def hasOnlyOneField(classSymbol: Symbol): Boolean = + classSymbol.caseFields.length == 1 - private def hasOnlyOneField(fromCode: Term): Boolean = - getType(fromCode).classSymbol.exists(_.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(fromType: TypeRepr): FocusResult[Term] = - getClassSymbol(fromType).map(sym => Ref(sym.companionModule)) + private def getCompanionObject(classSymbol: Symbol): Term = + Ref(classSymbol.companionModule) private case class LiftException(error: FocusError) extends Exception From 459403745163f109368fe0ab833591c92652060c Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:54:50 +0100 Subject: [PATCH 4/6] Merge very similar Select(Only)FieldGenerator & Parser --- .../focus/features/GeneratorLoop.scala | 2 - .../internal/focus/features/ParserLoop.scala | 5 - .../selectfield/SelectFieldGenerator.scala | 30 ++++++ .../selectfield/SelectFieldParser.scala | 88 ++++++++++++++--- .../SelectOnlyFieldGenerator.scala | 42 --------- .../SelectOnlyFieldParser.scala | 94 ------------------- 6 files changed, 103 insertions(+), 158 deletions(-) delete mode 100644 core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldGenerator.scala delete mode 100644 core/shared/src/main/scala-3.x/monocle/internal/focus/features/selectonlyfield/SelectOnlyFieldParser.scala 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)) - } - } -} From 293e6c56794318a21642e6b4e4632700b4078df6 Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Thu, 10 Mar 2022 22:19:38 +0100 Subject: [PATCH 5/6] Introduce explicit error for non-implicit non-case parameters Fix compilation error when case class has a method named like a case field --- .../internal/focus/ErrorHandling.scala | 4 +- .../monocle/internal/focus/FocusBase.scala | 26 ++-- .../focus/features/SelectParserBase.scala | 56 +++++---- .../selectfield/SelectFieldGenerator.scala | 22 ++-- .../selectfield/SelectFieldParser.scala | 116 +++++++++--------- .../ContextBoundCompilationIssueSpec.scala | 4 +- 6 files changed, 124 insertions(+), 104 deletions(-) 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 226f81365..96a45974d 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 @@ -8,6 +8,8 @@ private[focus] trait ErrorHandling { 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 => @@ -20,7 +22,7 @@ private[focus] trait ErrorHandling { 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." + s"Could not find implicit for '$implicitType'. 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 6a9ec2da2..01a1ef516 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,22 +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 SelectFieldWithImplicits(fieldName: String, fromType: TypeRepr, toType: TypeRepr, setter: Term) + 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(fieldName: String, fromType: TypeRepr, toType: TypeRepr, reverseGet: Term) + 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) @@ -31,14 +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 SelectFieldWithImplicits(fieldName, fromType, toType, setter) => - s"SelectFieldWithImplicits($fieldName, ${fromType.show}, ${toType.show}, ...)" - case SelectOnlyField(fieldName, fromType, fromTypeArgs, _, toType) => - s"SelectOnlyField($fieldName, ${fromType.show}, ${fromTypeArgs.map(_.show)}, ..., ${toType.show})" - case SelectOnlyFieldWithImplicits(fieldName, fromType, toType, reverseGet) => - s"SelectOnlyFieldWithImplicits($fieldName, ${fromType.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}, ...)" @@ -51,6 +52,7 @@ 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 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 2720debf6..02e55ac19 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 @@ -11,11 +11,34 @@ private[focus] trait SelectParserBase extends ParserBase { import this.macroContext.reflect._ - // Match on a term that is an instance of a case class - object CaseClass { - def unapply(term: Term): Option[(Term, Symbol)] = + 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(_.head.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] = + getFieldType(typeRepr, caseFieldSymbol) + } + + object CaseClassExtractor { + def unapply(term: Term): Option[CaseClass] = term.tpe.classSymbol.flatMap { sym => - Option.when(sym.flags.is(Flags.Case))((term, sym)) + Option.when(sym.flags.is(Flags.Case))(CaseClass(getType(term), sym)) } } @@ -25,24 +48,11 @@ private[focus] trait SelectParserBase extends ParserBase { 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.fieldMembers.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 - } + def getFieldType(fromType: TypeRepr, caseFieldSymbol: Symbol): FocusResult[TypeRepr] = + caseFieldSymbol match { + case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg)) + case _ => FocusError.CouldntFindFieldType(fromType.show, caseFieldSymbol.name).asResult } - } private object FieldType { def unapply(fieldSymbol: Symbol): Option[TypeRepr] = fieldSymbol match { @@ -50,7 +60,9 @@ private[focus] trait SelectParserBase extends ParserBase { 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 a ValDef. + case DefDef(_, _, typeTree, _) => Some(typeTree.tpe) + case _ => None } } } 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 951973112..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 @@ -9,19 +9,19 @@ private[focus] trait SelectFieldGenerator { import macroContext.reflect._ - private def generateGetter(from: Term, fieldName: String): Term = - Select.unique(from, fieldName) // o.field + private def generateGetter(from: Term, caseFieldSymbol: Symbol): Term = + Select(from, caseFieldSymbol) // o.field def generateSelectField(action: FocusAction.SelectField): Term = { - import action.{fieldName, fromType, fromTypeArgs, toType} + 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, fieldName).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 @@ -29,12 +29,12 @@ private[focus] trait SelectFieldGenerator { } def generateSelectFieldWithImplicits(action: FocusAction.SelectFieldWithImplicits): Term = { - import action.{fieldName, fromType, toType, setter} + import action.{caseFieldSymbol, fromType, toType, setter} (fromType.asType, toType.asType) match { case ('[f], '[t]) => '{ - Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })( + Lens.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })( ${ setter.asExprOf[t => f => f] } ) }.asTerm @@ -42,7 +42,7 @@ private[focus] trait SelectFieldGenerator { } def generateSelectOnlyField(action: FocusAction.SelectOnlyField): Term = { - import action.{fieldName, fromType, fromTypeArgs, fromCompanion, toType} + import action.{caseFieldSymbol, fromType, fromTypeArgs, fromCompanion, toType} def generateReverseGet(to: Term): Term = Select.overloaded(fromCompanion, "apply", fromTypeArgs, List(to)) // Companion.apply(value) @@ -50,7 +50,7 @@ private[focus] trait SelectFieldGenerator { (fromType.asType, toType.asType) match { case ('[f], '[t]) => '{ - Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[t] })((to: t) => + Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, caseFieldSymbol).asExprOf[t] })((to: t) => ${ generateReverseGet('{ to }.asTerm).asExprOf[f] } ) }.asTerm @@ -58,12 +58,12 @@ private[focus] trait SelectFieldGenerator { } def generateSelectOnlyFieldWithImplicits(action: FocusAction.SelectOnlyFieldWithImplicits): Term = { - import action.{fieldName, fromType, toType, reverseGet} + import action.{caseFieldSymbol, fromType, toType, reverseGet} (fromType.asType, toType.asType) match { case ('[f], '[t]) => '{ - Iso.apply[f, t]((from: f) => ${ generateGetter('{ from }.asTerm, fieldName).asExprOf[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 85d46ccfc..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,20 +12,19 @@ private[focus] trait SelectFieldParser { def unapply(term: Term): Option[FocusResult[(RemainingCode, FocusAction)]] = term match { - case Select(CaseClass(remainingCode, classSymbol), 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 { - Some(FocusError.NotACaseField(remainingCode.tpe.show, fieldName).asResult) - } + 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) @@ -34,57 +33,60 @@ 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 - case (head :: _) :: _ :: Nil if head.isTypeParam => true - case _ => false - } - - 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 getSelectFieldActionWithImplicits(fromType: TypeRepr, fieldName: String): FocusResult[FocusAction] = - getFieldType(fromType, fieldName).flatMap { toType => - val typeArgs = getSuppliedTypeArgs(fromType) - constructSetter(fieldName, fromType, toType, typeArgs).map { setter => - FocusAction.SelectFieldWithImplicits(fieldName, fromType, toType, setter) - } - } + 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( - fromType: TypeRepr, - fromClassSymbol: Symbol, - fieldName: String + caseClass: CaseClass, + caseFieldSymbol: Symbol ): FocusResult[FocusAction] = for { - toType <- getFieldType(fromType, fieldName) - companion = getCompanionObject(fromClassSymbol) - supplied = getSuppliedTypeArgs(fromType) - } yield FocusAction.SelectOnlyField(fieldName, fromType, supplied, companion, toType) + toType <- caseClass.getCaseFieldType(caseFieldSymbol) + } yield FocusAction.SelectOnlyField( + caseFieldSymbol, + caseClass.typeRepr, + caseClass.typeArgs, + caseClass.companionObject, + toType + ) private def getSelectOnlyFieldActionWithImplicits( - fromType: TypeRepr, - fromClassSymbol: Symbol, - fieldName: String + caseClass: CaseClass, + caseFieldSymbol: Symbol ): 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) + 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 diff --git a/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala b/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala index 0115c1484..8066bce60 100644 --- a/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala +++ b/macro/src/test/scala/monocle/macros/internal/ContextBoundCompilationIssueSpec.scala @@ -13,7 +13,9 @@ class ContextBoundCompilationIssueSpec extends DisciplineSuite { val lens: Lens[S[T], Bar[T]] = GenLens[S[T]](_.bar) } - private 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] From 01ac719324b00cbd23aa091c4b3a9fc5d5bf4ab7 Mon Sep 17 00:00:00 2001 From: NTPape <10488949+NTPape@users.noreply.github.com> Date: Thu, 17 Mar 2022 15:51:01 +0100 Subject: [PATCH 6/6] Can be different explanations for not finding implicit, be more precise Some small fixes --- .../internal/focus/ErrorHandling.scala | 4 +-- .../monocle/internal/focus/FocusBase.scala | 2 +- .../focus/features/SelectParserBase.scala | 27 +++++++++---------- 3 files changed, 15 insertions(+), 18 deletions(-) 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 96a45974d..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 @@ -21,8 +21,8 @@ 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: implicits with default values are not supported." + 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 01a1ef516..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 @@ -61,7 +61,7 @@ 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 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/SelectParserBase.scala b/core/shared/src/main/scala-3.x/monocle/internal/focus/features/SelectParserBase.scala index 02e55ac19..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 @@ -16,7 +16,7 @@ private[focus] trait SelectParserBase extends ParserBase { val companionObject: Term = Ref(classSymbol.companionModule) private val (typeParams, caseFieldParams :: otherParams) = - classSymbol.primaryConstructor.paramSymss.span(_.head.isTypeParam) + classSymbol.primaryConstructor.paramSymss.span(_.headOption.fold(false)(_.isTypeParam)) val hasOnlyOneCaseField: Boolean = caseFieldParams.length == 1 val hasOnlyOneParameterList: Boolean = otherParams.isEmpty private val nonCaseNonImplicitParameters: List[Symbol] = @@ -32,7 +32,10 @@ private[focus] trait SelectParserBase extends ParserBase { case None => FocusError.NotACaseField(typeRepr.show, fieldName).asResult } def getCaseFieldType(caseFieldSymbol: Symbol): FocusResult[TypeRepr] = - getFieldType(typeRepr, caseFieldSymbol) + caseFieldSymbol match { + case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(typeRepr, possiblyTypeArg)) + case _ => FocusError.CouldntFindFieldType(typeRepr.show, caseFieldSymbol.name).asResult + } } object CaseClassExtractor { @@ -42,25 +45,19 @@ private[focus] trait SelectParserBase extends ParserBase { } } - def getSuppliedTypeArgs(fromType: TypeRepr): List[TypeRepr] = + private def getSuppliedTypeArgs(fromType: TypeRepr): List[TypeRepr] = fromType match { case AppliedType(_, argTypeReprs) => argTypeReprs case _ => Nil } - def getFieldType(fromType: TypeRepr, caseFieldSymbol: Symbol): FocusResult[TypeRepr] = - caseFieldSymbol match { - case FieldType(possiblyTypeArg) => Right(swapWithSuppliedType(fromType, possiblyTypeArg)) - case _ => FocusError.CouldntFindFieldType(fromType.show, caseFieldSymbol.name).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) - // Only needed for Tuples because `_1` is a DefDef while `_1 ` is a ValDef. + // Only needed for Tuples because `_1` is a DefDef while `_1 ` is the corresponding ValDef. case DefDef(_, _, typeTree, _) => Some(typeTree.tpe) case _ => None } @@ -100,13 +97,13 @@ private[focus] trait SelectParserBase extends ParserBase { case (Right(acc), ValDef(_, t, _)) => def searchForImplicit(typeRepr: TypeRepr): FocusResult[Term] = Implicits.search(typeRepr) match { - case success: ImplicitSearchSuccess => Right(success.tree) - case _ => FocusError.ImplicitNotFound(typeRepr.show).asResult + case success: ImplicitSearchSuccess => + Right(success.tree) + case failure: ImplicitSearchFailure => + FocusError.ImplicitNotFound(typeRepr.show, failure.explanation).asResult } + searchForImplicit(t.tpe) - .orElse(searchForImplicit(t.tpe.dealias)) - .orElse(searchForImplicit(t.tpe.widen)) - .orElse(searchForImplicit(t.tpe.widen.dealias)) .map(acc :+ _) case (Right(acc), other) =>