Browse Source

Local-return-aware `Promise new:`. Fixes #1257.

Herby Vojčík 3 years ago
parent
commit
8394923393
4 changed files with 55 additions and 10 deletions
  1. 24 0
      lang/src/Compiler-Tests.js
  2. 6 0
      lang/src/Compiler-Tests.st
  3. 13 6
      lang/src/Kernel-Promises.js
  4. 12 4
      lang/src/Kernel-Promises.st

+ 24 - 0
lang/src/Compiler-Tests.js

@@ -1062,6 +1062,30 @@ return self;
 }; }),
 $globals.AbstractCompilerTest);
 
+$core.addMethod(
+$core.method({
+selector: "testPromiseWithAsyncExecutorAndLocalReturn",
+protocol: "tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseWithAsyncExecutorAndLocalReturn\x0a\x09self\x0a\x09\x09should: 'foo ^ Promise new: [ :m | [ 3 + 4 ] fork. ^ 5 ]'\x0a\x09\x09return: 5",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["should:return:"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+$self._should_return_("foo ^ Promise new: [ :m | [ 3 + 4 ] fork. ^ 5 ]",(5));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseWithAsyncExecutorAndLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.AbstractCompilerTest);
+
 $core.addMethod(
 $core.method({
 selector: "testReceiverEvaluatedOnceInSpecials",

+ 6 - 0
lang/src/Compiler-Tests.st

@@ -245,6 +245,12 @@ testPragmaJSStatement
 	self should: 'foo < inlineJS: ''return 2+3'' >' return: 5
 !
 
+testPromiseWithAsyncExecutorAndLocalReturn
+	self
+		should: 'foo ^ Promise new: [ :m | [ 3 + 4 ] fork. ^ 5 ]'
+		return: 5
+!
+
 testReceiverEvaluatedOnceInSpecials
 	self should: 'foo |x| x := 1. ^ {[ x := x+1 ] value ifNil: []. x}' return: {2. 2}.
 	self should: 'foo |xs| xs := {nil. nil}. ^ {[ xs removeLast ] value ifNotNil: []. xs}' return: {nil. {nil}}.

+ 13 - 6
lang/src/Kernel-Promises.js

@@ -145,20 +145,27 @@ selector: "new:",
 protocol: "instance creation",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
-source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: 'return new Promise(function (resolve, reject) {\x0a    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a    aBlock._value_(model);\x0a})'>",
+source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: '\x0a\x09var localReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) localReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (localReturn) throw localReturn; else return promise;\x0a'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["return new Promise(function (resolve, reject) {\x0a    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a    aBlock._value_(model);\x0a})"]]],
+pragmas: [["inlineJS:", ["\x0a\x09var localReturn = null,\x0a\x09\x09promise = new Promise(function (resolve, reject) {\x0a\x09\x09    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);\x0a\x09\x09    try { aBlock._value_(model); }\x0a\x09\x09\x09catch (ex) {\x0a\x09\x09\x09\x09if (Array.isArray(ex) && ex.length === 1) localReturn = ex;\x0a\x09\x09\x09\x09else reject(ex);\x0a\x09\x09\x09}\x0a\x09\x09});\x0a\x09if (localReturn) throw localReturn; else return promise;"]]],
 messageSends: []
 }, function ($methodClass){ return function (aBlock){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return new Promise(function (resolve, reject) {
-    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
-    aBlock._value_(model);
-});
+
+	var localReturn = null,
+		promise = new Promise(function (resolve, reject) {
+		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
+		    try { aBlock._value_(model); }
+			catch (ex) {
+				if (Array.isArray(ex) && ex.length === 1) localReturn = ex;
+				else reject(ex);
+			}
+		});
+	if (localReturn) throw localReturn; else return promise;;
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"new:",{aBlock:aBlock})});

+ 12 - 4
lang/src/Kernel-Promises.st

@@ -39,10 +39,18 @@ You should call model value: ... to resolve the promise
 and model signal: ... to reject the promise.
 If error happens during run of the block,
 promise is rejected with that error as well."
-<inlineJS: 'return new Promise(function (resolve, reject) {
-    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
-    aBlock._value_(model);
-})'>
+<inlineJS: '
+	var localReturn = null,
+		promise = new Promise(function (resolve, reject) {
+		    var model = $globals.PromiseExecution._resolveBlock_rejectBlock_(resolve, reject);
+		    try { aBlock._value_(model); }
+			catch (ex) {
+				if (Array.isArray(ex) && ex.length === 1) localReturn = ex;
+				else reject(ex);
+			}
+		});
+	if (localReturn) throw localReturn; else return promise;
+'>
 !
 
 signal: anObject