Evaluate a polymorphic variant argument of an external once, in place
Why
The translation of a polymorphic variant argument of an external read an argument other than a literal twice, as arg.NAME and arg.VAL of an event-style @string, or inside the arrow function that switches on it for @int and @as. So on(t, next()) called next() twice, and an await in the argument was a syntax error in the arrow function. The functions in Js_of_lam_variant return an expression and cannot bind the argument, so Lam_compile now binds it to a temporary among the statements of the call's arguments, unless reading it twice or in the function is the same as reading it once.
modifiedJS polymorphic variant argument
type t@send external on: (t, @string [#click(int => unit) | #keydown(string => unit)]) => unit = "on"@send external setLevel: (t, @int [#low | #high]) => unit = "setLevel" let f = (t, next) => on(t, next())let g = async (t, p) => setLevel(t, await p)
js
Before
g is a SyntaxError when the module loadsAfter
function f(t, next) {function f(t, next) { t.on(next().NAME, next().VAL); let tmp = next(); t.on(tmp.NAME, tmp.VAL);}}async function g(t, p) {async function g(t, p) { let tmp = await p; t.setLevel((() => { t.setLevel((() => { switch (await p) { switch (tmp) { … … } } })()); })());}}
modifiedJS literal with a payload
type t@send external on: (t, @string [#click(int => unit) | #keydown(string => unit)]) => unit = "on"@val external wrap: (int => unit) => int => unit = "wrap" let l = (t, handler) => on(t, #click(wrap(handler)))
js
Before
After
function l(t, handler) {function l(t, handler) { t.on(({ NAME: "click", VAL: wrap(handler) }).NAME, ({ NAME: "click", VAL: wrap(handler) }).VAL); t.on("click", wrap(handler)); }}
modifiedJS while condition with statements
type t@send external has: (t, @string [#click(int => unit) | #keydown(string => unit)]) => bool = "has" let w = async (t, p) => { let n = ref(0) while has(t, await p) { n := n.contents + 1 } n.contents}
js
Before
After
async function w(t, p) {async function w(t, p) { let n = 0; let n = 0; while (t.has((await p).NAME, (await p).VAL)) { while (true) { let tmp = await p; if (!t.has(tmp.NAME, tmp.VAL)) { break; } n = n + 1 | 0; n = n + 1 | 0; }; }; return n; return n;}}
Review
- Which arguments keep their output? Literals, constants and variables, and for an
@intor@stringdispatch, which reads the argument once, any argument without side effects. An event-style argument is read twice, so fields, array elements and conditionals of variables stay in place, but a@valglobal or a@getproperty read is bound, since it may run a getter.gpr_2633_test.mjschanges because it pinnedfoo.on(h(event).NAME, h(event).VAL). - Why does
compile_whilechange? The temporaries are statements, and it put a condition with statements into a closure called on each iteration, where anawait,breakorcontinueis a syntax error, sowhile has(t, await p)would stop loading. Such a condition now compiles towhile (true) { <statements>; if (!cond) break; <body> }. The change is the same as in #9177, with the same new output incomplex_while_loop.mjsandtest_while_side_effect.mjs.