zio / zio-protoquill

Quill for Scala 3
Apache License 2.0
203 stars 48 forks source link

Arbitrary long Tuples (*:) produce Quat.Generic instead of Quat.Product #411

Closed Primetalk closed 3 months ago

Primetalk commented 5 months ago

Version: 4.8.1 Module: quill-sql Database: postgres

type MyRow1 = (Int, String)
type MyRow2 = Int *: String *: EmptyTuple

inline def q = quote{
  querySchema[MyRow2]("my_table", t => t._1 -> "int_field", t => t._2 -> "string_field")
}
run(q)

Expected behavior

Both types should work fine.

Actual behavior

MyRow1 produces "SELECT x.int_field, x.string_field FROM my_table". MyRow1 fails with io.getquill.quotation.QuatException: Was expecting SQL-level type must be a Product but found '<G>'.

Steps to reproduce the behavior

https://scastie.scala-lang.org/i2RFvK3SS7SzIWwJW8fugw

Workaround

I'm currently using TupleConverter:

type TupleConverter[T <: Tuple] =
  T match
    case EmptyTuple => EmptyTuple
    case a *: EmptyTuple => Tuple1[a]
    case a *: b *: EmptyTuple => (a, b)
    case a *: b *: c *: EmptyTuple => (a, b, c)
    case a *: b *: c *: d *: EmptyTuple => (a, b, c, d)
    case a *: b *: c *: d *: e *: EmptyTuple => (a, b, c, d, e)
    case a *: b *: c *: d *: e *: f *: EmptyTuple => (a, b, c, d, e, f)

This helps.

@getquill/maintainers

Primetalk commented 4 months ago

I found some strange behavior of Scala 3 inline of *:. Here is a small example: https://scastie.scala-lang.org/9GwHy3bOTsKDwd89bULfeA

Error message (super helpful, btw) contains detailed information about the tree that quill parser could not convert to Ast:

==== Tree cannot be parsed to 'Ast' ====
  runtime.Tuples
  .cons(1, Tuple_this)
  .asInstanceOf[*:[Int, *:[String, EmptyTuple]]]
==== Extractors ===
  TypeApply(
  Select(
    Apply(
      Select(Select(Ident("runtime"), "Tuples"), "cons"),
      List(Literal(IntConstant(1)), Ident("Tuple_this"))
    ),
    "asInstanceOf"
  ),
  List(Applied(TypeIdent("*:"), List(Inferred(), Inferred())))
)
...

It seems that the top level *: has been inlined as Tuples.cons according to it's definition:

  inline def *: [H, This >: this.type <: Tuple] (x: H): H *: This =
    runtime.Tuples.cons(x, this).asInstanceOf[H *: This]

The inner *: is not present in the tree, though. It seems that it is represented as Tuple_this (Ident("Tuple_this")). As far as I can see, Tuple_this might be a local val, that had been created by compiler instead of this. It's unclear, how to get it's definition to process by parser.

Upd Looks like Scala creates a block with intermediate vals:

{
  val Tuple_this: scala.Tuple$package.EmptyTuple.type = scala.Tuple$package.EmptyTuple
  val `Tuple_this₂`: scala.*:[java.lang.String, scala.Tuple$package.EmptyTuple] = (scala.runtime.Tuples.cons("", Tuple_this).asInstanceOf[scala.*:[java.lang.String, scala.Tuple$package.EmptyTuple.type]]: scala.*:[java.lang.String, scala.Tuple$package.EmptyTuple])

  (scala.runtime.Tuples.cons(1, `Tuple_this₂`).asInstanceOf[scala.*:[scala.Int, scala.*:[java.lang.String, scala.Tuple$package.EmptyTuple]]]: scala.*:[scala.Int, scala.*:[java.lang.String, scala.Tuple$package.EmptyTuple]])
}