Skip to content

Commit

Permalink
Remove one promise tick in yield* (tc39/ecma262#2819)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Aug 25, 2022
1 parent a31fb6d commit 4165bd3
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 119 deletions.
10 changes: 7 additions & 3 deletions packages/babel-helpers/src/helpers-generated.ts
Expand Up @@ -15,23 +15,27 @@ function helper(minVersion: string, source: string) {
export default Object.freeze({
AsyncGenerator: helper(
"7.0.0-beta.0",
'import AwaitValue from"AwaitValue";export default function AsyncGenerator(gen){var front,back;function resume(key,arg){try{var result=gen[key](arg),value=result.value,wrappedAwait=value instanceof AwaitValue;Promise.resolve(wrappedAwait?value.wrapped:value).then((function(arg){wrappedAwait?resume("return"===key?"return":"next",arg):settle(result.done?"return":"normal",arg)}),(function(err){resume("throw",err)}))}catch(err){settle("throw",err)}}function settle(type,value){switch(type){case"return":front.resolve({value:value,done:!0});break;case"throw":front.reject(value);break;default:front.resolve({value:value,done:!1})}(front=front.next)?resume(front.key,front.arg):back=null}this._invoke=function(key,arg){return new Promise((function(resolve,reject){var request={key:key,arg:arg,resolve:resolve,reject:reject,next:null};back?back=back.next=request:(front=back=request,resume(key,arg))}))},"function"!=typeof gen.return&&(this.return=void 0)}AsyncGenerator.prototype["function"==typeof Symbol&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},AsyncGenerator.prototype.next=function(arg){return this._invoke("next",arg)},AsyncGenerator.prototype.throw=function(arg){return this._invoke("throw",arg)},AsyncGenerator.prototype.return=function(arg){return this._invoke("return",arg)};',
'import OverloadYield from"OverloadYield";export default function AsyncGenerator(gen){var front,back;function resume(key,arg){try{var result=gen[key](arg),value=result.value,overloaded=value instanceof OverloadYield;Promise.resolve(overloaded?value.v:value).then((function(arg){if(overloaded){var nextKey="return"===key?"return":"next";if(!value.k||arg.done)return resume(nextKey,arg);arg=gen[nextKey](arg).value}settle(result.done?"return":"normal",arg)}),(function(err){resume("throw",err)}))}catch(err){settle("throw",err)}}function settle(type,value){switch(type){case"return":front.resolve({value:value,done:!0});break;case"throw":front.reject(value);break;default:front.resolve({value:value,done:!1})}(front=front.next)?resume(front.key,front.arg):back=null}this._invoke=function(key,arg){return new Promise((function(resolve,reject){var request={key:key,arg:arg,resolve:resolve,reject:reject,next:null};back?back=back.next=request:(front=back=request,resume(key,arg))}))},"function"!=typeof gen.return&&(this.return=void 0)}AsyncGenerator.prototype["function"==typeof Symbol&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},AsyncGenerator.prototype.next=function(arg){return this._invoke("next",arg)},AsyncGenerator.prototype.throw=function(arg){return this._invoke("throw",arg)},AsyncGenerator.prototype.return=function(arg){return this._invoke("return",arg)};',
),
OverloadYield: helper(
"7.18.14",
"export default function _OverloadYield(value,kind){this.v=value,this.k=kind}",
),
applyDecs: helper(
"7.17.8",
'function createMetadataMethodsForProperty(metadataMap,kind,property,decoratorFinishedRef){return{getMetadata:function(key){assertNotFinished(decoratorFinishedRef,"getMetadata"),assertMetadataKey(key);var metadataForKey=metadataMap[key];if(void 0!==metadataForKey)if(1===kind){var pub=metadataForKey.public;if(void 0!==pub)return pub[property]}else if(2===kind){var priv=metadataForKey.private;if(void 0!==priv)return priv.get(property)}else if(Object.hasOwnProperty.call(metadataForKey,"constructor"))return metadataForKey.constructor},setMetadata:function(key,value){assertNotFinished(decoratorFinishedRef,"setMetadata"),assertMetadataKey(key);var metadataForKey=metadataMap[key];if(void 0===metadataForKey&&(metadataForKey=metadataMap[key]={}),1===kind){var pub=metadataForKey.public;void 0===pub&&(pub=metadataForKey.public={}),pub[property]=value}else if(2===kind){var priv=metadataForKey.priv;void 0===priv&&(priv=metadataForKey.private=new Map),priv.set(property,value)}else metadataForKey.constructor=value}}}function convertMetadataMapToFinal(obj,metadataMap){var parentMetadataMap=obj[Symbol.metadata||Symbol.for("Symbol.metadata")],metadataKeys=Object.getOwnPropertySymbols(metadataMap);if(0!==metadataKeys.length){for(var i=0;i<metadataKeys.length;i++){var key=metadataKeys[i],metaForKey=metadataMap[key],parentMetaForKey=parentMetadataMap?parentMetadataMap[key]:null,pub=metaForKey.public,parentPub=parentMetaForKey?parentMetaForKey.public:null;pub&&parentPub&&Object.setPrototypeOf(pub,parentPub);var priv=metaForKey.private;if(priv){var privArr=Array.from(priv.values()),parentPriv=parentMetaForKey?parentMetaForKey.private:null;parentPriv&&(privArr=privArr.concat(parentPriv)),metaForKey.private=privArr}parentMetaForKey&&Object.setPrototypeOf(metaForKey,parentMetaForKey)}parentMetadataMap&&Object.setPrototypeOf(metadataMap,parentMetadataMap),obj[Symbol.metadata||Symbol.for("Symbol.metadata")]=metadataMap}}function createAddInitializerMethod(initializers,decoratorFinishedRef){return function(initializer){assertNotFinished(decoratorFinishedRef,"addInitializer"),assertCallable(initializer,"An initializer"),initializers.push(initializer)}}function memberDec(dec,name,desc,metadataMap,initializers,kind,isStatic,isPrivate,value){var kindStr;switch(kind){case 1:kindStr="accessor";break;case 2:kindStr="method";break;case 3:kindStr="getter";break;case 4:kindStr="setter";break;default:kindStr="field"}var metadataKind,metadataName,ctx={kind:kindStr,name:isPrivate?"#"+name:name,isStatic:isStatic,isPrivate:isPrivate},decoratorFinishedRef={v:!1};if(0!==kind&&(ctx.addInitializer=createAddInitializerMethod(initializers,decoratorFinishedRef)),isPrivate){metadataKind=2,metadataName=Symbol(name);var access={};0===kind?(access.get=desc.get,access.set=desc.set):2===kind?access.get=function(){return desc.value}:(1!==kind&&3!==kind||(access.get=function(){return desc.get.call(this)}),1!==kind&&4!==kind||(access.set=function(v){desc.set.call(this,v)})),ctx.access=access}else metadataKind=1,metadataName=name;try{return dec(value,Object.assign(ctx,createMetadataMethodsForProperty(metadataMap,metadataKind,metadataName,decoratorFinishedRef)))}finally{decoratorFinishedRef.v=!0}}function assertNotFinished(decoratorFinishedRef,fnName){if(decoratorFinishedRef.v)throw new Error("attempted to call "+fnName+" after decoration was finished")}function assertMetadataKey(key){if("symbol"!=typeof key)throw new TypeError("Metadata keys must be symbols, received: "+key)}function assertCallable(fn,hint){if("function"!=typeof fn)throw new TypeError(hint+" must be a function")}function assertValidReturnValue(kind,value){var type=typeof value;if(1===kind){if("object"!==type||null===value)throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0");void 0!==value.get&&assertCallable(value.get,"accessor.get"),void 0!==value.set&&assertCallable(value.set,"accessor.set"),void 0!==value.init&&assertCallable(value.init,"accessor.init"),void 0!==value.initializer&&assertCallable(value.initializer,"accessor.initializer")}else if("function"!==type){var hint;throw hint=0===kind?"field":10===kind?"class":"method",new TypeError(hint+" decorators must return a function or void 0")}}function getInit(desc){var initializer;return null==(initializer=desc.init)&&(initializer=desc.initializer)&&"undefined"!=typeof console&&console.warn(".initializer has been renamed to .init as of March 2022"),initializer}function applyMemberDec(ret,base,decInfo,name,kind,isStatic,isPrivate,metadataMap,initializers){var desc,initializer,value,newValue,get,set,decs=decInfo[0];if(isPrivate?desc=0===kind||1===kind?{get:decInfo[3],set:decInfo[4]}:3===kind?{get:decInfo[3]}:4===kind?{set:decInfo[3]}:{value:decInfo[3]}:0!==kind&&(desc=Object.getOwnPropertyDescriptor(base,name)),1===kind?value={get:desc.get,set:desc.set}:2===kind?value=desc.value:3===kind?value=desc.get:4===kind&&(value=desc.set),"function"==typeof decs)void 0!==(newValue=memberDec(decs,name,desc,metadataMap,initializers,kind,isStatic,isPrivate,value))&&(assertValidReturnValue(kind,newValue),0===kind?initializer=newValue:1===kind?(initializer=getInit(newValue),get=newValue.get||value.get,set=newValue.set||value.set,value={get:get,set:set}):value=newValue);else for(var i=decs.length-1;i>=0;i--){var newInit;if(void 0!==(newValue=memberDec(decs[i],name,desc,metadataMap,initializers,kind,isStatic,isPrivate,value)))assertValidReturnValue(kind,newValue),0===kind?newInit=newValue:1===kind?(newInit=getInit(newValue),get=newValue.get||value.get,set=newValue.set||value.set,value={get:get,set:set}):value=newValue,void 0!==newInit&&(void 0===initializer?initializer=newInit:"function"==typeof initializer?initializer=[initializer,newInit]:initializer.push(newInit))}if(0===kind||1===kind){if(void 0===initializer)initializer=function(instance,init){return init};else if("function"!=typeof initializer){var ownInitializers=initializer;initializer=function(instance,init){for(var value=init,i=0;i<ownInitializers.length;i++)value=ownInitializers[i].call(instance,value);return value}}else{var originalInitializer=initializer;initializer=function(instance,init){return originalInitializer.call(instance,init)}}ret.push(initializer)}0!==kind&&(1===kind?(desc.get=value.get,desc.set=value.set):2===kind?desc.value=value:3===kind?desc.get=value:4===kind&&(desc.set=value),isPrivate?1===kind?(ret.push((function(instance,args){return value.get.call(instance,args)})),ret.push((function(instance,args){return value.set.call(instance,args)}))):2===kind?ret.push(value):ret.push((function(instance,args){return value.call(instance,args)})):Object.defineProperty(base,name,desc))}function applyMemberDecs(ret,Class,protoMetadataMap,staticMetadataMap,decInfos){for(var protoInitializers,staticInitializers,existingProtoNonFields=new Map,existingStaticNonFields=new Map,i=0;i<decInfos.length;i++){var decInfo=decInfos[i];if(Array.isArray(decInfo)){var base,metadataMap,initializers,kind=decInfo[1],name=decInfo[2],isPrivate=decInfo.length>3,isStatic=kind>=5;if(isStatic?(base=Class,metadataMap=staticMetadataMap,0!==(kind-=5)&&(initializers=staticInitializers=staticInitializers||[])):(base=Class.prototype,metadataMap=protoMetadataMap,0!==kind&&(initializers=protoInitializers=protoInitializers||[])),0!==kind&&!isPrivate){var existingNonFields=isStatic?existingStaticNonFields:existingProtoNonFields,existingKind=existingNonFields.get(name)||0;if(!0===existingKind||3===existingKind&&4!==kind||4===existingKind&&3!==kind)throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+name);!existingKind&&kind>2?existingNonFields.set(name,kind):existingNonFields.set(name,!0)}applyMemberDec(ret,base,decInfo,name,kind,isStatic,isPrivate,metadataMap,initializers)}}pushInitializers(ret,protoInitializers),pushInitializers(ret,staticInitializers)}function pushInitializers(ret,initializers){initializers&&ret.push((function(instance){for(var i=0;i<initializers.length;i++)initializers[i].call(instance);return instance}))}function applyClassDecs(ret,targetClass,metadataMap,classDecs){if(classDecs.length>0){for(var initializers=[],newClass=targetClass,name=targetClass.name,i=classDecs.length-1;i>=0;i--){var decoratorFinishedRef={v:!1};try{var ctx=Object.assign({kind:"class",name:name,addInitializer:createAddInitializerMethod(initializers,decoratorFinishedRef)},createMetadataMethodsForProperty(metadataMap,0,name,decoratorFinishedRef)),nextNewClass=classDecs[i](newClass,ctx)}finally{decoratorFinishedRef.v=!0}void 0!==nextNewClass&&(assertValidReturnValue(10,nextNewClass),newClass=nextNewClass)}ret.push(newClass,(function(){for(var i=0;i<initializers.length;i++)initializers[i].call(newClass)}))}}export default function applyDecs(targetClass,memberDecs,classDecs){var ret=[],staticMetadataMap={},protoMetadataMap={};return applyMemberDecs(ret,targetClass,protoMetadataMap,staticMetadataMap,memberDecs),convertMetadataMapToFinal(targetClass.prototype,protoMetadataMap),applyClassDecs(ret,targetClass,staticMetadataMap,classDecs),convertMetadataMapToFinal(targetClass,staticMetadataMap),ret}',
),
asyncGeneratorDelegate: helper(
"7.0.0-beta.0",
'export default function _asyncGeneratorDelegate(inner,awaitWrap){var iter={},waiting=!1;function pump(key,value){return waiting=!0,value=new Promise((function(resolve){resolve(inner[key](value))})),{done:!1,value:awaitWrap(value)}}return iter["undefined"!=typeof Symbol&&Symbol.iterator||"@@iterator"]=function(){return this},iter.next=function(value){return waiting?(waiting=!1,value):pump("next",value)},"function"==typeof inner.throw&&(iter.throw=function(value){if(waiting)throw waiting=!1,value;return pump("throw",value)}),"function"==typeof inner.return&&(iter.return=function(value){return waiting?(waiting=!1,value):pump("return",value)}),iter}',
'import OverloadYield from"OverloadYield";export default function _asyncGeneratorDelegate(inner){var iter={},waiting=!1;function pump(key,value){return waiting=!0,value=new Promise((function(resolve){resolve(inner[key](value))})),{done:!1,value:new OverloadYield(value,1)}}return iter["undefined"!=typeof Symbol&&Symbol.iterator||"@@iterator"]=function(){return this},iter.next=function(value){return waiting?(waiting=!1,value):pump("next",value)},"function"==typeof inner.throw&&(iter.throw=function(value){if(waiting)throw waiting=!1,value;return pump("throw",value)}),"function"==typeof inner.return&&(iter.return=function(value){return waiting?(waiting=!1,value):pump("return",value)}),iter}',
),
asyncIterator: helper(
"7.15.9",
'export default function _asyncIterator(iterable){var method,async,sync,retry=2;for("undefined"!=typeof Symbol&&(async=Symbol.asyncIterator,sync=Symbol.iterator);retry--;){if(async&&null!=(method=iterable[async]))return method.call(iterable);if(sync&&null!=(method=iterable[sync]))return new AsyncFromSyncIterator(method.call(iterable));async="@@asyncIterator",sync="@@iterator"}throw new TypeError("Object is not async iterable")}function AsyncFromSyncIterator(s){function AsyncFromSyncIteratorContinuation(r){if(Object(r)!==r)return Promise.reject(new TypeError(r+" is not an object."));var done=r.done;return Promise.resolve(r.value).then((function(value){return{value:value,done:done}}))}return AsyncFromSyncIterator=function(s){this.s=s,this.n=s.next},AsyncFromSyncIterator.prototype={s:null,n:null,next:function(){return AsyncFromSyncIteratorContinuation(this.n.apply(this.s,arguments))},return:function(value){var ret=this.s.return;return void 0===ret?Promise.resolve({value:value,done:!0}):AsyncFromSyncIteratorContinuation(ret.apply(this.s,arguments))},throw:function(value){var thr=this.s.return;return void 0===thr?Promise.reject(value):AsyncFromSyncIteratorContinuation(thr.apply(this.s,arguments))}},new AsyncFromSyncIterator(s)}',
),
awaitAsyncGenerator: helper(
"7.0.0-beta.0",
'import AwaitValue from"AwaitValue";export default function _awaitAsyncGenerator(value){return new AwaitValue(value)}',
'import OverloadYield from"OverloadYield";export default function _awaitAsyncGenerator(value){return new OverloadYield(value,0)}',
),
jsx: helper(
"7.0.0-beta.0",
Expand Down
12 changes: 7 additions & 5 deletions packages/babel-helpers/src/helpers.ts
Expand Up @@ -16,11 +16,13 @@ const helper = (minVersion: string) => (tpl: TemplateStringsArray) => ({
ast: () => template.program.ast(tpl),
});

helpers.AwaitValue = helper("7.0.0-beta.0")`
export default function _AwaitValue(value) {
this.wrapped = value;
}
`;
if (!process.env.BABEL_8_BREAKING) {
helpers.AwaitValue = helper("7.0.0-beta.0")`
export default function _AwaitValue(value) {
this.wrapped = value;
}
`;
}

helpers.wrapAsyncGenerator = helper("7.0.0-beta.0")`
import AsyncGenerator from "AsyncGenerator";
Expand Down
30 changes: 24 additions & 6 deletions packages/babel-helpers/src/helpers/AsyncGenerator.js
@@ -1,6 +1,6 @@
/* @minVersion 7.0.0-beta.0 */

import AwaitValue from "AwaitValue";
import OverloadYield from "OverloadYield";

export default function AsyncGenerator(gen) {
var front, back;
Expand Down Expand Up @@ -28,13 +28,31 @@ export default function AsyncGenerator(gen) {
try {
var result = gen[key](arg);
var value = result.value;
var wrappedAwait = value instanceof AwaitValue;
var overloaded = value instanceof OverloadYield;

Promise.resolve(wrappedAwait ? value.wrapped : value).then(
Promise.resolve(overloaded ? value.v : value).then(
function (arg) {
if (wrappedAwait) {
resume(key === "return" ? "return" : "next", arg);
return;
if (overloaded) {
// Overloaded yield requires calling into the generator twice:
// - first we get the iterator result wrapped in a promise
// (the gen[key](arg) call above)
// - then we await it (the Promise.resolve call above)
// - then we give the result back to the iterator, so that it can:
// * if it was an await, use its result
// * if it was a yield*, possibly return the `done: true` signal
// so that yield* knows that the iterator is finished.
// This needs to happen in the second call, because in the
// first one `done: true` was hidden in the promise and thus
// not visible to the (sync) yield*.
// The other part of this implementation is in asyncGeneratorDelegate.
var nextKey = key === "return" ? "return" : "next";
if (!value.k || arg.done) {
// await or end of yield*
return resume(nextKey, arg);
} else {
// yield*, not done
arg = gen[nextKey](arg).value;
}
}

settle(result.done ? "return" : "normal", arg);
Expand Down
11 changes: 11 additions & 0 deletions packages/babel-helpers/src/helpers/OverloadYield.js
@@ -0,0 +1,11 @@
/* @minVersion 7.18.14 */

/*
* 'kind' is an enum:
* 0 => This yield was an await expression
* 1 => This yield comes from yield*
*/
export default function _OverloadYield(value, kind) {
this.v = value;
this.k = kind;
}
10 changes: 8 additions & 2 deletions packages/babel-helpers/src/helpers/asyncGeneratorDelegate.js
@@ -1,15 +1,21 @@
/* @minVersion 7.0.0-beta.0 */

export default function _asyncGeneratorDelegate(inner, awaitWrap) {
import OverloadYield from "OverloadYield";

export default function _asyncGeneratorDelegate(inner) {
var iter = {},
// See the comment in AsyncGenerator to understand what this is.
waiting = false;

function pump(key, value) {
waiting = true;
value = new Promise(function (resolve) {
resolve(inner[key](value));
});
return { done: false, value: awaitWrap(value) };
return {
done: false,
value: new OverloadYield(value, /* kind: delegate */ 1),
};
}

iter[(typeof Symbol !== "undefined" && Symbol.iterator) || "@@iterator"] =
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers/awaitAsyncGenerator.js
@@ -1,7 +1,7 @@
/* @minVersion 7.0.0-beta.0 */

import AwaitValue from "AwaitValue";
import OverloadYield from "OverloadYield";

export default function _awaitAsyncGenerator(value) {
return new AwaitValue(value);
return new OverloadYield(value, /* kind: await */ 0);
}
@@ -0,0 +1,25 @@
// https://github.com/tc39/ecma262/pull/2819

let done = false;
let inner = {
[Symbol.asyncIterator]: () => ({
next() {
if (done) {
return Promise.resolve({ done: true });
}
done = true;
return Promise.resolve({ done: false, value: Promise.resolve(0) });
},
}),
};

async function* outer() {
yield* inner;
}

return (async function () {
for await (let x of outer()) {
expect(x).toBeInstanceOf(Promise);
expect(await x).toBe(0);
}
})();

0 comments on commit 4165bd3

Please sign in to comment.