Skip to content

Commit

Permalink
Merge pull request #2007 from SebsLittleHelpers/string-plus-cst-fold
Browse files Browse the repository at this point in the history
Fix #1524: Implement constant folding for +[string].
  • Loading branch information
sjrd committed Nov 20, 2015
2 parents f6542f6 + 61fa19c commit 4cde878
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 0 deletions.
Expand Up @@ -81,6 +81,217 @@ object OptimizerTest extends JasmineTest {

}

describe("+[string] constant folding") {
it("must not break when folding two constant strings") {
@inline def str: String = "I am "
expect(str + "constant").toEqual("I am constant")
}

it("must not break when folding the empty string when associated with a string") {
@noinline def str: String = "hello"
expect(str + "").toEqual("hello")
expect("" + str).toEqual("hello")
}

it("must not break when folding 1.4f and a stringLit") {
expect(1.4f + "hello").toEqual("1.399999976158142hello")
expect("hello" + 1.4f).toEqual("hello1.399999976158142")
}

it("must not break when folding cascading +[string]") {
@noinline def str: String = "awesome! 10/10"
expect("Scala.js" + (" is " + str)).toEqual("Scala.js is awesome! 10/10")
expect((str + " is ") + "Scala.js").toEqual("awesome! 10/10 is Scala.js")
}

it("must not break when folding a chain of +[string]") {
@inline def b: String = "b"
@inline def d: String = "d"
@inline def f: String = "f"
expect("a" + b + "c" + d + "e" + f + "g").toEqual("abcdefg")
}

it("must not break when folding integer in double and stringLit") {
expect(1.0 + "hello").toEqual("1hello")
expect("hello" + 1.0).toEqual("hello1")
}

it("must not break when folding zero and stringLit") {
expect(0.0 + "hello").toEqual("0hello")
expect("hello" + 0.0).toEqual("hello0")
expect(-0.0 + "hello").toEqual("0hello")
expect("hello" + (-0.0)).toEqual("hello0")
}

it("must not break when folding Infinities and stringLit") {
expect(Double.PositiveInfinity + "hello").toEqual("Infinityhello")
expect("hello" + Double.PositiveInfinity).toEqual("helloInfinity")
expect(Double.NegativeInfinity + "hello").toEqual("-Infinityhello")
expect("hello" + Double.NegativeInfinity).toEqual("hello-Infinity")
}

it("must not break when folding NaN and stringLit") {
expect(Double.NaN + "hello").toEqual("NaNhello")
expect("hello" + Double.NaN).toEqual("helloNaN")
}

unless("fullopt-stage").
it("must not break when folding double with decimal and stringLit") {
expect(1.2323919403474454E21 + "hello")
.toEqual("1.2323919403474454e+21hello")
expect("hello" + 1.2323919403474454E21)
.toEqual("hello1.2323919403474454e+21")
}

unless("fullopt-stage").
it("must not break when folding double that JVM would print in scientific notation and stringLit") {
expect(123456789012345d + "hello")
.toEqual("123456789012345hello")
expect("hello" + 123456789012345d)
.toEqual("hello123456789012345")
}

unless("fullopt-stage").
it("must not break when folding doubles to String"){
@noinline def toStringNoInline(v: Double): String = v.toString
@inline def test(v: Double): Unit =
expect(v.toString).toEqual(toStringNoInline(v))

// Special cases
test(0.0)
test(-0.0)
test(Double.NaN)
test(Double.PositiveInfinity)
test(Double.NegativeInfinity)

// k <= n <= 21
test(1.0)
test(12.0)
test(123.0)
test(1234.0)
test(12345.0)
test(123456.0)
test(1234567.0)
test(12345678.0)
test(123456789.0)
test(1234567890.0)
test(12345678901.0)
test(123456789012.0)
test(1234567890123.0)
test(12345678901234.0)
test(123456789012345.0)
test(1234567890123456.0)
test(12345678901234657.0)
test(123456789012345678.0)
test(1234567890123456789.0)
test(12345678901234567890.0)
test(123456789012345678901.0)

// 0 < n <= 21
test(1.42)
test(12.42)
test(123.42)
test(1234.42)
test(12345.42)
test(123456.42)
test(1234567.42)
test(12345678.42)
test(123456789.42)
test(1234567890.42)
test(12345678901.42)
test(123456789012.42)
test(1234567890123.42)
test(12345678901234.42)
test(123456789012345.42)
test(1234567890123456.42)
test(12345678901234657.42)
test(123456789012345678.42)
test(1234567890123456789.42)
test(12345678901234567890.42)
test(123456789012345678901.42)

// -6 < n <= 0
test(0.1)
test(0.01)
test(0.001)
test(0.0001)
test(0.00001)
test(0.000001)

// k == 1
test(1e22)
test(2e25)
test(3e50)
test(4e100)
test(5e200)
test(6e300)
test(7e307)
test(1e-22)
test(2e-25)
test(3e-50)
test(4e-100)
test(5e-200)
test(6e-300)
test(7e-307)

// else
test(1.42e22)
test(2.42e25)
test(3.42e50)
test(4.42e100)
test(5.42e200)
test(6.42e300)
test(7.42e307)
test(1.42e-22)
test(2.42e-25)
test(3.42e-50)
test(4.42e-100)
test(5.42e-200)
test(6.42e-300)
test(7.42e-307)

// special cases when ulp > 1
test(18271179521433728.0)
test(1.15292150460684685E18)
test(1234567890123456770.0)
test(2234567890123456770.0)
test(4234567890123450000.0)
test(149170297077708820000.0)
test(296938164846899230000.0)
test(607681513323520000000.0)
}

it("must not break when folding long and stringLit") {
expect(1L + "hello").toEqual("1hello")
expect("hello" + 1L).toEqual("hello1")
}

it("must not break when folding integer and stringLit") {
expect(42 + "hello").toEqual("42hello")
expect("hello" + 42).toEqual("hello42")
}

it("must not break when folding boolean and stringLit") {
expect(false + " is not true").toEqual("false is not true")
expect("false is not " + true).toEqual("false is not true")
}

it("must not break when folding unit and stringLit") {
expect(() + " is undefined?").toEqual("undefined is undefined?")
expect("undefined is " + ()).toEqual("undefined is undefined")
}

it("must not break when folding null and stringLit") {
expect("Damien is not " + null).toEqual("Damien is not null")
}

it("must not break when folding char and stringLit") {
expect('S' + "cala.js").toEqual("Scala.js")
expect("Scala.j" + 's').toEqual("Scala.js")
}

}

