StringContext change in macros between 2.12.10 and 2.13.1

I took a bit of time typing up this “bug” https://github.com/scala/bug/issues/11744. It was closed but I’m still curious how to proceed?

TL;DR: In Scala 2.12.10 a macro accepting a String passed a StringContext has a different type tree between the two.

Any suggestions?

Thanks

I don’t think that the behavior of StringContext itself changed between 2.12 and 2.13, what changed is that the string interpolators s"" and raw"" in the standard library are now themselves macros which get directly expanded by the typechecker, this is what you observe from your macro. Note that even in 2.12, the string interpolator f"" in the standard library is a macro, and user-written string interpolators can also be macros, so you can never assume a fixed shape for the typechecked tree of a string interpolator.

Hi @fooblahblah,

why don’t you just define your own string interpolator (macro) instead of trying to shoehorn the existing s interpolator into something it isn’t meant for?!

Yeah, that might be what needs to happen, but the codebase this actually came from (Datomisca) hasn’t been touched for years, so I was trying to make sense of what was there.

Good suggestion though!

@cbley thanks for the idea about a custom StringContext. I think I’ve got something working now where I can call the macro from a custom StringContext called query.

The macro now can match using a quasiquote extractor to pull out the parts from a StringContext

package demo

import scala.reflect.macros.blackbox.Context

object Demo {

  def queryImpl(c: Context)(args: c.Expr[Any]*): c.Expr[String] = {
    import c.universe._

    c.info(c.enclosingPosition, s"queryImpl: ${showRaw(c.prefix.tree)}", false)

    c.prefix.tree match {
      case Literal(Constant(s: String)) =>
        c.info(c.enclosingPosition, s"Got literal string $s", false)
        c.Expr[String](Literal(Constant(s)))

      case Apply(_, List(a @ Apply(_, _))) =>
        c.info(c.enclosingPosition, s"Got StringContext ${show(a)} ${show(args)}", false)
        val q"scala.StringContext.apply(..$parts)" = a
        val partsWithPlaceholders = q"""Seq(..$parts).mkString(" ! ")"""
        val query = c.eval(c.Expr[String](c.untypecheck(partsWithPlaceholders.duplicate)))
        c.Expr[String](Literal(Constant(s"QueryHelper: $query")))

      case _ =>
        c.abort(c.enclosingPosition, s"Expected a string literal but got ${showRaw(c.prefix.tree)}")
    }
  }
}

An example call is:

package demo

import Queries._

object Queries {
   implicit class QueryHelper(private val sc: StringContext) extends AnyVal {
      def query(args: Any*): String =
         macro Demo.queryImpl
   }
}


object Usage {
   object Schema {
      val name = "Foo.name"
   }

   def main(args: Array[String]): Unit = {
      println(query"""
        [:find ?a
         :in $$
         :where [?a ${Schema.name}]]
      """)
   }
}

Thanks for the help. Now I’ll try porting this to Datomisca which is where all this started.