8000 Merge pull request #5110 from sjrd/link-time-if-with-desugaring-phase · scala-js/scala-js@493130e · GitHub
[go: up one dir, main page]

Skip to content

Commit 493130e

Browse files
authored
Merge pull request #5110 from sjrd/link-time-if-with-desugaring-phase
Fix #4997: Add linkTimeIf for link-time conditional branching.
2 parents 008c33a + f0e7a33 commit 493130e

File tree

34 files changed

+1249
-117
lines changed

34 files changed

+1249
-117
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5511,6 +5511,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
55115511
js.UnaryOp(js.UnaryOp.UnwrapFromThrowable,
55125512
js.UnaryOp(js.UnaryOp.CheckNotNull, genArgs1))
55135513

5514+
case LINKTIME_IF =>
5515+
// LinkingInfo.linkTimeIf(cond, thenp, elsep)
5516+
val cond = genLinkTimeExpr(args(0))
5517+
val thenp = genExpr(args(1))
5518+
val elsep = genExpr(args(2))
5519+
val tpe =
5520+
if (isStat) jstpe.VoidType
5521+
else toIRType(tree.tpe)
5522+
js.LinkTimeIf(cond, thenp, elsep)(tpe)
5523+
55145524
case LINKTIME_PROPERTY =>
55155525
// LinkingInfo.linkTimePropertyXXX("...")
55165526
val arg = genArgs1
@@ -5529,6 +5539,83 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
55295539
}
55305540
}
55315541

