HList
stuff into a separate scala-2
directory within core-test
. Any ideas on how we might do that? I was checking kittens for inspo.
hlist
tests from DriverSpec.
DriverSpec
in each the scala-2
and scala-3
directories and the only differing bits would be hlist
vs. tuple
, respectivelyBaseDriverSpec
with the "common" tests that work for both versions in scala
and then extend this within scala-2
and scala-3
directories (as DriverSpec
) adding the hlist
and tuple
tests there.
DriverSpec
Hi @/all I just opened an issue that brings some complex topics on the table for discussion, I would be very grateful if you can find the time to read it and leave your thoughts.
Thanks in advance!
CypherStringInterpolator
scala3 port. Particularly the bit I am struggling with is how to work with the args: Any*
(within c
). In scala3, this ends up being Expr[Seq[Any]]
to whereas (IIUC) scala2 we are able to get an Iterator[Expr[Any]]
(in which we can ultimately get the Tree
and type
for each element to splice in the code the QueryArgMapper
for said type
).
protoquill
for some inspo but that stuff is still above my head.
Literally
actually uses the Expr[Seq[Any]]
- only in reporting an error if interpolation is not supported (here)[https://github.com/typelevel/literally/blob/main/core/shared/src/main/scala-3/org/typelevel/literally/Literally.scala#L41]
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)