Skip to content

Commit

Permalink
Draft for scala 3
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Martini committed Dec 6, 2020
1 parent fe499bf commit fdb313e
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 39 deletions.
1 change: 0 additions & 1 deletion .gitignore
Expand Up @@ -9,7 +9,6 @@
**/#*
**/.#*
tmp/
project
target
.bsp
.metals/
Expand Down
27 changes: 14 additions & 13 deletions build.sbt
@@ -1,4 +1,4 @@
ThisBuild / scalaVersion := "2.12.12"
ThisBuild / scalaVersion := "3.0.0-M2"
ThisBuild / organization := "com.propensive"
ThisBuild / organizationName := "Propensive OÜ"
ThisBuild / organizationHomepage := Some(url("https://propensive.com/"))
Expand Down Expand Up @@ -37,23 +37,24 @@ lazy val core = (project in file(".core"))
.settings(
name := "magnolia-core",
Compile / scalaSource := baseDirectory.value / ".." / "src" / "core",
libraryDependencies += "com.propensive" %% "mercator" % "0.2.1"
libraryDependencies += ("com.propensive" %% "mercator" % "0.2.1").withDottyCompat(scalaVersion.value),
//scalacOptions += "-Xprint:typer"
)

/*
lazy val examples = (project in file(".examples"))
.dependsOn(core)
.settings(
scalacOptions ++= Seq("-Xexperimental", "-Xfuture"),
name := "magnolia-examples",
Compile / scalaSource := baseDirectory.value / ".." / "src" / "examples",
libraryDependencies += "com.propensive" %% "magnolia" % "0.17.0",
)

lazy val test = (project in file(".test"))
.dependsOn(examples)
.settings(
name := "magnolia-test",
Compile / scalaSource := baseDirectory.value / ".." / "src" / "test",
libraryDependencies += "com.propensive" %% "probably-cli" % "0.5.0",
libraryDependencies += "com.propensive" %% "contextual-examples" % "1.5.0"
libraryDependencies += "com.propensive" %% "magnolia" % "0.18.0",
)
*/
// lazy val test = (project in file(".test"))
// .dependsOn(examples)
// .settings(
// name := "magnolia-test",
// Compile / scalaSource := baseDirectory.value / ".." / "src" / "test",
// libraryDependencies += "com.propensive" %% "probably-cli" % "0.5.0",
// libraryDependencies += "com.propensive" %% "contextual-examples" % "1.5.0"
// )
1 change: 1 addition & 0 deletions project/build.properties
@@ -0,0 +1 @@
sbt.version=1.4.4
1 change: 1 addition & 0 deletions project/plugins.sbt
@@ -0,0 +1 @@
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.6")
File renamed without changes.
12 changes: 12 additions & 0 deletions src/core/IsObject.scala
@@ -0,0 +1,12 @@
package magnolia

import scala.quoted._

object IsObject {
inline def apply[T]: Boolean = ${ isObjectImpl[T] }

def isObjectImpl[T](using qctx: Quotes, tpe: Type[T]): Expr[Boolean] = {
import qctx.reflect._
Expr(TypeRepr.of[T].typeSymbol.flags.is(Flags.Object))
}
}
27 changes: 27 additions & 0 deletions src/core/ParamAnnotations.scala
@@ -0,0 +1,27 @@
package magnolia

import scala.quoted._

object ParamAnnotations {
inline def apply[T]: List[(String, List[Any])] = ${ paramAnnotationsImpl[T] }

def paramAnnotationsImpl[T](using qctx: Quotes, tpe: Type[T]): Expr[List[(String, List[Any])]] = {
import qctx.reflect._

val tpe = TypeRepr.of[T]

Expr.ofList(
tpe
.typeSymbol
.caseFields
.map(field =>
Expr(field.name) -> field.annots.filter(a =>
a.tpe.typeSymbol.maybeOwner.isNoSymbol ||
a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal"
).map(_.asExpr.asInstanceOf[Expr[Any]])
)
.filter(_._2.nonEmpty)
.map{ case (name, annots) => Expr.ofTuple(name, Expr.ofList(annots)) }
)
}
}
40 changes: 40 additions & 0 deletions src/core/TestApp.scala
@@ -0,0 +1,40 @@
package magnolia

