Skip to content

Commit 1a985b7

Browse files
committed
PoC: Support for async/await and function*/yield.
Would fix #5079. * `async/await` works on JS and Wasm. The latter uses the JSPI proposal. * `function*/yield` only works on JS. * `yield_*` should work (on JS) but is untested. * `async function*` has theoretical support in the linker, but no support in the compiler backend for now.
1 parent 030eff0 commit 1a985b7

File tree

36 files changed

+541
-174
lines changed

36 files changed

+541
-174
lines changed

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

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
10051005

10061006
def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef],
10071007
body: js.Tree)(implicit pos: ir.Position) = {
1008-
js.Closure(arrow = false, captureParams = Nil, params, restParam, body,
1008+
js.Closure(js.ClosureFlags.function, captureParams = Nil, params, restParam, body,
10091009
captureValues = Nil)
10101010
}
10111011

@@ -1124,7 +1124,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
11241124
beforeSuper ::: superCall ::: afterSuper
11251125
}
11261126

1127-
val closure = js.Closure(arrow = true, jsClassCaptures, Nil, None,
1127+
val closure = js.Closure(js.ClosureFlags.arrow, jsClassCaptures, Nil, None,
11281128
js.Block(inlinedCtorStats, selfRef), jsSuperClassValue :: args)
11291129
js.JSFunctionApply(closure, Nil)
11301130
}
@@ -1430,7 +1430,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
14301430
val fqcnArg = js.StringLiteral(sym.fullName + "$")
14311431
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
14321432
val loadModuleFunArg =
1433-
js.Closure(arrow = true, Nil, Nil, None, genLoadModule(sym), Nil)
1433+
js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, genLoadModule(sym), Nil)
14341434

