Skip to content

Commit 2e82189

Browse files
committed
String interpolation optimization miniphase
1 parent d2d7d99 commit 2e82189

File tree

5 files changed

+85
-5
lines changed

5 files changed

+85
-5
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ import Periods._
77
import Symbols._
88
import Types._
99
import Scopes._
10-
import typer.{FrontEnd, Typer, ImportInfo, RefChecks}
11-
import reporting.{Reporter, ConsoleReporter}
10+
import typer.{FrontEnd, ImportInfo, RefChecks, Typer}
11+
import reporting.{ConsoleReporter, Reporter}
1212
import Phases.Phase
1313
import transform._
1414
import util.FreshNameCreator
1515
import core.DenotTransformers.DenotTransformer
1616
import core.Denotations.SingleDenotation
17-
18-
import dotty.tools.backend.jvm.{LabelDefs, GenBCode, CollectSuperCalls}
19-
import dotty.tools.dotc.transform.localopt.Simplify
17+
import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode, LabelDefs}
18+
import dotty.tools.dotc.transform.localopt.{Simplify, StringInterpolatorOpt}
2019

2120
/** The central class of the dotc compiler. The job of a compiler is to create
2221
* runs, which process given `phases` in a given `rootContext`.
@@ -78,6 +77,7 @@ class Compiler {
7877
new ExplicitOuter, // Add accessors to outer classes from nested ones.
7978
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
8079
new ShortcutImplicits, // Allow implicit functions without creating closures
80+
new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations
8181
new CrossCastAnd, // Normalize selections involving intersection types.
8282
new Splitter) :: // Expand selections involving union types into conditionals
8383
List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call.

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ object StdNames {
483483
val productElement: N = "productElement"
484484
val productIterator: N = "productIterator"
485485
val productPrefix: N = "productPrefix"
486+
val raw_ : N = "raw"
486487
val readResolve: N = "readResolve"
487488
val reflect : N = "reflect"
488489
val reify : N = "reify"
@@ -492,6 +493,7 @@ object StdNames {
492493
val runtime: N = "runtime"
493494
val runtimeClass: N = "runtimeClass"
494495
val runtimeMirror: N = "runtimeMirror"
496+
val s: N = "s"
495497
val sameElements: N = "sameElements"
496498
val scala_ : N = "scala"
497499
val scalaShadowing : N = "scalaShadowing"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package dotty.tools.dotc.transform.localopt
2+
3+
import dotty.tools.dotc.ast.Trees._
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.core.Constants.Constant
6+
import dotty.tools.dotc.core.Contexts.Context
7+
import dotty.tools.dotc.core.StdNames._
8+
import dotty.tools.dotc.core.Symbols._
9+
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
10+
11+
/**
12+
* Created by wojtekswiderski on 2018-01-24.
13+
*/
14+
class StringInterpolatorOpt extends MiniPhase {
15+
import tpd._
16+
17+
override def phaseName: String = "stringInterpolatorOpt"
18+
19+
private object StringContextIntrinsic {
20+
def unapply(tree: Apply)(implicit ctx: Context): Option[(List[Tree], List[Tree])] = {
21+
tree match {
22+
case Apply(Select(Apply(Select(Ident(nme.StringContext), nme.apply),
23+
List(SeqLiteral(strs, _))), id), List(SeqLiteral(elems, _))) =>
24+
if (id == nme.raw_) Some(strs, elems)
25+
else if (id == nme.s) {
26+
try {
27+
val escapedStrs = strs.mapConserve { str =>
28+
val strValue = str.asInstanceOf[Literal].const.stringValue
29+
val escapedValue = StringContext.processEscapes(strValue)
30+
cpy.Literal(str)(Constant(escapedValue))
31+
}
32+
Some(escapedStrs, elems)
33+
} catch {
34+
case _: StringContext.InvalidEscapeException => None
35+
}
36+
} else None
37+
case _ => None
38+
}
39+
}
40+
}
41+
42+
override def transformApply(tree: Apply)(implicit ctx: Context): Tree = {
43+
tree match {
44+
case StringContextIntrinsic(strs: List[Tree], elems: List[Tree]) =>
45+
val numLits = strs.length
46+
strs.tail.foldLeft((0, strs.head)) { (acc: (Int, Tree), str: Tree) =>
47+
val (i, result) = acc
48+
val resultWithElem =
49+
if (i < numLits - 1) result.select(defn.String_+).appliedTo(elems(i))
50+
else result
51+
val resultWithStr =
52+
if (str.asInstanceOf[Literal].const.stringValue.isEmpty) resultWithElem
53+
else resultWithElem.select(defn.String_+).appliedTo(str)
54+
(i + 1, resultWithStr)
55+
}._2
56+
case _ => tree
57+
}
58+
}
59+
}

tests/run/interpolation-opt.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
1 plus two\nis 3.0
2+
1 plus two
3+
is 3.0
4+
a1two3.0b
5+
a1two3.0b

tests/run/interpolation-opt.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
object Test extends App {
2+
3+
val one = 1
4+
val two = "two"
5+
val three = 3.0
6+
7+
// Test escaping
8+
println(raw"$one plus $two\nis $three")
9+
println(s"$one plus $two\nis $three")
10+
11+
// Test empty strings between elements
12+
println(raw"a$one$two${three}b")
13+
println(s"a$one$two${three}b")
14+
}

0 commit comments

Comments
 (0)