Skip to content

Commit

Permalink
fix #6 - implicit class support
Browse files Browse the repository at this point in the history
  • Loading branch information
fwbrasil committed Dec 17, 2015
1 parent 3ff5941 commit 1991d69
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 83 deletions.
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,20 +449,18 @@ db.run(people(Senior))

Infix is a very flexible mechanism to use non-supported features without having to use plain SQL queries. It allows insertion of arbitrary SQL strings within quotations.

For instance, quill doesn't support the `FOR UPDATE` SQL feature. It can still be used through infix:
For instance, quill doesn't support the `FOR UPDATE` SQL feature. It can still be used through infix and implicit classes:

```scala
val forUpdate = quote {
new {
def apply[T](q: Query[T]) = infix"$q FOR UPDATE".as[Query[T]]
}
implicit class ForUpdate[T](q: Query[T]) {
def forUpdate = quote(infix"$q FOR UPDATE".as[Query[T]])
}

val a = quote {
query[Person].filter(p => p.age < 18)
query[Person].filter(p => p.age < 18).forUpdate
}

db.run(forUpdate(a))
db.run(a)
// SELECT p.id, p.name, p.age FROM (SELECT * FROM Person p WHERE p.age < 18 FOR UPDATE) p
```

Expand All @@ -471,17 +469,15 @@ The `forUpdate` quotation can be reused for multiple queries.
The same approach can be used for `RETURNING ID`:

```scala
val returningId = quote {
new {
def apply[T](a: Action[T]) = infix"$a RETURNING ID".as[Action[T]]
}
implicit class ReturningId[T](a: Action[T]) {
def returningId = quote(infix"$a RETURNING ID".as[Action[T]])
}

val a = quote {
query[Person].insert(_.name -> "John", _.age -> 21)
query[Person].insert(_.name -> "John", _.age -> 21).returningId
}

db.run(returningId(a))
db.run(a)
// INSERT INTO Person (name,age) VALUES ('John', 21) RETURNING ID
```

Expand Down
1 change: 1 addition & 0 deletions quill-core/src/main/scala/io/getquill/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package object getquill {

def lift[T](v: T): T = v

def quote[T](body: Quoted[T]): Quoted[T] = macro Macro.doubleQuote[T]
implicit def quote[T](body: T): Quoted[T] = macro Macro.quote[T]
implicit def unquote[T](quoted: Quoted[T]): T = NonQuotedException()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ case class FreeVariables(state: State)
}