14351435
val stat = genApplyMethod(
14361436
genLoadModule(ReflectModule),
@@ -1480,7 +1480,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
14801480

14811481
val paramTypesArray = js.JSArrayConstr(parameterTypes)
14821482

1483-
val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, None, {
1483+
val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil, formalParams, None, {
14841484
genNew(sym, ctor, actualParams)
14851485
}, Nil)
14861486

@@ -2175,8 +2175,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
21752175
assert(isStat, s"found a VarDef in expression position at ${tree.pos}")
21762176
super.transform(js.VarDef(name, originalName, vtpe,
21772177
newMutable(name.name, mutable), rhs)(tree.pos), isStat)
2178-
case js.Closure(arrow, captureParams, params, restParam, body, captureValues) =>
2179-
js.Closure(arrow, captureParams, params, restParam, body,
2178+
case js.Closure(flags, captureParams, params, restParam, body, captureValues) =>
2179+
js.Closure(flags, captureParams, params, restParam, body,
21802180
captureValues.map(transformExpr))(tree.pos)
21812181
case _ =>
21822182
super.transform(tree, isStat)
@@ -2211,8 +2211,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
22112211
override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
22122212
case tree @ js.VarRef(name) =>
22132213
js.VarRef(name)(newType(name, tree.tpe))(tree.pos)
2214-
case js.Closure(arrow, captureParams, params, restParam, body, captureValues) =>
2215-
js.Closure(arrow, captureParams, params, restParam, body,
2214+
case js.Closure(flags, captureParams, params, restParam, body, captureValues) =>
2215+
js.Closure(flags, captureParams, params, restParam, body,
22162216
captureValues.map(transformExpr))(tree.pos)
22172217
case _ =>
22182218
super.transform(tree, isStat)
@@ -5375,6 +5375,47 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
53755375
// js.import.meta
53765376
js.JSImportMeta()
53775377

5378+
case JS_ASYNC =>
5379+
// js.async(arg)
5380+
assert(args.size == 1,
5381+
s"Expected exactly 1 argument for JS primitive $code but got " +
5382+
s"${args.size} at $pos")
5383+
val Block(stats, fun: Function) = args.head
5384+
val genStats = stats.map(genStat(_))
5385+
val asyncExpr = genAnonFunction(fun) match {
5386+
case js.New(_, _, List(closure: js.Closure)) =>
5387+
js.JSFunctionApply(closure.copy(flags = closure.flags.withAsync(true)), Nil)
5388+
case other =>
5389+
abort(s"oops: $other")
5390+
}
5391+
js.Block(genStats, asyncExpr)
5392+
5393+
case JS_AWAIT =>
5394+
// js.await(arg)
5395+
val arg = genArgs1
5396+
js.JSAwait(arg)
5397+
5398+
case JS_GENERATOR =>
5399+
// js.Generator.apply(arg)
5400+
assert(args.size == 1,
5401+
s"Expected exactly 1 argument for JS primitive $code but got " +
5402+
s"${args.size} at $pos")
5403+
val Block(stats, fun: Function) = args.head
5404+
val genStats = stats.map(genStat(_))
5405+
val asyncExpr = genAnonFunction(fun) match {
5406+
case js.New(_, _, List(closure: js.Closure)) =>
5407+
js.JSFunctionApply(closure.copy(flags = closure.flags.withArrow(false).withGenerator(true)),
5408+
js.Undefined() :: Nil)
5409+
case other =>
5410+
abort(s"oops: $other")
5411+
}
5412+
js.Block(genStats, asyncExpr)
5413+
5414+
case JS_YIELD | JS_YIELD_STAR =>
5415+
// js.yield(arg, evidence)
5416+
val (arg, yieldEvidence) = genArgs2
5417+
js.JSYield(arg, star = code == JS_YIELD_STAR)
5418+
53785419
case DYNAMIC_IMPORT =>
53795420
assert(args.size == 1,
53805421
s"Expected exactly 1 argument for JS primitive $code but got " +
@@ -6366,7 +6407,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
63666407
if (isThisFunction) {
63676408
val thisParam :: actualParams = patchedParams
63686409
js.Closure(
6369-
arrow = false,
6410+
js.ClosureFlags.function,
63706411
ctorParamDefs,
63716412
actualParams,
63726413
patchedRepeatedParam,
@@ -6377,7 +6418,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
63776418
patchedBody),
63786419
capturedArgs)
63796420
} else {
6380-
js.Closure(arrow = true, ctorParamDefs, patchedParams,
6421+
js.Closure(js.ClosureFlags.arrow, ctorParamDefs, patchedParams,
63816422
patchedRepeatedParam, patchedBody, capturedArgs)
63826423
}
63836424
}
@@ -6526,7 +6567,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
65266567
Nil
65276568
)
65286569
js.Closure(
6529-
arrow = true,
6570+
js.ClosureFlags.arrow,
65306571
formalCaptures,
65316572
patchedFormalArgs,
65326573
restParam = None,

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ trait JSDefinitions {
4747
lazy val JSPackage_native = getMemberMethod(ScalaJSJSPackageModule, newTermName("native"))
4848
lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackageModule, newTermName("undefined"))
4949
lazy val JSPackage_dynamicImport = getMemberMethod(ScalaJSJSPackageModule, newTermName("dynamicImport"))
50+
lazy val JSPackage_async = getMemberMethod(ScalaJSJSPackageModule, newTermName("async"))
51+
lazy val JSPackage_await = getMemberMethod(ScalaJSJSPackageModule, newTermName("await"))
5052

5153
lazy val JSNativeAnnotation = getRequiredClass("scala.scalajs.js.native")
5254

@@ -93,6 +95,12 @@ trait JSDefinitions {
9395
lazy val JSConstructorTagModule = getRequiredModule("scala.scalajs.js.ConstructorTag")
9496
lazy val JSConstructorTag_materialize = getMemberMethod(JSConstructorTagModule, newTermName("materialize"))
9597

98+
lazy val JSGeneratorModule = getRequiredModule("scala.scalajs.js.Generator")
99+
lazy val JSGeneratorModuleClass = JSGeneratorModule.moduleClass
100+
lazy val JSGenerator_apply = getMemberMethod(JSGeneratorModuleClass, nme.apply)
101+
lazy val JSGenerator_yield = getMemberMethod(JSGeneratorModuleClass, newTermName("yield"))
102+
lazy val JSGenerator_yield_* = getMemberMethod(JSGeneratorModuleClass, newTermName("yield_$times"))
103+
96104
lazy val JSNewModule = getRequiredModule("scala.scalajs.js.new")
97105
lazy val JSNewModuleClass = JSNewModule.moduleClass
98106
lazy val JSNew_target = getMemberMethod(JSNewModuleClass, newTermName("target"))

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ abstract class JSPrimitives {
5151
final val JS_IMPORT = JS_NEW_TARGET + 1 // js.import.apply(specifier)
5252
final val JS_IMPORT_META = JS_IMPORT + 1 // js.import.meta
5353

54-
final val CONSTRUCTOROF = JS_IMPORT_META + 1 // runtime.constructorOf(clazz)
54+
final val JS_ASYNC = JS_IMPORT_META + 1 // js.async
55+
final val JS_AWAIT = JS_ASYNC + 1 // js.await
56+
57+
final val JS_GENERATOR = JS_AWAIT + 1 // js.Generator.apply
58+
final val JS_YIELD = JS_GENERATOR + 1 // js.Generator.yield
59+
final val JS_YIELD_STAR = JS_YIELD + 1 // js.Generator.yield_*
60+
61+
final val CONSTRUCTOROF = JS_YIELD_STAR + 1 // runtime.constructorOf(clazz)
5562
final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass
5663
final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass
5764
final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue
@@ -96,6 +103,8 @@ abstract class JSPrimitives {
96103

97104
addPrimitive(JSPackage_typeOf, TYPEOF)
98105
addPrimitive(JSPackage_native, JS_NATIVE)
106+
addPrimitive(JSPackage_async, JS_ASYNC)
107+
addPrimitive(JSPackage_await, JS_AWAIT)
99108

100109
addPrimitive(BoxedUnit_UNIT, UNITVAL)
101110

@@ -104,6 +113,10 @@ abstract class JSPrimitives {
104113
addPrimitive(JSImport_apply, JS_IMPORT)
105114
addPrimitive(JSImport_meta, JS_IMPORT_META)
106115

116+
addPrimitive(JSGenerator_apply, JS_GENERATOR)
117+
addPrimitive(JSGenerator_yield, JS_YIELD)
118+
addPrimitive(JSGenerator_yield_*, JS_YIELD_STAR)
119+
107120
addPrimitive(Runtime_constructorOf, CONSTRUCTOROF)
108121
addPrimitive(Runtime_createInnerJSClass, CREATE_INNER_JS_CLASS)
109122
addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS)

examples/helloworld/src/main/scala/helloworld/HelloWorld.scala

Lines changed: 36 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,87 +9,48 @@ import scala.scalajs.js
99
import js.annotation._
1010

1111
object HelloWorld {
12-
def main(args: Array[String]): Unit = {
13-
import js.DynamicImplicits.truthValue
14-
15-
if (js.typeOf(js.Dynamic.global.document) != "undefined" &&
16-
js.Dynamic.global.document &&
17-
js.Dynamic.global.document.getElementById("playground")) {
18-
sayHelloFromDOM()
19-
sayHelloFromTypedDOM()
20-
sayHelloFromJQuery()
21-
sayHelloFromTypedJQuery()
22-
} else {
23-
println("Hello world!")
12+
def myGenerator(n: Int): js.Generator[Int, String, Int] = js.Generator[Int, String, Int] { implicit ev =>
13+
println("one")
14+
js.Generator.`yield`(42)
15+
println("two")
16+
var i = 0
17+
var j = 0
18+
while (i != n) {
19+
j += js.Generator.`yield`(j)
20+
i += 1
2421
}
22+
"result"
2523
}
2624

27-
def sayHelloFromDOM(): Unit = {
28-
val document = js.Dynamic.global.document
29-
val playground = document.getElementById("playground")
30-
31-
val newP = document.createElement("p")
32-
newP.innerHTML = "Hello world! <i>-- DOM</i>"
33-
playground.appendChild(newP)
34-
}
35-
36-
def sayHelloFromTypedDOM(): Unit = {
37-
val document = window.document
38-
val playground = document.getElementById("playground")
39-
40-
val newP = document.createElement("p")
41-
newP.innerHTML = "Hello world! <i>-- typed DOM</i>"
42-
playground.appendChild(newP)
25+
def sleep(ms: Int): js.Promise[Unit] = {
26+
new js.Promise[Unit]({ (resolve, reject) =>
27+
js.timers.setTimeout(ms)(resolve(()))
28+
})
4329
}
4430

45-
def sayHelloFromJQuery(): Unit = {
46-
// val $ is fine too, but not very recommended in Scala code
47-
val jQuery = js.Dynamic.global.jQuery
48-
val newP = jQuery("<p>").html("Hello world! <i>-- jQuery</i>")
49-
newP.appendTo(jQuery("#playground"))
31+
def myAsyncProgram(n: Int): js.Promise[Int] = js.async {
32+
var sum = 0
33+
var i = 0
34+
while (i != n) {
35+
js.await(HelloWorld.sleep(750))
36+
i += 1
37+
println(i)
38+
sum += i
39+
}
40+
i
5041
}
5142

52-
def sayHelloFromTypedJQuery(): Unit = {
53-
val jQuery = helloworld.JQuery
54-
val newP = jQuery("<p>").html("Hello world! <i>-- typed jQuery</i>")
55-
newP.appendTo(jQuery("#playground"))
43+
def main(args: Array[String]): Unit = {
44+
/*
45+
// Works on JS but not on WebAssembly
46+
val g = myGenerator(5)
47+
for (k <- 0 until 8)
48+
js.Dynamic.global.console.log(g.next(k))
49+
*/
50+
51+
val p = myAsyncProgram(5)
52+
p.`then`[Unit]({ (i: Int) =>
53+
println(i)
54+
})
5655
}
5756
}
58-
59-
@js.native
60-
@JSGlobalScope
61-
object window extends js.Object {
62-
val document: DOMDocument = js.native
63-
64-
def alert(msg: String): Unit = js.native
65-
}
66-
67-
@js.native
68-
trait DOMDocument extends js.Object {
69-
def getElementById(id: String): DOMElement = js.native
70-
def createElement(tag: String): DOMElement = js.native
71-
}
72-
73-
@js.native
74-
trait DOMElement extends js.Object {
75-
var innerHTML: String = js.native
76-
77-
def appendChild(child: DOMElement): Unit = js.native
78-
}
79-
80-
@js.native
81-
@JSGlobal("jQuery")
82-
object JQuery extends js.Object {
83-
def apply(selector: String): JQuery = js.native
84-
}
85-
86-
@js.native
87-
trait JQuery extends js.Object {
88-
def text(value: String): JQuery = js.native
89-
def text(): String = js.native
90-
91-
def html(value: String): JQuery = js.native
92-
def html(): String = js.native
93-
94-
def appendTo(parent: JQuery): JQuery = js.native
95-
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,15 @@ object Hashers {
246246
mixTree(default)
247247
mixType(tree.tpe)
248248

249+
case JSAwait(arg) =>
250+
mixTag(TagJSAwait)
251+
mixTree(arg)
252+
253+
case JSYield(arg, star) =>
254+
mixTag(TagJSYield)
255+
mixTree(arg)
256+
mixBoolean(star)
257+
249258
case Debugger() =>
250259
mixTag(TagDebugger)
251260

@@ -533,9 +542,9 @@ object Hashers {
533542
mixTag(TagThis)
534543
mixType(tree.tpe)
535544

536-
case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
545+
case Closure(flags, captureParams, params, restParam, body, captureValues) =>
537546
mixTag(TagClosure)
538-
mixBoolean(arrow)
547+
mixByte(ClosureFlags.toBits(flags).toByte)
539548
mixParamDefs(captureParams)
540549
mixParamDefs(params)
541550
restParam.foreach(mixParamDef(_))

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,19 @@ object Printers {
283283
undent()
284284
undent(); println(); print('}')
285285

286+
case JSAwait(arg) =>
287+
print("await(")
288+
print(arg)
289+
print(")")
290+
291+
case JSYield(arg, star) =>
292+
if (star)
293+
print("yield*(")
294+
else
295+
print("yield(")
296+
print(arg)
297+
print(")")
298+
286299
case Debugger() =>
287300
print("debugger")
288301

@@ -875,11 +888,17 @@ object Printers {
875888
case This() =>
876889
print("this")
877890

878-
case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
879-
if (arrow)
880-
print("(arrow-lambda<")
891+
case Closure(flags, captureParams, params, restParam, body, captureValues) =>
892+
print("(")
893+
if (flags.async)
894+
print("async ")
895+
if (flags.arrow)
896+
print("arrow-lambda")
881897
else
882-
print("(lambda<")
898+
print("lambda")
899+
if (flags.generator)
900+
print("*")
901+
print("<")
883902
var first = true
884903
for ((param, value) <- captureParams.zip(captureValues)) {
885904
if (first)

0 commit comments

Comments
 (0)