8000 PoC: Support for `function*/yield`. · sjrd/scala-js@804099c · GitHub
[go: up one dir, main page]

Skip to content

Commit 804099c

Browse files
committed
PoC: Support for function*/yield.
Would fix scala-js#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) =>

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ object Trees {
234234
val tpe = AnyType
235235
}
236236

237+
/** `yield arg` or `yield* arg`.
238+
*
239+
* This is directly equivalent to a JavaScript `yield`/`yield*` expression.
240+
* This node is only valid within a [[Closure]] node with the `generator`
241+
* flag.
242+
*/
243+
sealed case class JSYield(arg: Tree, star: Boolean)(implicit val pos: Position)
244+
extends Tree {
245+
val tpe = AnyType
246+
}
247+
237248
sealed case class Debugger()(implicit val pos: Position) extends Tree {
238249
val tpe = VoidType
239250
}
@@ -1235,6 +1246,12 @@ object Trees {
12351246
* If `flags.async` is `true`, it is an `async` closure. Async closures
12361247
* return a `Promise` of their body, and can contain [[JSAwait]] nodes.
12371248
* `flags.typed` and `flags.async` cannot both be `true`.
1249+
*
1250+
* If `flags.generator` is `true`, it is a generator (`*`) closure.
1251+
* Generator closures return a `{,Async}Generator` instance for their body,
1252+
* and can contain [[JSYield]] nodes.
1253+
*
1254+
* `flags.arrow` and `flags.generator` cannot both be `true`.
12381255
*/
12391256
sealed case class Closure(flags: ClosureFlags, captureParams: List[ParamDef],
12401257
params: List[ParamDef], restParam: Option[ParamDef], resultType: Type,
@@ -1596,6 +1613,8 @@ object Trees {
15961613

15971614
def async: Boolean = (bits & AsyncBit) != 0
15981615

1616+
def generator: Boolean = (bits & GeneratorBit) != 0
1617+
15991618
def withArrow(arrow: Boolean): ClosureFlags =
16001619
if (arrow) new ClosureFlags(bits | ArrowBit)
16011620
else new ClosureFlags(bits & ~ArrowBit)
@@ -1607,6 +1626,10 @@ object Trees {
16071626
def withAsync(async: Boolean): ClosureFlags =
16081627
if (async) new ClosureFlags(bits | AsyncBit)
16091628
else new ClosureFlags(bits & ~AsyncBit)
1629+
1630+
def withGenerator(generator: Boolean): ClosureFlags =
1631+
if (generator) new ClosureFlags(bits | GeneratorBit)
1632+
else new ClosureFlags(bits & GeneratorBit)
16101633
}
16111634

16121635
object ClosureFlags {
@@ -1622,6 +1645,9 @@ object Trees {
16221645
private final val AsyncShift = 2
16231646
private final val AsyncBit = 1 << AsyncShift
16241647

1648+
private final val GeneratorShift = 3
1649+
private final val GeneratorBit = 1 << GeneratorShift
1650+
16251651
/** `function` closure base flags. */
16261652
final val function: ClosureFlags =
16271653
new ClosureFlags(0)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 scala.scalajs.js
14+
15+
import scala.scalajs.js
16+
import scala.scalajs.js.annotation._
17+
18+
/** <span class="badge badge-ecma6" style="float: right;">ECMAScript 6</span>
19+
* JavaScript Generator.
20+
*/
21+
trait Generator[+Elem, +Result, -NextParam] extends js.Iterator[Elem] with js.Iterable[Elem] {
22+
def next(): js.Generator.Entry[Elem, Result]
23+
def next(param: NextParam): js.Generator.Entry[Elem, Result]
24+
def `return`[R >: Result](value: R): js.Generator.Entry[Elem, R]
25+
def `throw`(exception: scala.Any): js.Generator.Entry[Elem, Result]
26+
27+
@JSName(js.Symbol.iterator)
28+
def jsIterator(): this.type
29+
}
30+
31+
object Generator {
32+
def apply[Elem, Result, NextParam](
33+
body: YieldEvidence[Elem, NextParam] => Result): js.Generator[Elem, Result, NextParam] = {
34+
throw new java.lang.Error("stub")
35+
}
36+
37+
def `yield`[Elem, NextParam](value: Elem)(
38+
implicit evidence: YieldEvidence[Elem, NextParam]): NextParam = {
39+
throw new java.lang.Error("stub")
40+
}
41+
42+
def yield_*[Elem, NextParam](values: js.Iterable[Elem])(
43+
implicit evidence: YieldEvidence[Elem, NextParam]): NextParam = {
44+
throw new java.lang.Error("stub")
45+
}
46+
47+
/** Return value of `js.Generator.next`. */
48+
trait Entry[+Elem, +Result] extends js.Iterator.Entry[Elem] {
49+
/** The result value. Reading this value is only valid if done is true. */
50+
@JSName("value")
51+
def resultValue: Result
52+
}
53+
54+
sealed trait YieldEvidence[Elem, NextParam] extends js.Any
55+
}

linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
350350
mkUnaryOp(op, transformExpr(lhs))
351351
case Await(arg) =>
352352
new Node(Token.AWAIT, transformExpr(arg))
353+
case Yield(arg, star) =>
354+
val node = new Node(Token.YIELD, transformExpr(arg))
355+
node.setYieldAll(star)
356+
node
353357
case IncDec(prefix, inc, arg) =>
354358
val token = if (inc) Token.INC else Token.DEC
355359
val node = new Node(token, transformExpr(arg))
@@ -391,6 +395,7 @@ private class ClosureAstTransformer(featureSet: FeatureSet,
391395
val node = genFunction("", args, restParam, body)
392396
node.setIsArrowFunction(flags.arrow)
393397
node.setIsAsyncFunction(flags.async)
398+
node.setIsGeneratorFunction(flags.generator)
394399
node
395400
case FunctionDef(name, args, restParam, body) =>
396401
genFunction(name.resolveName(), args, restParam, body)

0 commit comments

Comments
 (0)
0