5542+
private def genLinkTimeExpr(tree: Tree): js.Tree = {
5543+
import scalaPrimitives._
5544+
5545+
implicit val pos = tree.pos
5546+
5547+
def invalid(): js.Tree = {
5548+
reporter.error(tree.pos,
5549+
"Illegal expression in the condition of a linkTimeIf. " +
5550+
"Valid expressions are: boolean and int primitives; " +
5551+
"references to link-time properties; " +
5552+
"primitive operations on booleans; " +
5553+
"and comparisons on ints.")
5554+
js.BooleanLiteral(false)
5555+
}
5556+
5557+
tree match {
5558+
case Literal(c) =>
5559+
c.tag match {
5560+
case BooleanTag => js.BooleanLiteral(c.booleanValue)
5561+
case IntTag => js.IntLiteral(c.intValue)
5562+
case _ => invalid()
5563+
}
5564+
5565+
case Apply(fun @ Select(receiver, _), args) =>
5566+
fun.symbol.getAnnotation(LinkTimePropertyAnnotation) match {
5567+
case Some(annotation) =>
5568+
val propName = annotation.constantAtIndex(0).get.stringValue
5569+
js.LinkTimeProperty(propName)(toIRType(tree.tpe))
5570+
5571+
case None if isPrimitive(fun.symbol) =>
5572+
val code = getPrimitive(fun.symbol)
5573+
5574+
def genLhs: js.Tree = genLinkTimeExpr(receiver)
5575+
def genRhs: js.Tree = genLinkTimeExpr(args.head)
5576+
5577+
def unaryOp(op: js.UnaryOp.Code): js.Tree =
5578+
js.UnaryOp(op, genLhs)
5579+
def binaryOp(op: js.BinaryOp.Code): js.Tree =
5580+
js.BinaryOp(op, genLhs, genRhs)
5581+
5582+
toIRType(receiver.tpe) match {
5583+
case jstpe.BooleanType =>
5584+
(code: @switch) match {
5585+
case ZNOT => unaryOp(js.UnaryOp.Boolean_!)
5586+
case EQ => binaryOp(js.BinaryOp.Boolean_==)
5587+
case NE | XOR => binaryOp(js.BinaryOp.Boolean_!=)
5588+
case OR => binaryOp(js.BinaryOp.Boolean_|)
5589+
case AND => binaryOp(js.BinaryOp.Boolean_&)
5590+
case ZOR => js.LinkTimeIf(genLhs, js.BooleanLiteral(true), genRhs)(jstpe.BooleanType)
5591+
case ZAND => js.LinkTimeIf(genLhs, genRhs, js.BooleanLiteral(false))(jstpe.BooleanType)
5592+
case _ => invalid()
5593+
}
5594+
5595+
case jstpe.IntType =>
5596+
(code: @switch) match {
5597+
case EQ => binaryOp(js.BinaryOp.Int_==)
5598+
case NE => binaryOp(js.BinaryOp.Int_!=)
5599+
case LT => binaryOp(js.BinaryOp.Int_<)
5600+
case LE => binaryOp(js.BinaryOp.Int_<=)
5601+
case GT => binaryOp(js.BinaryOp.Int_>)
5602+
case GE => binaryOp(js.BinaryOp.Int_>=)
5603+
case _ => invalid()
5604+
}
5605+
5606+
case _ =>
5607+
invalid()
5608+
}
5609+
5610+
case None => // if !isPrimitive
5611+
invalid()
5612+
}
5613+
5614+
case _ =>
5615+
invalid()
5616+
}
5617+
}
5618+
55325619
/** Gen JS code for a primitive JS call (to a method of a subclass of js.Any)
55335620
* This is the typed Scala.js to JS bridge feature. Basically it boils
55345621
* down to calling the method without name mangling. But other aspects

compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,13 @@ trait JSDefinitions {
135135
lazy val Runtime_dynamicImport = getMemberMethod(RuntimePackageModule, newTermName("dynamicImport"))
136136

137137
lazy val LinkingInfoModule = getRequiredModule("scala.scalajs.LinkingInfo")
138+
lazy val LinkingInfo_linkTimeIf = getMemberMethod(LinkingInfoModule, newTermName("linkTimeIf"))
138139
lazy val LinkingInfo_linkTimePropertyBoolean = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyBoolean"))
139140
lazy val LinkingInfo_linkTimePropertyInt = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyInt"))
140141
lazy val LinkingInfo_linkTimePropertyString = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyString"))
141142

143+
lazy val LinkTimePropertyAnnotation = getRequiredClass("scala.scalajs.annotation.linkTimeProperty")
144+
142145
lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk")
143146
lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply)
144147

compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ abstract class JSPrimitives {
7171
final val WRAP_AS_THROWABLE = JS_TRY_CATCH + 1 // js.special.wrapAsThrowable
7272
final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable
7373
final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger
74-
final val LINKTIME_PROPERTY = DEBUGGER + 1 // LinkingInfo.linkTimePropertyXXX
74+
final val LINKTIME_IF = DEBUGGER + 1 // LinkingInfo.linkTimeIf
75+
final val LINKTIME_PROPERTY = LINKTIME_IF + 1 // LinkingInfo.linkTimePropertyXXX
7576

7677
final val LastJSPrimitiveCode = LINKTIME_PROPERTY
7778

@@ -128,6 +129,7 @@ abstract class JSPrimitives {
128129
addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE)
129130
addPrimitive(Special_debugger, DEBUGGER)
130131

132+
addPrimitive(LinkingInfo_linkTimeIf, LINKTIME_IF)
131133
addPrimitive(LinkingInfo_linkTimePropertyBoolean, LINKTIME_PROPERTY)
132134
addPrimitive(LinkingInfo_linkTimePropertyInt, LINKTIME_PROPERTY)
133135
addPrimitive(LinkingInfo_linkTimePropertyString, LINKTIME_PROPERTY)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.nscplugin.test
14+
15+
import util._
16+
17+
import org.junit.Test
18+
import org.junit.Assert._
19+
20+
// scalastyle:off line.size.limit
21+
22+
class LinkTimeIfTest extends TestHelpers {
23+
override def preamble: String = "import scala.scalajs.LinkingInfo._"
24+
25+
private final val IllegalLinkTimeIfArgMessage = {
26+
"Illegal expression in the condition of a linkTimeIf. " +
27+
"Valid expressions are: boolean and int primitives; " +
28+
"references to link-time properties; " +
29+
"primitive operations on booleans; " +
30+
"and comparisons on ints."
31+
}
32+
33+
@Test
34+
def linkTimeErrorInvalidOp(): Unit = {
35+
"""
36+
object A {
37+
def foo =
38+
linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
39+
}
40+
""" hasErrors
41+
s"""
42+
|newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage
43+
| linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
44+
| ^
45+
"""
46+
}
47+
48+
@Test
49+
def linkTimeErrorInvalidEntities(): Unit = {
50+
"""
51+
object A {
52+
def foo(x: String) = {
53+
val bar = 1
54+
linkTimeIf(bar == 0) { } { }
55+
}
56+
}
57+
""" hasErrors
58+
s"""
59+
|newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
60+
| linkTimeIf(bar == 0) { } { }
61+
| ^
62+
"""
63+
64+
// String comparison is a `BinaryOp.===`, which is not allowed
65+
"""
66+
object A {
67+
def foo(x: String) =
68+
linkTimeIf("foo" == x) { } { }
69+
}
70+
""" hasErrors
71+
s"""
72+
|newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage
73+
| linkTimeIf("foo" == x) { } { }
74+
| ^
75+
"""
76+
77+
"""
78+
object A {
79+
def bar = true
80+
def foo(x: String) =
81+
linkTimeIf(bar || !bar) { } { }
82+
}
83+
""" hasErrors
84+
s" 10000 ;""
85+
|newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
86+
| linkTimeIf(bar || !bar) { } { }
87+
| ^
88+
|newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
89+
| linkTimeIf(bar || !bar) { } { }
90+
| ^
91+
"""
92+
}
93+
94+
@Test
95+
def linkTimeCondInvalidTree(): Unit = {
96+
"""
97+
object A {
98+
def bar = true
99+
def foo(x: String) =
100+
linkTimeIf(if (bar) true else false) { } { }
101+
}
102+
""" hasErrors
103+
s"""
104+
|newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
105+
| linkTimeIf(if (bar) true else false) { } { }
106+
| ^
107+
"""
108+
}
109+
}

ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ object Hashers {
206206
mixTree(elsep)
207207
mixType(tree.tpe)
208208

209+
case LinkTimeIf(cond, thenp, elsep) =>
210+
mixTag(TagLinkTimeIf)
211+
mixTree(cond)
212+
mixTree(thenp)
213+
mixTree(elsep)
214+
mixType(tree.tpe)
215+
209216
case While(cond, body) =>
210217
mixTag(TagWhile)
211218
mixTree(cond)

ir/shared/src/main/scala/org/scalajs/ir/Printers.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ object Printers {
9393
protected def printBlock(tree: Tree): Unit = {
9494
val trees = tree match {
9595
case Block(trees) => trees
96+
case Skip() => Nil
9697
case _ => tree :: Nil
9798
}
9899
printBlock(trees)
@@ -232,6 +233,14 @@ object Printers {
232233
printBlock(elsep)
233234
}
234235

236+
case LinkTimeIf(cond, thenp, elsep) =>
237+
print("link-time-if (")
238+
print(cond)
239+
print(") ")
240+
printBlock(thenp)
241+
print(" else ")
242+
printBlock(elsep)
243+
235244
case While(cond, body) =>
236245
print("while (")
237246
print(cond)

ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap
1717
import scala.util.matching.Regex
1818

1919
object ScalaJSVersions extends VersionChecks(
20-
current = "1.19.1-SNAPSHOT",
21-
binaryEmitted = "1.19"
20+
current = "1.20.0-SNAPSHOT",
21+
binaryEmitted = "1.20-SNAPSHOT"
2222
)
2323

2424
/** Helper class to allow for testing of logic. */

ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala

