Skip to content

Commit

Permalink
Update build (#1018)
Browse files Browse the repository at this point in the history
* 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
julien-truffaut committed Feb 8, 2021
1 parent e14b621 commit ce7fdc7
Show file tree
Hide file tree
Showing 16 changed files with 605 additions and 37 deletions.
79 changes: 42 additions & 37 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import com.typesafe.tools.mima.plugin.MimaKeys.mimaPreviousArtifacts
import com.typesafe.tools.mima.plugin.MimaPlugin.mimaDefaultSettings
import com.typesafe.tools.mima.core._
import sbt.Keys._
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}

Expand All @@ -9,40 +8,29 @@ inThisBuild(List(
organization := "com.github.julien-truffaut",
homepage := Some(url("https://github.com/optics-dev/Monocle")),
licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")),
developers := List(
Developer(
"julien-truffaut",
"Julien Truffaut",
"truffaut.julien@gmail.com",
url("https://github.com/julien-truffaut")
),
Developer(
"NightRa",
"Ilan Godik",
"",
url("https://github.com/NightRa")
),
Developer(
"aoiroaoino",
"Naoki Aoyama",
"aoiro.aoino@gmail.com",
url("https://github.com/aoiroaoino")
),
Developer(
"xuwei-k",
"Kenji Yoshida",
" 6b656e6a69@gmail.com",
url("https://github.com/xuwei-k")
),
developers :=
List(
"aoiroaoino" -> "Naoki Aoyama",
"cquiroz" -> "Carlos Quiroz",
"kenbot" -> " Ken Scambler",
"julien-truffaut" -> "Julien Truffaut",
"NightRa" -> "Ilan Godik",
"xuwei-k" -> "Kenji Yoshida",
"yilinwei" -> "Yilin Wei",
).map { case (username, fullName) =>
Developer(username, fullName, s"@$username", url(s"https://github.com/$username"))
}
)
))
)

lazy val kindProjector = "org.typelevel" % "kind-projector" % "0.11.3" cross CrossVersion.full

lazy val buildSettings = Seq(
scalaVersion := "2.13.3",
crossScalaVersions := Seq("2.13.3"),
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
Compile / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("main", baseDirectory.value, scalaVersion.value),
Test / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("test", baseDirectory.value, scalaVersion.value),
scalacOptions ++= Seq(
"-encoding",
"UTF-8",
Expand Down Expand Up @@ -94,24 +82,24 @@ lazy val buildSettings = Seq(
Compile / doc / sources := { if (isDotty.value) Seq() else (Compile / doc / sources).value }
)

lazy val catsVersion = "2.3.0"
lazy val dottyVersions = Seq("3.0.0-M1", "3.0.0-M2")
lazy val catsVersion = "2.3.1"
lazy val dottyVersions = Seq("3.0.0-M3")

lazy val cats = Def.setting("org.typelevel" %%% "cats-core" % catsVersion)
lazy val catsFree = Def.setting("org.typelevel" %%% "cats-free" % catsVersion)
lazy val catsLaws = Def.setting("org.typelevel" %%% "cats-laws" % catsVersion)
lazy val alleycats = Def.setting("org.typelevel" %%% "alleycats-core" % catsVersion)
lazy val shapeless = Def.setting("com.chuusai" %%% "shapeless" % "2.3.3")
lazy val refinedDep = Def.setting("eu.timepit" %%% "refined" % "0.9.19")
lazy val refinedScalacheck = Def.setting("eu.timepit" %%% "refined-scalacheck" % "0.9.19" % "test")
lazy val refinedDep = Def.setting("eu.timepit" %%% "refined" % "0.9.20")
lazy val refinedScalacheck = Def.setting("eu.timepit" %%% "refined-scalacheck" % "0.9.20" % "test")

lazy val discipline = Def.setting("org.typelevel" %%% "discipline-core" % "1.1.2")
lazy val munit = Def.setting("org.scalameta" %% "munit" % "0.7.16" % Test)
lazy val discipline = Def.setting("org.typelevel" %%% "discipline-core" % "1.1.3")
lazy val munit = Def.setting("org.scalameta" %% "munit" % "0.7.21" % Test)
lazy val munitDiscipline = Def.setting("org.typelevel" %% "discipline-munit" % "1.0.5" % Test)

lazy val macroVersion = "2.1.1"

def mimaSettings(module: String): Seq[Setting[_]] = mimaDefaultSettings ++ Seq(
def mimaSettings(module: String): Seq[Setting[_]] = Seq(
mimaPreviousArtifacts := Set("com.github.julien-truffaut" %% s"monocle-${module}" % "2.0.0")
)

Expand All @@ -132,6 +120,19 @@ lazy val scalajsSettings = Seq(
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-maxSize", "8", "-minSuccessfulTests", "50")
)

// copied from cats build
def scalaVersionSpecificFolders(srcName: String, srcBaseDir: java.io.File, scalaVersion: String) = {
def extraDirs(suffix: String) =
List(CrossType.Pure, CrossType.Full)
.flatMap(_.sharedSrcDir(srcBaseDir, srcName).toList.map(f => file(f.getPath + suffix)))

CrossVersion.partialVersion(scalaVersion) match {
case Some((2, y)) => extraDirs("-2.x") ++ (if (y >= 13) extraDirs("-2.13+") else Nil)
case Some((0 | 3, _)) => extraDirs("-2.13+") ++ extraDirs("-3.x")
case _ => Nil
}
}

lazy val monocleSettings = buildSettings
lazy val monocleJvmSettings = monocleSettings
lazy val monocleJsSettings = monocleSettings ++ scalajsSettings
Expand Down Expand Up @@ -171,9 +172,13 @@ lazy val core = crossProject(JVMPlatform, JSPlatform)
moduleName := "monocle-core",
scalacOptions ~= (_.filterNot(
Set(
"-Xfatal-warnings" // Workaround for sbt bug
"-Xfatal-warnings", // Workaround for sbt bug
"-source:3.0-migration",
)
))
)),
libraryDependencies ++= Seq(
munitDiscipline.value,
)
)

lazy val generic = crossProject(JVMPlatform, JSPlatform)
Expand Down
16 changes: 16 additions & 0 deletions core/shared/src/main/scala-3.x/monocle/Focus.scala
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) }
}
}
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"
}
}
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]]
}
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)
}
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
}
}
}
Loading

0 comments on commit ce7fdc7

Please sign in to comment.