Thanks to visit codestin.com
Credit goes to github.com

Skip to content

#4997 Allow for link-time dispatching #5000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5361,6 +5361,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case UNWRAP_FROM_THROWABLE =>
// js.special.unwrapFromThrowable(arg)
js.UnwrapFromThrowable(genArgs1)

case LINKTIME_IF =>
// linkingInfo.linkTimeIf(cond, thenp, elsep)
assert(args.size == 3,
s"Expected exactly 3 arguments for JS primitive $code but got " +
s"${args.size} at $pos")
val condp = genLinkTimeTree(args(0))
val thenp = genExpr(args(1))
val elsep = genExpr(args(2))
js.LinkTimeIf(condp, thenp, elsep)(toIRType(tree.tpe))
}
}

Expand Down Expand Up @@ -6827,8 +6837,103 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.tpe))
}
}

private def genLinkTimeTree(cond: Tree)(
implicit pos: Position): js.LinkTimeTree = {
import js.LinkTimeOp._
val dummy = js.LinkTimeTree.Property("dummy", toIRType(cond.tpe))
cond match {
case Literal(Constant(b: Boolean)) =>
js.LinkTimeTree.BooleanConst(b)

case Literal(Constant(i: Int)) =>
js.LinkTimeTree.IntConst(i)

case Literal(_) =>
reporter.error(cond.pos,
s"Invalid literal $cond inside linkTimeIf. " +
"Only boolean and int values can be used in linkTimeIf.")
dummy

case Ident(name) =>
reporter.error(cond.pos,
s"Invalid identifier $name inside linkTimeIf. " +
"Only @linkTimeProperty annotated values can be used in linkTimeIf.")
dummy

// !x
case Apply(Select(t, nme.UNARY_!), Nil) if cond.symbol == definitions.Boolean_not =>
val lt = genLinkTimeTree(t)
js.LinkTimeTree.BinaryOp(Boolean_==, lt, js.LinkTimeTree.BooleanConst(false))

// if(foo()) (...)
case Apply(prop, Nil) =>
getLinkTimeProperty(prop).getOrElse {
reporter.error(prop.pos,
s"Invalid identifier inside linkTimeIf. " +
"Only @linkTimeProperty annotated values can be used in linkTimeIf.")
dummy
}

// if(lhs <comp> rhs) (...)
case Apply(Select(cond1, comp), List(cond2)) =>
val tpe = toIRType(cond.tpe)
val c1 = genLinkTimeTree(cond1)
val c2 = genLinkTimeTree(cond2)
val dummyOp = -1
val op: Code =
if (c1.tpe == jstpe.IntType) {
comp match {
case nme.EQ => Int_==
case nme.NE => Int_!=
case nme.GT => Int_>
case nme.GE => Int_>=
case nme.LT => Int_<
case nme.LE => Int_<=
case _ =>
reporter.error(cond.pos,
s"Invalid operation '$comp' inside linkTimeIf. " +
"Only '==', '!=', '>', '>=', '<', '<=' " +
"operations are allowed for integer values in linkTimeIf.")
dummyOp
}
} else if (c1.tpe == jstpe.BooleanType) {
comp match {
case nme.EQ => Boolean_==
case nme.NE => Boolean_!=
case nme.ZAND => Boolean_&&
case nme.ZOR => Boolean_||
case _ =>
reporter.error(cond.pos,
s"Invalid operation '$comp' inside linkTimeIf. " +
"Only '==', '!=', '&&', and '||' operations are allowed for boolean values in linkTimeIf.")
dummyOp
}
} else {
dummyOp
}
if (op == dummyOp) dummy
else js.LinkTimeTree.BinaryOp(op, c1, c2)

case t =>
reporter.error(t.pos,
s"Only @linkTimeProperty annotated values, int and boolean constants, " +
"and binary operations are allowd in linkTimeIf.")
dummy
}
}
}

private def getLinkTimeProperty(tree: Tree): Option[js.LinkTimeTree.Property] = {
tree.symbol.getAnnotation(LinkTimePropertyAnnotation)
.flatMap(_.args.headOption)
.flatMap {
case Literal(Constant(v: String)) =>
Some(js.LinkTimeTree.Property(v, toIRType(tree.symbol.tpe.resultType))(tree.pos))
case _ => None
}
}

