From f5df7e63ce41375871524f442d0438aba698c6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szafraniuk?= Date: Thu, 22 Dec 2022 14:37:42 +0100 Subject: [PATCH] Revert "Support for value classes" --- src/core/impl.scala | 54 ---- src/core/interface.scala | 3 +- src/core/macro.scala | 507 ++++++++----------------------- src/core/magnolia.scala | 35 +-- src/examples/print.scala | 14 +- src/examples/show.scala | 80 ++--- src/test/SumsTests.scala | 8 +- src/test/ValueClassesTests.scala | 153 ++-------- 8 files changed, 209 insertions(+), 645 deletions(-) diff --git a/src/core/impl.scala b/src/core/impl.scala index 3caadd04..2ce1b0cf 100644 --- a/src/core/impl.scala +++ b/src/core/impl.scala @@ -7,59 +7,6 @@ import scala.reflect.* import Macro.* object CaseClassDerivation: - - inline def valueClassDerivation[TC[_], V]: CaseClass[TC, V] = - - val theParam: CaseClass.Param[TC, V] = - Macro.ValueClassDerivation.getValueClassParam[TC, V] - - new CaseClass[TC, V]( - typeInfo = typeInfo[V], - isObject = isObject[V], - isValueClass = true, - params = IArray.from(theParam :: Nil), - annotations = IArray.from(anns[V]), - inheritedAnnotations = IArray.from(inheritedAnns[V]), - typeAnnotations = IArray.from(typeAnns[V]) - ): - - def rawConstruct(fieldValues: Seq[Any]): V = - Macro.ValueClassDerivation.rawContructByMacro[V](fieldValues.toList) - - def construct[PType: ClassTag](makeParam: Param => PType): V = - Macro.ValueClassDerivation.rawContructByMacro[V]( - params.map(makeParam).toList - ) - - def constructEither[Err, PType: ClassTag]( - makeParam: Param => Either[Err, PType] - ): Either[List[Err], V] = - params - .map(makeParam) - .foldLeft[Either[List[Err], Array[PType]]](Right(Array())) { - case (Left(errs), Left(err)) => Left(errs ++ List(err)) - case (Right(acc), Right(param)) => Right(acc ++ Array(param)) - case (errs @ Left(_), _) => errs - case (_, Left(err)) => Left(List(err)) - } - .map { params => - Macro.ValueClassDerivation.rawContructByMacro[V](params.toList) - } - - def constructMonadic[M[_]: Monadic, PType: ClassTag]( - makeParam: Param => M[PType] - ): M[V] = - val m = summon[Monadic[M]] - m.map { - params.map(makeParam).foldLeft(m.point(Array())) { (accM, paramM) => - m.flatMap(accM) { acc => - m.map(paramM)(acc ++ List(_)) - } - } - } { params => - Macro.ValueClassDerivation.rawContructByMacro[V](params.toList) - } - inline def fromMirror[Typeclass[_], A]( product: Mirror.ProductOf[A] ): CaseClass[Typeclass, A] = @@ -150,7 +97,6 @@ object CaseClassDerivation: defaults, idx + 1 ) - end CaseClassDerivation trait SealedTraitDerivation: diff --git a/src/core/interface.scala b/src/core/interface.scala index c0874a3c..f6c45a26 100644 --- a/src/core/interface.scala +++ b/src/core/interface.scala @@ -44,7 +44,6 @@ object CaseClass: override def toString: String = s"Param($label)" object Param: - def apply[F[_], T, P]( name: String, idx: Int, @@ -64,7 +63,7 @@ object CaseClass: ): type PType = P def default: Option[PType] = defaultVal.value - def typeclass: F[PType] = cbn.value + def typeclass = cbn.value override def inheritedAnnotations = inheritedAnns def deref(value: T): P = value.asInstanceOf[Product].productElement(idx).asInstanceOf[P] diff --git a/src/core/macro.scala b/src/core/macro.scala index 1ffc48f4..9ac0c2a9 100644 --- a/src/core/macro.scala +++ b/src/core/macro.scala @@ -1,52 +1,24 @@ package magnolia1 import scala.quoted.* -import scala.annotation.meta.field -import scala.annotation.Annotation object Macro: - - inline def showType[A]: String = - ${ showTypeImpl[A] } - - inline def isObject[T]: Boolean = - ${ isObject[T] } - - inline def isEnum[T]: Boolean = - ${ isEnum[T] } - - inline def anns[T]: List[Any] = - ${ anns[T] } - - inline def inheritedAnns[T]: List[Any] = - ${ inheritedAnns[T] } - - inline def typeAnns[T]: List[Any] = - ${ typeAnns[T] } - - inline def paramAnns[T]: List[(String, List[Any])] = - ${ paramAnns[T] } - - inline def inheritedParamAnns[T]: List[(String, List[Any])] = - ${ inheritedParamAnns[T] } - - inline def isValueClass[T]: Boolean = - ${ isValueClass[T] } - - inline def defaultValue[T]: List[(String, Option[() => Any])] = - ${ defaultValue[T] } - - inline def paramTypeAnns[T]: List[(String, List[Any])] = - ${ paramTypeAnns[T] } - - inline def repeated[T]: List[(String, Boolean)] = - ${ repeated[T] } - - inline def typeInfo[T]: TypeInfo = - ${ typeInfo[T] } - - private def showTypeImpl[A: Type](using Quotes): Expr[String] = - Expr(Type.show[A]) + inline def isObject[T]: Boolean = ${ isObject[T] } + inline def isEnum[T]: Boolean = ${ isEnum[T] } + inline def anns[T]: List[Any] = ${ anns[T] } + inline def inheritedAnns[T]: List[Any] = ${ inheritedAnns[T] } + inline def typeAnns[T]: List[Any] = ${ typeAnns[T] } + inline def paramAnns[T]: List[(String, List[Any])] = ${ paramAnns[T] } + inline def inheritedParamAnns[T]: List[(String, List[Any])] = ${ + inheritedParamAnns[T] + } + inline def isValueClass[T]: Boolean = ${ isValueClass[T] } + inline def defaultValue[T]: List[(String, Option[() => Any])] = ${ + defaultValue[T] + } + inline def paramTypeAnns[T]: List[(String, List[Any])] = ${ paramTypeAnns[T] } + inline def repeated[T]: List[(String, Boolean)] = ${ repeated[T] } + inline def typeInfo[T]: TypeInfo = ${ typeInfo[T] } def isObject[T: Type](using Quotes): Expr[Boolean] = import quotes.reflect.* @@ -58,29 +30,22 @@ object Macro: Expr(TypeRepr.of[T].typeSymbol.flags.is(Flags.Enum)) - def paramTypeAnns[T: Type](using - q: Quotes - ): Expr[List[(String, List[Any])]] = - new CollectAnnotations[q.type, T].paramTypeAnns - - def anns[T: Type](using q: Quotes): Expr[List[Any]] = - new CollectAnnotations[q.type, T].anns + def anns[T: Type](using Quotes): Expr[List[Any]] = + new CollectAnnotations[T].anns - def inheritedAnns[T: Type](using q: Quotes): Expr[List[Any]] = - new CollectAnnotations[q.type, T].inheritedAnns + def inheritedAnns[T: Type](using Quotes): Expr[List[Any]] = + new CollectAnnotations[T].inheritedAnns - def typeAnns[T: Type](using q: Quotes): Expr[List[Any]] = - new CollectAnnotations[q.type, T].typeAnns + def typeAnns[T: Type](using Quotes): Expr[List[Any]] = + new CollectAnnotations[T].typeAnns - def paramAnns[T: Type](using - q: Quotes - ): Expr[List[(String, List[Any])]] = - new CollectAnnotations[q.type, T].paramAnns + def paramAnns[T: Type](using Quotes): Expr[List[(String, List[Any])]] = + new CollectAnnotations[T].paramAnns def inheritedParamAnns[T: Type](using - q: Quotes + Quotes ): Expr[List[(String, List[Any])]] = - new CollectAnnotations[q.type, T].inheritedParamAnns + new CollectAnnotations[T].inheritedParamAnns def isValueClass[T: Type](using Quotes): Expr[Boolean] = import quotes.reflect.* @@ -92,39 +57,55 @@ object Macro: def defaultValue[T: Type](using Quotes ): Expr[List[(String, Option[() => Any])]] = - import quotes.reflect.* - + import quotes.reflect._ def exprOfOption( oet: (Expr[String], Option[Expr[Any]]) - ): Expr[(String, Option[() => Any])] = - oet match - case (label, None) => Expr(label.valueOrAbort -> None) - case (label, Some(et)) => '{ $label -> Some(() => $et) } - - Expr.ofList { - defaultValueOnTerms - .map { p => - exprOfOption(Expr(p._1) -> p._2) - } + ): Expr[(String, Option[() => Any])] = oet match { + case (label, None) => Expr(label.valueOrAbort -> None) + case (label, Some(et)) => '{ $label -> Some(() => $et) } } - - private def defaultValueOnTerms[T: Type](using - Quotes - ): List[(String, Option[Expr[Any]])] = - import quotes.reflect.* - val tpe = TypeRepr.of[T].typeSymbol - - tpe.primaryConstructor.paramSymss.flatten + val terms = tpe.primaryConstructor.paramSymss.flatten .filter(_.isValDef) .zipWithIndex .map { case (field, i) => - field.name -> tpe.companionClass - .declaredMethod(s"$$lessinit$$greater$$default$$${i + 1}") - .headOption - .flatMap(_.tree.asInstanceOf[DefDef].rhs) - .map(_.asExprOf[Any]) + exprOfOption { + Expr(field.name) -> tpe.companionClass + .declaredMethod(s"$$lessinit$$greater$$default$$${i + 1}") + .headOption + .flatMap(_.tree.asInstanceOf[DefDef].rhs) + .map(_.asExprOf[Any]) + } } + Expr.ofList(terms) + + def paramTypeAnns[T: Type](using Quotes): Expr[List[(String, List[Any])]] = + import quotes.reflect._ + + def getAnnotations(t: TypeRepr): List[Term] = t match + case AnnotatedType(inner, ann) => ann :: getAnnotations(inner) + case _ => Nil + + Expr.ofList { + TypeRepr + .of[T] + .typeSymbol + .caseFields + .map { field => + val tpeRepr = field.tree match + case v: ValDef => v.tpt.tpe + case d: DefDef => d.returnTpt.tpe + + Expr(field.name) -> getAnnotations(tpeRepr) + .filter { a => + a.tpe.typeSymbol.maybeOwner.isNoSymbol || + a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" + } + .map(_.asExpr.asInstanceOf[Expr[Any]]) + } + .filter(_._2.nonEmpty) + .map { (name, annots) => Expr.ofTuple(name, Expr.ofList(annots)) } + } def repeated[T: Type](using Quotes): Expr[List[(String, Boolean)]] = import quotes.reflect.* @@ -152,7 +133,7 @@ object Macro: Expr(areRepeated) def typeInfo[T: Type](using Quotes): Expr[TypeInfo] = - import quotes.reflect.* + import quotes.reflect._ def normalizedName(s: Symbol): String = if s.flags.is(Flags.Module) then s.name.stripSuffix("$") else s.name @@ -187,10 +168,10 @@ object Macro: typeInfo(TypeRepr.of[T]) - private class CollectAnnotations[Q <: Quotes, T: Type](using val quotes: Q): + private class CollectAnnotations[T: Type](using val quotes: Quotes) { import quotes.reflect.* - val tpe: TypeRepr = TypeRepr.of[T] + private val tpe: TypeRepr = TypeRepr.of[T] def anns: Expr[List[Any]] = Expr.ofList { @@ -211,323 +192,79 @@ object Macro: .map(_.asExpr.asInstanceOf[Expr[Any]]) } - def typeAnns: Expr[List[Any]] = + def typeAnns: Expr[List[Any]] = { + + def getAnnotations(t: TypeRepr): List[Term] = t match + case AnnotatedType(inner, ann) => ann :: getAnnotations(inner) + case _ => Nil + val symbol: Option[Symbol] = if tpe.typeSymbol.isNoSymbol then None else Some(tpe.typeSymbol) - Expr.ofList { - symbol.toList - .map(_.tree) - .flatMap { - case ClassDef(_, _, parents, _, _) => - parents - .collect { - - case t: TypeTree => t.tpe - - // case for AnyVal type annotations: with "-Yretain-trees" scalac option, the TypeTree of the annotation gets erased, - // so we need to extract the annotations from apply - case Apply(Select(New(a), _), _) => a.tpe - } - .flatMap(loopForAnnotations) - .filter(filterAnnotation) - .map { _.asExpr.asInstanceOf[Expr[Any]] } - case _ => - List.empty - } - } - - def paramTypeAnnsOnTerms: List[(String, List[Term])] = - tpe.typeSymbol.caseFields - .map { field => - val tpeRepr = field.tree match - case v: ValDef => v.tpt.tpe - case d: DefDef => d.returnTpt.tpe - - field.name -> loopForAnnotations(tpeRepr).filter(filterAnnotation) + symbol.toList.map(_.tree).flatMap { + case ClassDef(_, _, parents, _, _) => + parents + .collect { case t: TypeTree => t.tpe } + .flatMap(getAnnotations) + .filter(filterAnnotation) + .map(_.asExpr.asInstanceOf[Expr[Any]]) + case _ => + List.empty } - .filter(_._2.nonEmpty) - - def paramTypeAnns: Expr[List[(String, List[Any])]] = - liftTermsDict(paramTypeAnnsOnTerms) - - def paramAnnsOnTerms: List[(String, List[Term])] = - val terms = (annotationsFromConstructorOnTerms( - tpe.typeSymbol - ) ++ annotationsFromDeclarationsOnTerms(tpe.typeSymbol)) - .filter { case (_, as) => as.nonEmpty } - - groupByNameOnTerms(terms) + } + } def paramAnns: Expr[List[(String, List[Any])]] = - liftTermsDict(paramAnnsOnTerms) - - def inheritedParamAnnsOnTerms: List[(String, List[Term])] = - val annTerms: List[(String, List[Term])] = - tpe.baseClasses - .filterNot(isObjectOrScala) - .collect { - case s if s != tpe.typeSymbol => - (annotationsFromConstructorOnTerms( - s - ) ++ annotationsFromDeclarationsOnTerms(s)) - .filter { case (_, anns) => - anns.nonEmpty - } - } - .flatten - - groupByNameOnTerms(annTerms) - - def inheritedParamAnns: Expr[List[(String, List[Any])]] = - liftTermsDict(inheritedParamAnnsOnTerms) - - private def loopForAnnotations(t: TypeRepr): List[Term] = - t match - case AnnotatedType(inner, ann) => ann :: loopForAnnotations(inner) - case _ => Nil - - private def liftTermsDict( - tss: List[(String, List[Term])] - ): Expr[List[(String, List[Any])]] = Expr.ofList { - tss - .map { case (name, terms) => - Expr.ofTuple( - Expr(name), - Expr.ofList(terms.map(_.asExpr)) - ) - } - } - - private def annotationsFromConstructorOnTerms( - from: Symbol - ): List[(String, List[Term])] = - from.primaryConstructor.paramSymss.flatten - .map { field => - field.name -> field.annotations.filter(filterAnnotation) + groupByParamName { + (fromConstructor(tpe.typeSymbol) ++ fromDeclarations(tpe.typeSymbol)) + .filter { case (_, anns) => anns.nonEmpty } } + } - private def annotationsfromConstructor( - from: Symbol - ): List[(String, List[Expr[Any]])] = - annotationsFromConstructorOnTerms(from) - .map { p => - p._1 -> p._2.map(_.asExpr.asInstanceOf[Expr[Any]]) + def inheritedParamAnns: Expr[List[(String, List[Any])]] = + Expr.ofList { + groupByParamName { + tpe.baseClasses + .filterNot(isObjectOrScala) + .collect { + case s if s != tpe.typeSymbol => + (fromConstructor(s) ++ fromDeclarations(s)).filter { + case (_, anns) => anns.nonEmpty + } + } + .flatten } + } - private def annotationsFromDeclarationsOnTerms( - from: Symbol - ): List[(String, List[Term])] = - from.declarations - .collect { - case field: Symbol if field.tree.isInstanceOf[ValDef] => - field.name -> field.annotations.filter(filterAnnotation) - } + private def fromConstructor(from: Symbol): List[(String, List[Expr[Any]])] = + from.primaryConstructor.paramSymss.flatten.map { field => + field.name -> field.annotations + .filter(filterAnnotation) + .map(_.asExpr.asInstanceOf[Expr[Any]]) + } - private def annotationsFromDeclarations( + private def fromDeclarations( from: Symbol ): List[(String, List[Expr[Any]])] = - annotationsFromDeclarationsOnTerms(from) - .map { p => - p._1 -> p._2.map(_.asExpr.asInstanceOf[Expr[Any]]) - } + from.declarations.collect { + case field: Symbol if field.tree.isInstanceOf[ValDef] => + field.name -> field.annotations + .filter(filterAnnotation) + .map(_.asExpr.asInstanceOf[Expr[Any]]) + } - private def groupByNameOnTerms( - tss: List[(String, List[Term])] - ): List[(String, List[Term])] = - tss + private def groupByParamName(anns: List[(String, List[Expr[Any]])]) = + anns .groupBy { case (name, _) => name } .toList .map { case (name, l) => name -> l.flatMap(_._2) } + .map { (name, anns) => Expr.ofTuple(Expr(name), Expr.ofList(anns)) } private def isObjectOrScala(bc: Symbol) = - bc.name.contains("java.lang.Object") || - bc.fullName.startsWith("scala.") + bc.name.contains("java.lang.Object") || bc.fullName.startsWith("scala.") private def filterAnnotation(a: Term): Boolean = a.tpe.typeSymbol.maybeOwner.isNoSymbol || a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" - - object ValueClassDerivation: - - inline def getValueClassParam[TC[_], V]: CaseClass.Param[TC, V] = - ${ getValueClassParamImpl[TC, V] } - - inline def rawContructByMacro[V](as: List[Any]): V = - ${ rawContructByMacroImpl[V]('as) } - - private def getValueClassParamImpl[TC[_]: Type, A: Type](using - q: Quotes - ): Expr[CaseClass.Param[TC, A]] = - import quotes.reflect.* - - def selectTermsByName( - tss: List[(String, List[Term])], - name: String - ): List[Term] = - tss.toMap - .getOrElse(name, Nil) - - def selectDefault[P: Type]( - os: List[(String, Option[Expr[Any]])], - name: String - )(using Quotes): Expr[CallByNeed[Option[Any]]] = - import quotes.reflect.* - - os.toMap - .get(name) - .flatten match - case None => - '{ new CallByNeed(() => None) } - case Some(expr) => - '{ new CallByNeed(() => Some(($expr).asInstanceOf[P])) } - - def selectFromDict( - dict: List[(String, List[Term])], - name: String - ) = - Expr - .ofList { - selectTermsByName( - dict, - name - ) - .map(_.asExpr) - } - - def toIArray(es: Expr[List[Any]]): Expr[IArray[Any]] = - '{ IArray($es: _*) } - - extension [B: Type](e: Expr[B]) - def asCallByNeedExpr: Expr[CallByNeed[B]] = - '{ new CallByNeed[B](() => $e) } - - val aTpe: TypeRepr = TypeRepr.of[A] - val aSym: Symbol = aTpe.typeSymbol - val aCtor: Symbol = aSym.primaryConstructor - - aCtor.paramSymss match - case List(paramSymbol: Symbol) :: Nil => - val paramTypeTree = - paramSymbol.tree match - case v: ValDef => v.tpt - case _ => - report.errorAndAbort( - s"Error handling param symbol tree '${paramSymbol.tree}'." - ) - - val paramTypeTpe = paramTypeTree.tpe - val paramTC = TypeRepr.of[TC].appliedTo(paramTypeTpe) - - val paramCallByNeed = paramTC.asType match - case '[t] => - Expr - .summon[t] - .map { _.asCallByNeedExpr } - .getOrElse { - report.errorAndAbort( - s"Cannot summon instance for ${Type.show[t]}." - ) - } - - val defaultValue = - (aTpe.asType, paramTypeTpe.asType) match - case ('[a], '[p]) => - selectDefault[p](defaultValueOnTerms[a], paramSymbol.name) - - val annotations = - toIArray( - selectFromDict( - new CollectAnnotations[q.type, A].paramAnnsOnTerms, - paramSymbol.name - ) - ) - - val inheritedAnnotations = - toIArray( - selectFromDict( - new CollectAnnotations[q.type, A].inheritedParamAnnsOnTerms, - paramSymbol.name - ) - ) - - val typeAnnotations = - toIArray( - selectFromDict( - new CollectAnnotations[q.type, A].paramTypeAnnsOnTerms, - paramSymbol.name - ) - ) - - val applyFuncTerm = - '{ CaseClass.Param }.asTerm - .select( - TypeRepr - .of[CaseClass.Param.type] - .termSymbol - .declaredMethod("apply") - .head - ) - .appliedToTypes( - TypeRepr.of[TC] :: TypeRepr.of[A] :: paramTypeTree.tpe :: Nil - ) - - val args = List( - Expr(paramSymbol.name).asTerm, - Expr(0).asTerm, - Expr(false).asTerm, - paramCallByNeed.asTerm, - defaultValue.asTerm, - annotations.asTerm, - inheritedAnnotations.asTerm, - typeAnnotations.asTerm - ) - - val app = - Apply( - fun = applyFuncTerm, - args = args - ) - - app.asExprOf[CaseClass.Param[TC, A]] - - case _ => report.errorAndAbort(s"Error handling symbol '${aSym.name}") - - private def rawContructByMacroImpl[V: Type](es: Expr[List[Any]])(using - Quotes - ): Expr[V] = - import quotes.reflect.* - - val vTpe = TypeRepr.of[V] - val vCtor = vTpe.typeSymbol.primaryConstructor - val vApp = New(Inferred(vTpe)).select(vCtor) - - val argsTerm: Term = es.asTerm - val appl = argsTerm.tpe.typeSymbol.methodMember("apply").head - val argApp = argsTerm.select(appl).appliedTo(Literal(IntConstant(0))) - val as = argApp.tpe.typeSymbol.methodMember("asInstanceOf").head - - val arg = - vCtor.paramSymss match - case List(paramSymbol: Symbol) :: Nil => - val paramTypeTree = - paramSymbol.tree match - case v: ValDef => v.tpt - case _ => - report.errorAndAbort( - s"Error handling param symbol tree '${paramSymbol.tree}'." - ) - val aTpe = paramTypeTree.tpe - argApp.select(as).appliedToType(aTpe) - - case _ => - report.errorAndAbort( - s"Error handling symbol '${vTpe.typeSymbol.name}'." - ) - - vApp.appliedToArgs(arg :: Nil).asExprOf[V] - - end ValueClassDerivation - -end Macro + } diff --git a/src/core/magnolia.scala b/src/core/magnolia.scala index a1a2f7e0..c6a57805 100644 --- a/src/core/magnolia.scala +++ b/src/core/magnolia.scala @@ -25,7 +25,7 @@ trait CommonDerivation[TypeClass[_]]: */ def join[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] - protected inline def derivedMirrorProduct[A]( + inline def derivedMirrorProduct[A]( product: Mirror.ProductOf[A] ): Typeclass[A] = join(CaseClassDerivation.fromMirror(product)) @@ -66,9 +66,7 @@ trait CommonDerivation[TypeClass[_]]: end CommonDerivation trait ProductDerivation[TypeClass[_]] extends CommonDerivation[TypeClass]: - private inline def derivedMirror[A](using - mirror: Mirror.Of[A] - ): Typeclass[A] = + inline def derivedMirror[A](using mirror: Mirror.Of[A]): Typeclass[A] = inline mirror match case product: Mirror.ProductOf[A] => derivedMirrorProduct[A](product) @@ -102,35 +100,15 @@ trait Derivation[TypeClass[_]] ): List[SealedTrait.Subtype[Typeclass, T, _]] = subtypesFromMirror[T, SubtypeTuple](m, idx) - private inline def derivedMirrorSum[A](sum: Mirror.SumOf[A]): Typeclass[A] = + inline def derivedMirrorSum[A](sum: Mirror.SumOf[A]): Typeclass[A] = split(sealedTraitFromMirror(sum)) - private inline def derivedMirror[A](using - mirror: Mirror.Of[A] - ): Typeclass[A] = + inline def derivedMirror[A](using mirror: Mirror.Of[A]): Typeclass[A] = inline mirror match case sum: Mirror.SumOf[A] => derivedMirrorSum[A](sum) case product: Mirror.ProductOf[A] => derivedMirrorProduct[A](product) - private inline def derivedValueClass[A]: Typeclass[A] = join( - CaseClassDerivation.valueClassDerivation - ) - - private inline def derivedMirrorless[A]: Typeclass[A] = - inline if Macro.isValueClass[A] then derivedValueClass[A] - else - error( - "Deriving the typeclass based on mirrors or directly is not possible for " + Macro - .showType[A] + - ". Please refer to the documentation or report a feature request." - ) - - inline def derived[A]: Typeclass[A] = - summonFrom { - case prod: Mirror.ProductOf[A] => derivedMirrorProduct[A](prod) - case sum: Mirror.SumOf[A] => derivedMirrorSum[A](sum) - case _ => derivedMirrorless[A] - } + inline def derived[A](using Mirror.Of[A]): Typeclass[A] = derivedMirror[A] protected override inline def deriveSubtype[s]( m: Mirror.Of[s] @@ -138,5 +116,4 @@ trait Derivation[TypeClass[_]] end Derivation trait AutoDerivation[TypeClass[_]] extends Derivation[TypeClass]: - inline given autoDerived[A]: TypeClass[A] = - derived + inline given autoDerived[A](using Mirror.Of[A]): TypeClass[A] = derived diff --git a/src/examples/print.scala b/src/examples/print.scala index bcb5030f..9255f4af 100644 --- a/src/examples/print.scala +++ b/src/examples/print.scala @@ -9,11 +9,15 @@ trait Print[T] { trait GenericPrint extends AutoDerivation[Print]: def join[T](ctx: CaseClass[Typeclass, T]): Print[T] = value => - ctx.params - .map { param => - param.typeclass.print(param.deref(value)) - } - .mkString(s"${ctx.typeInfo.short}(", ",", ")") + if ctx.isValueClass then + val param = ctx.params.head + param.typeclass.print(param.deref(value)) + else + ctx.params + .map { param => + param.typeclass.print(param.deref(value)) + } + .mkString(s"${ctx.typeInfo.short}(", ",", ")") override def split[T](ctx: SealedTrait[Print, T]): Print[T] = ctx.choose(_) { sub => sub.typeclass.print(sub.value) } diff --git a/src/examples/show.scala b/src/examples/show.scala index 2e5010f8..3fb37967 100644 --- a/src/examples/show.scala +++ b/src/examples/show.scala @@ -18,44 +18,48 @@ trait GenericShow[Out] extends AutoDerivation[[X] =>> Show[Out, X]] { * the result of showing each parameter, and prefixing it with the class name */ def join[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = { value => - val paramStrings = ctx.params.map { param => - val attribStr = - if (param.annotations.isEmpty && param.inheritedAnnotations.isEmpty) - "" - else { - (param.annotations ++ param.inheritedAnnotations).distinct - .mkString("{", ",", "}") - } - - val tpeAttribStr = - if (param.typeAnnotations.isEmpty) "" - else { - param.typeAnnotations.mkString("{", ",", "}") - } - - s"${param.label}$attribStr$tpeAttribStr=${param.typeclass.show(param.deref(value))}" - } - - val anns = (ctx.annotations ++ ctx.inheritedAnnotations).distinct - val annotationStr = if (anns.isEmpty) "" else anns.mkString("{", ",", "}") - - val tpeAnns = ctx.typeAnnotations - val typeAnnotationStr = - if (tpeAnns.isEmpty) "" else tpeAnns.mkString("{", ",", "}") - - def typeArgsString(typeInfo: TypeInfo): String = - if typeInfo.typeParams.isEmpty then "" - else - typeInfo.typeParams - .map(arg => s"${arg.short}${typeArgsString(arg)}") - .mkString("[", ",", "]") - - joinElems( - ctx.typeInfo.short + typeArgsString( - ctx.typeInfo - ) + annotationStr + typeAnnotationStr, - paramStrings - ) + if ctx.isValueClass then + val param = ctx.params.head + param.typeclass.show(param.deref(value)) + else + val paramStrings = ctx.params.map { param => + val attribStr = + if (param.annotations.isEmpty && param.inheritedAnnotations.isEmpty) + "" + else { + (param.annotations ++ param.inheritedAnnotations).distinct + .mkString("{", ",", "}") + } + + val tpeAttribStr = + if (param.typeAnnotations.isEmpty) "" + else { + param.typeAnnotations.mkString("{", ",", "}") + } + + s"${param.label}$attribStr$tpeAttribStr=${param.typeclass.show(param.deref(value))}" + } + + val anns = (ctx.annotations ++ ctx.inheritedAnnotations).distinct + val annotationStr = if (anns.isEmpty) "" else anns.mkString("{", ",", "}") + + val tpeAnns = ctx.typeAnnotations + val typeAnnotationStr = + if (tpeAnns.isEmpty) "" else tpeAnns.mkString("{", ",", "}") + + def typeArgsString(typeInfo: TypeInfo): String = + if typeInfo.typeParams.isEmpty then "" + else + typeInfo.typeParams + .map(arg => s"${arg.short}${typeArgsString(arg)}") + .mkString("[", ",", "]") + + joinElems( + ctx.typeInfo.short + typeArgsString( + ctx.typeInfo + ) + annotationStr + typeAnnotationStr, + paramStrings + ) } /** choose which typeclass to use based on the subtype of the sealed trait and diff --git a/src/test/SumsTests.scala b/src/test/SumsTests.scala index e7f5181e..735b4865 100644 --- a/src/test/SumsTests.scala +++ b/src/test/SumsTests.scala @@ -101,7 +101,10 @@ class SumsTests extends munit.FunSuite: ) { val error = compileErrors("Show.derived[Parent]") assert( - error contains "Deriving the typeclass based on mirrors or directly is not possible for magnolia1.tests.SumsTests.Parent. Please refer to the documentation or report a feature request." + error contains "No given instance of type deriving.Mirror.Of[magnolia1.tests.SumsTests.Parent] was found for parameter x$1 of method derived in trait Derivation." + ) + assert( + error contains "trait Parent is not a generic sum because its child trait BadChild is not a generic product because it is not a case class" ) } @@ -110,7 +113,7 @@ class SumsTests extends munit.FunSuite: ) { val error = compileErrors("Show.derived[GoodChild]") assert( - error contains "Deriving the typeclass based on mirrors or directly is not possible for magnolia1.tests.SumsTests.GoodChild. Please refer to the documentation or report a feature request." + error contains "trait GoodChild is not a generic sum because its child class Dewey is not a generic product because it is not a case class" ) } @@ -179,6 +182,7 @@ object SumsTests: enum Size: case S, M, L + sealed trait Sport case object Boxing extends Sport case class Soccer(players: Int) extends Sport diff --git a/src/test/ValueClassesTests.scala b/src/test/ValueClassesTests.scala index 341a004c..9401f6ea 100644 --- a/src/test/ValueClassesTests.scala +++ b/src/test/ValueClassesTests.scala @@ -2,115 +2,41 @@ package magnolia1.tests import magnolia1.* import magnolia1.examples.* -import scala.annotation.StaticAnnotation -import scala.runtime.Static -/** Supports mirrorless value classes derivation for non-generic products with - * annotations. TODO: 1) non-product derivation 2) generic derivation 3) access - * modifiers +/** TODO: Support for value classes is missing for scala3 branch. Eventually + * refactor and uncomment the tests below once the feature is implemented. */ class ValueClassesTests extends munit.FunSuite: import ValueClassesTests.* - test("Derive Print TC for simple value class") { - given Print[Int] = _.toString - val res = Print.derived[SimpleVC].print(SimpleVC(555)) - assertEquals(res, "SimpleVC(555)") - } - - test( - "Derive SemiDefault TC for simple value class with no default argument" - ) { - given SemiDefault[SimpleVC] = SemiDefault.derived - val res = summon[SemiDefault[SimpleVC]].default - assertEquals(res, SimpleVC(0)) - } - - test("Derive SemiDefault for nested BigBox value class") { - given SemiDefault[BigBox] = SemiDefault.derived - val res = summon[SemiDefault[BigBox]].default - assertEquals(res, BigBox(NormalBox(SmallBox(TinyBox(0))))) - } - - test("Derive SemiDefault for heavily annotated value class") { - given SemiDefault[HeavilyAnnotated] = SemiDefault.derived - val res = summon[SemiDefault[HeavilyAnnotated]].default - assertEquals(res, HeavilyAnnotated(0)) - } - - test("Derive Show for heavily annotated value class") { - val res = Show.derived[HeavilyAnnotated].show(HeavilyAnnotated(33)) - assertEquals( - res, - "HeavilyAnnotated{MyAnnotation(0)}{MyTypeAnnotation(3)}(k{MyParamAnnotation(1)}{MyTypeAnnotation(2)}=33)" - ) - } - - test( - "Derive Print for a value class with default argument value of basic type" - ) { - given Print[Int] = _.toString - val res = - Print.derived[ValueClassWithDefault].print(ValueClassWithDefault()) - assertEquals(res, "ValueClassWithDefault(123)") - } - - test( - "Derive SemiDefault for a value class with default value of basic type" - ) { - val res = SemiDefault.derived[ValueClassWithDefault].default - assertEquals(res, ValueClassWithDefault(123)) - } - - test("Derive SemiDefault for a wrapped value class with default") { - val res = SemiDefault[WrappedValueClassWithDefault].default - assertEquals(res, WrappedValueClassWithDefault(ValueClassWithDefault(123))) - } - - test( - "Derive SemiDefault for a value class wrapper of plain case class with default " - ) { - val res = SemiDefault[WrappedPlainWithDefault].default - assertEquals(res, WrappedPlainWithDefault(PlainWithDefault("abrakadabra"))) - - } - - test("Derive HasDefault for a value class with default value") { - val res = HasDefault.derived[ValueClassWithDefault].defaultValue - assertEquals(res, Right(ValueClassWithDefault())) - } - - test("Derive Show for a value class with nested annotations") { - val res = Show.derived[BigBox].show(BigBox(NormalBox(SmallBox(TinyBox(7))))) - val expected = - "BigBox(normalBox=NormalBox{MyAnnotation(4)}(smallBox{MyAnnotation(5)}{MyAnnotation(6)}=SmallBox(tinyBox=TinyBox{MyAnnotation(1),MyAnnotation(0)}(size{MyAnnotation(2)}{MyAnnotation(3)}=7))))" - assertEquals(res, expected) - } - - test("Construct a Show instance for a final value case class") { - val res = Show.derived[ServiceName].show(ServiceName("service")) - assertEquals(res, "ServiceName(value=service)") - } - - test("Assume full auto derivation of external value classes") { - case class LogingConfig(n: ServiceName) - val res = SemiDefault[LogingConfig].default - assertEquals(res, LogingConfig(ServiceName(""))) - } - - // support for non-product value classes is not yet supported // test("serialize a value class") { // val res = Show.derived[Length].show(new Length(100)) // assertEquals(res, "100") // } - // support for non-product value classes is not yet supported + // test("construct a Show instance for value case class") { + // val res = Show.derived[ServiceName1].show(ServiceName1("service")) + // assertEquals(res, "service") + // } + // test("read-only typeclass can serialize value case class with inaccessible private constructor") { // val res = implicitly[Print[PrivateValueClass]].print(PrivateValueClass(42)) // assertEquals(res, "42") // } - // support for non-product value classes is not yet supported + // test("not assume full auto derivation of external value classes") { + // val error = compileErrors(""" + // case class LoggingConfig(n: ServiceName1) + // object LoggingConfig { + // implicit val semi: SemiDefault[LoggingConfig] = SemiDefault.gen + // } + // """) + // assert(error contains """ + // |magnolia: could not find SemiDefault.Typeclass for type magnolia1.tests.ServiceName1 + // | in parameter 'n' of product type LoggingConfig + // |""".stripMargin) + // } + // test("serialize value case class with accessible private constructor") { // class PrivateValueClass private (val value: Int) extends AnyVal // object PrivateValueClass { @@ -128,45 +54,12 @@ class ValueClassesTests extends munit.FunSuite: object ValueClassesTests: - case class SimpleVC(k: Int) extends AnyVal - - @MyAnnotation(0) - @MyAnnotation(1) - case class TinyBox(@MyAnnotation(2) size: Int @MyAnnotation(3)) - - case class SmallBox(tinyBox: TinyBox) - - @MyAnnotation(4) - case class NormalBox(@MyAnnotation(5) smallBox: SmallBox @MyAnnotation(6)) - - case class BigBox(normalBox: NormalBox) extends AnyVal - - case class ValueClassWithDefault(k: Int = 123) extends AnyVal - - case class PlainWithDefault(name: String = "abrakadabra") - - case class WrappedValueClassWithDefault(withDefault: ValueClassWithDefault) - - case class WrappedPlainWithDefault(PlainWithDefault: PlainWithDefault) - extends AnyVal - - @MyAnnotation(0) - case class HeavilyAnnotated( - @MyParamAnnotation(1) k: Int @MyTypeAnnotation(2) - ) extends AnyVal @MyTypeAnnotation(3) - class Length(val value: Int) extends AnyVal - final case class ServiceName(value: String) extends AnyVal + final case class ServiceName1(value: String) extends AnyVal class PrivateValueClass private (val value: Int) extends AnyVal - - object PrivateValueClass: + object PrivateValueClass { def apply(l: Int) = new PrivateValueClass(l) // given Show[String, PrivateValueClass] = Show.derived - - case class MyAnnotation(order: Int) extends scala.annotation.Annotation - - case class MyParamAnnotation(order: Int) extends StaticAnnotation - - case class MyTypeAnnotation(order: Int) extends StaticAnnotation + }