|
| 1 | +package org.virtuslab.unicorn |
| 2 | + |
| 3 | +import scala.language.experimental.macros |
| 4 | +import scala.reflect.macros.blackbox.Context |
| 5 | +import scala.util.control.NonFatal |
| 6 | + |
| 7 | +/** An isomorphism between two types that can be used for mapped column types. */ |
| 8 | +class Isomorphism[A, B](val map: A => B, val comap: B => A) |
| 9 | + |
| 10 | +trait MappedToBase extends Any { |
| 11 | + type Underlying |
| 12 | + def value: Underlying |
| 13 | +} |
| 14 | + |
| 15 | +object MappedToBase { |
| 16 | + implicit def mappedToIsomorphism[E <: MappedToBase]: Isomorphism[E, E#Underlying] = macro mappedToIsomorphismMacroImpl[E] |
| 17 | + |
| 18 | + def mappedToIsomorphismMacroImpl[E <: MappedToBase](c: Context)(implicit e: c.WeakTypeTag[E]): c.Expr[Isomorphism[E, E#Underlying]] = { |
| 19 | + import c.universe._ |
| 20 | + // Check that E <: MappedToBase. Due to SI-8351 the macro can be expanded before scalac has |
| 21 | + // checked this. The error message here will never be seen because scalac's subsequent bounds |
| 22 | + // check fails, overriding our error (or backtracking in implicit search). |
| 23 | + if (!(e.tpe <:< c.typeOf[MappedToBase])) |
| 24 | + c.abort(c.enclosingPosition, "Work-around for SI-8351 leading to illegal macro-invocation -- You should not see this message") |
| 25 | + implicit val eutag = c.TypeTag[E#Underlying](e.tpe.member(TypeName("Underlying")).typeSignatureIn(e.tpe)) |
| 26 | + val cons = c.Expr[E#Underlying => E](Function( |
| 27 | + List(ValDef(Modifiers(Flag.PARAM), TermName("v"), /*Ident(eu.tpe.typeSymbol)*/ TypeTree(), EmptyTree)), |
| 28 | + Apply( |
| 29 | + Select(New(TypeTree(e.tpe)), termNames.CONSTRUCTOR), |
| 30 | + List(Ident(TermName("v")))))) |
| 31 | + val res = reify { new Isomorphism[E, E#Underlying](_.value, cons.splice) } |
| 32 | + try c.typecheck(res.tree) catch { |
| 33 | + case NonFatal(ex) => |
| 34 | + val p = c.enclosingPosition |
| 35 | + val msg = "Error typechecking MappedTo expansion: " + ex.getMessage |
| 36 | + println(p.source.path + ":" + p.line + ": " + msg) |
| 37 | + c.error(c.enclosingPosition, msg) |
| 38 | + } |
| 39 | + res |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +/** |
| 44 | + * The base type for automatically mapped column types. |
| 45 | + * Extending this type (with a type parameter ``T`` which is already a |
| 46 | + * supported column type) lets you use your custom type as a column |
| 47 | + * type in the Lifted Embedding. You must provide a constructor that |
| 48 | + * takes a single value of the underlying type (same restriction as |
| 49 | + * for value classes). |
| 50 | + */ |
| 51 | +trait MappedTo[T] extends Any with MappedToBase { |
| 52 | + type Underlying = T |
| 53 | + def value: T |
| 54 | +} |
0 commit comments