Browse Source

Reify NonLifoReturn in then:/on:do:/catch:, too.

Herby Vojčík 7 months ago
parent
commit
08a7172372

+ 2 - 2
CHANGELOG

@@ -1,10 +1,10 @@
-11 Oct 2020 - Release 0.29.6
+12 Oct 2020 - Release 0.29.7
 ===================================
 
 * A model in `Promise new: [ :model | ... ]` reified as PromiseExecution.
 * Non-LIFO return represented by own Error subclass NonLifoReturn.
 
-Commits: https://lolg.it/amber/amber/commits/0.29.6.
+Commits: https://lolg.it/amber/amber/commits/0.29.7.
 
 
 7 Oct 2020 - Release 0.29.5

+ 2 - 1
lang/API-CHANGES.txt

@@ -1,8 +1,9 @@
-0.29.6:
+0.29.7:
 
 * Add TPromiseModel with unary value / signal passing to 1-arg.
   * Promise class as well as PromiseExecution use it.
 * Add class NonLifoReturn.
+  * Including convenient NonLifoReturn class >> #reifyIfFeasible:.
 
 + Error class >>
   + messageText:

+ 27 - 0
lang/src/Kernel-Exceptions.js

@@ -945,6 +945,33 @@ return self;
 $globals.NonLifoReturn);
 
 
