Alex Ioffe suggested doing something like this
def myMacro(builder: DeferredQueryBuilder): Something = ${ myMacroImpl('builder) }
def myMacroImpl(builder: Expr[DeferredQueryBuilder])(using Quotes): Expr[Something] =
builder match
case '{ CypherStringInterpolator($partsExpr).c(${ Varargs(params) }: _*) } =>
partsExpr match
case '{ StringContext.apply(${ Varargs(parts) }: _*) } =>
// at this point you have parts: Expr[Seq[String]] and params: Expr[Seq[Any]] or something like that
but I am not sure how we would pass in DeferredQueryBuilder
since that is what the macro itself returns.
def myMacroImpl(builder: Expr[Seq[Any]])(using Quotes): Expr[DeferredQueryBuilder] =
parts
is currently is downright wonky) but after a few days of wrestling with this, I at least have something that compiles so I will savor this as a milestone :sweat_smile: package neotypes
import scala.quoted._
import internal.utils.queryparts.createQuery
import scala.reflect.*
import neotypes.types.QueryParam
import scala.annotation.tailrec
final case class CypherStringInterpolator(private val sc: StringContext) extends AnyVal {
inline def c(inline args: Any*): DeferredQueryBuilder = ${ CypherStringInterpolator.macroImpl('args, 'sc) }
}
object CypherStringInterpolator {
type Res = Either[String, QueryArg]
def macroImpl(argsExpr: Expr[Seq[Any]], clz: Expr[StringContext])(using quotes: Quotes): Expr[DeferredQueryBuilder] =
import quotes.reflect.*
def invert[T: Type](exprs: Expr[Seq[T]]): List[Expr[T]] =
exprs match {
case Varargs(props) => props.toList
case '{ Seq(${ Varargs(props) }) } => props.toList.map(_.asExprOf[T])
case _ => println(s"Unable to match ${exprs.show} "); null
}
def argMapperFor(tt: TypeRepr) =
TypeRepr.of[QueryArgMapper].appliedTo(tt)
def asRightQueryArg(expr: Expr[Any]): Expr[Res] =
val tt = expr.asTerm.tpe.widen
Implicits.search(argMapperFor(tt)) match {
case iss: ImplicitSearchSuccess => {
val tcl = iss.tree
val e = Apply(Select.unique(tcl, "toArg"), expr.asTerm :: Nil)
.asExprOf[QueryArg]
'{ Right($e) }
}
case isf: ImplicitSearchFailure =>
val e = Expr(isf.explanation)
'{ compiletime.error($e) }
}
def parts: List[Expr[Boolean => Res]] = {
val inverted = invert(argsExpr)
inverted
.map { (expr: Expr[Any]) =>
val qarg: Expr[Res] = asRightQueryArg(expr)
'{
(b: Boolean) =>
if (b)
$qarg
else Left($expr.toString)
}
}
}
val partsPacked: Expr[List[Boolean => Res]] = Expr.ofList(parts)
val boolsAndRes: Expr[(List[Boolean], List[Res])] = '{
$clz
.parts
.foldLeft((List.empty[Boolean], List.empty[Res])) { case ((hashTags, res), s) =>
val nextRes = Left(s) :: res
if (s.endsWith("#"))
(false :: hashTags) -> nextRes
else
(true :: hashTags) -> nextRes
}
}
val interleaved: Expr[List[Res]] =
'{
Interleave($partsPacked, $boolsAndRes._2, $boolsAndRes._1)
}
'{ createQuery($interleaved: _*) }
}
object Interleave {
import CypherStringInterpolator.Res
def apply(parts: List[Boolean => Res], queries: List[Res], endWithHashTags: List[Boolean]): List[Res] =
interleave(parts, queries, endWithHashTags, List.empty)
private[this] def interleave(parts: List[Boolean => Res], queries: List[Res], endWithHashTags: List[Boolean], acc: List[Res]): List[Res] =
(queries, parts, endWithHashTags) match {
case (Nil, l2, b) => acc.reverse ++ (l2 zip b).map { case (e, b) => e(b)}
case (l1, Nil, _) => acc.reverse ++ l1
case (h1 :: t1, h2 :: t2, bh :: bt) => interleave(t2, t1, bt, h2(bh) +: h1 +: acc)
case _ => acc
}
}
0.23.0
soon. However, wanted to confirm first with you, because I would rather publish for Scala 3 before doing that.
generic
and CypherStringInterpolator
stuff is not totally off base, the remaining work to be done it to move the shapeless2 specific tests to a separate scala-2
directory. I can try to spend some time on this week in which case we might have something ready for review in the next week or two? Sorry this is taking so long btw.
0.23.0
was released last weekend! It upgraded to the Java driver series 5
and thus requires Java 17.
Hi all, I have been working on something related to neotypes.
I am calling the project Neotypes-Schema, and is basically a complete refactor of the decoders API from the current approach based on typeclasses (full implicit) into a combinators-based API which focuses on explicit with opt-in implicit derivation when useful (for example, to preserve a similar UX for simple use cases).
As some may know, this is also my current Master's project.
I am just starting with the real work, but I have an initial draft of how the API will look like here: https://gist.github.com/BalmungSan/075a7485163dce26ea5a7029ec6f9fcd
In case anyone would like to give it a look and leave feedback, I would be very grateful.
(PS: Sorry for the spanglish in the gist, is for my professor)
BTW, since my work here neotypes/neotypes#584 will change a lot of things I don't recommend continue working on your PR
I see. I figured I would need to rebase one way or the other since it's been a few months.
But now, I think it would be best to tackle Scala 3 support after that
That sounds good to me. Do you think the MR you linked to is far enough along for me to base my work off of the respective branch?