Quill use-cases for Quoted Matching Update

After a short break for Passover, I am back and attempting to implement SchemaMeta for Dotty-Quill, this requires more usage of Quoted Matching in some new ways and revisits some current issues.

  • My only real show-stopper is this: https://github.com/lampepfl/dotty/issues/8745.
    It involves an issue where Dotty throws a Class Cast Exception whenever one attempts to match a method on a trait T that was then extended into an object O extends T and statically imported via import O._. This is how schemaMeta, queryMeta, insertMeta and updateMeta all work. It’s very important to get this fixed otherwise everyone is going to have to do QueryDsl.schemaMeta instead of schemaMeta everywhere.

  • Quoted Matching is generally nicer then quasi-quotes but certain use-cases are not possible by design. For example, quasi-quotes allowed the matching of aribtrary selections case q"$a.$b" => without requiring any knowledge of what $a's type, we use this in Quill when parsing Query-Schema property mappings. This kind of functionality is intentially prohibited with Quoted Matching because it offers zero type safety (i.e. how the heck do you know $b is available on $a!?) however, the underlying Tasty-Reflect API can still be used to get this kind of behavior e.g: case Select(a, b) =>, you just need to unseal the Expr[_] into a Term first.

  • I initially expected Quoted Matching to allow the same kinds of sugar as standard Scala. For example, matching o.map(func) (where o is a Option[T]) could be done either in via the regular

    case '{ $o.map[$t]($func) }` =>
    

    as well as the shortened:

    case '{ $o.map($func) }` =>
    

    With the advent of #8695 this is finally possible! This is very exciting since the requirment of specifying all types is very unintuitive.

  • Matching a higher-order function as well as it’s lambda argument is currently not possible with Quoted Matching.

    case '{ (o: Option[String]).map[$ot](($x: String) => $out) } => // does not match
    

    Whereas using qusai-quotes it is:

    case q"o.map($x => $out)" => // works!
    

    Additionally, using the $x variable in a quoted match causes a Not-Found exception. I think this behavior is a bug: #8749.
    This is not a show-stopper because I can work around it using the Lambda(...) matcher from Tasty-Reflect or matching on a DefDef tree directly.

  • Re-declaring a type via a type-ascription that has already been defined will cause an exception. For example, matching this:

    object FunObject { def fun[T](t: T => String) = t }
    matchMacro(fun((x: String) => x.toUpperCase))
    

    … by doing this:

    case '{ FunObject.fun[$tt](($arg: $ttt) => $out) } =>
    

    Will blow up because $arg is of type $tt, not $ttt!

    @bishabosha had a great solution for this issue:

    case 'type $tt; { FunObject.fun[`$tt`](($arg: `$tt`) => $out) } =>
    

    I would use this pattern throughout my codebase if it wasn’t for #8749.

  • In general, Quoted Matching is still rough around the edges and needs a little bit of production hardening. It is still relatively easy to get it to break (e.g. see my first issue #8745) or to freeze up the compiler e.g: #8746. However, I still firmly believe that it is a substantial improvement over quasi-quotes.

Thank you @biboudis, @liufengyun, @nicolasstucki, and @smarter for all of the assistance you have provided me up to this point as well as for your continuing help. It has been quite a challenge re-architecting Quill to Dotty paradigms but the light at the end of the tunnel is brighter every day!

4 Likes

Great work.

Quill is one of the most macro heavy libraries in the Scala ecosystem; successfully porting it over to Scala 3 will help other Scala 2 macro based projects follow suit.

Once the Quill >> Scala 3 migration is complete then begins the next task: porting Quill to Scala.js and finding a way to hook into native drivers for SQLite on Android and iOS :sweat_smile:

1 Like

I’m glad that Quill issues are getting fixed.

We have several blockers for complete izumi-reflect implementation (#8520, #8514, #8764). I really hope they can be fixed in foreseeable future.

By the way, right now our last release correctly handles =:= and <:< works for many simple cases when no type constructors with variant argument are involved.

1 Like