+$core.addMethod(
+$core.method({
+selector: "reifyIfFeasible:",
+protocol: "instance creation",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anObject"],
+source: "reifyIfFeasible: anObject\x0a\x09\x22If anObject represents non-local return, reify it as my instance.\x0a\x09Otherwise, return anObject as-is.\x22\x0a\x09<inlineJS: '\x0a\x09\x09return Array.isArray(anObject) && anObject.length === 1 ?\x0a\x09\x09\x09$self._value_(anObject[0]) : anObject\x0a\x09'>",
+referencedClasses: [],
+//>>excludeEnd("ide");
+pragmas: [["inlineJS:", ["\x0a\x09\x09return Array.isArray(anObject) && anObject.length === 1 ?\x0a\x09\x09\x09$self._value_(anObject[0]) : anObject\x0a\x09"]]],
+messageSends: []
+}, function ($methodClass){ return function (anObject){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+
+		return Array.isArray(anObject) && anObject.length === 1 ?
+			$self._value_(anObject[0]) : anObject
+	;
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"reifyIfFeasible:",{anObject:anObject})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.NonLifoReturn.a$cls);
+
 $core.addMethod(
 $core.method({
 selector: "value:",

+ 9 - 0
lang/src/Kernel-Exceptions.st

@@ -262,6 +262,15 @@ value: anObject
 
 !NonLifoReturn class methodsFor: 'instance creation'!
 
+reifyIfFeasible: anObject
+	"If anObject represents non-local return, reify it as my instance.
+	Otherwise, return anObject as-is."
+	<inlineJS: '
+		return Array.isArray(anObject) && anObject.length === 1 ?
+			$self._value_(anObject[0]) : anObject
+	'>
+!
+
 value: anObject
 	^ super new
 		value: anObject;

+ 7 - 29
lang/src/Kernel-Infrastructure.js

@@ -3087,11 +3087,11 @@ selector: "asSmalltalkException:",
 protocol: "error handling",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["anObject"],
-source: "asSmalltalkException: anObject\x0a\x09\x22A JavaScript exception may be thrown.\x0a\x09We then need to convert it back to a Smalltalk object\x22\x0a\x09\x0a\x09^ anObject\x0a\x09\x09ifNil: [ [ self error: 'Error: nil' ] on: Error do: [ :e | e ] ]\x0a\x09\x09ifNotNil: [\x0a\x09\x09\x09(self isError: anObject)\x0a\x09\x09\x09\x09ifTrue: [ anObject ]\x0a\x09\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09\x09(self isNonLocalReturn: anObject)\x0a\x09\x09\x09\x09\x09\x09ifTrue: [ NonLifoReturn value: anObject first ]\x0a\x09\x09\x09\x09\x09\x09ifFalse: [ JavaScriptException on: anObject ] ] ]",
+source: "asSmalltalkException: anObject\x0a\x09\x22A JavaScript exception may be thrown.\x0a\x09We then need to convert it back to a Smalltalk object\x22\x0a\x09\x0a\x09^ anObject\x0a\x09\x09ifNil: [ [ self error: 'Error: nil' ] on: Error do: [ :e | e ] ]\x0a\x09\x09ifNotNil: [\x0a\x09\x09\x09(self isError: anObject)\x0a\x09\x09\x09\x09ifTrue: [ anObject ]\x0a\x09\x09\x09\x09ifFalse: [\x0a\x09\x09\x09\x09\x09| asNonLocalReturn |\x0a\x09\x09\x09\x09\x09asNonLocalReturn := NonLifoReturn reifyIfFeasible: anObject.\x0a\x09\x09\x09\x09\x09asNonLocalReturn == anObject\x0a\x09\x09\x09\x09\x09\x09ifFalse: [ asNonLocalReturn ]\x0a\x09\x09\x09\x09\x09\x09ifTrue: [ JavaScriptException on: anObject ] ] ]",
 referencedClasses: ["Error", "NonLifoReturn", "JavaScriptException"],
 //>>excludeEnd("ide");
 pragmas: [],
-messageSends: ["ifNil:ifNotNil:", "on:do:", "error:", "ifTrue:ifFalse:", "isError:", "isNonLocalReturn:", "value:", "first", "on:"]
+messageSends: ["ifNil:ifNotNil:", "on:do:", "error:", "ifTrue:ifFalse:", "isError:", "reifyIfFeasible:", "ifFalse:ifTrue:", "==", "on:"]
 }, function ($methodClass){ return function (anObject){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -3114,10 +3114,12 @@ return e;
 if($core.assert($self._isError_(anObject))){
 return anObject;
 } else {
-if($core.assert($self._isNonLocalReturn_(anObject))){
-return $recv($globals.NonLifoReturn)._value_($recv(anObject)._first());
-} else {
+var asNonLocalReturn;
+asNonLocalReturn=$recv($globals.NonLifoReturn)._reifyIfFeasible_(anObject);
+if($core.assert($recv(asNonLocalReturn).__eq_eq(anObject))){
 return $recv($globals.JavaScriptException)._on_(anObject);
+} else {
+return asNonLocalReturn;
 }
 }
 }
@@ -3485,30 +3487,6 @@ return false;
 }; }),
 $globals.SmalltalkImage);
 
-$core.addMethod(
-$core.method({
-selector: "isNonLocalReturn:",
-protocol: "testing",
-//>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["anObject"],
-source: "isNonLocalReturn: anObject\x0a\x09<inlineJS: 'return Array.isArray(anObject) && anObject.length === 1'>",
-referencedClasses: [],
-//>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["return Array.isArray(anObject) && anObject.length === 1"]]],
-messageSends: []
-}, function ($methodClass){ return function (anObject){
-var self=this,$self=this;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-return $core.withContext(function($ctx1) {
-//>>excludeEnd("ctx");
-return Array.isArray(anObject) && anObject.length === 1;
-return self;
-//>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"isNonLocalReturn:",{anObject:anObject})});
-//>>excludeEnd("ctx");
-}; }),
-$globals.SmalltalkImage);
-
 $core.addMethod(
 $core.method({
 selector: "isSmalltalkObject:",

+ 5 - 7
lang/src/Kernel-Infrastructure.st

@@ -943,9 +943,11 @@ asSmalltalkException: anObject
 			(self isError: anObject)
 				ifTrue: [ anObject ]
 				ifFalse: [
-					(self isNonLocalReturn: anObject)
-						ifTrue: [ NonLifoReturn value: anObject first ]
-						ifFalse: [ JavaScriptException on: anObject ] ] ]
+					| asNonLocalReturn |
+					asNonLocalReturn := NonLifoReturn reifyIfFeasible: anObject.
+					asNonLocalReturn == anObject
+						ifFalse: [ asNonLocalReturn ]
+						ifTrue: [ JavaScriptException on: anObject ] ] ]
 !
 
 try: actionBlock ifTrue: aBlock catch: anotherBlock
@@ -1110,10 +1112,6 @@ isError: anObject
 	^ (self isSmalltalkObject: anObject) and: [ anObject isError ]
 !
 
-isNonLocalReturn: anObject
-	<inlineJS: 'return Array.isArray(anObject) && anObject.length === 1'>
-!
-
 isSmalltalkObject: anObject
 	"Consider anObject a Smalltalk object if it has a 'a$cls' property.
 	Note that this may be unaccurate"

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

@@ -298,10 +298,10 @@ selector: "try:",
 protocol: "evaluating",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
-source: "try: aBlock\x0a\x09\x22Executes a block 'in the context of a promise'.\x0a\x09That is, if it ends with an error, promise is rejected.\x0a\x09Non-local returns are also treated as an error and reified as rejections.\x22\x0a\x09<inlineJS: '\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_(\x0a\x09\x09\x09\x09Array.isArray(error) && error.length === 1 ?\x0a\x09\x09\x09\x09\x09$globals.NonLifoReturn._value_(error[0]) :\x0a\x09\x09\x09\x09\x09error\x0a\x09\x09\x09);\x0a\x09\x09}\x0a\x09'>",
+source: "try: aBlock\x0a\x09\x22Executes a block 'in the context of a promise'.\x0a\x09That is, if it ends with an error, promise is rejected.\x0a\x09Non-local returns are also treated as an error and reified as rejections.\x22\x0a\x09<inlineJS: '\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_($globals.NonLifoReturn._reifyIfFeasible_(error));\x0a\x09\x09}\x0a\x09'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_(\x0a\x09\x09\x09\x09Array.isArray(error) && error.length === 1 ?\x0a\x09\x09\x09\x09\x09$globals.NonLifoReturn._value_(error[0]) :\x0a\x09\x09\x09\x09\x09error\x0a\x09\x09\x09);\x0a\x09\x09}\x0a\x09"]]],
+pragmas: [["inlineJS:", ["\x0a\x09\x09try {\x0a\x09\x09\x09return aBlock._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09$self._signal_($globals.NonLifoReturn._reifyIfFeasible_(error));\x0a\x09\x09}\x0a\x09"]]],
 messageSends: []
 }, function ($methodClass){ return function (aBlock){
 var self=this,$self=this;
@@ -312,11 +312,7 @@ return $core.withContext(function($ctx1) {
 		try {
 			return aBlock._value();
 		} catch(error) {
-			$self._signal_(
-				Array.isArray(error) && error.length === 1 ?
-					$globals.NonLifoReturn._value_(error[0]) :
-					error
-			);
+			$self._signal_($globals.NonLifoReturn._reifyIfFeasible_(error));
 		}
 	;
 return self;
@@ -489,17 +485,19 @@ selector: "catch:",
 protocol: "promises",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
-source: "catch: aBlock\x0a<inlineJS: 'return self.then(null, function (err) { return aBlock._value_(err); })'>",
+source: "catch: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {\x0a\x09return aBlock._value_($globals.NonLifoReturn._reifyIfFeasible_(err));\x0a})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["return self.then(null, function (err) { return aBlock._value_(err); })"]]],
+pragmas: [["inlineJS:", ["return self.then(null, function (err) {\x0a\x09return aBlock._value_($globals.NonLifoReturn._reifyIfFeasible_(err));\x0a})"]]],
 messageSends: []
 }, function ($methodClass){ return function (aBlock){
 var self=this,$self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) { return aBlock._value_(err); });
+return self.then(null, function (err) {
+	return aBlock._value_($globals.NonLifoReturn._reifyIfFeasible_(err));
+});
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"catch:",{aBlock:aBlock})});
@@ -531,10 +529,10 @@ selector: "on:do:",
 protocol: "promises",
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aClass", "aBlock"],
-source: "on: aClass do: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {\x0a    if (err._isKindOf_(aClass)) return aBlock._value_(err);\x0a    else throw err;\x0a})'>",
+source: "on: aClass do: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {\x0a\x09var reified = $globals.NonLifoReturn._reifyIfFeasible_(err);\x0a    if (reified._isKindOf_(aClass)) return aBlock._value_(reified);\x0a    else throw err;\x0a})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-pragmas: [["inlineJS:", ["return self.then(null, function (err) {\x0a    if (err._isKindOf_(aClass)) return aBlock._value_(err);\x0a    else throw err;\x0a})"]]],
+pragmas: [["inlineJS:", ["return self.then(null, function (err) {\x0a\x09var reified = $globals.NonLifoReturn._reifyIfFeasible_(err);\x0a    if (reified._isKindOf_(aClass)) return aBlock._value_(reified);\x0a    else throw err;\x0a})"]]],
 messageSends: []
 }, function ($methodClass){ return function (aClass,aBlock){
 var self=this,$self=this;
@@ -542,7 +540,8 @@ var self=this,$self=this;
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
 return self.then(null, function (err) {
-    if (err._isKindOf_(aClass)) return aBlock._value_(err);
+	var reified = $globals.NonLifoReturn._reifyIfFeasible_(err);
+    if (reified._isKindOf_(aClass)) return aBlock._value_(reified);
     else throw err;
 });
 return self;

+ 6 - 7
lang/src/Kernel-Promises.st

@@ -92,11 +92,7 @@ try: aBlock
 		try {
 			return aBlock._value();
 		} catch(error) {
-			$self._signal_(
-				Array.isArray(error) && error.length === 1 ?
-					$globals.NonLifoReturn._value_(error[0]) :
-					error
-			);
+			$self._signal_($globals.NonLifoReturn._reifyIfFeasible_(error));
 		}
 	'>
 ! !
@@ -146,12 +142,15 @@ Trait named: #TThenable
 !TThenable methodsFor: 'promises'!
 
 catch: aBlock
-<inlineJS: 'return self.then(null, function (err) { return aBlock._value_(err); })'>
+<inlineJS: 'return self.then(null, function (err) {
+	return aBlock._value_($globals.NonLifoReturn._reifyIfFeasible_(err));
+})'>
 !
 
 on: aClass do: aBlock
 <inlineJS: 'return self.then(null, function (err) {