-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ object Serializers {
297297
writeTree(cond); writeTree(thenp); writeTree(elsep)
298298
writeType(tree.tpe)
299299

300+
case LinkTimeIf(cond, thenp, elsep) =>
301+
writeTagAndPos(TagLinkTimeIf)
302+
writeTree(cond); writeTree(thenp); writeTree(elsep)
303+
writeType(tree.tpe)
304+
300305
case While(cond, body) =>
301306
writeTagAndPos(TagWhile)
302307
writeTree(cond); writeTree(body)
@@ -1196,9 +1201,14 @@ object Serializers {
11961201

11971202
Assign(lhs.asInstanceOf[AssignLhs], rhs)
11981203

1199-
case TagReturn => Return(readTree(), readLabelName())
1200-
case TagIf => If(readTree(), readTree(), readTree())(readType())
1201-
case TagWhile => While(readTree(), readTree())
1204+
case TagReturn =>
1205+
Return(readTree(), readLabelName())
1206+
case TagIf =>
1207+
If(readTree(), readTree(), readTree())(readType())
1208+
case TagLinkTimeIf =>
1209+
LinkTimeIf(readTree(), readTree(), readTree())(readType())
1210+
case TagWhile =>
1211+
While(readTree(), readTree())
12021212

12031213
case TagDoWhile =>
12041214
if (!hacks.useBelow(13))

ir/shared/src/main/scala/org/scalajs/ir/Tags.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ private[ir] object Tags {
135135
final val TagNewLambda = TagApplyTypedClosure + 1
136136
final val TagJSAwait = TagNewLambda + 1
137137

138+
// New in 1.20
139+
final val TagLinkTimeIf = TagJSAwait + 1
140+
138141
// Tags for member defs
139142

140143
final val TagFieldDef = 1

ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ object Transformers {
6060
case If(cond, thenp, elsep) =>
6161
If(transform(cond), transform(thenp), transform(elsep))(tree.tpe)
6262

63+
case LinkTimeIf(cond, thenp, elsep) =>
64+
LinkTimeIf(transform(cond), transform(thenp), transform(elsep))(tree.tpe)
65+
6366
case While(cond, body) =>
6467
While(transform(cond), transform(body))
6568

0 commit comments

Comments
 (0)
0