diff --git a/build.sbt b/build.sbt index dcac2edf..dff521ce 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import com.softwaremill.UpdateVersionInDocs import com.softwaremill.SbtSoftwareMillCommon.commonSmlBuildSettings import com.softwaremill.Publish.{updateDocs, ossPublishSettings} -val scala3 = "3.1.3" +val scala3 = "3.2.0-RC4" ThisBuild / dynverTagPrefix := "scala3-v" // a custom prefix is needed to differentiate tags between scala2 & scala3 versions @@ -54,7 +54,7 @@ lazy val core = (projectMatrix in file(".core")) ) .jvmPlatform(scalaVersions = List(scala3)) .jsPlatform(scalaVersions = List(scala3)) - .nativePlatform(scalaVersions = List(scala3)) + // .nativePlatform(scalaVersions = List(scala3)) lazy val examples = (projectMatrix in file(".examples")) .dependsOn(core) @@ -66,7 +66,7 @@ lazy val examples = (projectMatrix in file(".examples")) ) .jvmPlatform(scalaVersions = List(scala3)) .jsPlatform(scalaVersions = List(scala3)) - .nativePlatform(scalaVersions = List(scala3)) + // .nativePlatform(scalaVersions = List(scala3)) lazy val test = (projectMatrix in file(".test")) .dependsOn(examples) @@ -83,4 +83,4 @@ lazy val test = (projectMatrix in file(".test")) ) .jvmPlatform(scalaVersions = List(scala3)) .jsPlatform(scalaVersions = List(scala3)) - .nativePlatform(scalaVersions = List(scala3)) + // .nativePlatform(scalaVersions = List(scala3)) diff --git a/project/plugins.sbt b/project/plugins.sbt index f010aae3..cf3b6d0b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -9,5 +9,5 @@ addSbtPlugin( ) addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.5") +// addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.5") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.0") diff --git a/src/core/impl.scala b/src/core/impl.scala index bb1f02f1..7f3f721b 100644 --- a/src/core/impl.scala +++ b/src/core/impl.scala @@ -1,141 +1,474 @@ package magnolia1 import scala.compiletime.* -import scala.deriving.Mirror +import scala.quoted.* import scala.reflect.* -import Macro.* - -object CaseClassDerivation: - inline def fromMirror[Typeclass[_], A]( - product: Mirror.ProductOf[A] - ): CaseClass[Typeclass, A] = - val parameters = IArray( - paramsFromMaps[ - Typeclass, - A, - product.MirroredElemLabels, - product.MirroredElemTypes - ]( - paramAnns[A].to(Map), - inheritedParamAnns[A].to(Map), - paramTypeAnns[A].to(Map), - repeated[A].to(Map), - defaultValue[A].to(Map) - )* +class DerivationImpl(using Quotes): + import quotes.reflect.* + + def rawConstruct[T: Type](fieldValuesExpr: Expr[List[Any]]): Expr[T] = { + val tpe = TypeRepr.of[T] + if (tpe.typeSymbol.flags.is(Flags.Case) || tpe.baseClasses.contains(defn.AnyValClass)) && !tpe.typeSymbol.flags.is(Flags.Module) then + val fieldValues = fieldValuesExpr.asTerm + val getIdx = fieldValues.select(fieldValues.tpe.typeSymbol.methodMember("apply").head) + val init = tpe.typeSymbol.primaryConstructor + val applies = tpe.typeSymbol.companionModule.methodMember("apply") + val (prefix, constrSym) = + if applies.size >= 1 && !tpe.baseClasses.contains(defn.AnyValClass) then + Ref(tpe.typeSymbol.companionModule) -> applies.head + else + New(Inferred(tpe)) -> init + val typeParamLookup = tpe match { + case AppliedType(_, typeArgs) => + constrSym.paramSymss.flatten.filter(_.isType).zip(typeArgs).toMap + case _ => + Map.empty + } + val vals = constrSym.paramSymss.flatten.filter(_.isValDef).zipWithIndex.map { case (paramSym, idx) => + val elem = getIdx.appliedTo(Literal(IntConstant(idx))) + val valdef = paramSym.tree.asInstanceOf[ValDef] + val t = valdef.tpt.tpe.alphaRenameTypes(typeParamLookup) + if isRepeated(t) then + val drop = fieldValues.select(fieldValues.tpe.typeSymbol.methodMember("drop").head) + val rest = drop.appliedTo(Literal(IntConstant(idx))) + val AnnotatedType(AppliedType(_, List(aTpe)), _) = t: @unchecked + val size = rest.select(rest.tpe.typeSymbol.methodMember("size").head) + val equals = size.select(size.tpe.typeSymbol.methodMember("==").head).appliedTo(Literal(IntConstant(1))) + If( + equals, //TODO(kπ) I'm not sure about this (seems like a hack TBH) + Typed(elem, Inferred(defn.RepeatedParamClass.typeRef.appliedTo(aTpe))), + Typed(rest, Inferred(defn.RepeatedParamClass.typeRef.appliedTo(aTpe))) + ) + else + elem.select(elem.tpe.typeSymbol.methodMember("asInstanceOf").head).appliedToType(t) + } + val select = prefix.select(constrSym) + val method = tpe match { + case AppliedType(_, typeArgs) => + select.appliedToTypes(typeArgs) + case _ => + select + } + method + .appliedToArgs(vals) + .asExprOf[T] + else // if tpe.widen.typeSymbol.flags.is(Flags.Module) || tpe.termSymbol.flags.is(Flags.Case & Flags.Enum) then + tpe match { + case _: TypeRef => + Ref(tpe.typeSymbol.companionModule) + .asExprOf[T] + case _ => + Ref(tpe.termSymbol) + .asExprOf[T] + } + } + + def isProduct[T: Type]: Expr[Boolean] = Expr(isProductImpl(TypeRepr.of[T])) + def isSum[T: Type]: Expr[Boolean] = Expr(isSumImpl(TypeRepr.of[T])) + + private def isProductImpl(tpe: TypeRepr): Boolean = + val typeSymbol = tpe.typeSymbol + tpe.isSingleton || + (typeSymbol.isClassDef && tpe.baseClasses.contains(defn.AnyValClass)) || + (typeSymbol.isClassDef && typeSymbol.flags.is(Flags.Case)) + + private def isSumImpl(tpe: TypeRepr): Boolean = + val typeSymbol = tpe.typeSymbol + !tpe.isSingleton && + ((typeSymbol.flags.is(Flags.Sealed) && typeSymbol.flags.is(Flags.Trait)) || + typeSymbol.flags.is(Flags.Enum) || + (!typeSymbol.flags.is(Flags.Case) && !typeSymbol.children.isEmpty)) + + private def repeatedParams(tpe: TypeRepr): List[(String, Boolean)] = + val symbol: Option[Symbol] = if tpe.typeSymbol.isNoSymbol then None else Some(tpe.typeSymbol) + val constr: Option[DefDef] = symbol.map(_.primaryConstructor.tree.asInstanceOf[DefDef]) + + constr.toList.flatMap(_.paramss).flatMap(_.params.flatMap { + case ValDef(name, tpeTree, _) => Some(name -> isRepeated(tpeTree.tpe)) + case _ => None + }) + + private def isRepeated[T](tpeRepr: TypeRepr): Boolean = tpeRepr match + case a: AnnotatedType => + a.annotation.tpe match + case tr: TypeRef => tr.name == "Repeated" + case _ => false + case _ => false + + def isObject[T: Type]: Expr[Boolean] = Expr(isObject(TypeRepr.of[T])) + + private def isObject(typeRepr: TypeRepr): Boolean = + typeRepr.typeSymbol.flags.is(Flags.Module) + + def isEnum[T: Type]: Expr[Boolean] = + Expr(TypeRepr.of[T].typeSymbol.flags.is(Flags.Enum)) + + def isValueClass[T: Type]: Expr[Boolean] = + Expr(TypeRepr.of[T].baseClasses.contains(defn.AnyValClass)) + + def typeInfo[T: Type]: Expr[TypeInfo] = Expr(typeInfo(TypeRepr.of[T])) + given ToExpr[TypeInfo] = new ToExpr[TypeInfo] { + def apply(x: TypeInfo)(using Quotes): Expr[TypeInfo] = + '{ + TypeInfo( + ${ Expr(x.owner) }, + ${ Expr(x.short) }, + ${ Expr.ofList(x.typeParams.toSeq.map(Expr.apply)) } + ) + } + } + private def typeInfo(tpe: TypeRepr): TypeInfo = { + + def normalizedName(s: Symbol): String = + if s.flags.is(Flags.Module) then s.name.stripSuffix("$") else s.name + + def name(tpe: TypeRepr): String = tpe match + case TermRef(typeRepr, name) if tpe.typeSymbol.flags.is(Flags.Module) => + name.stripSuffix("$") + case TermRef(typeRepr, name) => name + case _ => normalizedName(tpe.typeSymbol) + + def ownerNameChain(sym: Symbol): List[String] = + if sym.isNoSymbol then List.empty + else if sym == defn.EmptyPackageClass then List.empty + else if sym == defn.RootPackage then List.empty + else if sym == defn.RootClass then List.empty + else ownerNameChain(sym.owner) :+ normalizedName(sym) + + def owner(tpe: TypeRepr): String = + ownerNameChain(tpe.typeSymbol.maybeOwner).mkString(".") + + tpe match + case AppliedType(tpe, args) => + val typeParamBoundLookup: Map[Symbol, TypeRepr] = + tpe.typeSymbol.typeMembers.flatMap { tm => + tm.tree match { + case TypeDef(_, tpt: TypeTree) => Some(tm -> tpt.tpe.widenIfBounds) + case _ => None + } + }.toMap + TypeInfo( + owner(tpe), + name(tpe), + args.map(_.alphaRenameTypes(typeParamBoundLookup)).map(typeInfo) + ) + case _ => + TypeInfo(owner(tpe), name(tpe), Nil) + } + + def to[T: Type, R: Type](f: Expr[T] => Expr[R])(using Quotes): Expr[T => R] = '{ (x: T) => ${ f('x) } } + + //TODO(kπ) will be needed for AnyVals + private def derefImpl(paramSym: Symbol, valueTpe: TypeRepr, paramTpe: TypeRepr): Term = { + val methodType = MethodType(List("value"))(_ => List(valueTpe), _ => paramTpe) + val defdefSymbol = Symbol.newMethod(Symbol.spliceOwner, "$anonfun", methodType) + val defdef = DefDef( + defdefSymbol, + { + case List(List(value)) => + Some(value.asInstanceOf[Term].select(paramSym)) + } ) + Block(List(defdef), Closure(Ref(defdefSymbol), None)) + } + + private def isType[T: Type](typeRepr: TypeRepr): Expr[T => Boolean] = to { other => + val term = other.asTerm + term + .select(term.tpe.typeSymbol.methodMember("isInstanceOf").head) + .appliedToType(typeRepr) + .asExprOf[Boolean] + } + + private def asType[T: Type, S: Type](parent: TypeRepr, child: TypeRepr): Expr[T => S & T] = to[T, S & T] { other => + val term = other.asTerm + term + .select(term.tpe.typeSymbol.methodMember("asInstanceOf").head) + .appliedToType(AndType(child, parent)) + .asExprOf[S & T] + } + + private def getTypeAnnotations(t: TypeRepr): List[Term] = t match + case AnnotatedType(inner, ann) => ann :: getTypeAnnotations(inner) + case _ => Nil + + private def isObjectOrScala(bc: Symbol) = + bc.name.contains("java.lang.Object") || bc.fullName.startsWith("scala.") + + private def groupByParamName(anns: List[(String, List[Term])]) = + anns + .groupBy { case (name, _) => name } + .toList + .map { case (name, l) => name -> l.flatMap(_._2) } + + private def fromConstructor(from: Symbol): List[(String, List[Term])] = + from.primaryConstructor.paramSymss.flatten.map { field => + field.name -> field.annotations + .filter(filterAnnotation) + } + + private def fromDeclarations(from: Symbol): List[(String, List[Term])] = + from.declarations.collect { + case (field: Symbol) if field.isValDef || field.isDefDef => + field.name -> field.annotations.filter(filterAnnotation) + } + + def anns[T: Type]: Expr[List[Any]] = + Expr.ofList(anns(TypeRepr.of[T]).map(_.asExpr)) + + private def anns(tpe: TypeRepr): List[Term] = + tpe.typeSymbol.annotations + .filter(filterAnnotation) - new CaseClass( - typeInfo[A], - isObject[A], - isValueClass[A], - parameters, - IArray(anns[A]*), - IArray(inheritedAnns[A]*), - IArray[Any](typeAnns[A]*) - ): - def construct[PType: ClassTag](makeParam: Param => PType): A = - product.fromProduct(Tuple.fromArray(params.map(makeParam).to(Array))) - - def rawConstruct(fieldValues: Seq[Any]): A = - product.fromProduct(Tuple.fromArray(fieldValues.to(Array))) - - def constructEither[Err, PType: ClassTag]( - makeParam: Param => Either[Err, PType] - ): Either[List[Err], A] = - 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)) + def typeAnns[T: Type]: Expr[List[Any]] = + Expr.ofList(typeAnns(TypeRepr.of[T]).map(_.asExpr)) + + private def typeAnns(tpe: TypeRepr): List[Term] = { + val symbol: Option[Symbol] = if tpe.typeSymbol.isNoSymbol then None else Some(tpe.typeSymbol) + symbol.toList.map(_.tree).flatMap { + case ClassDef(_, _, parents, _, _) => + parents + .collect { case t: TypeTree => t.tpe } + .flatMap(getTypeAnnotations) + .filter(filterAnnotation) + case _ => + List.empty + } + } + + def inheritedAnns[T: Type]: Expr[List[Any]] = + Expr.ofList(inheritedAnns(TypeRepr.of[T]).map(_.asExpr)) + + private def inheritedAnns(tpe: TypeRepr): List[Term] = { + tpe.baseClasses + .filterNot(isObjectOrScala) + .collect { + case s if s != tpe.typeSymbol => s.annotations + } + .flatten + .filter(filterAnnotation) + } + + private def paramAnns(tpe: TypeRepr): List[(String, List[Term])] = + groupByParamName { + (fromConstructor(tpe.typeSymbol) ++ fromDeclarations(tpe.typeSymbol)) + .filter { case (_, anns) => anns.nonEmpty } + } + + private def paramTypeAnns(tpe: TypeRepr): 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 -> getTypeAnnotations(tpeRepr) + .filter { a => + a.tpe.typeSymbol.maybeOwner.isNoSymbol || + a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" } - .map { params => product.fromProduct(Tuple.fromArray(params)) } - - def constructMonadic[M[_]: Monadic, PType: ClassTag]( - makeParam: Param => M[PType] - ): M[A] = { - 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(_)) + } + .filter(_._2.nonEmpty) + } + + private def inheritedParamAnns(typeRepr: TypeRepr): List[(String, List[Term])] = + groupByParamName { + typeRepr.baseClasses + .filterNot(isObjectOrScala) + .collect { + case s if s != typeRepr.typeSymbol => + (fromConstructor(s) ++ fromDeclarations(s)).filter { + case (_, anns) => anns.nonEmpty + } + } + .flatten + } + + private def defaultValue(tpe: TypeRepr): List[(String, Option[Term])] = + val typeSymbol = tpe.typeSymbol + val optionSymbol = Symbol.requiredMethod("scala.Some.apply") + typeSymbol.primaryConstructor.paramSymss.flatten + .filter(_.isValDef) + .zipWithIndex + .map { case (field, i) => + field.name -> typeSymbol.companionClass + .declaredMethod(s"$$lessinit$$greater$$default$$${i + 1}") + .headOption + .map { m => + m.tree.asInstanceOf[DefDef].rhs match { + case None => '{None}.asTerm + case Some(rhs) => Ref(optionSymbol).appliedToType(rhs.tpe).appliedTo(rhs) } } - } { params => product.fromProduct(Tuple.fromArray(params)) } } - inline def paramsFromMaps[Typeclass[_], A, Labels <: Tuple, Params <: Tuple]( - annotations: Map[String, List[Any]], - inheritedAnnotations: Map[String, List[Any]], - typeAnnotations: Map[String, List[Any]], - repeated: Map[String, Boolean], - defaults: Map[String, Option[() => Any]], - idx: Int = 0 - ): List[CaseClass.Param[Typeclass, A]] = - inline erasedValue[(Labels, Params)] match - case _: (EmptyTuple, EmptyTuple) => - Nil - case _: ((l *: ltail), (p *: ptail)) => - val label = constValue[l].asInstanceOf[String] - CaseClass.Param[Typeclass, A, p]( - label, - idx, - repeated.getOrElse(label, false), - CallByNeed(summonInline[Typeclass[p]]), - CallByNeed(defaults.get(label).flatten.map(_.apply.asInstanceOf[p])), - IArray.from(annotations.getOrElse(label, List())), - IArray.from(inheritedAnnotations.getOrElse(label, List())), - IArray.from(typeAnnotations.getOrElse(label, List())) - ) :: - paramsFromMaps[Typeclass, A, ltail, ptail]( - annotations, - inheritedAnnotations, - typeAnnotations, - repeated, - defaults, - idx + 1 - ) -end CaseClassDerivation - -trait SealedTraitDerivation: - type Typeclass[T] - - protected inline def deriveSubtype[s](m: Mirror.Of[s]): Typeclass[s] - - protected inline def sealedTraitFromMirror[A]( - m: Mirror.SumOf[A] - ): SealedTrait[Typeclass, A] = - SealedTrait( - typeInfo[A], - IArray(subtypesFromMirror[A, m.MirroredElemTypes](m)*), - IArray.from(anns[A]), - IArray(paramTypeAnns[A]*), - isEnum[A], - IArray.from(inheritedAnns[A]) - ) + private def filterAnnotation(a: Term): Boolean = + a.tpe.typeSymbol.maybeOwner.isNoSymbol || + a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" + + private def wrapInCallByNeedTerm(term: Term, tpe: TypeRepr): Term = + val callByNeedTerm = '{CallByNeed}.asTerm + val callByNeedApply = TypeRepr.of[CallByNeed.type].termSymbol.declaredMethod("apply").head + Select(callByNeedTerm, callByNeedApply).appliedToType(tpe).appliedTo(term) + + def getParamsImpl[Typeclass[_]: Type, T: Type](fallback: Expr[Derivation[Typeclass]]): Expr[List[CaseClass.Param[Typeclass, T]]] = + + val typeSymbol = TypeRepr.of[T].typeSymbol + + val typeParamLookup: Map[Symbol, TypeRepr] = TypeRepr.of[T] match { + case tpe@AppliedType(_, argTypes) => + val typeParams = + tpe.typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTypeParam) + typeParams.zip(argTypes).toMap + case _ => Map.empty + } + val typeParamBoundLookup: Map[Symbol, TypeRepr] = + typeSymbol.typeMembers.flatMap { tm => + tm.tree match { + case TypeDef(_, tpt: TypeTree) => Some(tm -> tpt.tpe.widenIfBounds) + case _ => None + } + }.toMap + + val paramObj = '{CaseClass.Param}.asTerm + val paramConstrSymbol = TypeRepr.of[CaseClass.Param.type].termSymbol.declaredMethod("apply").filter(_.paramSymss.flatten.size == 11).head + + val annotationsLookup = paramAnns(TypeRepr.of[T]).toMap + val inheritedAnnotationsLookup = inheritedParamAnns(TypeRepr.of[T]).toMap + val typeAnnotationsLookup = paramTypeAnns(TypeRepr.of[T]).toMap + val defaultValuesLookup = defaultValue(TypeRepr.of[T]).toMap + val repeatedLookup = repeatedParams(TypeRepr.of[T]).toMap + + Expr.ofList { + typeSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm).zipWithIndex.collect { + case (paramSymbol: Symbol, idx: Int) => + val paramTpt = paramSymbol.tree match + case valDef: ValDef => valDef.tpt + case defdef: DefDef => defdef.returnTpt + val paramTypeTpe = paramTpt.tpe.alphaRenameTypes(typeParamLookup).alphaRenameTypes(typeParamBoundLookup) + val paramTypeclassTpe = AppliedType(TypeRepr.of[Typeclass], List(paramTypeTpe)) + val Inlined(_, _, TypeApply(summonInlineTerm, _)) = '{scala.compiletime.summonInline}.asTerm: @unchecked + val summonInlineApp = summonInlineTerm.appliedToType(paramTypeclassTpe) + val instance = wrapInCallByNeedTerm(summonInlineApp, paramTypeclassTpe) + val unit = '{()}.asTerm + val fallbackTerm = fallback.asTerm match + case Inlined(_, _, i) => i + val appliedFallbackTerm = Select(Ref(fallbackTerm.symbol), fallbackTerm.symbol.methodMember("doDerive").head).appliedToType(paramTypeTpe) + val fallbackCallByNeedTerm = wrapInCallByNeedTerm(appliedFallbackTerm, paramTypeclassTpe) + val optionSymbol = Symbol.requiredClass("scala.Option") + val defaultVal = defaultValuesLookup.get(paramSymbol.name).flatten.map(t => wrapInCallByNeedTerm(t, optionSymbol.typeRef.appliedTo(paramTypeTpe))) + Apply( + fun = + paramObj + .select(paramConstrSymbol) + .appliedToTypes(List(TypeRepr.of[Typeclass], TypeRepr.of[T], paramTypeTpe)), + args = List( + /*name =*/ Expr(paramSymbol.name).asTerm, + /*idx =*/ Expr(idx).asTerm, + /*repeated =*/ Expr(repeatedLookup(paramSymbol.name)).asTerm, + /*cbn =*/ paramTypeclassTpe.asType match + case '[p] => + Expr.summon[p].map(_ => instance).getOrElse(fallbackCallByNeedTerm) + , + /*defaultVal =*/ defaultVal.getOrElse('{CallByNeed(None)}.asTerm), + /*annotations =*/ Expr.ofList(annotationsLookup.getOrElse(paramSymbol.name, List.empty).toSeq.map(_.asExpr)).asTerm, + /*inheritedAnns =*/ Expr.ofList(inheritedAnnotationsLookup.getOrElse(paramSymbol.name, List.empty).toSeq.map(_.asExpr)).asTerm, + /*typeAnnotations =*/ Expr.ofList(typeAnnotationsLookup.getOrElse(paramSymbol.name, List.empty).toSeq.map(_.asExpr)).asTerm + ) + ).asExprOf[CaseClass.Param[Typeclass, T]] + } + } + end getParamsImpl + + def getSubtypesImpl[Typeclass[_]: Type, T: Type](fallback: Expr[Derivation[Typeclass]]): Expr[List[SealedTrait.Subtype[Typeclass, T, _]]] = + + val typeSymbol = TypeRepr.of[T].typeSymbol + val parentArgs = TypeRepr.of[T].extractArgs + + val subTypeObj = '{SealedTrait.Subtype}.asTerm + val subTypeConstrSymbol = TypeRepr.of[SealedTrait.Subtype.type].termSymbol.declaredMethod("apply").head + + val annotations = paramAnns(TypeRepr.of[T]).toMap + val typeAnnotations = paramTypeAnns(TypeRepr.of[T]).toMap + + Expr.ofList { + typeSymbol.children.zipWithIndex.collect { + case (subTypeSymbol: Symbol, idx: Int) => + val subTypeTpe = subTypeSymbol.tree match { + case valDef: ValDef => + subTypeSymbol.termRef + case classDef: ClassDef => + val parentExtendsTpt = + classDef.parents + .collect { case tpt: TypeTree if tpt.tpe.typeSymbol == typeSymbol => tpt }.head + val paramLookup = parentExtendsTpt.tpe.extractArgs.map(_.typeSymbol).zip(parentArgs).toMap + val declaredTypesLookup = + classDef.symbol.declaredTypes.map(s => s.name -> s).toMap + val args = + classDef.constructor.paramss.flatMap(_.params) + .map(_.symbol) + .filter(_.isTypeParam) + .map(s => declaredTypesLookup(s.name)) + .map(_.typeRef) + if args.isEmpty then + subTypeSymbol.typeRef + else + AppliedType(subTypeSymbol.typeRef, args).alphaRenameTypes(paramLookup) + } + val subTypeTypeclassTpe = AppliedType(TypeRepr.of[Typeclass], List(subTypeTpe)) + val Inlined(_, _, TypeApply(summonInlineTerm, _)) = '{scala.compiletime.summonInline}.asTerm: @unchecked + val summonInlineApp = summonInlineTerm.appliedToType(subTypeTypeclassTpe) + val instance = wrapInCallByNeedTerm(summonInlineApp, subTypeTypeclassTpe) + val fallbackTerm = fallback.asTerm match + case Inlined(_, _, i) => i + val appliedFallbackTerm = Select(Ref(fallbackTerm.symbol), fallbackTerm.symbol.methodMember("doDerive").head).appliedToType(subTypeTpe) + val fallbackCallByNeedTerm = wrapInCallByNeedTerm(appliedFallbackTerm, subTypeTypeclassTpe) + Apply( + fun = + subTypeObj + .select(subTypeConstrSymbol) + .appliedToTypes(List(TypeRepr.of[Typeclass], TypeRepr.of[T], subTypeTpe)), + args = List( + /*name =*/ Expr(typeInfo(subTypeTpe)).asTerm, + /*annotations =*/ Expr.ofList(anns(subTypeTpe).toSeq.map(_.asExpr)).asTerm, + /*typeAnnotations =*/ Expr.ofList(typeAnns(subTypeTpe).toSeq.map(_.asExpr)).asTerm, + /*inheritedAnnotations =*/ Expr.ofList(inheritedAnns(subTypeTpe).toSeq.map(_.asExpr)).asTerm, + /*isObject =*/ Expr(isObject(subTypeTpe)).asTerm, + /*idx =*/ Expr(idx).asTerm, + /*cbn =*/ subTypeTypeclassTpe.asType match { + case '[p] => + Expr.summon[p].map(_ => instance).getOrElse(fallbackCallByNeedTerm) + }, + /*isType =*/ isType(subTypeTpe).asTerm + ) + ).asExprOf[SealedTrait.Subtype[Typeclass, T, _]] + } + } + end getSubtypesImpl + + extension (tpe: TypeRepr) + private def alphaRenameTypes(renames: Map[Symbol, TypeRepr]): TypeRepr = tpe match { + case tpe: NamedType => renames.get(tpe.typeSymbol).getOrElse(tpe) + case AppliedType(tpe, args) => AppliedType(tpe.alphaRenameTypes(renames), args.map(_.alphaRenameTypes(renames))) + case AnnotatedType(tpe, annot) => AnnotatedType(tpe.alphaRenameTypes(renames), annot) + case _ => tpe //TODO(kπ) probably needs some more cases + } + + private def widenIfBounds: TypeRepr = tpe match { + case TypeBounds(lo, hi) => + if hi.typeSymbol != defn.AnyClass then hi + else lo + case _ => tpe + } + + private def extractArgs: List[TypeRepr] = tpe match { + case tpe: NamedType => List.empty + case AppliedType(tpe, args) => args + case AnnotatedType(tpe, _) => tpe.extractArgs + case _ => List.empty + } - protected transparent inline def subtypesFromMirror[A, SubtypeTuple <: Tuple]( - m: Mirror.SumOf[A], - idx: Int = 0 - ): List[SealedTrait.Subtype[Typeclass, A, _]] = - inline erasedValue[SubtypeTuple] match - case _: EmptyTuple => - Nil - case _: (s *: tail) => - new SealedTrait.Subtype( - typeInfo[s], - IArray.from(anns[s]), - IArray.from(inheritedAnns[s]), - IArray.from(paramTypeAnns[A]), - isObject[s], - idx, - CallByNeed(summonFrom { - case tc: Typeclass[`s`] => tc - case _ => deriveSubtype(summonInline[Mirror.Of[s]]) - }), - x => m.ordinal(x) == idx, - _.asInstanceOf[s & A] - ) :: subtypesFromMirror[A, tail](m, idx + 1) -end SealedTraitDerivation +end DerivationImpl diff --git a/src/core/interface.scala b/src/core/interface.scala index c3055904..a1626d5e 100644 --- a/src/core/interface.scala +++ b/src/core/interface.scala @@ -39,21 +39,21 @@ object CaseClass: repeated: Boolean, cbn: CallByNeed[F[P]], defaultVal: CallByNeed[Option[P]], - annotations: IArray[Any], - inheritedAnns: IArray[Any], - typeAnnotations: IArray[Any] + annotations: List[Any], + inheritedAnns: List[Any], + typeAnnotations: List[Any] ): Param[F, T] = new CaseClass.Param[F, T]( name, idx, repeated, - annotations, - typeAnnotations + IArray.from(annotations), + IArray.from(typeAnnotations) ): type PType = P def default: Option[PType] = defaultVal.value def typeclass = cbn.value - override def inheritedAnnotations = inheritedAnns + override def inheritedAnnotations = IArray.from(inheritedAnns) def deref(value: T): P = value.asInstanceOf[Product].productElement(idx).asInstanceOf[P] @@ -114,14 +114,30 @@ abstract class CaseClass[Typeclass[_], Type]( override def toString: String = s"CaseClass(${typeInfo.full}, ${params.mkString(",")})" - def construct[PType](makeParam: Param => PType)(using ClassTag[PType]): Type - def constructMonadic[Monad[_]: Monadic, PType: ClassTag]( - make: Param => Monad[PType] - ): Monad[Type] - def constructEither[Err, PType: ClassTag]( - makeParam: Param => Either[Err, PType] - ): Either[List[Err], Type] def rawConstruct(fieldValues: Seq[Any]): Type + def construct[PType](makeParam: Param => PType)(using ClassTag[PType]): Type = + rawConstruct( + params.map(makeParam) + ) + def constructEither[Err, PType: ClassTag](makeParam: Param => Either[Err, PType]): Either[List[Err], Type] = + params + .map(makeParam) + .foldLeft[Either[List[Err], Seq[PType]]](Right(Seq.empty)) { + 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(rawConstruct) + def constructMonadic[M[_]: Monadic, PType: ClassTag](makeParam: Param => M[PType]): M[Type] = + 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 => rawConstruct(params.toSeq) } def param[P]( name: String, @@ -216,15 +232,16 @@ case class SealedTrait[Typeclass[_], Type]( s"SealedTrait($typeInfo, IArray[${subtypes.mkString(",")}])" def choose[Return](value: Type)(handle: Subtype[_] => Return): Return = - @tailrec def rec(ix: Int): Return = + @tailrec + def rec(ix: Int): Return = if ix < subtypes.length then val sub = subtypes(ix) - if sub.cast.isDefinedAt(value) then + if sub.isDefinedAt(value) then handle(SealedTrait.SubtypeValue(sub, value)) else rec(ix + 1) else throw new IllegalArgumentException( - s"The given value `$value` is not a sub type of `$typeInfo`" + s"The given value `$value` is not a sub type of `${typeInfo.short}`" ) rec(0) @@ -291,6 +308,29 @@ object SealedTrait: def apply(t: Type): SType & Type = asType(t) override def toString: String = s"Subtype(${typeInfo.full})" + object Subtype: + def apply[Typeclass[_], Type, SType]( + typeInfo: TypeInfo, + annotations: List[Any], + typeAnnotations: List[Any], + inheritedAnnotations: List[Any], + isObject: Boolean, + index: Int, + callByNeed: CallByNeed[Typeclass[SType]], + isType: Type => Boolean + ) = + new Subtype[Typeclass, Type, SType]( + typeInfo, + IArray.from(annotations), + IArray.from(inheritedAnnotations), + IArray.from(typeAnnotations), + isObject, + index, + callByNeed, + isType, + _.asInstanceOf[SType & Type] + ) + class SubtypeValue[Typeclass[_], Type, S]( val subtype: Subtype[Typeclass, Type, S], v: Type diff --git a/src/core/macro.scala b/src/core/macro.scala deleted file mode 100644 index 51ac92b7..00000000 --- a/src/core/macro.scala +++ /dev/null @@ -1,265 +0,0 @@ -package magnolia1 - -import scala.quoted.* - -object Macro: - 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.* - - Expr(TypeRepr.of[T].typeSymbol.flags.is(Flags.Module)) - - def isEnum[T: Type](using Quotes): Expr[Boolean] = - import quotes.reflect.* - - Expr(TypeRepr.of[T].typeSymbol.flags.is(Flags.Enum)) - - def anns[T: Type](using Quotes): Expr[List[Any]] = - new CollectAnnotations[T].anns - - def inheritedAnns[T: Type](using Quotes): Expr[List[Any]] = - new CollectAnnotations[T].inheritedAnns - - def typeAnns[T: Type](using Quotes): Expr[List[Any]] = - new CollectAnnotations[T].typeAnns - - def paramAnns[T: Type](using Quotes): Expr[List[(String, List[Any])]] = - new CollectAnnotations[T].paramAnns - - def inheritedParamAnns[T: Type](using - Quotes - ): Expr[List[(String, List[Any])]] = - new CollectAnnotations[T].inheritedParamAnns - - def isValueClass[T: Type](using Quotes): Expr[Boolean] = - import quotes.reflect.* - - Expr( - TypeRepr.of[T].baseClasses.contains(Symbol.classSymbol("scala.AnyVal")) - ) - - def defaultValue[T: Type](using - Quotes - ): Expr[List[(String, Option[() => Any])]] = - 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) } - } - val tpe = TypeRepr.of[T].typeSymbol - val terms = tpe.primaryConstructor.paramSymss.flatten - .filter(_.isValDef) - .zipWithIndex - .map { case (field, i) => - 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.* - - def isRepeated[T](tpeRepr: TypeRepr): Boolean = tpeRepr match - case a: AnnotatedType => - a.annotation.tpe match - case tr: TypeRef => tr.name == "Repeated" - case _ => false - case _ => false - - val tpe = TypeRepr.of[T] - val symbol: Option[Symbol] = if tpe.typeSymbol.isNoSymbol then None else Some(tpe.typeSymbol) - val constr: Option[DefDef] = symbol.map(_.primaryConstructor.tree.asInstanceOf[DefDef]) - - val areRepeated = constr.toList.flatMap(_.paramss).flatMap(_.params.flatMap { - case ValDef(name, tpeTree, _) => Some(name -> isRepeated(tpeTree.tpe)) - case _ => None - }) - - Expr(areRepeated) - - def typeInfo[T: Type](using Quotes): Expr[TypeInfo] = - import quotes.reflect._ - - def normalizedName(s: Symbol): String = - if s.flags.is(Flags.Module) then s.name.stripSuffix("$") else s.name - def name(tpe: TypeRepr): Expr[String] = tpe match - case TermRef(typeRepr, name) if tpe.typeSymbol.flags.is(Flags.Module) => - Expr(name.stripSuffix("$")) - case TermRef(typeRepr, name) => Expr(name) - case _ => Expr(normalizedName(tpe.typeSymbol)) - - def ownerNameChain(sym: Symbol): List[String] = - if sym.isNoSymbol then List.empty - else if sym == defn.EmptyPackageClass then List.empty - else if sym == defn.RootPackage then List.empty - else if sym == defn.RootClass then List.empty - else ownerNameChain(sym.owner) :+ normalizedName(sym) - - def owner(tpe: TypeRepr): Expr[String] = Expr( - ownerNameChain(tpe.typeSymbol.maybeOwner).mkString(".") - ) - - def typeInfo(tpe: TypeRepr): Expr[TypeInfo] = tpe match - case AppliedType(tpe, args) => - '{ - TypeInfo( - ${ owner(tpe) }, - ${ name(tpe) }, - ${ Expr.ofList(args.map(typeInfo)) } - ) - } - case _ => - '{ TypeInfo(${ owner(tpe) }, ${ name(tpe) }, Nil) } - - typeInfo(TypeRepr.of[T]) - - private class CollectAnnotations[T: Type](using val quotes: Quotes) { - import quotes.reflect.* - - private val tpe: TypeRepr = TypeRepr.of[T] - - def anns: Expr[List[Any]] = - Expr.ofList { - tpe.typeSymbol.annotations - .filter(filterAnnotation) - .map(_.asExpr.asInstanceOf[Expr[Any]]) - } - - def inheritedAnns: Expr[List[Any]] = - Expr.ofList { - tpe.baseClasses - .filterNot(isObjectOrScala) - .collect { - case s if s != tpe.typeSymbol => s.annotations - } // skip self - .flatten - .filter(filterAnnotation) - .map(_.asExpr.asInstanceOf[Expr[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 } - .flatMap(getAnnotations) - .filter(filterAnnotation) - .map(_.asExpr.asInstanceOf[Expr[Any]]) - case _ => - List.empty - } - } - } - - def paramAnns: Expr[List[(String, List[Any])]] = - Expr.ofList { - groupByParamName { - (fromConstructor(tpe.typeSymbol) ++ fromDeclarations(tpe.typeSymbol)) - .filter { case (_, anns) => anns.nonEmpty } - } - } - - 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 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 fromDeclarations( - from: Symbol - ): List[(String, List[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 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.") - - private def filterAnnotation(a: Term): Boolean = - a.tpe.typeSymbol.maybeOwner.isNoSymbol || - a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" - } diff --git a/src/core/magnolia.scala b/src/core/magnolia.scala index ca6e20f3..cc05b208 100644 --- a/src/core/magnolia.scala +++ b/src/core/magnolia.scala @@ -1,88 +1,99 @@ package magnolia1 import scala.compiletime.* -import scala.deriving.Mirror +import scala.quoted.* import scala.reflect.* -import Macro.* +trait AutoDerivation[TypeClass[_]] extends Derivation[TypeClass]: + inline given autoDerived[A]: TypeClass[A] = derived -trait CommonDerivation[TypeClass[_]]: +trait Derivation[TypeClass[_]]: type Typeclass[T] = TypeClass[T] + def split[T](ctx: SealedTrait[Typeclass, T]): Typeclass[T] def join[T](ctx: CaseClass[Typeclass, T]): Typeclass[T] - inline def derivedMirrorProduct[A]( - product: Mirror.ProductOf[A] - ): Typeclass[A] = join(CaseClassDerivation.fromMirror(product)) - - inline def getParams__[T, Labels <: Tuple, Params <: Tuple]( - annotations: Map[String, List[Any]], - inheritedAnnotations: Map[String, List[Any]], - typeAnnotations: Map[String, List[Any]], - repeated: Map[String, Boolean], - defaults: Map[String, Option[() => Any]], - idx: Int = 0 - ): List[CaseClass.Param[Typeclass, T]] = CaseClassDerivation.paramsFromMaps( - annotations, - inheritedAnnotations, - typeAnnotations, - repeated, - defaults - ) - - // for backward compatibility with v1.1.1 - inline def getParams_[T, Labels <: Tuple, Params <: Tuple]( - annotations: Map[String, List[Any]], - inheritedAnnotations: Map[String, List[Any]], - typeAnnotations: Map[String, List[Any]], - repeated: Map[String, Boolean], - idx: Int = 0 - ): List[CaseClass.Param[Typeclass, T]] = - getParams__(annotations, Map.empty, typeAnnotations, repeated, Map(), idx) - - // for backward compatibility with v1.0.0 - inline def getParams[T, Labels <: Tuple, Params <: Tuple]( - annotations: Map[String, List[Any]], - typeAnnotations: Map[String, List[Any]], - repeated: Map[String, Boolean], - idx: Int = 0 - ): List[CaseClass.Param[Typeclass, T]] = - getParams__(annotations, Map.empty, typeAnnotations, repeated, Map(), idx) - -end CommonDerivation - -trait ProductDerivation[TypeClass[_]] extends CommonDerivation[TypeClass]: - inline def derivedMirror[A](using mirror: Mirror.Of[A]): Typeclass[A] = - inline mirror match - case product: Mirror.ProductOf[A] => derivedMirrorProduct[A](product) - - inline given derived[A](using Mirror.Of[A]): Typeclass[A] = derivedMirror[A] -end ProductDerivation - -trait Derivation[TypeClass[_]] - extends CommonDerivation[TypeClass] - with SealedTraitDerivation: - def split[T](ctx: SealedTrait[Typeclass, T]): Typeclass[T] + inline def derived[T]: Typeclass[T] = doDerive[T] + + inline def doDerive[T]: Typeclass[T] = + inline if Derivation.isProduct[T] then + val cc = new CaseClass[Typeclass, T]( + Derivation.typeInfo[T], + Derivation.isObject[T], + Derivation.isValueClass[T], + IArray.from(Derivation.getParams[Typeclass, T](this)), + IArray.from(Derivation.anns[T]), + IArray.from(Derivation.inheritedAnns[T]), + IArray.from(Derivation.typeAnns[T]) + ) { + def rawConstruct(fieldValues: Seq[Any]): T = + Derivation.rawConstruct[T](fieldValues.toList) + } + join(cc) + else if Derivation.isSum[T] then + val sealedTrait = new SealedTrait[Typeclass, T]( + Derivation.typeInfo[T], + IArray.from(Derivation.getSubtypes[Typeclass, T](this)), + IArray.from(Derivation.anns[T]), + IArray.from(Derivation.typeAnns[T]), + Derivation.isEnum[T], + IArray.from(Derivation.inheritedAnns[T]) + ) + split(sealedTrait) + else + throw new IllegalArgumentException(s"The given type ${Derivation.typeInfo[T].short} was neither a sum nor a product") + +end Derivation - transparent inline def subtypes[T, SubtypeTuple <: Tuple]( - m: Mirror.SumOf[T], - idx: Int = 0 - ): List[SealedTrait.Subtype[Typeclass, T, _]] = - subtypesFromMirror[T, SubtypeTuple](m, idx) +object Derivation: - inline def derivedMirrorSum[A](sum: Mirror.SumOf[A]): Typeclass[A] = - split(sealedTraitFromMirror(sum)) + inline def getParams[Typeclass[_], T](fallback: Derivation[Typeclass]): List[CaseClass.Param[Typeclass, T]] = + ${ getParamsImpl[Typeclass, T]('fallback) } + def getParamsImpl[Typeclass[_]: Type, T: Type](using Quotes)(fallback: Expr[Derivation[Typeclass]]): Expr[List[CaseClass.Param[Typeclass, T]]] = + new DerivationImpl(using quotes).getParamsImpl[Typeclass, T](fallback) - 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) + inline def getSubtypes[Typeclass[_], T](fallback: Derivation[Typeclass]): List[SealedTrait.Subtype[Typeclass, T, _]] = + ${ getSubtypesImpl[Typeclass, T]('fallback) } + def getSubtypesImpl[Typeclass[_]: Type, T: Type](fallback: Expr[Derivation[Typeclass]])(using Quotes): Expr[List[SealedTrait.Subtype[Typeclass, T, _]]] = + new DerivationImpl(using quotes).getSubtypesImpl[Typeclass, T](fallback) - inline def derived[A](using Mirror.Of[A]): Typeclass[A] = derivedMirror[A] + inline def typeInfo[T]: TypeInfo = ${ typeInfoImpl[T] } + def typeInfoImpl[T: Type](using Quotes): Expr[TypeInfo] = + new DerivationImpl(using quotes).typeInfo[T] - protected override inline def deriveSubtype[s]( - m: Mirror.Of[s] - ): Typeclass[s] = derivedMirror[s](using m) -end Derivation + inline def isObject[T]: Boolean = ${ isObjectImpl[T] } + def isObjectImpl[T: Type](using Quotes): Expr[Boolean] = + new DerivationImpl(using quotes).isObject[T] -trait AutoDerivation[TypeClass[_]] extends Derivation[TypeClass]: - inline given autoDerived[A](using Mirror.Of[A]): TypeClass[A] = derived + inline def isEnum[T]: Boolean = ${ isEnumImpl[T] } + def isEnumImpl[T: Type](using Quotes): Expr[Boolean] = + new DerivationImpl(using quotes).isEnum[T] + + inline def isValueClass[T]: Boolean = ${ isValueClassImpl[T] } + def isValueClassImpl[T: Type](using Quotes): Expr[Boolean] = + new DerivationImpl(using quotes).isValueClass[T] + + inline def anns[T]: List[Any] = ${ annsImpl[T] } + def annsImpl[T: Type](using Quotes): Expr[List[Any]] = + new DerivationImpl(using quotes).anns[T] + + inline def inheritedAnns[T]: List[Any] = ${ inheritedAnnsImpl[T] } + def inheritedAnnsImpl[T: Type](using Quotes): Expr[List[Any]] = + new DerivationImpl(using quotes).inheritedAnns[T] + + inline def typeAnns[T]: List[Any] = ${ typeAnnsImpl[T] } + def typeAnnsImpl[T: Type](using Quotes): Expr[List[Any]] = + new DerivationImpl(using quotes).typeAnns[T] + + inline def isProduct[T]: Boolean = ${ isProductImpl[T] } + def isProductImpl[T: Type](using Quotes): Expr[Boolean] = + new DerivationImpl(using quotes).isProduct[T] + + inline def isSum[T]: Boolean = ${ isSumImpl[T] } + def isSumImpl[T: Type](using Quotes): Expr[Boolean] = + new DerivationImpl(using quotes).isSum[T] + + inline def rawConstruct[T](inline fieldValues: List[Any]): T = ${ rawConstructImpl[T]('fieldValues) } + def rawConstructImpl[T: Type](fieldValues: Expr[List[Any]])(using Quotes): Expr[T] = + new DerivationImpl(using quotes).rawConstruct[T](fieldValues) + +end Derivation diff --git a/src/examples/csv.scala b/src/examples/csv.scala index b0dc57b4..abcbb1f2 100644 --- a/src/examples/csv.scala +++ b/src/examples/csv.scala @@ -7,7 +7,7 @@ extension [A: Csv](value: A) def csv: List[String] = summon[Csv[A]](value) trait Csv[A]: def apply(a: A): List[String] -object Csv extends Derivation[Csv]: +object Csv extends AutoDerivation[Csv]: def join[A](ctx: CaseClass[Csv, A]): Csv[A] = a => ctx.params.foldLeft(List[String]()) { (acc, p) => acc ++ p.typeclass(p.deref(a)) @@ -20,6 +20,3 @@ object Csv extends Derivation[Csv]: given Csv[Int] = i => List(i.toString) given Csv[Char] = c => List(c.toString) given [T: Csv]: Csv[Seq[T]] = _.to(List).flatMap(summon[Csv[T]](_)) - -case class Foo(x: Int, y: String) derives Csv -case class Bar(c: Char, fs: Foo*) derives Csv diff --git a/src/examples/decode.scala b/src/examples/decode.scala index 27129464..5255e340 100644 --- a/src/examples/decode.scala +++ b/src/examples/decode.scala @@ -11,6 +11,10 @@ object Decoder extends AutoDerivation[Decoder]: given Decoder[String] = (s: String) => s given Decoder[Int] = _.toInt + given [T: Decoder]: Decoder[Seq[T]] = { (s: String) => + val decoder = summon[Decoder[T]] + s.split(';').toSeq.map(decoder.decode) + } /** defines how new [[Decoder]]s for case classes should be constructed */ def join[T](ctx: CaseClass[Decoder, T]): Decoder[T] = value => diff --git a/src/examples/print.scala b/src/examples/print.scala index 9255f4af..3f434609 100644 --- a/src/examples/print.scala +++ b/src/examples/print.scala @@ -7,7 +7,7 @@ trait Print[T] { def print(t: T): String } -trait GenericPrint extends AutoDerivation[Print]: +trait GenericPrint extends magnolia1.AutoDerivation[Print]: def join[T](ctx: CaseClass[Typeclass, T]): Print[T] = value => if ctx.isValueClass then val param = ctx.params.head @@ -15,7 +15,9 @@ trait GenericPrint extends AutoDerivation[Print]: else ctx.params .map { param => - param.typeclass.print(param.deref(value)) + param.typeclass.print( + param.deref(value) + ) } .mkString(s"${ctx.typeInfo.short}(", ",", ")") @@ -25,5 +27,6 @@ trait GenericPrint extends AutoDerivation[Print]: object Print extends GenericPrint: given Print[String] = identity(_) given Print[Int] = _.toString + given Print[Double] = _.toString given seq[T](using printT: Print[T]): Print[Seq[T]] = _.map(printT.print).mkString("[", ",", "]") diff --git a/src/examples/show.scala b/src/examples/show.scala index 3fb37967..9f29f165 100644 --- a/src/examples/show.scala +++ b/src/examples/show.scala @@ -86,6 +86,7 @@ object Show extends GenericShow[String]: given Show[String, String] = identity(_) given Show[String, Int] = _.toString + given Show[String, Double] = _.toString given Show[String, Long] = _.toString + "L" given Show[String, Boolean] = _.toString given [A](using A: Show[String, A]): Show[String, Seq[A]] = diff --git a/src/test/tests.scala b/src/test/tests.scala index e341c1e2..92f44052 100644 --- a/src/test/tests.scala +++ b/src/test/tests.scala @@ -66,6 +66,8 @@ case class Deprecated(@MyAnnotation(0) @deprecated f: Int) case class `%%`(`/`: Int, `#`: String) +case class Tup2[T1, T2](_1: T1, _2: T2) + case class Param(a: String, b: String) case class TestEntry(param: Param) object TestEntry { @@ -97,8 +99,8 @@ object Recursive { } // This tests compilation. -// class GenericCsv[A: Csv] -// object ParamCsv extends GenericCsv[Param] +class GenericCsv[A: Csv] +object ParamCsv extends GenericCsv[Param] class NotDerivable @@ -135,39 +137,39 @@ object PrivateCons { given show: Show[String, PrivateCons] = Show.derived } -// class PrivateValueClass private (val value: Int) extends AnyVal -// object PrivateValueClass { -// def apply(l: Int) = new PrivateValueClass(l) -// implicit val show: Show[String, PrivateValueClass] = Show.derived -// } +class PrivateValueClass private (val value: Int) extends AnyVal +object PrivateValueClass { + def apply(l: Int) = new PrivateValueClass(l) + implicit val show: Show[String, PrivateValueClass] = Show.derived +} case class KArray(value: List[KArray]) derives Eq case class Wrapper(v: Option[KArray]) case class VeryLong( - p1: String, - p2: String, - p3: String, - p4: String, - p5: String, - p6: String, - p7: String, - p8: String, - p9: String, - p10: String, - p11: String, - p12: String, - p13: String, - p14: String, - p15: String, - p16: String, - p17: String, - p18: String, - p19: String, - p20: String, - p21: String, - p22: String, - p23: String + p1: String, + p2: String, + p3: String, + p4: String, + p5: String, + p6: String, + p7: String, + p8: String, + p9: String, + p10: String, + p11: String, + p12: String, + p13: String, + p14: String, + p15: String, + p16: String, + p17: String, + p18: String, + p19: String, + p20: String, + p21: String, + p22: String, + p23: String ) case class Character(id: Character.Id) @@ -185,9 +187,9 @@ object AnotherCharacter { } final case class Abc( - private val a: Int, - private val b: Long, - c: String + private val a: Int, + private val b: Long, + c: String ) sealed trait Covariant[+A] @@ -279,6 +281,11 @@ enum ExtendingTraits: case B extends ExtendingTraits with ExtendingTraits.Two case C extends ExtendingTraits with ExtendingTraits.Two +sealed trait TreeValue +sealed trait SubLevel extends TreeValue +case class Leaf1(value: String) extends TreeValue +case class Leaf2(value: Int) extends SubLevel + // trait PrintRepeated[T] { @@ -299,6 +306,43 @@ object PrintRepeated extends AutoDerivation[PrintRepeated]: class Tests extends munit.FunSuite { + test("work in some other way") { + sealed trait T + case class C(s: String) extends T + val res = Print.derived[T].print(C("XD")) + assertEquals(res, """C(XD)""") + } + + test("work for a product type") { + case class C(s: String) + val res = Print.derived[C].print(C("XD")) + assertEquals(res, """C(XD)""") + } + + test("work for nested product type with derives clause") { + case class A(a: Int) derives Print + case class C(s: String, a: A) + val res = Print.derived[C].print(C("XD", A(1))) + assertEquals(res, """C(XD,A(1))""") + } + + test("work for nested product type without derives clause") { + case class A(a: Int) + case class C(s: String, a: A) + val res = Print.derived[C].print(C("XD", A(1))) + assertEquals(res, """C(XD,A(1))""") + } + + test("work for nested hierarchies") { + val res = Print.derived[TreeValue].print(Leaf1("XD")) + assertEquals(res, "Leaf1(XD)") + } + + test("work for nested hierarchies 1") { + val res = Print.derived[TreeValue].print(Leaf2(420)) + assertEquals(res, "Leaf2(420)") + } + test("construct a Show product instance with alternative apply functions") { val res = Show.derived[TestEntry].show(TestEntry("a", "b")) assertEquals(res, """TestEntry(param=Param(a=a,b=b))""") @@ -319,11 +363,31 @@ class Tests extends munit.FunSuite { assertEquals(res, "Abc(a=12,b=54L,c=pm)") } + test("work for a value class") { + val res = Print.derived[ServiceName1].print(ServiceName1("A")) + assertEquals(res, "A") + } + + test("construct a Show instance for value case class") { + val res = Show.derived[ServiceName1].show(ServiceName1("service")) + assertEquals(res, "service") + } + test("construct a Show instance for a product with multiple default values") { val res = Show.derived[ParamsWithDefault].show(ParamsWithDefault()) assertEquals(res, "ParamsWithDefault(a=3,b=4)") } + test("construct a Show instance for a product with multiple default values") { + val res = Show.derived[ParamsWithDefaultGeneric[String, String]].show(ParamsWithDefaultGeneric()) + assertEquals(res, "ParamsWithDefaultGeneric[String,String](a=A,b=B)") + } + + // test("construct a HasDefault instance for a generic product with default values") { + // val res = HasDefault.derived[ParamsWithDefaultGeneric[String, Int]].defaultValue + // assertEquals(res, Right(ParamsWithDefaultGeneric("A", 0))) + // } + test("local implicit beats Magnolia") { given showPerson: Show[String, Person] = _ => "nobody" val res = summon[Show[String, Address]].show( @@ -382,8 +446,7 @@ class Tests extends munit.FunSuite { } test("test branch equality true") { - val res = Eq - .derived[Tree[String]] + val res = Eq.derived[Tree[String]] .equal(Branch(Leaf("one"), Leaf("two")), Branch(Leaf("one"), Leaf("two"))) assert(res) } @@ -437,6 +500,12 @@ class Tests extends munit.FunSuite { assertEquals(res, "ProtectedCons(dada phil)") } + test("decode a class with repeated params") { + case class Names(names: String*) + val res = Decoder.derived[Names].decode("""Company(names=Kacper;Kamil)""") + assertEquals(res, Names("Kacper", "Kamil")) + } + test("decode a company") { val res = Decoder.derived[Company].decode("""Company(name=Acme Inc)""") assertEquals(res, Company("Acme Inc")) @@ -483,13 +552,29 @@ class Tests extends munit.FunSuite { assertEquals(res, "%%(/=1,#=two)") } - val tupleDerivation = summon[Show[String, (Int, String)]] + val tup2Derivation = summon[Print[Tup2[Int, Double]]] + test("print a tuple-like class") { + val res = tup2Derivation.print(Tup2(42, 2.3)) + assertEquals(res, "Tup2(42,2.3)") + } + val tupleDerivationPrint = summon[Print[(Int, Double)]] + test("print a tuple") { + val res = tupleDerivationPrint.print((42, 2.3)) + assertEquals(res, "Tuple2(42,2.3)") + } + + val tupleDerivation = summon[Show[String, (Int, String)]] test("serialize a tuple") { val res = tupleDerivation.show((42, "Hello World")) assertEquals(res, "Tuple2[Int,String](_1=42,_2=Hello World)") } + // test("serialize a value class") { + // val res = Show.derived[Length].show(new Length(100)) + // assertEquals(res, "100") + // } + // Corrupt being covariant in L <: Seq[Company] enables the derivation for Corrupt[String, _] test("show a Politician with covariant lobby") { val res = Show @@ -503,8 +588,10 @@ class Tests extends munit.FunSuite { // test("patch a Person via a Patcher[Entity]") { // val person = Person("Bob", 42) - // summon[Patcher[Entity]].patch(person, Seq(null, 21)) - // }.assert(_ == Person("Bob", 21)) + // val res = summon[Patcher[Entity]].patch(person, Seq(null, 21)) + // assertEquals(res, Person("Bob", 21)) + // } + test("show an Account") { val res = Show @@ -536,13 +623,39 @@ class Tests extends munit.FunSuite { ) } + test("print a List[Int]") { + given [X: Print]: Print[List[X]] = Print.derived + + assertEquals(summon[Print[List[Int]]].print(List(1, 2, 3)), "::(1,::(2,::(3,Nil())))") + } + + //TODO(kπ) not sure how to fix this one (The typeInfo is computed, when the given is declared) // test("show a List[Int]") { - // given [T: [X] =>> Show[String, X]] : Show[String, List[T]] = Show.derived + // given [T: [X] =>> Show[String, X]]: Show[String, List[T]] = Show.derived[List[T]] - // Show.derived[List[Int]].show(List(1, 2, 3)) - // .assert(_ == "::[Int](head=1,tl=::[Int](head=2,tl=::[Int](head=3,tl=Nil())))") + // assertEquals(Show.derived[List[Int]].show(List(1, 2, 3)), "::[Int](head=1,next=::[Int](head=2,next=::[Int](head=3,next=Nil())))") // } + test("show a ST1[Int]") { + sealed trait ST1[+A] + sealed trait ST2[+A] extends ST1[A] + case class CC1[+A](a: A) extends ST2[A] + + given [T: [X] =>> Show[String, X]] : Show[String, ST1[T]] = Show.derived + + assertEquals(Show.derived[ST1[Int]].show(CC1(1)), "CC1[Int](a=1)") + } + + test("show a Lst[Int]") { + case class Lst1[+A](head: A, next: Lst2[A]) + case class Lst2[+A](head: A, next: Lst3[A]) + case class Lst3[+A](head: A) + + given [T: [X] =>> Show[String, X]] : Show[String, Lst1[T]] = Show.derived + + assertEquals(Show.derived[Lst1[Int]].show(Lst1(1, Lst2(2, Lst3(3)))), "Lst1[Int](head=1,next=Lst2[Int](head=2,next=Lst3[Int](head=3)))") + } + test("sealed trait typeName should be complete and unchanged") { val res = TypeNameInfo.derived[Color].name assertEquals(res.full, "magnolia1.tests.Color")