Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yilin's powerful syntax technique #1079

Merged
merged 5 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package monocle.syntax

trait AppliedFocusSyntax

This file was deleted.

22 changes: 15 additions & 7 deletions core/shared/src/main/scala-3.x/monocle/Focus.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package monocle

import monocle.syntax.AppliedFocusSyntax
import monocle.function.Each
import monocle.internal.focus.{FocusImpl, AppliedFocusImpl}
import monocle.syntax.FocusSyntax
import monocle.internal.focus.FocusImpl
import monocle.function.Each

object Focus extends AppliedFocusSyntax {

object Focus extends FocusSyntax {
sealed trait KeywordContext {
extension [CastTo] (from: Any)
def as: CastTo = scala.sys.error("Extension method 'as[CastTo]' should only be used within the monocle.Focus macro.")

extension [From, To] (from: From)
transparent inline def focus(inline lambda: (From => To)): Any =
${AppliedFocusImpl[From, To]('from, 'lambda)}
extension [A] (opt: Option[A])
def some: A = scala.sys.error("Extension method 'some' should only be used within the monocle.Focus macro.")

extension [From, To] (from: From)(using Each[From, To])
def each: To = scala.sys.error("Extension method 'each' should only be used within the monocle.Focus macro.")
}

def apply[S] = new MkFocus[S]

class MkFocus[From] {
transparent inline def apply[To](inline lambda: (From => To)): Any =
transparent inline def apply[To](inline lambda: (KeywordContext ?=> From => To)): Any =
${ FocusImpl('lambda) }
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package monocle.internal.focus

import monocle.{Focus, Lens, Iso, Prism, Optional}
import monocle.{Focus, Lens, Iso, Prism, Optional, Traversal }
import monocle.syntax.{ApplyLens, ApplyPrism, ApplyIso, ApplyOptional, ApplyTraversal }
import scala.quoted.{Type, Expr, Quotes, quotes}


private[monocle] object AppliedFocusImpl {
def apply[From: Type, To: Type](from: Expr[From], lambda: Expr[From => To])(using Quotes): Expr[Any] = {
def apply[From: Type, To: Type](from: Expr[From], lambda: Expr[Focus.KeywordContext ?=> From => To])(using Quotes): Expr[Any] = {

import quotes.reflect._

val generatedOptic = new FocusImpl(quotes).run(lambda)
val generatedOptic = FocusImpl(lambda)

generatedOptic.asTerm.tpe.asType match {
case '[Lens[f, t]] => '{ _root_.monocle.syntax.ApplyLens[From, From, To, To]($from, ${generatedOptic.asExprOf[Lens[From,To]]}) }
case '[Prism[f, t]] => '{ _root_.monocle.syntax.ApplyPrism[From, From, To, To]($from, ${generatedOptic.asExprOf[Prism[From,To]]}) }
case '[Iso[f, t]] => '{ _root_.monocle.syntax.ApplyIso[From, From, To, To]($from, ${generatedOptic.asExprOf[Iso[From,To]]}) }
case '[Optional[f, t]] => '{ _root_.monocle.syntax.ApplyOptional[From, From, To, To]($from, ${generatedOptic.asExprOf[Optional[From,To]]}) }
case '[Lens[f, t]] => '{ ApplyLens[From, From, To, To]($from, ${generatedOptic.asExprOf[Lens[From,To]]}) }
case '[Prism[f, t]] => '{ ApplyPrism[From, From, To, To]($from, ${generatedOptic.asExprOf[Prism[From,To]]}) }
case '[Iso[f, t]] => '{ ApplyIso[From, From, To, To]($from, ${generatedOptic.asExprOf[Iso[From,To]]}) }
case '[Optional[f, t]] => '{ ApplyOptional[From, From, To, To]($from, ${generatedOptic.asExprOf[Optional[From,To]]}) }
case '[Traversal[f, t]] => '{ ApplyTraversal[From, From, To, To]($from, ${generatedOptic.asExprOf[Traversal[From,To]]}) }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ 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.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.NotASimpleLambdaFunction => s"Expecting a lambda function that directly accesses a field. Example: `Focus[Address](_.streetNumber)`"
case FocusError.CouldntUnderstandKeywordContext => s"Internal error in monocle.Focus; cannot access special syntax."
case FocusError.DidNotDirectlyAccessArgument(argName) => s"Expecting a lambda function that directly accesses the argument; other variable `$argName` found. Example: `Focus[Address](_.streetNumber)`"
case FocusError.ComposeMismatch(type1, type2) => s"Could not compose $type1.andThen($type2)"
case FocusError.UnexpectedCodeStructure(code) => s"Unexpected code structure: $code"
case FocusError.CouldntFindFieldType(fromType, fieldName) => s"Couldn't find type for $fromType.$fieldName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ private[focus] trait FocusBase {
type Term = macroContext.reflect.Term
type TypeRepr = macroContext.reflect.TypeRepr

case class LambdaConfig(argName: String, lambdaBody: Term)

enum FocusAction {
case FieldSelect(name: String, fromType: TypeRepr, fromTypeArgs: List[TypeRepr], toType: TypeRepr)
case OptionSome(toType: TypeRepr)
Expand All @@ -29,6 +31,7 @@ private[focus] trait FocusBase {
case NotAConcreteClass(className: String)
case DidNotDirectlyAccessArgument(argName: String)
case NotASimpleLambdaFunction
case CouldntUnderstandKeywordContext
case UnexpectedCodeStructure(code: String)
case CouldntFindFieldType(fromType: String, fieldName: String)
case ComposeMismatch(type1: String, type2: String)
Expand All @@ -39,7 +42,18 @@ private[focus] trait FocusBase {

trait FocusParser {
def unapply(term: Term): Option[FocusResult[(Term, FocusAction)]]

object FocusKeyword {
import macroContext.reflect._

def unapply(term: Term): Option[String] = term match {
case Select(_, keyword) => Some(keyword)
case _ => None
}
}
}



type FocusResult[+A] = Either[FocusError, A]
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package monocle.internal.focus

import monocle.Lens
import monocle.{Lens, Focus}
import scala.quoted.{Type, Expr, Quotes, quotes}

private[focus] class FocusImpl(val macroContext: Quotes)
extends FocusBase
private[focus] class FocusImpl(val macroContext: Quotes)
extends FocusBase
with ErrorHandling
with ParserLoop with AllParsers
with GeneratorLoop with AllGenerators {
with LambdaConfigParser
with ParserLoop with AllFeatureParsers
with GeneratorLoop with AllFeatureGenerators {

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])

def run[From: Type, To: Type](lambda: Expr[Focus.KeywordContext ?=> From => To]): Expr[Any] = {
val generatedCode =
for {
config <- parseLambdaConfig[From](lambda.asTerm)
focusActions <- parseFocusActions(config)
code <- generateCode[From](focusActions)
} yield code

generatedCode match {
case Right(code) => code.asExpr
case Left(error) => report.error(errorMessage(error)); '{???}
Expand All @@ -26,6 +28,6 @@ private[focus] class FocusImpl(val macroContext: Quotes)
}

private[monocle] object FocusImpl {
def apply[From: Type, To: Type](lambda: Expr[From => To])(using Quotes): Expr[Any] =
def apply[From: Type, To: Type](lambda: Expr[Focus.KeywordContext ?=> From => To])(using Quotes): Expr[Any] =
new FocusImpl(quotes).run(lambda)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import monocle.{Lens, Iso, Prism, Optional, Traversal}
import scala.quoted.Type


private[focus] trait AllGenerators
private[focus] trait AllFeatureGenerators
extends FocusBase
with FieldSelectGenerator
with OptionSomeGenerator
with CastAsGenerator
with EachGenerator

private[focus] trait GeneratorLoop {
this: FocusBase with AllGenerators =>
this: FocusBase with AllFeatureGenerators =>

import macroContext.reflect._

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package monocle.internal.focus

import scala.quoted.Type

private[focus] trait LambdaConfigParser {
this: FocusBase =>

import macroContext.reflect._

def parseLambdaConfig[From: Type](lambda: Term): FocusResult[LambdaConfig] = {
val fromTypeIsConcrete = TypeRepr.of[From].classSymbol.isDefined

lambda match {
case WithKeywordContext(ExpectedLambdaFunction(config)) if fromTypeIsConcrete => Right(config)
case WithKeywordContext(ExpectedLambdaFunction(_)) => FocusError.NotAConcreteClass(Type.show[Type[From]]).asResult
case WithKeywordContext(_) => FocusError.NotASimpleLambdaFunction.asResult
case _ => FocusError.CouldntUnderstandKeywordContext.asResult
}
}

private object WithKeywordContext {
def unapply(lambdaWithMagic: Term): Option[Term] = unwrap(lambdaWithMagic) match {
case Block(List(DefDef(_, _, _, _, Some(magicFreeLambda))), _) => Some(magicFreeLambda)
case _ => None
}
}

private def unwrap(term: Term): Term = {
term match {
case Block(List(), inner) => unwrap(inner)
case Inlined(_, _, inner) => unwrap(inner)
case x => x
}
}

private object ExpectedLambdaFunction {
def unapply(term: Term): Option[LambdaConfig] =
unwrap(term) match {
case Lambda(List(ValDef(argName, _, _)), body) => Some(LambdaConfig(argName, body))
case _ => None
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,23 @@ import monocle.internal.focus.features.optionsome.OptionSomeParser
import monocle.internal.focus.features.castas.CastAsParser
import monocle.internal.focus.features.each.EachParser

private[focus] trait AllParsers
private[focus] trait AllFeatureParsers
extends FocusBase
with FieldSelectParser
with OptionSomeParser
with CastAsParser
with EachParser

private[focus] trait ParserLoop {
this: FocusBase with AllParsers =>
this: FocusBase with AllFeatureParsers =>

import macroContext.reflect._

def parseLambda[From: Type](lambda: Term): FocusResult[List[FocusAction]] = {
val fromTypeIsConcrete = TypeRepr.of[From].classSymbol.isDefined

lambda match {
case ExpectedLambdaFunction(params) if fromTypeIsConcrete => parseLambdaBody(params)
case ExpectedLambdaFunction(_) => FocusError.NotASimpleLambdaFunction.asResult
case _ => FocusError.NotAConcreteClass(Type.show[Type[From]]).asResult
}
}

private case class ParseParams(argName: String, argType: TypeRepr, lambdaBody: Term)

private object LambdaArgument {
def unapply(term: Term): Option[String] = term match {
case Ident(idName) => Some(idName)
case _ => None
}
}

private def parseLambdaBody(params: ParseParams): FocusResult[List[FocusAction]] = {
def parseFocusActions(config: LambdaConfig): FocusResult[List[FocusAction]] = {
def loop(remainingBody: Term, listSoFar: List[FocusAction]): FocusResult[List[FocusAction]] = {

remainingBody match {
case LambdaArgument(idName) if idName == params.argName => Right(listSoFar)
case LambdaArgument(idName) if idName == config.argName => Right(listSoFar)
case LambdaArgument(idName) => FocusError.DidNotDirectlyAccessArgument(idName).asResult

case OptionSome(Right(remainingCode, action)) => loop(remainingCode, action :: listSoFar)
Expand All @@ -59,22 +40,14 @@ private[focus] trait ParserLoop {
case unexpected => FocusError.UnexpectedCodeStructure(unexpected.toString).asResult
}
}
loop(params.lambdaBody, Nil)
loop(config.lambdaBody, Nil)
}

private def unwrap(term: Term): Term = {
term match {
case Block(List(), inner) => unwrap(inner)
case Inlined(_, _, inner) => unwrap(inner)
case x => x

private object LambdaArgument {
def unapply(term: Term): Option[String] = term match {
case Ident(idName) => Some(idName)
case _ => None
}
}

private object ExpectedLambdaFunction {
def unapply(term: Term): Option[ParseParams] =
unwrap(term) match {
case Lambda(List(ValDef(argName, typeTree, _)), body) => Some(ParseParams(argName, typeTree.tpe, body))
case _ => None
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ package monocle.internal.focus.features.castas
import monocle.internal.focus.FocusBase

private[focus] trait CastAsParser {
this: FocusBase =>
this: FocusBase =>

import macroContext.reflect._

object CastAs extends FocusParser {

def unapply(term: Term): Option[FocusResult[(Term, FocusAction)]] = term match {
case Apply(TypeApply(Ident("as"), List(typeArg)), List(remainingCode)) =>
case Apply(TypeApply(FocusKeyword("as"), List(typeArg)), List(remainingCode)) =>

val fromType = remainingCode.tpe.widen
val toType = typeArg.tpe


if (toType <:< fromType) {
val action = FocusAction.CastAs(fromType, toType)
Some(Right(remainingCode, action))
}
else Some(Left(FocusError.InvalidDowncast(fromType.show, toType.show)))

case _ => None
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private[focus] trait EachParser {
object Each extends FocusParser {

def unapply(term: Term): Option[FocusResult[(Term, FocusAction)]] = term match {
case Apply(Apply(TypeApply(Ident("each"), List(_, toTypeTree)), List(remainingCode)), List(eachInstance)) =>
case Apply(Apply(TypeApply(FocusKeyword("each"), List(_, toTypeTree)), List(remainingCode)), List(eachInstance)) =>
val fromType = remainingCode.tpe.widen
val toType = toTypeTree.tpe
val action = FocusAction.Each(fromType, toType, eachInstance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ package monocle.internal.focus.features.optionsome
import monocle.internal.focus.FocusBase

private[focus] trait OptionSomeParser {
this: FocusBase =>
this: FocusBase =>

import macroContext.reflect._

object OptionSome extends FocusParser {

def unapply(term: Term): Option[FocusResult[(Term, FocusAction)]] = term match {
case Apply(TypeApply(Ident("some"), List(typeArg)), List(remainingCode)) =>
case Apply(TypeApply(FocusKeyword("some"), List(typeArg)), List(remainingCode)) =>
val toType = typeArg.tpe
val action = FocusAction.OptionSome(toType)
Some(Right(remainingCode, action))

case _ => None
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package monocle.syntax

import monocle.Focus
import monocle.internal.focus.AppliedFocusImpl

trait AppliedFocusSyntax {

extension [From, To] (from: From)
transparent inline def focus(inline lambda: (Focus.KeywordContext ?=> From => To)): Any =
${AppliedFocusImpl[From, To]('from, 'lambda)}
}
Loading