-    if (err._isKindOf_(aClass)) return aBlock._value_(err);
+	var reified = $globals.NonLifoReturn._reifyIfFeasible_(err);
+    if (reified._isKindOf_(aClass)) return aBlock._value_(reified);
     else throw err;
 })'>
 !

+ 151 - 0
lang/src/Kernel-Tests.js

@@ -15672,6 +15672,58 @@ $globals.PointTest);
 
 
 $core.addClass("PromiseTest", $globals.TestCase, "Kernel-Tests");
+$core.addMethod(
+$core.method({
+selector: "testPromiseCatchOnDoWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseCatchOnDoWithNonLocalReturn\x0a\x09self timeout: 20.\x0a\x09^ ((Promise signal: 4) catch: [ :err | ^ 'Caught ', err asString ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Caught 4' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "catch:", "signal:", ",", "asString", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((20));
+return $recv($recv($recv($globals.Promise)._signal_((4)))._catch_((function(err){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+throw $early=["Caught ".__comma($recv(err)._asString())];
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({err:err},$ctx1,1)});
+//>>excludeEnd("ctx");
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Caught 4");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,3)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseCatchOnDoWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
 $core.addMethod(
 $core.method({
 selector: "testPromiseExecutorAsyncDoWithNonLocalReturn",
@@ -16448,6 +16500,105 @@ return $self._assert_equals_(result,nil);
 }; }),
 $globals.PromiseTest);
 
+$core.addMethod(
+$core.method({
+selector: "testPromiseThenCatchWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseThenCatchWithNonLocalReturn\x0a\x09self timeout: 20.\x0a\x09^ (Promise new then: [ ^ 'Intentional' ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09catch: [ :err |\x0a\x09\x09\x09self assert: (err isKindOf: NonLifoReturn) description: 'Expected a NonLifoReturn'.\x0a\x09\x09\x09self assert: err value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:catch:", "then:", "new", "assert:description:", "isKindOf:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((20));
+return $recv($recv($recv($globals.Promise)._new())._then_((function(){
+throw $early=["Intentional"];
+
+})))._then_catch_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return [$self._assert_description_(false,"Should not have been resolved")
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+,$ctx2.sendIdx["assert:description:"]=1
+//>>excludeEnd("ctx");
+][0];
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}),(function(err){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$self._assert_description_($recv(err)._isKindOf_($globals.NonLifoReturn),"Expected a NonLifoReturn");
+return $self._assert_equals_($recv(err)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({err:err},$ctx1,3)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseThenCatchWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
+$core.addMethod(
+$core.method({
+selector: "testPromiseThenOnDoWithNonLocalReturn",
+protocol: " tests",
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testPromiseThenOnDoWithNonLocalReturn\x0a\x09self timeout: 20.\x0a\x09^ (Promise new then: [ ^ 'Intentional' ])\x0a\x09\x09then: [ self assert: false description: 'Should not have been resolved' ]\x0a\x09\x09on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]",
+referencedClasses: ["Promise", "NonLifoReturn"],
+//>>excludeEnd("ide");
+pragmas: [],
+messageSends: ["timeout:", "then:on:do:", "then:", "new", "assert:description:", "assert:equals:", "value"]
+}, function ($methodClass){ return function (){
+var self=this,$self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$self._timeout_((20));
+return $recv($recv($recv($globals.Promise)._new())._then_((function(){
+throw $early=["Intentional"];
+
+})))._then_on_do_((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_description_(false,"Should not have been resolved");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}),$globals.NonLifoReturn,(function(nonlifo){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $self._assert_equals_($recv(nonlifo)._value(),"Intentional");
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({nonlifo:nonlifo},$ctx1,3)});
+//>>excludeEnd("ctx");
+}));
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testPromiseThenOnDoWithNonLocalReturn",{})});
+//>>excludeEnd("ctx");
+}; }),
+$globals.PromiseTest);
+
 $core.addMethod(
 $core.method({
 selector: "testPromiseWithAsyncPassingRejectingExecutor",

+ 23 - 0
lang/src/Kernel-Tests.st

@@ -2786,6 +2786,13 @@ TestCase subclass: #PromiseTest
 
 !PromiseTest methodsFor: ' tests'!
 
+testPromiseCatchOnDoWithNonLocalReturn
+	self timeout: 20.
+	^ ((Promise signal: 4) catch: [ :err | ^ 'Caught ', err asString ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Caught 4' ]
+!
+
 testPromiseExecutorAsyncDoWithNonLocalReturn
 	self timeout: 40.
 	^ (Promise new: [ :m | [ m do: [ ^  'Intentional' ] ] fork ])
@@ -2878,6 +2885,22 @@ testPromiseNew
 		then: [ :result | self assert: result equals: nil ]
 !
 
+testPromiseThenCatchWithNonLocalReturn
+	self timeout: 20.
+	^ (Promise new then: [ ^ 'Intentional' ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		catch: [ :err |
+			self assert: (err isKindOf: NonLifoReturn) description: 'Expected a NonLifoReturn'.
+			self assert: err value equals: 'Intentional' ]
+!
+
+testPromiseThenOnDoWithNonLocalReturn
+	self timeout: 20.
+	^ (Promise new then: [ ^ 'Intentional' ])
+		then: [ self assert: false description: 'Should not have been resolved' ]
+		on: NonLifoReturn do: [ :nonlifo | self assert: nonlifo value equals: 'Intentional' ]
+!
+
 testPromiseWithAsyncPassingRejectingExecutor
 	self timeout: 60.
 	^ (Promise new: [ :m | [