-
-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update maintainers (copied from fs2) * update mima settings * bump to Scala 3.0.0-M3 * bump cats * nbump other libraries * only release for M3 * port Focus macro developed by @kenbot and @yilinwei in Monocly * remove source:3.0-migration in core only * remove extra empty line
- Loading branch information
1 parent
e14b621
commit ce7fdc7
Showing
16 changed files
with
605 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package monocle | ||
|
||
import monocle.internal.focus.FocusImpl | ||
|
||
object Focus { | ||
|
||
extension [A] (opt: Option[A]) | ||
def some: A = scala.sys.error("Extension method 'some' should only be used within the moocle.Focus macro.") | ||
|
||
def apply[S] = new MkFocus[S] | ||
|
||
class MkFocus[S] { | ||
transparent inline def apply[T](inline get: (S => T)): Any = | ||
${ FocusImpl('get) } | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
core/shared/src/main/scala-3.x/monocle/internal/focus/ErrorHandling.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package monocle.internal.focus | ||
|
||
private[focus] trait ErrorHandling { | ||
this: FocusBase => | ||
|
||
def errorMessage(error: FocusError): String = error match { | ||
case FocusError.NotACaseClass(fromClass) => s"Expecting a case class in the 'From' position; found $fromClass" | ||
case FocusError.NotAConcreteClass(fromClass) => s"Expecting a concrete case class in the 'From' position; cannot reify type $fromClass" | ||
case FocusError.NotASimpleLambdaFunction => s"Expecting a lambda function that directly accesses a field. Example: `GenLens[Address](_.streetNumber)`" | ||
case FocusError.DidNotDirectlyAccessArgument(argName) => s"Expecting a lambda function that directly accesses the argument; other variable `$argName` found. Example: `GenLens[Address](_.streetNumber)`" | ||
case FocusError.ComposeMismatch(type1, type2) => s"Could not compose $type1 >>> $type2" | ||
case FocusError.UnexpectedCodeStructure(code) => s"Unexpected code structure: $code" | ||
case FocusError.CouldntFindFieldType(fromType, fieldName) => s"Couldn't find type for $fromType.$fieldName" | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
core/shared/src/main/scala-3.x/monocle/internal/focus/FocusBase.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package monocle.internal.focus | ||
|
||
import scala.quoted.Quotes | ||
|
||
private[focus] trait FocusBase { | ||
val macroContext: Quotes | ||
|
||
given Quotes = macroContext | ||
|
||
type Term = macroContext.reflect.Term | ||
type TypeRepr = macroContext.reflect.TypeRepr | ||
|
||
enum FocusAction { | ||
case FieldSelect(name: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr) | ||
case OptionSome(toType: TypeRepr) | ||
|
||
override def toString(): String = this match { | ||
case FieldSelect(name, fromType, fromTypeArgs, toType) => s"FieldSelect($name, ${fromType.show}, ${fromTypeArgs.map(_.show).mkString("[", ",", "]")}, ${toType.show})" | ||
case OptionSome(toType) => s"OptionSome(${toType.show})" | ||
} | ||
} | ||
|
||
enum FocusError { | ||
case NotACaseClass(className: String) | ||
case NotAConcreteClass(className: String) | ||
case DidNotDirectlyAccessArgument(argName: String) | ||
case NotASimpleLambdaFunction | ||
case UnexpectedCodeStructure(code: String) | ||
case CouldntFindFieldType(fromType: String, fieldName: String) | ||
case ComposeMismatch(type1: String, type2: String) | ||
|
||
def asResult: FocusResult[Nothing] = Left(this) | ||
} | ||
|
||
trait FocusParser { | ||
def unapply(term: Term): Option[FocusResult[(Term, FocusAction)]] | ||
} | ||
|
||
type FocusResult[+A] = Either[FocusError, A] | ||
type ParseResult = FocusResult[List[FocusAction]] | ||
} |
31 changes: 31 additions & 0 deletions
31
core/shared/src/main/scala-3.x/monocle/internal/focus/FocusImpl.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package monocle.internal.focus | ||
|
||
import monocle.Lens | ||
import scala.quoted.{Type, Expr, Quotes, quotes} | ||
|
||
private[focus] class FocusImpl(val macroContext: Quotes) | ||
extends FocusBase | ||
with ErrorHandling | ||
with ParserLoop with AllParsers | ||
with GeneratorLoop with AllGenerators { | ||
|
||
import macroContext.reflect._ | ||
|
||
def run[From: Type, To: Type](lambda: Expr[From => To]): Expr[Any] = { | ||
val parseResult: FocusResult[List[FocusAction]] = | ||
parseLambda[From](lambda.asTerm) | ||
|
||
val generatedCode: FocusResult[Term] = | ||
parseResult.flatMap(generateCode[From]) | ||
|
||
generatedCode match { | ||
case Right(code) => code.asExpr | ||
case Left(error) => report.error(errorMessage(error)); '{???} | ||
} | ||
} | ||
} | ||
|
||
object FocusImpl { | ||
def apply[From: Type, To: Type](lambda: Expr[From => To])(using Quotes): Expr[Any] = | ||
new FocusImpl(quotes).run(lambda) | ||
} |
87 changes: 87 additions & 0 deletions
87
core/shared/src/main/scala-3.x/monocle/internal/focus/GeneratorLoop.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package monocle.internal.focus | ||
|
||
import monocle.internal.focus.features.fieldselect.FieldSelectGenerator | ||
import monocle.internal.focus.features.optionsome.OptionSomeGenerator | ||
import monocle.{Lens, Iso, Prism, Optional} | ||
import scala.quoted.Type | ||
|
||
|
||
private[focus] trait AllGenerators | ||
extends FocusBase | ||
with FieldSelectGenerator | ||
with OptionSomeGenerator | ||
|
||
private[focus] trait GeneratorLoop { | ||
this: FocusBase with AllGenerators => | ||
|
||
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))) | ||
} | ||
} | ||
|
||
private def generateActionCode(action: FocusAction): Term = | ||
action match { | ||
case FocusAction.FieldSelect(name, fromType, fromTypeArgs, toType) => generateFieldSelect(name, fromType, fromTypeArgs, toType) | ||
case FocusAction.OptionSome(toType) => generateOptionSome(toType) | ||
} | ||
|
||
private def composeOptics(lens1: Term, lens2: Term): FocusResult[Term] = { | ||
(lens1.tpe.asType, lens2.tpe.asType) match { | ||
case ('[Lens[from1, to1]], '[Lens[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Lens[from1, to1]]}.andThen(${lens2.asExprOf[Lens[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Lens[from1, to1]], '[Prism[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Lens[from1, to1]]}.andThen(${lens2.asExprOf[Prism[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Lens[from1, to1]], '[Optional[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Lens[from1, to1]]}.andThen(${lens2.asExprOf[Optional[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Lens[from1, to1]], '[Iso[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Lens[from1, to1]]}.andThen(${lens2.asExprOf[Iso[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Prism[from1, to1]], '[Prism[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Prism[from1, to1]]}.andThen(${lens2.asExprOf[Prism[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Prism[from1, to1]], '[Lens[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Prism[from1, to1]]}.andThen(${lens2.asExprOf[Lens[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Prism[from1, to1]], '[Optional[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Prism[from1, to1]]}.andThen(${lens2.asExprOf[Optional[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Prism[from1, to1]], '[Iso[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Prism[from1, to1]]}.andThen(${lens2.asExprOf[Iso[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Optional[from1, to1]], '[Lens[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Optional[from1, to1]]}.andThen(${lens2.asExprOf[Lens[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Optional[from1, to1]], '[Optional[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Optional[from1, to1]]}.andThen(${lens2.asExprOf[Optional[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Optional[from1, to1]], '[Prism[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Optional[from1, to1]]}.andThen(${lens2.asExprOf[Prism[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Optional[from1, to1]], '[Iso[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Optional[from1, to1]]}.andThen(${lens2.asExprOf[Iso[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Iso[from1, to1]], '[Lens[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Iso[from1, to1]]}.andThen(${lens2.asExprOf[Lens[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Iso[from1, to1]], '[Iso[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Iso[from1, to1]]}.andThen(${lens2.asExprOf[Iso[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Iso[from1, to1]], '[Optional[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Iso[from1, to1]]}.andThen(${lens2.asExprOf[Optional[to1, to2]]}) }.asTerm) | ||
|
||
case ('[Iso[from1, to1]], '[Prism[from2, to2]]) => | ||
Right('{ ${lens1.asExprOf[Iso[from1, to1]]}.andThen(${lens2.asExprOf[Prism[to1, to2]]}) }.asTerm) | ||
|
||
case ('[a], '[b]) => | ||
FocusError.ComposeMismatch(TypeRepr.of[a].show, TypeRepr.of[b].show).asResult | ||
} | ||
} | ||
} |
Oops, something went wrong.