Skip to content

Commit

Permalink
Add async iteration support
Browse files Browse the repository at this point in the history
  • Loading branch information
othree committed Aug 14, 2018
1 parent 62db791 commit 601d174
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 17 deletions.
19 changes: 19 additions & 0 deletions defs/ecmascript.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@
"return": "fn(value?: ?) -> iter_result",
"throw": "fn(exception: +Error)"
},
"async_iter_prototype": {
":Symbol.asyncIterator": "fn() -> !this"
},
"async_iter": {
"!proto": "async_iter_prototype",
"next": {
"!type": "fn() -> +Promise[:t=+iter_result[value=!this.:t]]",
"!doc": "Return the next item in the sequence.",
"!url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators"
},
"!url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators"
},
"async_generator_prototype": {
"!proto": "async_iter_prototype",
"next": "fn(value?: ?) -> +Promise[:t=iter_result]",
"return": "fn(value?: ?) -> +Promise[:t=iter_result]",
"throw": "fn(exception: +Error)"
},
"Proxy_handler": {
"!doc": "The proxy's handler object is a placeholder object which contains traps for proxies.",
"!url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler",
Expand Down Expand Up @@ -1887,6 +1905,7 @@
"hasInstance": ":Symbol.hasInstance",
"isConcatSpreadable": ":Symbol.isConcatSpreadable",
"iterator": ":Symbol.iterator",
"asyncIterator": ":Symbol.asyncIterator",
"match": ":Symbol.match",
"replace": ":Symbol.replace",
"search": ":Symbol.search",
Expand Down
48 changes: 31 additions & 17 deletions lib/infer.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@
result = compute(this.self, this.args, this.argNodes)
cx.disabledComputing = old;
}
if (fn.isAsyncFn()) {
if (fn.async && !fn.generator) {
var tp = result.getType();
if (!(tp && tp.constructor == Obj && tp.name == "Promise")) {
var defs = cx.definitions.ecmascript;
Expand Down Expand Up @@ -702,13 +702,14 @@
return name == "__proto__" || name == "✖" || geckoIterators && name == "__iterator__";
}

var Fn = exports.Fn = function(name, self, args, argNames, retval, generator) {
var Fn = exports.Fn = function(name, self, args, argNames, retval, generator, async) {
Obj.call(this, cx.protos.Function, name);
this.self = self;
this.args = args;
this.argNames = argNames;
this.retval = retval;
this.generator = generator
this.generator = generator;
this.async = async;
};
Fn.prototype = extend(Obj.prototype, {
constructor: Fn,
Expand Down Expand Up @@ -751,8 +752,7 @@
return Obj.prototype.defProp.call(this, prop, originNode);
},
getFunctionType: function() { return this; },
isArrowFn: function() { return this.originNode && this.originNode.type == "ArrowFunctionExpression" },
isAsyncFn: function() { return this.originNode && this.originNode.async }
isArrowFn: function() { return this.originNode && this.originNode.type == "ArrowFunctionExpression" }
});

var Arr = exports.Arr = function(contentType) {
Expand Down Expand Up @@ -1008,7 +1008,7 @@
}
var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
while (argNames.length < args.length) argNames.push("?");
scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull, fn.generator);
scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull, fn.generator, fn.async);
scopeCopy.fnType.originNode = fn.originNode;
if (fn.arguments) {
var argset = scopeCopy.fnType.arguments = new AVal;
Expand Down Expand Up @@ -1120,7 +1120,7 @@
c(param, patternScopes(inner), "Pattern")
}
}
inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull, node.generator)
inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull, node.generator, node.async)
inner.fnType.originNode = node;
if (node.id) {
var decl = node.type == "FunctionDeclaration";
Expand Down Expand Up @@ -1720,9 +1720,17 @@
target = ensureVar(pattern, scope)
else
connectPattern(pattern, scope, target = new AVal)
infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null,
new HasMethodCall("next", [], null,
new GetProp("value", target))))

if (node.await) {
infer(node.right, scope, new HasMethodCall(":Symbol.asyncIterator", [], null,
new HasMethodCall("next", [], null,
new GetProp(":t",
new GetProp("value", target)))))
} else {
infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null,
new HasMethodCall("next", [], null,
new GetProp("value", target))))
}
c(node.body, scope, "Statement")
}
});
Expand Down Expand Up @@ -1836,23 +1844,29 @@
return ANull;
}

function generatorResult(input, output) {
var retObj = new Obj(true)
retObj.defProp("done").addType(cx.bool)
output.propagate(retObj.defProp("value"))
function generatorResult(input, output, async) {
var defs = cx.definitions.ecmascript
var valObj = new Obj(true)
valObj.defProp("done").addType(cx.bool)
output.propagate(valObj.defProp("value"))
var retObj = valObj
if (async && defs) {
retObj = new Obj(defs["Promise.prototype"])
retObj.getType().propagate(new DefProp(':t', valObj))
}
var method = new Fn(null, ANull, input ? [input] : [], input ? ["?"] : [], retObj)
var result = new Obj(cx.definitions.ecmascript && cx.definitions.ecmascript.generator_prototype || true)
var result = new Obj(defs ? async ? defs.async_generator_prototype : defs.generator_prototype : true)
result.defProp("next").addType(method)
return result
}

function maybeIterator(fn, output) {
if (!fn.generator) return output
if (!fn.computeRet) { // Reuse iterator objects for non-computed return types
if (fn.generator === true) fn.generator = generatorResult(fn.yieldval, output)
if (fn.generator === true) fn.generator = generatorResult(fn.yieldval, output, fn.async)
return fn.generator
}
return generatorResult(fn.yieldval, output)
return generatorResult(fn.yieldval, output, fn.async)
}

function computeReturnType(funcNode, argNodes, scope) {
Expand Down
22 changes: 22 additions & 0 deletions test/cases/async_for_of.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
var myIter = {
[Symbol.asyncIterator]() {
return {
next() {
return Promise.resolve({value: {a: 1, b: true}, done: false})
}
}
}
}

async function run () {

for await (var hello of myIter) {
hello //:: {a: number, b: bool}
}

for await (var {a, b} of myIter) {
a //: number
b //: bool
}

}
12 changes: 12 additions & 0 deletions test/cases/async_generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
async function scope () {
async function * myGen () {
yield {c: 1};
return {c: 2};
}

var iter = myGen();

for await (const item of iter) {
item; //:: {c: number}
}
}

0 comments on commit 601d174

Please sign in to comment.