object FreeVariables {
def apply(ast: Ast) =
def apply(ast: Ast): Set[Ident] =
new FreeVariables(State(Set(), Set()))(ast) match {
case (_, transformer) =>
transformer.state.free
Expand Down
4 changes: 2 additions & 2 deletions quill-core/src/main/scala/io/getquill/quotation/Parsing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ trait Parsing {

case t if (t.tpe <:< c.weakTypeOf[Quoted[Any]]) =>
unquote[Ast](t) match {
case Some(ast) if (!IsDynamic(ast)) => ast
case Some(ast) if (!IsDynamic(ast)) => Rebind(c)(t, ast, astParser(_))
case other => Dynamic(t)
}

}

val queryParser: Parser[Query] = Parser[Query] {
Expand Down Expand Up @@ -151,6 +150,7 @@ trait Parsing {
case t: ValDef => Ident(t.name.decodedName.toString)
case c.universe.Ident(TermName(name)) => Ident(name)
case q"$i: $typ" => identParser(i)
case q"$cls.this.$i" => Ident(i.decodedName.toString)
case c.universe.Bind(TermName(name), c.universe.Ident(termNames.WILDCARD)) =>
Ident(name)
}
Expand Down
16 changes: 8 additions & 8 deletions quill-core/src/main/scala/io/getquill/quotation/Quotation.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.getquill.quotation

import io.getquill.util.Messages._
import scala.annotation.StaticAnnotation
import scala.reflect.ClassTag
import scala.reflect.macros.whitebox.Context

import io.getquill.ast._
import io.getquill.util.Messages.RichContext

trait Quoted[+T] {
def ast: Ast
Expand All @@ -20,7 +20,6 @@ trait Quotation extends Parsing with Liftables with Unliftables {

def quote[T: WeakTypeTag](body: Expr[T]) = {
val ast = astParser(body.tree)
verifyFreeVariables(ast)
q"""
new ${c.weakTypeOf[Quoted[T]]} {
@${c.weakTypeOf[QuotedAst]}($ast)
Expand All @@ -31,6 +30,13 @@ trait Quotation extends Parsing with Liftables with Unliftables {
"""
}

def doubleQuote[T: WeakTypeTag](body: Expr[Quoted[T]]) = {
body.tree match {
case q"null" => c.fail("Can't quote null")
case tree => q"io.getquill.unquote($tree)"
}
}

protected def unquote[T](tree: Tree)(implicit ct: ClassTag[T]) =
astTree(tree).flatMap(astUnliftable.unapply).map {
case ast: T => ast
Expand All @@ -42,10 +48,4 @@ trait Quotation extends Parsing with Liftables with Unliftables {
annotation <- method.annotations.headOption
astTree <- annotation.tree.children.lastOption
} yield (astTree)

private def verifyFreeVariables(ast: Ast) =
FreeVariables(ast).toList match {
case Nil =>
case vars => c.fail(s"A quotation must not have references to variables outside its scope. Found: '${vars.mkString(", ")}' in '$ast'.")
}
}
34 changes: 34 additions & 0 deletions quill-core/src/main/scala/io/getquill/quotation/Rebind.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.getquill.quotation

import scala.reflect.macros.whitebox.Context
import io.getquill.ast._
import io.getquill.norm.BetaReduction

object Rebind {

def apply(c: Context)(tree: c.Tree, ast: Ast, astParser: c.Tree => Ast) = {
import c.universe.{ Ident => _, _ }

def toIdent(s: Symbol) =
Ident(s.name.decodedName.toString)

def origRebind(conv: Tree, orig: Tree, astParser: Tree => Ast) = {
val paramSymbol = conv.symbol.asMethod.paramLists.flatten.head
(toIdent(paramSymbol) -> astParser(orig))
}

tree match {
case q"$conv($orig).$method" =>
BetaReduction(ast, origRebind(conv, orig, astParser))
case q"$conv($orig).$m(...$params)" =>
val method =
conv.symbol.asMethod.returnType.member(m)
val paramsIdents =
method.asMethod.paramLists.flatten.map(toIdent)
val paramsRebind =
paramsIdents.zip(params.flatten.map(astParser))
BetaReduction(ast, (origRebind(conv, orig, astParser) +: paramsRebind): _*)
case other => ast
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.getquill.quotation

import scala.reflect.macros.whitebox.Context

import io.getquill.util.Messages._
import io.getquill.ast._

trait Unliftables {
Expand Down Expand Up @@ -32,7 +33,7 @@ trait Unliftables {

implicit def listUnliftable[T](implicit u: Unliftable[T]): Unliftable[List[T]] = Unliftable[List[T]] {
case q"$pack.Nil" => Nil
case q"$pack.List.apply[..$t](..$values)" => values.map(v => u.unapply(v)).flatten
case q"$pack.List.apply[..$t](..$values)" => values.map(v => u.unapply(v).getOrElse(c.fail(s"Can't unlift $v")))
}

implicit val binaryOperatorUnliftable: Unliftable[BinaryOperator] = Unliftable[BinaryOperator] {
Expand Down
10 changes: 8 additions & 2 deletions quill-core/src/main/scala/io/getquill/source/SourceMacro.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.getquill.source

import scala.reflect.macros.whitebox.Context

import io.getquill._
import io.getquill.ast.{ Query => _, Action => _, _ }
import io.getquill.norm.Normalize
import io.getquill.quotation.Quotation
import io.getquill.quotation.Quoted
import io.getquill.util.Messages.RichContext
import io.getquill.quotation.FreeVariables

trait SourceMacro extends Quotation with ActionMacro with QueryMacro with ResolveSourceMacro {
val c: Context
Expand All @@ -17,7 +17,7 @@ trait SourceMacro extends Quotation with ActionMacro with QueryMacro with Resolv

def run[R, S, T](quoted: Expr[Quoted[T]])(implicit r: WeakTypeTag[R], s: WeakTypeTag[S], t: WeakTypeTag[T]): Tree = {

ast(quoted) match {
verifyFreeVariables(ast(quoted)) match {

case ast if (t.tpe.typeSymbol.fullName.startsWith("scala.Function")) =>
val bodyType = c.WeakTypeTag(t.tpe.typeArgs.takeRight(1).head)
Expand Down Expand Up @@ -51,4 +51,10 @@ trait SourceMacro extends Quotation with ActionMacro with QueryMacro with Resolv

private def paramsTypes[T](implicit t: WeakTypeTag[T]) =
t.tpe.typeArgs.dropRight(1)

private def verifyFreeVariables(ast: Ast) =
FreeVariables(ast).toList match {
case Nil => ast
case vars => c.fail(s"A quotation must not have references to variables outside its scope. Found: '${vars.mkString(", ")}' in '$ast'.")
}
}
4 changes: 2 additions & 2 deletions quill-core/src/test/scala/io/getquill/ast/AstShowSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,10 @@ class AstShowSpec extends Spec {
}
"null" in {
val q = quote {
null
1 != null
}
(q.ast: Ast).show mustEqual
"""null"""
"""1 != null"""
}
"tuple" in {
val q = quote {
Expand Down
Loading

0 comments on commit 1991d69

Please sign in to comment.