Skip to content

Commit

Permalink
WIP: Start porting to Scala 3
Browse files Browse the repository at this point in the history
  • Loading branch information
davegurnell committed Aug 28, 2024
1 parent c3c3bf3 commit 79aafbf
Show file tree
Hide file tree
Showing 27 changed files with 400 additions and 148 deletions.
8 changes: 4 additions & 4 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
version = "3.5.9"
version = "3.7.17"
runner.dialect=scala213

style = defaultWithAlign

danglingParentheses = true
indentOperator = spray
danglingParentheses.preset = true
indentOperator.preset = spray
maxColumn = 140
project.excludeFilters = [".*\\.sbt"]
rewrite.rules = [AsciiSortImports, RedundantBraces, RedundantParens]
spaces.inImportCurlyBraces = true
unindentTopLevelOperators = true
28 changes: 24 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ enablePlugins(GitBranchPrompt)
organization := "com.davegurnell"
name := "bridges"

ThisBuild / scalaVersion := "2.13.8"
ThisBuild / scalaVersion := "3.5.0"

ThisBuild / crossScalaVersions := Seq("2.13.8", "2.12.17")
ThisBuild / crossScalaVersions := Seq("2.13.13", "3.5.0")

ThisBuild / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
Expand All @@ -29,6 +29,15 @@ ThisBuild / scalacOptions ++= {
"-Xfatal-warnings",
)

case Some((3, _)) =>
Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Xfatal-warnings",
"-old-syntax",
)

case _ =>
Seq(
"-feature",
Expand All @@ -41,14 +50,25 @@ ThisBuild / scalacOptions ++= {
}

ThisBuild / libraryDependencies ++= Seq(
"com.chuusai" %% "shapeless" % "2.3.10",
"com.davegurnell" %% "unindent" % "1.8.0",
"org.apache.commons" % "commons-text" % "1.9",
"org.scalatest" %% "scalatest" % "3.2.13" % Test,
"eu.timepit" %% "refined" % "0.10.1" % Provided,
"eu.timepit" %% "refined-shapeless" % "0.10.1" % Provided
)

ThisBuild / libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
Seq(
"com.chuusai" %% "shapeless" % "2.3.10",
"eu.timepit" %% "refined-shapeless" % "0.10.1" % Provided
)

case _ =>
Seq.empty
}
}

// Versioning -----------------------------------

ThisBuild / versionScheme := Some("early-semver")
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.7.1
sbt.version=1.10.0
4 changes: 3 additions & 1 deletion project/metals.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// DO NOT EDIT! This file is auto-generated.

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1032048a")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.13")

6 changes: 6 additions & 0 deletions project/project/metals.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// DO NOT EDIT! This file is auto-generated.

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.13")

6 changes: 6 additions & 0 deletions project/project/project/metals.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// DO NOT EDIT! This file is auto-generated.

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.13")

62 changes: 62 additions & 0 deletions src/main/scala-2/bridges/core/DerivedEncoderInstances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package bridges.core

import shapeless._
import shapeless.labelled.FieldType

import scala.reflect.runtime.universe.WeakTypeTag

trait DerivedEncoderInstances extends DerivedEncoderInstances1 {
implicit def valueClassEncoder[A <: AnyVal, B](implicit unwrapped: Unwrapped.Aux[A, B], encoder: BasicEncoder[B]): BasicEncoder[A] =
pure(encoder.encode)
}

