Skip to content

Commit 804099c

Browse files
committed
PoC: Support for function*/yield.
Would fix #5079. * Only works on JS; not on Wasm. * `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 ce4276e commit 804099c

File tree

23 files changed

+255
-92
lines changed

23 files changed

+255
-92
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5374,6 +5374,28 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
53745374
*/
53755375
js.JSAwait(arg)
53765376

5377+
case JS_GENERATOR =>
5378+
// js.Generator.apply(arg)
5379+
assert(args.size == 1,
5380+
s"Expected exactly 1 argument for JS primitive $code but got " +
5381+
s"${args.size} at $pos")
5382+
val Block(stats, fun: Function) = args.head
5383+
val genStats = stats.map(genStat(_))
5384+
val asyncExpr = genAnonFunction(fun) match {
5385+
case js.NewLambda(_, closure: js.Closure)
5386+
if closure.params.size == 1 && closure.resultType == jstpe.AnyType =>
5387+
val newFlags = closure.flags.withTyped(false).withArrow(false).withGenerator(true)
5388+
js.JSFunctionApply(closure.copy(flags = newFlags), js.Undefined() :: Nil)
5389+
case other =>
5390+
abort(s"Unexpected tree generated for the Function1 argument to js.Generator.apply at $pos: $other")
5391+
}
5392+
js.Block(genStats, asyncExpr)
5393+
5394+
case JS_YIELD | JS_YIELD_STAR =>
5395+
// js.yield(arg, evidence)
5396+
val (arg, yieldEvidence) = genArgs2
5397+
js.JSYield(arg, star = code == JS_YIELD_STAR)
5398+
53775399
case DYNAMIC_IMPORT =>
53785400
assert(args.size == 1,
53795401
s"Expected exactly 1 argument for JS primitive $code but got " +

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ trait JSDefinitions {
9595
lazy val JSConstructorTagModule = getRequiredModule("scala.scalajs.js.ConstructorTag")
9696
lazy val JSConstructorTag_materialize = getMemberMethod(JSConstructorTagModule, newTermName("materialize"))
9797

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+
98104
lazy val JSNewModule = getRequiredModule("scala.scalajs.js.new")
99105
lazy val JSNewModuleClass = JSNewModule.moduleClass
100106
lazy val JSNew_target = getMemberMethod(JSNewModuleClass, newTermName("target"))

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ abstract class JSPrimitives {
5454
final val JS_ASYNC = JS_IMPORT_META + 1 // js.async
5555
final val JS_AWAIT = JS_ASYNC + 1 // js.await
5656

57-
final val CONSTRUCTOROF = JS_AWAIT + 1 // runtime.constructorOf(clazz)
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)
5862
final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass
5963
final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass
6064
final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue
@@ -109,6 +113,10 @@ abstract class JSPrimitives {
109113
addPrimitive(JSImport_apply, JS_IMPORT)
110114
addPrimitive(JSImport_meta, JS_IMPORT_META)
111115

116+
addPrimitive(JSGenerator_apply, JS_GENERATOR)
117+
addPrimitive(JSGenerator_yield, JS_YIELD)
118+
addPrimitive(JSGenerator_yield_*, JS_YIELD_STAR)
119+
112120
addPrimitive(Runtime_constructorOf, CONSTRUCTOROF)
113121
addPrimitive(Runtime_createInnerJSClass, CREATE_INNER_JS_CLASS)
114122
addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS)

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

Lines changed: 19 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,87 +9,27 @@ 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)
43-
}
44-
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"))
50-
}
51-
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"))
25+
def main(args: Array[String]): Unit = {
26+
println("hello")
27+
28+
/*
29+
// Works on JS but not on WebAssembly
30+
val g = myGenerator(5)
31+
for (k <- 0 until 8)
32+
js.Dynamic.global.console.log(g.next(k))
33+
*/
5634
}
5735
}
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ object Hashers {
246246
mixTag(TagJSAwait)
247247
mixTree(arg)
248248

249+
case JSYield(arg, star) =>
250+
mixTag(TagJSYield)
251+
mixTree(arg)
252+
mixBoolean(star)
253+
249254
case Debugger() =>
250255
mixTag(TagDebugger)
251256

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@ object Printers {
296296
print(arg)
297297
print(")")
298298

299+
case JSYield(arg, star) =>
300+
if (star)
301+
print("yield*(")
302+
else
303+
print("yield(")
304+
print(arg)
305+
print(")")
306+
299307
case Debugger() =>
300308
print("debugger")
301309

@@ -910,6 +918,8 @@ object Printers {
910918
print("arrow-lambda")
911919
else
912920
print("lambda")
921+
if (flags.generator)
922+
print("*")
913923
print("<")
914924
var first = true
915925
for ((param, value) <- captureParams.zip(captureValues)) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ object Serializers {
330330
writeTagAndPos(TagJSAwait)
331331
writeTree(arg)
332332

333+
case JSYield(arg, star) =>
334+
writeTagAndPos(TagJSYield)
335+
writeTree(arg)
336+
writeBoolean(star)
337+
333338
case Debugger() =>
334339
writeTagAndPos(TagDebugger)
335340

@@ -1223,6 +1228,8 @@ object Serializers {
12231228

12241229
case TagJSAwait =>
12251230
JSAwait(readTree())
1231+
case TagJSYield =>
1232+
JSYield(readTree(), readBoolean())
12261233

12271234
case TagDebugger => Debugger()
12281235

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ private[ir] object Tags {
134134
final val TagApplyTypedClosure = TagLinkTimeProperty + 1
135135
final val TagNewLambda = TagApplyTypedClosure + 1
136136
final val TagJSAwait = TagNewLambda + 1
137+
final val TagJSYield = TagJSAwait + 1
137138

138139
// Tags for member defs
139140

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ object Transformers {
8080
case JSAwait(arg) =>
8181
JSAwait(transform(arg))
8282

83+
case JSYield(arg, star) =>
84+
JSYield(transform(arg), star)
85+
8386
// Scala expressions
8487

8588
case New(className, ctor, args) =>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ object Traversers {
7272
case JSAwait(arg) =>
7373
traverse(arg)
7474

75+
case JSYield(arg, star) =>
76+
traverse(arg)
77+
7578
// Scala expressions
7679

7780
case New(_, _, args) =>

0 commit comments

Comments
 (0)