object TestApp extends App {
case class MyAnnotation(a: Int) extends scala.annotation.Annotation

@MyAnnotation(1) case class MyCaseClass[A](@MyAnnotation(2) @MyAnnotation(10) i: A @MyAnnotation(3) ,@MyAnnotation(4) s: String @MyAnnotation(5))

trait Show[T] {
def show(t: T): String
}

object Show extends MagnoliaDerivation[Show] {
def combine[T](ctx: CaseClass[Show, T]): Show[T] = new Show[T] {
def show(value: T): String = ctx.parameters.map { p =>
s"${p.label}=${p.typeclass.show(p.dereference(value))}"
}.mkString("{", ",", "}")
}

def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] = {
new Show[T] {
def show(value: T): String = ctx.dispatch(value) { sub =>
sub.typeclass.show(sub.cast(value))
}
}
}

given IntShow as Show[Int] {
def show(t: Int): String = t.toString
}

given StringShow as Show[String] {
def show(t: String): String = t
}
}

List(
MyCaseClass[Int](1, "a")
).map(Show.derived[MyCaseClass[Int]].show).foreach(println)

}
22 changes: 22 additions & 0 deletions src/core/TypeAnnotations.scala
@@ -0,0 +1,22 @@
package magnolia

import scala.quoted._

object TypeAnnotations {
inline def apply[T]: List[Any] = ${ typeAnnotationsImpl[T] }

def typeAnnotationsImpl[T](using qctx: Quotes, tpe: Type[T]): Expr[List[Any]] = {
import qctx.reflect._

Expr.ofList(
TypeRepr
.of[T]
.typeSymbol
.annots
.filter(a =>
a.tpe.typeSymbol.maybeOwner.isNoSymbol ||
a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal"
).map(_.asExpr.asInstanceOf[Expr[Any]])
)
}
}
49 changes: 49 additions & 0 deletions src/core/TypeInfo.scala
@@ -0,0 +1,49 @@
package magnolia

import scala.quoted._

sealed trait TypeInfo {
def full: String
}

case class BaseTypeInfo(owner: String, name: String, typeParams: List[TypeInfo]) extends TypeInfo {
override def full: String = "" // TODO
}
case class OrTypeInfo(subtypes: List[TypeInfo]) extends TypeInfo {
override def full: String = "" // TODO
}
case class AndTypeInfo(subtypes: List[TypeInfo]) extends TypeInfo {
override def full: String = "" // TODO
}