trait DerivedEncoderInstances1 extends DerivedEncoderInstances0 {
import Type._

implicit val hnilProdEncoder: ProdEncoder[HNil] =
pureProd(Prod(Nil))

implicit def hconsProdEncoder[K <: Symbol, H, T <: HList](implicit
witness: Witness.Aux[K],
hEnc: Lazy[BasicEncoder[H]],
tEnc: ProdEncoder[T]
): ProdEncoder[FieldType[K, H] :: T] = {
val name = witness.value.name
val head = hEnc.value.encode
val tail = tEnc.encode
pureProd(Prod((name -> head) +: tail.fields))
}

implicit def cnilSumEncoder: SumEncoder[CNil] =
pureSum(Sum(Nil))

implicit def cconsSumEncoder[K <: Symbol, H, T <: Coproduct](implicit
witness: Witness.Aux[K],
hEnc: Lazy[ProdEncoder[H]],
tEnc: SumEncoder[T]
): SumEncoder[FieldType[K, H] :+: T] = {
val name = witness.value.name
val product = hEnc.value.encode
val tail = tEnc.encode
pureSum(Sum((name -> product) +: tail.products))
}

implicit def genericProdEncoder[A, R](implicit
gen: LabelledGeneric.Aux[A, R],
enc: Lazy[ProdEncoder[R]]
): ProdEncoder[A] =
pureProd(enc.value.encode)

implicit def genericSumEncoder[A, R](implicit
gen: LabelledGeneric.Aux[A, R],
enc: Lazy[SumEncoder[R]]
): SumEncoder[A] =
pureSum(enc.value.encode)
}

trait DerivedEncoderInstances0 extends EncoderConstructors {
import Type._

implicit def genericBasicEncoder[A](implicit low: LowPriority, tpeTag: WeakTypeTag[A]): BasicEncoder[A] =
pure(Ref(TypeName.getTypeName[A]))
}
15 changes: 15 additions & 0 deletions src/main/scala-2/bridges/core/TypeName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bridges.core

import scala.reflect.runtime.universe.WeakTypeTag

