-
Notifications
You must be signed in to change notification settings - Fork 128
Add genNarrow #637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: scala2
Are you sure you want to change the base?
Add genNarrow #637
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,8 +46,28 @@ object Magnolia { | |
| * [[SealedTrait]], like so, <pre> <derivation>.split(<sealedTrait>): Typeclass[T] </pre> so a definition such as, <pre> def | ||
| * split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = ... </pre> will suffice, however the qualifications regarding | ||
| * additional type parameters and implicit parameters apply equally to `split` as to `join`. | ||
| * | ||
| * `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 | ||
| * contrived example, when configured with `type Typeclass[x] = Either[Any, x]` and corresponding `join` and `split`, `gen` may derive an | ||
| * `Either[String, T]` if the target type and available instances allow it. To take advantage of it, one has to define `join` and `split` | ||
| * in such a way that they propagate narrowed typeclasses as appropriate: | ||
| * | ||
| * {{{ | ||
| * def join[L, T](caseClass: CaseClass[Either[L, *], T]): Either[L, T] | ||
| * }}} | ||
| * | ||
| * Note that narrowing is not perfect, and it will fail for (mutually) recursive target types, producing `Typeclass[T]` instead of a more | ||
| * specific variant. To handle such cases, please refer to the [[genNarrow]] macro which supports choosing a specific typeclass family member. | ||
| */ | ||
| def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = Stack.withContext(c) { (stack, depth) => | ||
| def gen[T](c: whitebox.Context)(implicit T: c.WeakTypeTag[T]): c.Tree = | ||
| genImpl(c)(TypeConstructor.fromTypeclass(c), T) | ||
|
|
||
| /** Like [[gen]], but instead of `Typeclass[T]` this produces `Tc[T]` for the specified `Tc[_]`. | ||
| */ | ||
| def genNarrow[Tc[_], T](c: whitebox.Context)(implicit Tc: c.WeakTypeTag[Tc[_]], T: c.WeakTypeTag[T]): c.Tree = | ||
| genImpl(c)(TypeConstructor.fromTag[Tc](c)(Tc), T) | ||
|
|
||
| private def genImpl[T: c.WeakTypeTag](c: whitebox.Context)(typeConstructor: c.Type, T: c.WeakTypeTag[T]): c.Tree = Stack.withContext(c) { (stack, depth) => | ||
| import c.internal._ | ||
| import c.universe._ | ||
| import definitions._ | ||
|
|
@@ -60,19 +80,6 @@ object Magnolia { | |
|
|
||
| val prefixType = c.prefix.tree.tpe | ||
| val prefixObject = prefixType.typeSymbol | ||
| val prefixName = prefixObject.name.decodedName | ||
|
|
||
| val TypeClassNme = TypeName("Typeclass") | ||
| val typeDefs = prefixType.baseClasses.flatMap { baseClass => | ||
| baseClass.asType.toType.decls.collectFirst { | ||
| case tpe: TypeSymbol if tpe.name == TypeClassNme => | ||
| tpe.toType.asSeenFrom(prefixType, baseClass) | ||
| } | ||
| } | ||
|
|
||
| val typeConstructor = typeDefs.headOption.fold( | ||
| error(s"the derivation $prefixObject does not define the Typeclass type constructor") | ||
| )(_.typeConstructor) | ||
|
|
||
| val searchType = appliedType(typeConstructor, genericType) | ||
| val directlyReentrant = stack.top.exists(_.searchType =:= searchType) | ||
|
|
@@ -326,7 +333,7 @@ object Magnolia { | |
| yield DeferredRef(searchType, methodName.decodedName.toString) | ||
|
|
||
| deferredRef.fold { | ||
| val path = ChainedImplicit(s"$prefixName.Typeclass", genericType.toString) | ||
| val path = ChainedImplicit(typeConstructor.toString, genericType.toString) | ||
| val frame = stack.Frame(path, searchType, assignedName) | ||
| stack.recurse(frame, searchType, shouldCache) { | ||
| Option(c.inferImplicitValue(searchType)) | ||
|
|
@@ -340,10 +347,8 @@ object Magnolia { | |
| else { | ||
| val (top, paths) = stack.trace | ||
| val missingType = top.fold(searchType)(_.searchType) | ||
| val typeClassName = s"${missingType.typeSymbol.name.decodedName}.Typeclass" | ||
| val genericType = missingType.typeArgs.head | ||
| val trace = paths.mkString(" in ", "\n in ", "\n") | ||
| s"could not find $typeClassName for type $genericType\n$trace" | ||
| s"could not find $missingType\n$trace" | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -428,7 +433,7 @@ object Magnolia { | |
| } | ||
|
|
||
| val result = if (isRefinedType) { | ||
| error(s"could not infer $prefixName.Typeclass for refined type $genericType") | ||
| error(s"could not infer $typeConstructor for refined type $genericType") | ||
| } else if (isCaseObject) { | ||
| val classBody = | ||
| if (isReadOnly) List(EmptyTree) | ||
|
|
@@ -734,7 +739,7 @@ object Magnolia { | |
| else for (tree <- result) yield c.untypecheck(expandDeferred.transform(tree)) | ||
|
|
||
| dereferencedResult.getOrElse { | ||
| error(s"could not infer $prefixName.Typeclass for type $genericType") | ||
| error(s"could not infer $typeConstructor for type $genericType") | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -847,6 +852,55 @@ object Magnolia { | |
|
|
||
| private[Magnolia] final def keepLeft[A](values: Either[A, _]*): List[A] = MagnoliaUtil.keepLeft(values: _*) | ||
|
|
||
| private object TypeConstructor { | ||
| def fromTypeclass(c: blackbox.Context): c.Type = { | ||
| import c.universe._ | ||
|
|
||
| val prefixType = c.prefix.tree.tpe | ||
| val prefixObject = prefixType.typeSymbol | ||
|
|
||
| val TypeClassNme = TypeName("Typeclass") | ||
| val typeDefs = prefixType.baseClasses.flatMap { baseClass => | ||
| baseClass.asType.toType.decls.collectFirst { | ||
| case tpe: TypeSymbol if tpe.name == TypeClassNme => | ||
| tpe.toType.asSeenFrom(prefixType, baseClass) | ||
| } | ||
| } | ||
| val typeclass = typeDefs.headOption.fold( | ||
| c.abort(c.enclosingPosition, s"the derivation $prefixObject does not define the Typeclass type constructor") | ||
| )(_.typeConstructor) | ||
|
|
||
| typeclass | ||
| } | ||
|
|
||
| def fromTag[F[_]](c: blackbox.Context)(tag: c.WeakTypeTag[F[_]]): c.Type = { | ||
| import c.universe._ | ||
| import c.internal.polyType | ||
|
|
||
| val tpe = tag.tpe | ||
|
|
||
| def fail() = | ||
| c.abort( | ||
| c.enclosingPosition, | ||
| s"""expected a * -> * HKT, | ||
| |got: $tpe as ${showRaw(tpe)} | ||
| |eta-expanded: ${tpe.etaExpand} as ${showRaw(tpe.etaExpand)}""" | ||
|
Comment on lines
+885
to
+887
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably too verbose, but I feel like the more information we get here the better. The end users probably wouldn't see this error. |
||
| ) | ||
|
|
||
| tpe.etaExpand match { | ||
| case poly: PolyType if poly.typeParams.nonEmpty => | ||
| val partiallyApplied = polyType( | ||
| poly.typeParams.takeRight(1), | ||
| poly.resultType.substituteTypes( | ||
| poly.typeParams.dropRight(1), | ||
| tpe.typeArgs.dropRight(1) | ||
| ) | ||
| ) | ||
| partiallyApplied | ||
| case _ => fail() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @compileTimeOnly("magnolia1.Deferred is used for derivation of recursive typeclasses") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,28 +14,44 @@ object CollectFields { | |
| case class IntField(int: Int) extends Field | ||
| case class StringField(string: String) extends Field | ||
|
|
||
| type Typeclass[A] = CollectFields[Any, A] | ||
|
|
||
| implicit val int: CollectFields[IntField, Int] = int => Seq(IntField(int)) | ||
| implicit val string: CollectFields[StringField, String] = string => Seq(StringField(string)) | ||
|
|
||
| implicit def gen[A]: CollectFields[Any, A] = macro Magnolia.gen[A] | ||
|
|
||
| def join[Out, A](caseClass: ReadOnlyCaseClass[CollectFields[Out, *], A]): CollectFields[Out, A] = | ||
| new CollectFields[Out, A] { | ||
| override def collectFields(a: A) = caseClass.parameters.flatMap { param => | ||
| param.typeclass.collectFields( | ||
| param.dereference(a) | ||
| ) | ||
| implicit def seq[Out, A](implicit A: CollectFields[Out, A]): CollectFields[Out, Seq[A]] = | ||
| _.flatMap(A.collectFields) | ||
| implicit def option[Out, A](implicit A: CollectFields[Out, A]): CollectFields[Out, Option[A]] = | ||
| _.fold(Seq.empty[Out])(A.collectFields) | ||
|
|
||
| def instance[Out, A](f: A => Seq[Out]): CollectFields[Out, A] = f(_) | ||
| def apply[A](implicit A: CollectFields[_, A]): A.type = A | ||
|
|
||
| object genDerivation extends Derivation { | ||
| type Typeclass[A] = CollectFields[Any, A] | ||
|
|
||
| implicit def gen[A]: Typeclass[A] = macro Magnolia.gen[A] | ||
| } | ||
|
|
||
| object genNarrowDerivation extends Derivation { | ||
| implicit def genNarrow[Tc[_] <: CollectFields[Any, _], A]: Tc[A] = | ||
| macro Magnolia.genNarrow[Tc, A] | ||
|
Comment on lines
+34
to
+35
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to be able to write implicit def genNarrow[Out, A]: CollectFields[Out, A] =
macro Magnolia.genNarrow[CollectFields[Out, *], A]However, for whatever reason, this seems to lose the information about what Wrapping the whole thing in another macro (as in the schema example) seems to solve this, but it's a weird magical workaround for what looks like a compiler bug. |
||
| } | ||
|
|
||
| protected trait Derivation { | ||
| def join[Out, A](caseClass: ReadOnlyCaseClass[CollectFields[Out, *], A]): CollectFields[Out, A] = | ||
| new CollectFields[Out, A] { | ||
| override def collectFields(a: A) = caseClass.parameters.flatMap { param => | ||
| param.typeclass.collectFields( | ||
| param.dereference(a) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| def split[Out, A](sealedTrait: SealedTrait[CollectFields[Out, *], A]): CollectFields[Out, A] = | ||
| new CollectFields[Out, A] { | ||
| override def collectFields(a: A) = sealedTrait.split(a) { subtype => | ||
| subtype.typeclass.collectFields( | ||
| subtype.cast(a) | ||
| ) | ||
|
|
||
| def split[Out, A](sealedTrait: SealedTrait[CollectFields[Out, *], A]): CollectFields[Out, A] = | ||
| new CollectFields[Out, A] { | ||
| override def collectFields(a: A) = sealedTrait.split(a) { subtype => | ||
| subtype.typeclass.collectFields( | ||
| subtype.cast(a) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package magnolia1.examples.schema | ||
|
|
||
| import magnolia1._ | ||
|
|
||
| // Slimmed down version of schema derivation from the Caliban library. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| case class Derived[T](schema: T) extends AnyVal | ||
|
|
||
| object DerivedMagnolia { | ||
| import magnolia1.Magnolia | ||
|
|
||
| import scala.reflect.macros.whitebox | ||
|
|
||
| def derivedMagnolia[TC[_], A](c: whitebox.Context)(implicit TC: c.WeakTypeTag[TC[_]], A: c.WeakTypeTag[A]): c.Expr[Derived[TC[A]]] = { | ||
| val magnoliaTree = c.Expr[TC[A]](Magnolia.genNarrow[TC, A](c)) | ||
| c.universe.reify(Derived(magnoliaTree.splice)) | ||
| } | ||
| } | ||
|
|
||
| sealed trait SchemaDerivation { | ||
| def join[R, T](ctx: ReadOnlyCaseClass[Schema[R, *], T]): Schema[R, T] = new Schema[R, T] {} | ||
| def split[R, T](ctx: SealedTrait[Schema[R, *], T]): Schema[R, T] = new Schema[R, T] {} | ||
|
|
||
| def genNarrow[R, T]: Schema[R, T] = macro Magnolia.genNarrow[Schema[R, *], T] | ||
| } | ||
|
|
||
| sealed trait Schema[-R, T] | ||
|
|
||
| object Schema extends SchemaDerivation { | ||
|
|
||
| implicit val StringSchema: Schema[Any, String] = new Schema[Any, String] {} | ||
|
|
||
| object auto extends SchemaDerivation { | ||
| implicit def genMacro[R, T]: Derived[Schema[R, T]] = | ||
| macro DerivedMagnolia.derivedMagnolia[Schema[R, *], T] | ||
|
|
||
| def genAll[R0, T](implicit derived: Derived[Schema[R0, T]]): Schema[R0, T] = derived.schema | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This describes the behavior introduced in #628