@@ -46,8 +46,28 @@ object Magnolia {
4646 * [[SealedTrait ]], like so, <pre> <derivation>.split(<sealedTrait>): Typeclass[T] </pre> so a definition such as, <pre> def
4747 * split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = ... </pre> will suffice, however the qualifications regarding
4848 * additional type parameters and implicit parameters apply equally to `split` as to `join`.
49+ *
50+ * `gen` will make an effort to keep the resulting value's type as narrow as possible, which can be useful for typeclass families. As a
51+ * contrived example, when configured with `type Typeclass[x] = Either[Any, x]` and corresponding `join` and `split`, `gen` may derive an
52+ * `Either[String, T]` if the target type and available instances allow it. To take advantage of it, one has to define `join` and `split`
53+ * in such a way that they propagate narrowed typeclasses as appropriate:
54+ *
55+ * {{{
56+ * def join[L, T](caseClass: CaseClass[Either[L, *], T]): Either[L, T]
57+ * }}}
58+ *
59+ * Note that narrowing is not perfect, and it will fail for (mutually) recursive target types, producing `Typeclass[T]` instead of a more
60+ * specific variant. To handle such cases, please refer to the [[genNarrow ]] macro which supports choosing a specific typeclass family member.
4961 */
50- def gen [T : c.WeakTypeTag ](c : whitebox.Context ): c.Tree = Stack .withContext(c) { (stack, depth) =>
62+ def gen [T ](c : whitebox.Context )(implicit T : c.WeakTypeTag [T ]): c.Tree =
63+ genImpl(c)(TypeConstructor .fromTypeclass(c), T )
64+
65+ /** Like [[gen ]], but instead of `Typeclass[T]` this produces `Tc[T]` for the specified `Tc[_]`.
66+ */
67+ def genNarrow [Tc [_], T ](c : whitebox.Context )(implicit Tc : c.WeakTypeTag [Tc [_]], T : c.WeakTypeTag [T ]): c.Tree =
68+ genImpl(c)(TypeConstructor .fromTag[Tc ](c)(Tc ), T )
69+
70+ private def genImpl [T : c.WeakTypeTag ](c : whitebox.Context )(typeConstructor : c.Type , T : c.WeakTypeTag [T ]): c.Tree = Stack .withContext(c) { (stack, depth) =>
5171 import c .internal ._
5272 import c .universe ._
5373 import definitions ._
@@ -60,19 +80,6 @@ object Magnolia {
6080
6181 val prefixType = c.prefix.tree.tpe
6282 val prefixObject = prefixType.typeSymbol
63- val prefixName = prefixObject.name.decodedName
64-
65- val TypeClassNme = TypeName (" Typeclass" )
66- val typeDefs = prefixType.baseClasses.flatMap { baseClass =>
67- baseClass.asType.toType.decls.collectFirst {
68- case tpe : TypeSymbol if tpe.name == TypeClassNme =>
69- tpe.toType.asSeenFrom(prefixType, baseClass)
70- }
71- }
72-
73- val typeConstructor = typeDefs.headOption.fold(
74- error(s " the derivation $prefixObject does not define the Typeclass type constructor " )
75- )(_.typeConstructor)
7683
7784 val searchType = appliedType(typeConstructor, genericType)
7885 val directlyReentrant = stack.top.exists(_.searchType =:= searchType)
@@ -319,14 +326,14 @@ object Magnolia {
319326 else q " val $name = $rhs"
320327 }
321328
322- def typeclassTree (genericType : Type , typeConstructor : Type , assignedName : TermName ): Either [String , Tree ] = {
329+ def typeclassTree (genericType : Type , typeConstructor : c. Type , assignedName : TermName ): Either [String , Tree ] = {
323330 val searchType = appliedType(typeConstructor, genericType)
324331 val deferredRef =
325332 for (methodName <- stack find searchType)
326333 yield DeferredRef (searchType, methodName.decodedName.toString)
327334
328335 deferredRef.fold {
329- val path = ChainedImplicit (s " $prefixName .Typeclass " , genericType.toString)
336+ val path = ChainedImplicit (typeConstructor.toString , genericType.toString)
330337 val frame = stack.Frame (path, searchType, assignedName)
331338 stack.recurse(frame, searchType, shouldCache) {
332339 Option (c.inferImplicitValue(searchType))
@@ -340,17 +347,15 @@ object Magnolia {
340347 else {
341348 val (top, paths) = stack.trace
342349 val missingType = top.fold(searchType)(_.searchType)
343- val typeClassName = s " ${missingType.typeSymbol.name.decodedName}.Typeclass "
344- val genericType = missingType.typeArgs.head
345350 val trace = paths.mkString(" in " , " \n in " , " \n " )
346- s " could not find $typeClassName for type $genericType \n $trace"
351+ s " could not find $missingType \n $trace"
347352 }
348353 }
349354 }
350355 }(Right (_))
351356 }
352357
353- def directInferImplicit (genericType : Type , typeConstructor : Type ): Option [Tree ] = {
358+ def directInferImplicit (genericType : Type , typeConstructor : c. Type ): Option [Tree ] = {
354359 val genericTypeName = genericType.typeSymbol.name.decodedName.toString.toLowerCase
355360 val assignedName = c.freshName(TermName (s " ${genericTypeName}Typeclass " )).encodedName.toTermName
356361 val typeSymbol = genericType.typeSymbol
@@ -428,7 +433,7 @@ object Magnolia {
428433 }
429434
430435 val result = if (isRefinedType) {
431- error(s " could not infer $prefixName .Typeclass for refined type $genericType" )
436+ error(s " could not infer $typeConstructor for refined type $genericType" )
432437 } else if (isCaseObject) {
433438 val classBody =
434439 if (isReadOnly) List (EmptyTree )
@@ -734,7 +739,7 @@ object Magnolia {
734739 else for (tree <- result) yield c.untypecheck(expandDeferred.transform(tree))
735740
736741 dereferencedResult.getOrElse {
737- error(s " could not infer $prefixName .Typeclass for type $genericType" )
742+ error(s " could not infer $typeConstructor for type $genericType" )
738743 }
739744 }
740745
@@ -847,6 +852,55 @@ object Magnolia {
847852
848853 private [Magnolia ] final def keepLeft [A ](values : Either [A , _]* ): List [A ] = MagnoliaUtil .keepLeft(values : _* )
849854
855+ private object TypeConstructor {
856+ def fromTypeclass (c : blackbox.Context ): c.Type = {
857+ import c .universe ._
858+
859+ val prefixType = c.prefix.tree.tpe
860+ val prefixObject = prefixType.typeSymbol
861+
862+ val TypeClassNme = TypeName (" Typeclass" )
863+ val typeDefs = prefixType.baseClasses.flatMap { baseClass =>
864+ baseClass.asType.toType.decls.collectFirst {
865+ case tpe : TypeSymbol if tpe.name == TypeClassNme =>
866+ tpe.toType.asSeenFrom(prefixType, baseClass)
867+ }
868+ }
869+ val typeclass = typeDefs.headOption.fold(
870+ c.abort(c.enclosingPosition, s " the derivation $prefixObject does not define the Typeclass type constructor " )
871+ )(_.typeConstructor)
872+
873+ typeclass
874+ }
875+
876+ def fromTag [F [_]](c : blackbox.Context )(tag : c.WeakTypeTag [F [_]]): c.Type = {
877+ import c .universe ._
878+ import c .internal .polyType
879+
880+ val tpe = tag.tpe
881+
882+ def fail () =
883+ c.abort(
884+ c.enclosingPosition,
885+ s """ expected a * -> * HKT,
886+ |got: $tpe as ${showRaw(tpe)}
887+ |eta-expanded: ${tpe.etaExpand} as ${showRaw(tpe.etaExpand)}"""
888+ )
889+
890+ tpe.etaExpand match {
891+ case poly : PolyType if poly.typeParams.nonEmpty =>
892+ val partiallyApplied = polyType(
893+ poly.typeParams.takeRight(1 ),
894+ poly.resultType.substituteTypes(
895+ poly.typeParams.dropRight(1 ),
896+ tpe.typeArgs.dropRight(1 )
897+ )
898+ )
899+ partiallyApplied
900+ case _ => fail()
901+ }
902+ }
903+ }
850904}
851905
852906@ compileTimeOnly(" magnolia1.Deferred is used for derivation of recursive typeclasses" )
0 commit comments