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

(WIP) Scala 3 cross compile #158

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 2.7.5
version = 3.0.0-RC6
preset = default
align.preset = most
maxColumn = 120
Expand All @@ -7,3 +7,8 @@ align.tokens.add = [
{code = ":=", owner = "Term.ApplyInfix"}
]
rewrite.rules = [RedundantBraces, RedundantParens]
fileOverride {
"glob:**/src/main/scala-3/**" {
runner.dialect = scala3
}
}
47 changes: 29 additions & 18 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ ThisBuild / githubWorkflowUseSbtThinClient := false

ThisBuild / githubWorkflowPublishTargetBranches := Seq()

ThisBuild / crossScalaVersions := Seq("2.10.7", "2.11.12", "2.13.6", "2.12.14")
ThisBuild / scalaVersion := (ThisBuild / crossScalaVersions).value.last
ThisBuild / crossScalaVersions := Seq("2.10.7", "2.11.12", "2.13.6", "3.0.1", "2.12.14")
ThisBuild / scalaVersion := "3.0.2-RC1"

// Coveralls doesn't really work with Scala 2.10.7 so we are disabling it for CI
ThisBuild / githubWorkflowScalaVersions -= "2.10.7"
Expand All @@ -43,6 +43,7 @@ lazy val root = project
description := "A Scala-friendly wrapper companion for Typesafe config",
startYear := Some(2013),
scalacOptions ++= Seq(
"-language:implicitConversions",
"-feature",
"-deprecation",
"-unchecked",
Expand All @@ -65,34 +66,44 @@ lazy val root = project
"-Xlint:deprecation"
),
Compile / unmanagedSourceDirectories ++= {
(Compile / unmanagedSourceDirectories).value.map { dir =>
(Compile / unmanagedSourceDirectories).value.flatMap { dir =>
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) => file(dir.getPath ++ "-2.13+")
case _ => file(dir.getPath ++ "-2.13-")
case Some((2, 13)) => Seq(file(dir.getPath ++ "-2.13+"))
case Some((2, _)) => Seq(file(dir.getPath ++ "-2.13-"))
case _ => Nil
}
}
},
Test / unmanagedSourceDirectories ++= {
(Test / unmanagedSourceDirectories).value.map { dir =>
(Test / unmanagedSourceDirectories).value.flatMap { dir =>
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) => file(dir.getPath ++ "-2.13+")
case _ => file(dir.getPath ++ "-2.13-")
case Some((2, 13)) => Seq(file(dir.getPath ++ "-2.13+"))
case Some((2, _)) => Seq(file(dir.getPath ++ "-2.13-"))
case _ => Nil
}
}
},
libraryDependencies ++=
(if (scalaVersion.value.startsWith("2.10"))
Seq("org.specs2" %% "specs2-core" % "3.10.0" % Test, "org.specs2" %% "specs2-scalacheck" % "3.10.0" % Test)
else
Seq("org.specs2" %% "specs2-core" % "4.8.3" % Test, "org.specs2" %% "specs2-scalacheck" % "4.8.3" % Test)) ++
(if (scalaVersion.value.startsWith("2.")) {
val specs2Version = if (scalaVersion.value.startsWith("2.10")) "3.10.0" else "4.8.3"
Seq(
"org.specs2" %% "specs2-core" % specs2Version % Test,
"org.specs2" %% "specs2-scalacheck" % specs2Version % Test,
"com.chuusai" %% "shapeless" % "2.3.3" % Test,
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
"org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided
)
} else
Seq(
"org.specs2" %% "specs2-core" % "5.0.0-ALPHA-03" % Test,
"org.specs2" %% "specs2-scalacheck" % "5.0.0-ALPHA-03" % Test,
"org.typelevel" %% "shapeless3-typeable" % "3.0.2" % Test
)) ++
Seq(
"org.scalacheck" %% "scalacheck" % "1.14.1" % Test,
"com.chuusai" %% "shapeless" % "2.3.3" % Test,
"com.typesafe" % "config" % "1.3.4",
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
"org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided
"org.scalacheck" %% "scalacheck" % "1.15.4" % Test,
"com.typesafe" % "config" % "1.3.4"
) ++
(if (!scalaVersion.value.startsWith("2.13"))
(if (scalaVersion.value.startsWith("2.") && !scalaVersion.value.startsWith("2.13"))
Seq(
compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full),
"org.typelevel" %% "macro-compat" % "1.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package net.ceedubs.ficus

import com.typesafe.config.Config
import net.ceedubs.ficus.readers._
import scala.language.implicitConversions

trait FicusInstances
extends AnyValReaders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package net.ceedubs.ficus

import com.typesafe.config.Config
import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader}
import scala.language.implicitConversions

trait FicusConfig {
def config: Config

def self: FicusConfig = this

def as[A](path: String)(implicit reader: ValueReader[A]): A = reader.read(config, path)

def as[A](implicit reader: ValueReader[A]): A = as(".")
def to[A](implicit reader: ValueReader[A]): A = as(".")

def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import scala.language.experimental.macros
import scala.reflect.internal.{Definitions, StdNames, SymbolTable}
import scala.reflect.macros.blackbox

case class Generated[+A](value: A) extends AnyVal

trait ArbitraryTypeReader {
implicit def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] =
macro ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T]
Expand Down
26 changes: 26 additions & 0 deletions src/main/scala-3/net/ceedubs/ficus/Ficus.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.ceedubs.ficus

import com.typesafe.config.Config
import net.ceedubs.ficus.readers._

trait FicusInstances
extends AnyValReaders
with StringReader
with SymbolReader
with OptionReader
with CollectionReaders
with ConfigReader
with DurationReaders
with TryReader
with ConfigValueReader
with BigNumberReaders
with ISOZonedDateTimeReader
with PeriodReader
with LocalDateReader
with URIReaders
with URLReader
with InetSocketAddressReaders

object Ficus extends FicusInstances {
implicit def toFicusConfig(config: Config): FicusConfig = SimpleFicusConfig(config)
}
23 changes: 23 additions & 0 deletions src/main/scala-3/net/ceedubs/ficus/FicusConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.ceedubs.ficus

import com.typesafe.config.Config
import net.ceedubs.ficus.readers.{AllValueReaderInstances, ValueReader}

trait FicusConfig {
def config: Config

def self: FicusConfig = this

def as[A](path: String)(using reader: ValueReader[A]): A = reader.read(config, path)

def to[A](using ValueReader[A]): A = as[A](".")

def getAs[A](path: String)(implicit reader: ValueReader[Option[A]]): Option[A] = reader.read(config, path)

def getOrElse[A](path: String, default: => A)(implicit reader: ValueReader[Option[A]]): A =
getAs[A](path).getOrElse(default)

def apply[A](key: ConfigKey[A])(implicit reader: ValueReader[A]): A = as[A](key.path)
}

final case class SimpleFicusConfig(config: Config) extends FicusConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package net.ceedubs.ficus.readers

import scala.quoted.*

import com.typesafe.config.Config

trait ArbitraryTypeReader {
implicit inline def arbitraryTypeValueReader[T]: Generated[ValueReader[T]] =
${ ArbitraryTypeReaderMacros.arbitraryTypeValueReader[T] }
}

object ArbitraryTypeReader extends ArbitraryTypeReader

object ArbitraryTypeReaderMacros {
def arbitraryTypeValueReader[T](using Quotes, Type[T]): Expr[Generated[ValueReader[T]]] = {
import quotes.reflect.*
'{
Generated(new ValueReader[T] {
def read(config: Config, path: String): T = ${
instantiateFromConfig[T](config = '{ config }, path = '{ path }, mapper = '{ NameMapper() })
}
})
}
}

def instantiateFromConfig[T](
config: Expr[Config],
path: Expr[String],
mapper: Expr[NameMapper],
default: Option[Expr[T]] = None
)(using Quotes, Type[T]): Expr[T] = {
import quotes.reflect.*
val tTypeTree = TypeTree.of[T]
val typeSymbol: Symbol = tTypeTree.symbol
val isCase = typeSymbol.flags.is(Flags.Case)
val hasCompanion = typeSymbol.companionClass != Symbol.noSymbol && typeSymbol.companionClass.isDefinedInCurrentRun
def getApplyO(companion: Symbol): Option[DefDef] =
companion.memberMethod("apply").filter(_.isDefDef).map(_.tree).collect{ case d: DefDef => d }.find(_.returnTpt.symbol == tTypeTree.symbol)
val companionHasApply = hasCompanion && getApplyO(typeSymbol.companionClass).isDefined
def defaultIfNoPath(expr: Expr[T]): Expr[T] = default match {
case Some(d) => '{ if (! $config.hasPath($path)) $d else $expr }
case None => expr
}
// val isClassDef = typeSymbol.isClassDef

if (companionHasApply) {
val classDef: ClassDef = typeSymbol.tree.asInstanceOf[ClassDef]
val companionApply: DefDef = getApplyO(typeSymbol.companionClass).get
println(s"companionApply.returnTpt = ${companionApply.returnTpt}")
println(s"companionApply.returnTpt.symbol = ${companionApply.returnTpt.symbol}")
println(s"companionApply.returnTpt.tpe = ${companionApply.returnTpt.tpe}")
println(s"tTypeTree.symbol = ${tTypeTree.symbol}")
println(s"tTypeTree.symbol == companionApply.returnTpt.symbol ${tTypeTree.symbol == companionApply.returnTpt.symbol}")
// println(s"companionApply.returnTpt = ${companionApply.returnTpt.tpe.symbol}")
val companionApplySymbol: Symbol = companionApply.symbol
val params: List[TermParamClause] = companionApply.termParamss
val filledParams: List[Term] = params.head.params.map { case v: ValDef =>
val nextPath: Expr[String] = '{ (if ($path == ".") "" else $path + ".") + $mapper.map(${ Expr(v.name) }) }
// v.asExpr match { case '{ $expr: t } => instantiateFromConfig[t](config, nextPath, mapper)(using summon[Quotes], v.tpt.tpe.asType.asInstanceOf[Type[t]]).asTerm }
type X
val tpe = v.tpt.tpe.asType.asInstanceOf[Type[X]]
val default: Option[Expr[X]] = v.rhs.map(_.asExpr.asInstanceOf[Expr[X]])
if (default.isDefined) println(s"Have a default of $default")
instantiateFromConfig[X](config, nextPath, mapper, default)(using summon[Quotes], tpe)
.asInstanceOf[Expr[Any]]
.asTerm
}
val res = Apply(Ref(companionApplySymbol), filledParams)
defaultIfNoPath(res.asExpr.asInstanceOf[Expr[T]])
} else {
val leafReader: Expr[ValueReader[T]] = Implicits.search(TypeTree.of[ValueReader[T]].tpe) match {
case succ: ImplicitSearchSuccess =>
// report.throwError(s"Actually succeeded, but could recurse...\n tree=${succ.tree}\nsymbol=${succ.tree.symbol}\nexpr=${succ.tree.asExpr.show}")
succ.tree.asExpr.asInstanceOf[Expr[ValueReader[T]]]
// case succ: ImplicitSearchSuccess => Ref(succ.tree.symbol).asExpr.asInstanceOf[Expr[ValueReader[T]]]
case fail: ImplicitSearchFailure => report.throwError(fail.explanation)
}
defaultIfNoPath('{ $leafReader.read($config, $path) })
}

}
}
41 changes: 41 additions & 0 deletions src/main/scala-3/net/ceedubs/ficus/readers/CollectionReaders.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.ceedubs.ficus.readers

import com.typesafe.config.{Config, ConfigUtil}
import scala.collection.{Factory, IterableFactory}
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.Builder
import scala.jdk.CollectionConverters._
import scala.language.postfixOps
import scala.language.higherKinds

trait CollectionReaders {
private[this] val DummyPathValue: String = "collection-entry-path"

implicit def traversableReader[C[_], A](implicit
entryReader: ValueReader[A],
factory: Factory[A, C[A]]
): ValueReader[C[A]] = new ValueReader[C[A]] {
def read(config: Config, path: String): C[A] = {
val list = config.getList(path).asScala
val builder: Builder[A, C[A]] = factory.newBuilder
builder.sizeHint(list.size)
list foreach { entry =>
val entryConfig = entry.atPath(DummyPathValue)
builder += entryReader.read(entryConfig, DummyPathValue)
}
builder.result()
}
}
implicit def mapValueReader[A](implicit entryReader: ValueReader[A]): ValueReader[Map[String, A]] =
new ValueReader[Map[String, A]] {
def read(config: Config, path: String): Map[String, A] = {
val relativeConfig = config.getConfig(path)
relativeConfig.root().entrySet().asScala map { entry =>
val key = entry.getKey
key -> entryReader.read(relativeConfig, ConfigUtil.quoteString(key))
} toMap
}
}
}

object CollectionReaders extends CollectionReaders
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.ceedubs.ficus.readers

trait EnumerationReader {}

object EnumerationReader extends EnumerationReader
61 changes: 61 additions & 0 deletions src/main/scala-3/net/ceedubs/ficus/util/ReflectionUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//package net.ceedubs.ficus.util
//
//import macrocompat.bundle
//import scala.reflect.macros.blackbox
//
//@bundle
//trait ReflectionUtils {
// val c: blackbox.Context
//
// import c.universe._
//
// def instantiationMethod[T: c.WeakTypeTag](fail: String => Nothing): c.universe.MethodSymbol = {
//
// val returnType = c.weakTypeOf[T]
//
// val returnTypeTypeArgs = returnType match {
// case TypeRef(_, _, args) => args
// case _ => Nil
// }
//
// if (returnTypeTypeArgs.nonEmpty)
// fail(
// s"value readers cannot be auto-generated for types with type parameters. Consider defining your own ValueReader[$returnType]"
// )
//
// val companionSymbol = returnType.typeSymbol.companion match {
// case NoSymbol => None
// case x => Some(x)
// }
//
// val applyMethods = companionSymbol.toList.flatMap(_.typeSignatureIn(returnType).members collect {
// case m: MethodSymbol if m.name.decodedName.toString == "apply" && m.returnType <:< returnType => m
// })
//
// val applyMethod = applyMethods match {
// case Nil => None
// case (head :: Nil) => Some(head)
// case _ => fail(s"its companion object has multiple apply methods that return type $returnType")
// }
//
// applyMethod getOrElse {
// val primaryConstructor = returnType.decl(termNames.CONSTRUCTOR) match {
// case t: TermSymbol =>
// val constructors = t.alternatives collect {
// case m: MethodSymbol if m.isConstructor => m
// }
// val primaryScalaConstructor = constructors.find(m => m.isPrimaryConstructor && !m.isJava)
// primaryScalaConstructor orElse {
// if (constructors.length == 1) constructors.headOption else None
// }
// case _ => None
// }
// primaryConstructor getOrElse {
// fail(
// s"it has no apply method in a companion object that returns type $returnType, and it doesn't have a primary constructor"
// )
// }
// }
// }
//
//}
Loading