object TypeName {
// NOTE: we can't use `shapeless.Typeable` in here as it breaks the code for recursive types like
// final case class Recursive(head: Int, tail: Option[Recursive])
//
// The only solution I found is to use a `WeakTypeTag` from scala runtime,
// which seems to manage the recursivity OK.
def getTypeName[A](implicit tpeTag: WeakTypeTag[A]): String = {
val fullName = tpeTag.tpe.typeSymbol.fullName
fullName.split('.').last
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
package bridges.core

import shapeless.Lazy

import scala.reflect.runtime.universe.WeakTypeTag

object syntax extends RenamableSyntax {
import Type._

// NOTE: we can't use `shapeless.Typeable` in here as it breaks the code for recursive types like
// final case class Recursive(head: Int, tail: Option[Recursive])
//
// The only solution I found is to use a `WeakTypeTag` from scala runtime,
// which seems to manage the recursivity OK.
def getCleanTagName[A](implicit tpeTag: WeakTypeTag[A]): String = {
val fullName = tpeTag.tpe.typeSymbol.fullName
fullName.split('.').last
}

def encode[A: Encoder]: Type =
Encoder[A].encode

def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[Encoder[A]]): Decl =
DeclF(getCleanTagName[A], encoder.value.encode)
DeclF(TypeName.getTypeName[A], encoder.value.encode)

// To be used for classes with generic parameters as we can't use shapeless to derive them
def decl(name: String, params: String*)(tpe: Type): Decl =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package bridges.flow

import bridges.core._
import bridges.core.syntax.getCleanTagName
import shapeless.Lazy

import scala.language.implicitConversions
import scala.reflect.runtime.universe.WeakTypeTag

Expand All @@ -13,7 +13,7 @@ object syntax extends RenamableSyntax {
encoder.encode

def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[FlowEncoder[A]]): FlowDecl =
DeclF(getCleanTagName[A], encoder.value.encode)
DeclF(TypeName.getTypeName[A], encoder.value.encode)

def decl(name: String, params: String*)(tpe: FlowType): FlowDecl =
DeclF(name, params.toList, tpe)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package bridges.typescript

import bridges.core.{ DeclF, Encoder, RenamableSyntax }
import bridges.core.syntax.getCleanTagName
import bridges.core.{ DeclF, RenamableSyntax, TypeName }
import shapeless.Lazy
import scala.reflect.runtime.universe.WeakTypeTag

import scala.language.implicitConversions
import scala.reflect.runtime.universe.WeakTypeTag

object syntax extends RenamableSyntax {
import TsType._
Expand All @@ -13,7 +13,7 @@ object syntax extends RenamableSyntax {
encoder.encode

def decl[A](implicit tpeTag: WeakTypeTag[A], encoder: Lazy[TsEncoder[A]]): TsDecl =
DeclF(getCleanTagName[A], encoder.value.encode)
DeclF(TypeName.getTypeName[A], encoder.value.encode)

def decl(name: String, params: String*)(tpe: TsType): TsDecl =
DeclF(name, params.toList, tpe)
Expand Down
64 changes: 64 additions & 0 deletions src/main/scala-3/bridges/core/DerivedEncoderInstances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package bridges.core

import scala.compiletime._
import scala.deriving._

trait DerivedEncoderInstances extends DerivedEncoderInstances1 {
// given valueClassEncoder[A <: AnyVal, B](implicit encoder: BasicEncoder[B]): BasicEncoder[A] =
// ???
}

trait DerivedEncoderInstances1 extends DerivedEncoderInstances0 {
inline given allEncoders[A <: Tuple]: List[Encoder[Any]] = {
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (h *: t) => summonInline[Encoder[h]].asInstanceOf[Encoder[Any]] :: allEncoders[t]
}
}

inline given allProdEncoders[A <: Tuple]: List[ProdEncoder[Any]] = {
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (h *: t) => summonInline[ProdEncoder[h]].asInstanceOf[ProdEncoder[Any]] :: allProdEncoders[t]
}
}

private inline def allLabels[A <: Tuple]: List[String] = {
inline erasedValue[A] match {
case _: EmptyTuple => Nil
case _: (h *: t) => constValue[h].asInstanceOf[String] :: allLabels[t]
}
}

inline given deriveEncoder[A](using mirror: Mirror.Of[A]): Encoder[A] = {
lazy val name = TypeName.getTypeName[A]

println(s"derivedEncoder[${name}]")

lazy val labels: List[String] =
allLabels[mirror.MirroredElemLabels]

inline mirror match {
case mirror: Mirror.ProductOf[A] => prodEncoder(mirror, labels)
case mirror: Mirror.SumOf[A] => sumEncoder(mirror, labels)
}
}

private inline def prodEncoder[A](mirror: Mirror.ProductOf[A], labels: List[String]): Encoder[A] = {
lazy val types: List[Type] = allEncoders[mirror.MirroredElemTypes].map(_.encode)
pureProd(Type.Prod(labels.zip(types)))
}

private inline def sumEncoder[A](mirror: Mirror.SumOf[A], labels: List[String]): Encoder[A] = {
lazy val types: List[Type.Prod] = allProdEncoders[mirror.MirroredElemTypes].map(_.encode)
pure(Type.Sum(labels.zip(types)))
}
}

trait DerivedEncoderInstances0 extends EncoderConstructors {
inline given genericBasicEncoder[A]: BasicEncoder[A] = {
lazy val name = TypeName.getTypeName[A]
println(s"genericBasicEncoder[${name}]")
pure(Type.Ref(name))
}
}
14 changes: 14 additions & 0 deletions src/main/scala-3/bridges/core/TypeName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package bridges.core

import scala.quoted.*

object TypeName {
inline def getTypeName[T]: String =
${ getTypeNameImpl[T] }

private def getTypeNameImpl[T: Type](using Quotes): Expr[String] = {
import quotes.reflect.*
val typeRepr = TypeRepr.of[T]
Expr(typeRepr.show.split("\\.").last)
}
}
28 changes: 28 additions & 0 deletions src/main/scala-3/bridges/core/syntax.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package bridges.core

object syntax extends RenamableSyntax {
import Type._

def encode[A: Encoder]: Type =
Encoder[A].encode

inline def decl[A](implicit encoder: => Encoder[A]): Decl =
DeclF(TypeName.getTypeName[A], encoder.encode)

def decl(name: String, params: String*)(tpe: Type): Decl =
DeclF(name, params.toList, tpe)

def prod(fields: (String, Type)*): Prod =
Prod(fields.toList)

def sum(products: (String, Prod)*): Sum =
Sum(products.toList)

def dict(keyType: Type, valueType: Type): Type =
Dict(keyType, valueType)

implicit class StringDeclOps(str: String) {
def :=[A](tpe: A): DeclF[A] =
DeclF(str, tpe)
}
}
Loading

0 comments on commit 79aafbf

Please sign in to comment.