Browse Source

tryCatch: pass 1-element arrays. Fix #1158.

A bit barbaric solution, but probably good enough.
Non-local returns are implemented by throwing
one-element array with a value (plus setting it in-method
to know it belongs there).

Solution is to pass all one-element arrays thrown as an exception
uncaught in tryCatch:, which is used by on:do:.
Herbert Vojčík 9 years ago
parent
commit
c09d6d8b2e
4 changed files with 141 additions and 1 deletions
  1. 3 1
      src/Kernel-Methods.js
  2. 2 0
      src/Kernel-Methods.st
  3. 116 0
      src/Kernel-Tests.js
  4. 20 0
      src/Kernel-Tests.st

+ 3 - 1
src/Kernel-Methods.js

@@ -409,6 +409,8 @@ return $core.withContext(function($ctx1) {
 		try {
 			return self._value();
 		} catch(error) {
+			// pass non-local returns undetected
+			if (Array.isArray(error) && error.length === 1) throw error;
 			return aBlock._value_(error);
 		}
 	;
@@ -419,7 +421,7 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
-source: "tryCatch: aBlock\x0a\x09<\x0a\x09\x09try {\x0a\x09\x09\x09return self._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09return aBlock._value_(error);\x0a\x09\x09}\x0a\x09>",
+source: "tryCatch: aBlock\x0a\x09<\x0a\x09\x09try {\x0a\x09\x09\x09return self._value();\x0a\x09\x09} catch(error) {\x0a\x09\x09\x09// pass non-local returns undetected\x0a\x09\x09\x09if (Array.isArray(error) && error.length === 1) throw error;\x0a\x09\x09\x09return aBlock._value_(error);\x0a\x09\x09}\x0a\x09>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []

+ 2 - 0
src/Kernel-Methods.st

@@ -97,6 +97,8 @@ tryCatch: aBlock
 		try {
 			return self._value();
 		} catch(error) {
+			// pass non-local returns undetected
+			if (Array.isArray(error) && error.length === 1) throw error;
 			return aBlock._value_(error);
 		}
 	>

+ 116 - 0
src/Kernel-Tests.js

@@ -319,6 +319,74 @@ $globals.AnnouncerTest);
 
 
 $core.addClass('BlockClosureTest', $globals.TestCase, [], 'Kernel-Tests');
+$core.addMethod(
+$core.method({
+selector: "localReturnOnDoCatch",
+protocol: 'fixture',
+fn: function (){
+var self=this;
+function $Error(){return $globals.Error||(typeof Error=="undefined"?nil:Error)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$recv((function(){
+throw $early=[(2)];
+
+}))._on_do_($Error(),(function(){
+
+}));
+return (3);
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"localReturnOnDoCatch",{},$globals.BlockClosureTest)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "localReturnOnDoCatch\x0a    [ ^ 2 ] on: Error do: [].\x0a    ^ 3",
+referencedClasses: ["Error"],
+//>>excludeEnd("ide");
+messageSends: ["on:do:"]
+}),
+$globals.BlockClosureTest);
+
+$core.addMethod(
+$core.method({
+selector: "localReturnOnDoMiss",
+protocol: 'fixture',
+fn: function (){
+var self=this;
+function $Class(){return $globals.Class||(typeof Class=="undefined"?nil:Class)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $early={};
+try {
+$recv((function(){
+throw $early=[(2)];
+
+}))._on_do_($Class(),(function(){
+
+}));
+return (3);
+}
+catch(e) {if(e===$early)return e[0]; throw e}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"localReturnOnDoMiss",{},$globals.BlockClosureTest)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "localReturnOnDoMiss\x0a    [ ^ 2 ] on: Class do: [].\x0a    ^ 3",
+referencedClasses: ["Class"],
+//>>excludeEnd("ide");
+messageSends: ["on:do:"]
+}),
+$globals.BlockClosureTest);
+
 $core.addMethod(
 $core.method({
 selector: "testCanClearInterval",
@@ -617,6 +685,54 @@ messageSends: ["timeout:", "valueWithTimeout:", "async:", "on:do:", "assert:", "
 }),
 $globals.BlockClosureTest);
 
+$core.addMethod(
+$core.method({
+selector: "testLocalReturnOnDoCatch",
+protocol: 'tests',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._assert_equals_(self._localReturnOnDoCatch(),(2));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testLocalReturnOnDoCatch",{},$globals.BlockClosureTest)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testLocalReturnOnDoCatch\x0a\x09self assert: self localReturnOnDoCatch equals: 2",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["assert:equals:", "localReturnOnDoCatch"]
+}),
+$globals.BlockClosureTest);
+
+$core.addMethod(
+$core.method({
+selector: "testLocalReturnOnDoMiss",
+protocol: 'tests',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._assert_equals_(self._localReturnOnDoMiss(),(2));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"testLocalReturnOnDoMiss",{},$globals.BlockClosureTest)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "testLocalReturnOnDoMiss\x0a\x09self assert: self localReturnOnDoMiss equals: 2",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["assert:equals:", "localReturnOnDoMiss"]
+}),
+$globals.BlockClosureTest);
+
 $core.addMethod(
 $core.method({
 selector: "testNewWithValues",

+ 20 - 0
src/Kernel-Tests.st

@@ -90,6 +90,18 @@ TestCase subclass: #BlockClosureTest
 	instanceVariableNames: ''
 	package: 'Kernel-Tests'!
 
+!BlockClosureTest methodsFor: 'fixture'!
+
+localReturnOnDoCatch
+    [ ^ 2 ] on: Error do: [].
+    ^ 3
+!
+
+localReturnOnDoMiss
+    [ ^ 2 ] on: Class do: [].
+    ^ 3
+! !
+
 !BlockClosureTest methodsFor: 'tests'!
 
 testCanClearInterval
@@ -136,6 +148,14 @@ testExceptionSemantics
 	]) valueWithTimeout: 0
 !
 
+testLocalReturnOnDoCatch
+	self assert: self localReturnOnDoCatch equals: 2
+!
+
+testLocalReturnOnDoMiss
+	self assert: self localReturnOnDoMiss equals: 2
+!
+
 testNewWithValues
 <
 	function TestConstructor(arg1, arg2, arg3) {}