private lazy val hasNewCollections =
!scala.util.Properties.versionNumberString.startsWith("2.12.")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ trait JSDefinitions {
lazy val JSGlobalScopeAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSGlobalScope")
lazy val JSOperatorAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSOperator")

lazy val LinkTimePropertyAnnotation = getRequiredClass("scala.scalajs.js.annotation.linkTimeProperty")

lazy val JSImportNamespaceObject = getRequiredModule("scala.scalajs.js.annotation.JSImport.Namespace")

lazy val ExposedJSMemberAnnot = getRequiredClass("scala.scalajs.js.annotation.internal.ExposedJSMember")
Expand Down Expand Up @@ -128,6 +130,9 @@ trait JSDefinitions {
lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk")
lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply)

lazy val LinkingInfoClass = getRequiredModule("scala.scalajs.LinkingInfo")
lazy val LinkingInfoClass_linkTimeIf = getMemberMethod(LinkingInfoClass, newTermName("linkTimeIf"))

lazy val Tuple2_apply = getMemberMethod(TupleClass(2).companionModule, nme.apply)

// This is a def, since similar symbols (arrayUpdateMethod, etc.) are in runDefinitions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ abstract class JSPrimitives {
final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable
final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger

final val LastJSPrimitiveCode = DEBUGGER
final val LINKTIME_IF = DEBUGGER + 1 // LinkingInfo.linkTimeIf

final val LastJSPrimitiveCode = LINKTIME_IF

/** Initialize the map of primitive methods (for GenJSCode) */
def init(): Unit = initWithPrimitives(addPrimitive)
Expand Down Expand Up @@ -123,6 +125,8 @@ abstract class JSPrimitives {
addPrimitive(Special_wrapAsThrowable, WRAP_AS_THROWABLE)
addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE)
addPrimitive(Special_debugger, DEBUGGER)

addPrimitive(LinkingInfoClass_linkTimeIf, LINKTIME_IF)
}

def isJavaScriptPrimitive(code: Int): Boolean =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package org.scalajs.nscplugin.test

import util._

import org.junit.Test
import org.junit.Assert._

