Skip to content

Commit

Permalink
allow constant interpolations of strings in addition to pure literals…
Browse files Browse the repository at this point in the history
… in scala3
  • Loading branch information
martijnhoekstra committed May 5, 2024
1 parent 2d366ed commit bb5c13b
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,29 @@ trait Literally[A]:
def apply(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using Quotes): Expr[A] =
apply(strCtxExpr.valueOrAbort.parts, argsExpr)

private def apply(parts: Seq[String], argsExpr: Expr[Seq[Any]])(using Quotes): Expr[A] =
if parts.size == 1 then
val literal = parts.head
validate(literal) match
case Left(err) =>
quotes.reflect.report.error(err)
???
case Right(a) =>
a
else
quotes.reflect.report.error("interpolation not supported", argsExpr)
???
private def apply(parts: Seq[String], argsExpr: Expr[Seq[Any]])(using q: Quotes): Expr[A] =
import q.reflect.*

val str = argsExpr.asTerm match
case Inlined(_, _, Typed(Repeated(terms, _), _)) =>
def termsAsStrings = terms.map { term =>
val staticVal = if (term.tpe <:< TypeRepr.of[String]) term.asExprOf[String].value
else if (term.tpe <:< TypeRepr.of[Int]) term.asExprOf[Int].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Double]) term.asExprOf[Double].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Float]) term.asExprOf[Float].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Long]) term.asExprOf[Long].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Short]) term.asExprOf[Short].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Byte]) term.asExprOf[Byte].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Char]) term.asExprOf[Char].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Boolean]) term.asExprOf[Boolean].value.map(_.toString)
else if (term.tpe <:< TypeRepr.of[Unit]) Some("()")
else report.errorAndAbort(s"interpolated literal values must be primitive types", term.pos)
staticVal.getOrElse(report.errorAndAbort("interpolated values must be compile-time constants"))
}
parts.zipAll(termsAsStrings, "", "").map(_ + _).mkString
case unknown =>
report.errorAndAbort(s"unexpected error interpolating with literally, which didn't expect an interpolation tree in the form of $unknown", unknown.pos)

validate(str) match
case Left(err) => report.errorAndAbort(err)
case Right(value) => value
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,27 @@ class LiterallySuite extends FunSuite {
assert(compileErrors("""port"-1"""").nonEmpty)
assert(compileErrors("""port"100000"""").nonEmpty)
}

final val intOne = 1
final val stringTwo = "2"
final val doubleThree = 3.0
final val charFour = '4'

test("literal allows interpolation of primitive constants") {
assertEquals(port"1$stringTwo", Port.fromInt(12).get)
assertEquals(port"$intOne", Port.fromInt(1).get)
assertEquals(short"$doubleThree", ShortString.fromString("3.0").get)
assertEquals(port"1${stringTwo}3$charFour", Port.fromInt(1234).get)
assertEquals(port"${1}${"2"}${'3'}", Port.fromInt(123).get)
}

final val onetwo = (1, 2)
test("literal disallows interpolation of non-primivive constants") {
assert(clue(compileErrors("""port"$onetwo"""")).contains("interpolated literal values must be primitive types"))
}

test("literal disallows interpolation of non-constant values") {
assert(clue(compileErrors("""val aString = "1"; port"$aString"""")).contains("interpolated values need to be compile-time constants"))
}

}

0 comments on commit bb5c13b

Please sign in to comment.