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.
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.
@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)}")
}
}
}