class LinkTimeIfTest extends TestHelpers {
override def preamble: String = "import scala.scalajs.LinkingInfo._"

// scalastyle:off line.size.limit
@Test
def linkTimeErrorInvalidOp(): Unit = {
"""
object A {
def foo =
linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
}
""" hasErrors
"""
|newSource1.scala:4: error: Invalid operation '$plus' inside linkTimeIf. Only '==', '!=', '>', '>=', '<', '<=' operations are allowed for integer values in linkTimeIf.
| linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
| ^
"""

"""
object A {
def foo =
linkTimeIf(productionMode | true) { } { }
}
""" hasErrors
"""
|newSource1.scala:4: error: Invalid operation '$bar' inside linkTimeIf. Only '==', '!=', '&&', and '||' operations are allowed for boolean values in linkTimeIf.
| linkTimeIf(productionMode | true) { } { }
| ^
"""
}

@Test
def linkTimeErrorInvalidEntities(): Unit = {
"""
object A {
def foo(x: String) = {
val bar = 1
linkTimeIf(bar == 0) { } { }
}
}
""" hasErrors
"""
|newSource1.scala:5: error: Invalid identifier bar inside linkTimeIf. Only @linkTimeProperty annotated values can be used in linkTimeIf.
| linkTimeIf(bar == 0) { } { }
| ^
"""

"""
object A {
def foo(x: String) =
linkTimeIf("foo" == x) { } { }
}
""" hasErrors
"""
|newSource1.scala:4: error: Invalid literal "foo" inside linkTimeIf. Only boolean and int values can be used in linkTimeIf.
| linkTimeIf("foo" == x) { } { }
| ^
|newSource1.scala:4: error: Invalid identifier x inside linkTimeIf. Only @linkTimeProperty annotated values can be used in linkTimeIf.
| linkTimeIf("foo" == x) { } { }
| ^
"""

"""
object A {
def bar = true
def foo(x: String) =
linkTimeIf(bar || !bar) { } { }
}
""" hasErrors
"""
|newSource1.scala:5: error: Invalid identifier inside linkTimeIf. Only @linkTimeProperty annotated values can be used in linkTimeIf.
| linkTimeIf(bar || !bar) { } { }
| ^
|newSource1.scala:5: error: Invalid identifier inside linkTimeIf. Only @linkTimeProperty annotated values can be used in linkTimeIf.
| linkTimeIf(bar || !bar) { } { }
| ^
"""
}

@Test
def linkTimeCondInvalidTree(): Unit = {
"""
object A {
def bar = true
def foo(x: String) =
linkTimeIf(if(bar) true else false) { } { }
}
""" hasErrors
"""
|newSource1.scala:5: error: Only @linkTimeProperty annotated values, int and boolean constants, and binary operations are allowd in linkTimeIf.
| linkTimeIf(if(bar) true else false) { } { }
| ^
"""
}
}
28 changes: 28 additions & 0 deletions ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ object Hashers {
mixTree(elsep)
mixType(tree.tpe)

case LinkTimeIf(cond, thenp, elsep) =>
mixTag(TagLinkTimeIf)
mixLinkTimeTree(cond)
mixTree(thenp)
mixTree(elsep)
mixType(tree.tpe)

case While(cond, body) =>
mixTag(TagWhile)
mixTree(cond)
Expand Down Expand Up @@ -700,6 +707,27 @@ object Hashers {
digestStream.writeInt(pos.column)
}

private def mixLinkTimeTree(cond: LinkTimeTree): Unit = {
cond match {
case LinkTimeTree.BinaryOp(op, lhs, rhs) =>
mixTag(TagLinkTimeTreeBinary)
digestStream.writeInt(op)
mixLinkTimeTree(lhs)
mixLinkTimeTree(rhs)
case LinkTimeTree.Property(name, tpe) =>
mixTag(TagLinkTimeProperty)
digestStream.writeUTF(name)
mixType(tpe)
case LinkTimeTree.BooleanConst(v) =>
mixTag(TagLinkTimeBooleanConst)
digestStream.writeBoolean(v)
case LinkTimeTree.IntConst(v) =>
mixTag(TagLinkTimeIntConst)
digestStream.writeInt(v)
}
mixPos(cond.pos)
}

@inline
final def mixTag(tag: Int): Unit = mixInt(tag)

Expand Down
42 changes: 42 additions & 0 deletions ir/shared/src/main/scala/org/scalajs/ir/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ object Printers {
case node: MemberDef => print(node)
case node: JSConstructorBody => printBlock(node.allStats)
case node: TopLevelExportDef => print(node)
case node: LinkTimeTree => print(node)
}
}

Expand Down Expand Up @@ -218,6 +219,15 @@ object Printers {
printBlock(elsep)
}

case LinkTimeIf(cond, thenp, elsep) =>
print("linkTimeIf (")
print(cond)
print(") ")

printBlock(thenp)
print(" else ")
printBlock(elsep)

case While(cond, body) =>
print("while (")
print(cond)
Expand Down Expand Up @@ -1181,6 +1191,38 @@ object Printers {
}
}

def print(cond: LinkTimeTree): Unit = {
import LinkTimeOp._
cond match {
case LinkTimeTree.BinaryOp(op, lhs, rhs) =>
print(lhs)
print(" ")
print(op match {
case Boolean_== => "=="
case Boolean_!= => "!="
case Boolean_|| => "||"
case Boolean_&& => "&&"

case Int_== => "=="
case Int_!= => "!="
case Int_< => "<"
case Int_<= => "<="
case Int_> => ">"
case Int_>= => ">="
})
print(" ")
print(rhs)
case LinkTimeTree.BooleanConst(v) =>
if (v) print("true") else print("false")
case LinkTimeTree.IntConst(v) =>
print(v.toString)
case LinkTimeTree.Property(name, _) =>
print("prop[")
print(name)
print("]")
}
}

def print(s: String): Unit =
out.write(s)

Expand Down
Loading