object TypeInfo {
inline def apply[T]: TypeInfo = ${ typeInfoImpl[T] }

def typeInfoImpl[T](using qctx: Quotes, tpe: Type[T]): Expr[TypeInfo] = {
import qctx.reflect._

def name(tpe: TypeRepr) : Expr[String] =
Expr(tpe.typeSymbol.name)

def owner(tpe: TypeRepr): Expr[String] =
if (tpe.typeSymbol.maybeOwner.isNoSymbol) {
println("Debug: No owner - " + tpe) // TODO - remove
Expr("<no owner>") // TODO: can this happen any more? are all cases catched? how to deal with unhandled cases?
} else if (tpe.typeSymbol.owner == defn.EmptyPackageClass) Expr("")
else Expr(tpe.typeSymbol.owner.fullName)

def typeInfo(tpe: TypeRepr): Expr[TypeInfo] =
tpe match {
case OrType(subtypes) =>
'{OrTypeInfo(${Expr.ofList(subtypes.toList.map(typeInfo))})}
case AndType(subtypes) =>
'{AndTypeInfo(${Expr.ofList(subtypes.toList.map(typeInfo))})}
case AppliedType(tpe, args) =>
'{BaseTypeInfo(${owner(tpe)}, ${name(tpe)}, ${Expr.ofList(args.map(typeInfo))})}
case _ =>
'{BaseTypeInfo(${owner(tpe)}, ${name(tpe)}, List.empty)}
}

typeInfo(TypeRepr.of[T])
}
}
52 changes: 27 additions & 25 deletions src/core/interface.scala
Expand Up @@ -27,12 +27,12 @@ import mercator._
trait Subtype[Typeclass[_], Type] extends Serializable {

/** the type of subtype */
type SType <: Type
type SType

/** the [[TypeName]] of the subtype
/** the [[TypeInfo]] of the subtype
*
* This is the full name information for the type of subclass. */
def typeName: TypeName
def typeInfo: TypeInfo

def index: Int

Expand All @@ -53,15 +53,15 @@ trait Subtype[Typeclass[_], Type] extends Serializable {
final def typeAnnotations: Seq[Any] = typeAnnotationsArray
def typeAnnotationsArray: Array[Any]

override def toString: String = s"Subtype(${typeName.full})"
override def toString: String = s"Subtype(${typeInfo.full})"
}

object Subtype {
/** constructs a new [[Subtype]] instance
*
* This method is intended to be called only from code generated by the Magnolia macro, and
* should not be called directly from users' code. */
def apply[Tc[_], T, S <: T](name: TypeName,
def apply[Tc[_], T, S](info: TypeInfo,
idx: Int,
anns: Array[Any],
tpeAnns: Array[Any],
Expand All @@ -70,15 +70,15 @@ object Subtype {
asType: T => S): Subtype[Tc, T] =
new Subtype[Tc, T] with PartialFunction[T, S] {
type SType = S
def typeName: TypeName = name
def typeInfo: TypeInfo = info
def index: Int = idx
def typeclass: Tc[SType] = tc.value
def cast: PartialFunction[T, SType] = this
def isDefinedAt(t: T) = isType(t)
def apply(t: T): SType = asType(t)
def annotationsArray: Array[Any] = anns
def typeAnnotationsArray: Array[Any] = tpeAnns
override def toString: String = s"Subtype(${typeName.full})"
override def toString: String = s"Subtype(${typeInfo.full})"
}
}

Expand Down Expand Up @@ -260,23 +260,23 @@ object Param {
* of the case class, the full name of the case class type, and a boolean to determine whether the type is a
* case class or case object.
*
* @param typeName the name of the case class
* @param typeInfo the name of the case class
* @param isObject true only if this represents a case object rather than a case class
* @param parametersArray an array of [[Param]] values for this case class
* @param annotationsArray an array of instantiated annotations applied to this case class
* @param typeAnnotationsArray an array of instantiated type annotations applied to this case class
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
abstract class ReadOnlyCaseClass[Typeclass[_], Type](
val typeName: TypeName,
val typeInfo: TypeInfo,
val isObject: Boolean,
val isValueClass: Boolean,
parametersArray: Array[ReadOnlyParam[Typeclass, Type]],
annotationsArray: Array[Any],
typeAnnotationsArray: Array[Any]
) extends Serializable {

override def toString: String = s"ReadOnlyCaseClass(${typeName.full}, ${parameters.mkString(",")})"
override def toString: String = s"ReadOnlyCaseClass(${typeInfo.full}, ${parameters.mkString(",")})"

/** a sequence of [[ReadOnlyParam]] objects representing all of the parameters in the case class
*
Expand All @@ -300,22 +300,22 @@ abstract class ReadOnlyCaseClass[Typeclass[_], Type](
/** [[CaseClass]] contains all information that exists in a [[ReadOnlyCaseClass]], as well as methods and context
* required for construct an instance of this case class/object (e.g. default values for constructor parameters)
*
* @param typeName the name of the case class
* @param typeInfo the name of the case class
* @param isObject true only if this represents a case object rather than a case class
* @param parametersArray an array of [[Param]] values for this case class
* @param annotationsArray an array of instantiated annotations applied to this case class
* @param typeAnnotationsArray an array of instantiated type annotations applied to this case class
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
abstract class CaseClass[Typeclass[_], Type] (
override val typeName: TypeName,
override val typeInfo: TypeInfo,
override val isObject: Boolean,
override val isValueClass: Boolean,
parametersArray: Array[Param[Typeclass, Type]],
annotationsArray: Array[Any],
typeAnnotationsArray: Array[Any]
) extends ReadOnlyCaseClass[Typeclass, Type](
typeName,
typeInfo,
isObject,
isValueClass,
// Safe to cast as we're never mutating the array
Expand All @@ -330,7 +330,7 @@ abstract class CaseClass[Typeclass[_], Type] (
* [[scala.collection.Seq]] to hide the mutable collection API. */
override def parameters: Seq[Param[Typeclass, Type]] = parametersArray

override def toString: String = s"CaseClass(${typeName.full}, ${parameters.mkString(",")})"
override def toString: String = s"CaseClass(${typeInfo.full}, ${parameters.mkString(",")})"
/** constructs a new instance of the case class type
*
* This method will be implemented by the Magnolia macro to make it possible to construct
Expand Down Expand Up @@ -370,20 +370,20 @@ abstract class CaseClass[Typeclass[_], Type] (
*
* Instances of `SealedTrait` provide access to all of the component subtypes of the sealed trait
* which form a coproduct, and to the fully-qualified name of the sealed trait.
* @param typeName the name of the sealed trait
* @param typeInfo the name of the sealed trait
* @param subtypesArray an array of [[Subtype]] instances for each subtype in the sealed trait
* @param annotationsArray an array of instantiated annotations applied to this case class
* @param typeAnnotationsArray an array of instantiated type annotations applied to this case class
* @tparam Typeclass type constructor for the typeclass being derived
* @tparam Type generic type of this parameter */
final class SealedTrait[Typeclass[_], Type](
val typeName: TypeName,
val typeInfo: TypeInfo,
subtypesArray: Array[Subtype[Typeclass, Type]],
annotationsArray: Array[Any],
typeAnnotationsArray: Array[Any]
) extends Serializable {

override def toString: String = s"SealedTrait($typeName, Array[${subtypes.mkString(",")}])"
override def toString: String = s"SealedTrait($typeInfo, Array[${subtypes.mkString(",")}])"

/** a sequence of all the subtypes of this sealed trait */
def subtypes: Seq[Subtype[Typeclass, Type]] = subtypesArray
Expand All @@ -405,7 +405,7 @@ final class SealedTrait[Typeclass[_], Type](
if (sub.cast.isDefinedAt(value)) handle(sub) else rec(ix + 1)
} else
throw new IllegalArgumentException(
s"The given value `$value` is not a sub type of `$typeName`"
s"The given value `$value` is not a sub type of `$typeInfo`"
)
rec(0)
}
Expand All @@ -423,13 +423,6 @@ final class SealedTrait[Typeclass[_], Type](
final def typeAnnotations: Seq[Any] = typeAnnotationsArray
}

/**
* Provides the different parts of a type's name.
*/
final case class TypeName(owner: String, short: String, typeArguments: Seq[TypeName]) {
val full: String = s"$owner.$short"
}

/**
* This annotation can be attached to the implicit `gen` method of a type class companion,
* which is implemented by the `Magnolia.gen` macro.
Expand All @@ -454,3 +447,12 @@ object MagnoliaUtil {
values.toList.collect { case Left(v) => v }

}

object CallByNeed { def apply[A](a: => A): CallByNeed[A] = new CallByNeed(() => a) }
final class CallByNeed[+A](private[this] var eval: () => A) extends Serializable {
lazy val value: A = {
val result = eval()
eval = null
result
}
}

0 comments on commit fdb313e

Please sign in to comment.