Skip to content

Commit

Permalink
Implements magnolia config
Browse files Browse the repository at this point in the history
  • Loading branch information
Pask authored and lukaszlenart committed Oct 20, 2021
1 parent af8627c commit 2e2a88b
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 6 deletions.
10 changes: 10 additions & 0 deletions core/src/main/scala/magnolia1/interface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,16 @@ final class debug(typeNamePart: String = "") extends scala.annotation.StaticAnno

private[magnolia1] final case class EarlyExit[E](e: E) extends Exception with util.control.NoStackTrace

trait Config {
type Proxy <: Singleton { type Typeclass[A] }
type Ignore <: annotation.Annotation
val readOnly: Boolean
val minFields: Int
val maxFields: Int
val minCases: Int
val maxCases: Int
}

object MagnoliaUtil {

final def checkParamLengths(fieldValues: Seq[Any], paramsLength: Int, typeName: String): Unit =
Expand Down
45 changes: 42 additions & 3 deletions core/src/main/scala/magnolia1/magnolia.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import scala.collection.mutable
import scala.language.higherKinds
import scala.reflect.macros._

private case class MagnoliaConfig[ProxyType, Ignore](
proxyType: ProxyType,
ignoreType: Ignore,
readOnly: Boolean = false,
minFields: Int = -1,
maxFields: Int = Int.MaxValue,
minCases: Int = -1,
maxCases: Int = Int.MaxValue
)

/** the object which defines the Magnolia macro */
object Magnolia {
import CompileTimeState._
Expand Down Expand Up @@ -42,12 +52,32 @@ object Magnolia {
* split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = ... </pre> will suffice, however the qualifications regarding
* additional type parameters and implicit parameters apply equally to `split` as to `join`.
*/
def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = Stack.withContext(c) { (stack, depth) =>
def genWith[T: c.WeakTypeTag, C <: Config with Singleton: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._

val weakConfig = weakTypeOf[C]
val proxyType: c.Type = weakConfig.decl(TypeName("Proxy")).info
val ignoreType: c.Type = weakConfig.decl(TypeName("Ignore")).info
val NullaryMethodType(ConstantType(Constant(readOnly: Boolean))) = weakConfig.decl(TermName("readOnly")).info
val NullaryMethodType(ConstantType(Constant(minFields: Int))) = weakConfig.decl(TermName("minFields")).info
val NullaryMethodType(ConstantType(Constant(maxFields: Int))) = weakConfig.decl(TermName("maxFields")).info
val NullaryMethodType(ConstantType(Constant(minCases: Int))) = weakConfig.decl(TermName("minCases")).info
val NullaryMethodType(ConstantType(Constant(maxCases: Int))) = weakConfig.decl(TermName("maxCases")).info

genMacro[T, c.Type, c.Type](c, Some(MagnoliaConfig(proxyType, ignoreType, readOnly, minFields, maxFields, minCases, maxCases)))
}

def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
genMacro(c, None)
}

private def genMacro[T: c.WeakTypeTag, ProxyType, IgnoreType](c: whitebox.Context, config: Option[MagnoliaConfig[ProxyType, IgnoreType]]): c.Tree = Stack.withContext(c) { (stack, depth) =>
import c.internal._
import c.universe._
import definitions._

val genericType = weakTypeOf[T]

val genericSymbol = genericType.typeSymbol

def error(message: => String): Nothing = c.abort(c.enclosingPosition, if (depth > 1) "" else s"magnolia: $message")
Expand Down Expand Up @@ -318,6 +348,12 @@ object Magnolia {
val resultType = appliedType(typeConstructor, genericType)
val typeName = c.freshName(TermName("typeName"))

def isIgnored(termSymbol: c.universe.TermSymbol): Boolean = {
config.map(_.ignoreType).exists { ignoreType =>
termSymbol.annotations.map(_.tree.symbol).contains(ignoreType)
}
}

def typeNameOf(tpe: Type): Tree = {
val symbol = tpe.typeSymbol
val typeArgNames = for (typeArg <- tpe.typeArgs) yield typeNameOf(typeArg)
Expand Down Expand Up @@ -383,8 +419,11 @@ object Magnolia {
.map(_.map(_.asTerm))

val caseClassParameters = genericType.decls.sorted.collect(
if (isValueClass) { case p: TermSymbol if p.isParamAccessor && p.isMethod => p }
else { case p: TermSymbol if p.isCaseAccessor && !p.isMethod => p }
if (isValueClass) {
case p: TermSymbol if p.isParamAccessor && p.isMethod => p
} else {
case p: TermSymbol if p.isCaseAccessor && !p.isMethod && !isIgnored(p) => p
}
)

val (factoryObject, factoryMethod) = {
Expand Down
18 changes: 16 additions & 2 deletions examples/src/main/scala/magnolia1/examples/csv.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package magnolia1.examples

import magnolia1.{CaseClass, Magnolia, SealedTrait}
import magnolia1.{CaseClass, Config, Magnolia, SealedTrait}

import scala.language.experimental.macros

trait Csv[A] {
def apply(a: A): List[String]
}

object CsvConfig extends Config {
type Proxy = Csv.type
type Ignore = transient
final val readOnly = true
final val minFields = 0
final val maxFields = -1
final val minCases = 0
final val maxCases = -1
}

object Csv {
type Typeclass[A] = Csv[A]

Expand All @@ -22,9 +32,13 @@ object Csv {
def apply(a: A): List[String] = ctx.split(a)(sub => sub.typeclass(sub.cast(a)))
}

implicit def deriveCsv[A]: Csv[A] = macro Magnolia.gen[A]
@transient
val ignoreMe: String = "ignored value"

implicit def deriveCsv[A]: Csv[A] = macro Magnolia.genWith[A, CsvConfig.type]

implicit val csvStr: Csv[String] = new Csv[String] {
def apply(a: String): List[String] = List(a)
}

}
5 changes: 4 additions & 1 deletion test/src/test/scala/magnolia1/tests/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ sealed trait AttributeParent

case class `%%`(`/`: Int, `#`: String)

case class Param(a: String, b: String)
case class Param(a: String, b: String) {
@transient
val daad = "Test"
}
case class TestEntry(param: Param)
object TestEntry {
def apply(): TestEntry = TestEntry(Param("", ""))
Expand Down

0 comments on commit 2e2a88b

Please sign in to comment.