@inline
class InlineClassDependentFields(val x: Int) {
val b = x > 3
Expand Down
Expand Up @@ -2165,6 +2165,39 @@ private[optimizer] abstract class OptimizerCore(
}
}

/** Translate literals to their Scala.js String representation. */
private def foldToStringForString_+(tree: Tree)(implicit pos : Position): Tree = tree match {
case FloatLiteral(value) =>
foldToStringForString_+(DoubleLiteral(value.toDouble))

case DoubleLiteral(value) =>
jsNumberToString(value).fold(tree)(StringLiteral(_))

case LongLiteral(value) => StringLiteral(value.toString)
case IntLiteral(value) => StringLiteral(value.toString)
case BooleanLiteral(value) => StringLiteral(value.toString)
case Null() => StringLiteral("null")
case Undefined() => StringLiteral("undefined")
case _ => tree
}

/* Following the ECMAScript 6 specification */
private def jsNumberToString(value: Double): Option[String] = {
if (1.0.toString == "1") {
// We are in a JS environment, so the host .toString() is the correct one.
Some(value.toString)
} else {
value match {
case _ if value.isNaN => Some("NaN")
case 0 => Some("0")
case _ if value < 0 => jsNumberToString(-value).map("-" + _)
case _ if value.isInfinity => Some("Infinity")
case _ if value.isValidInt => Some(value.toInt.toString)
case _ => None
}
}
}

private def foldBinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)(
implicit pos: Position): Tree = {
import BinaryOp._
Expand All @@ -2180,6 +2213,27 @@ private[optimizer] abstract class OptimizerCore(
case _ => default
}

case String_+ =>
val lhs1 = foldToStringForString_+(lhs)
val rhs1 = foldToStringForString_+(rhs)
@inline def stringDefault = BinaryOp(String_+, lhs1, rhs1)
(lhs1, rhs1) match {
case (StringLiteral(s1), StringLiteral(s2)) =>
StringLiteral(s1 + s2)
case (_, StringLiteral("")) =>
foldBinaryOp(op, rhs1, lhs1)
case (StringLiteral(""), _) if rhs1.tpe == StringType =>
rhs1
case (_, BinaryOp(String_+, rl, rr)) =>
foldBinaryOp(String_+, BinaryOp(String_+, lhs1, rl), rr)
case (BinaryOp(String_+, ll, StringLiteral(lr)), StringLiteral(r)) =>
BinaryOp(String_+, ll, StringLiteral(lr + r))
case (BinaryOp(String_+, StringLiteral(""), lr), _) =>
BinaryOp(String_+, lr, rhs1)
case _ =>
stringDefault
}

case Int_+ =>
(lhs, rhs) match {
case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l + r)
Expand Down

0 comments on commit 4cde878

Please sign in to comment.