You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by gr...@apache.org on 2021/12/06 02:25:25 UTC

[royale-compiler] 01/02: Fixes #199

This is an automated email from the ASF dual-hosted git repository.

gregdove pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git

commit b2b85131d9c994dfb45e69edd3e9c8cd227cc074
Author: greg-dove <gr...@gmail.com>
AuthorDate: Mon Dec 6 15:15:45 2021 +1300

    Fixes #199
---
 .../royale/compiler/codegen/js/IJSEmitter.java     |   4 +
 .../compiler/internal/codegen/js/JSEmitter.java    |  12 ++
 .../internal/codegen/js/jx/CatchEmitter.java       |   4 +
 .../internal/codegen/js/jx/TryEmitter.java         | 237 ++++++++++++++++++++-
 .../codegen/js/royale/JSRoyaleEmitter.java         |   8 +
 .../royale/compiler/utils/DefinitionUtils.java     |  15 ++
 .../codegen/js/goog/TestGoogStatements.java        |   6 +-
 .../codegen/js/royale/TestRoyaleStatements.java    |   6 +-
 .../js/sourcemaps/TestSourceMapStatements.java     |  36 ++--
 9 files changed, 302 insertions(+), 26 deletions(-)

diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/codegen/js/IJSEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/codegen/js/IJSEmitter.java
index c20261c..99af8d7 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/codegen/js/IJSEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/codegen/js/IJSEmitter.java
@@ -24,6 +24,8 @@ import java.io.Writer;
 import org.apache.royale.compiler.codegen.as.IASEmitter;
 import org.apache.royale.compiler.definitions.IDefinition;
 import org.apache.royale.compiler.internal.codegen.js.JSSessionModel;
+import org.apache.royale.compiler.internal.tree.as.BinaryOperatorNodeBase;
+import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase;
 import org.apache.royale.compiler.tree.as.IASNode;
 import org.apache.royale.compiler.tree.as.IExpressionNode;
 import org.apache.royale.compiler.tree.as.ITypeNode;
@@ -49,4 +51,6 @@ public interface IJSEmitter extends IASEmitter, IMappingEmitter
     void emitClosureEnd(IASNode node, IDefinition nodeDef);
 
     void emitAssignmentCoercion(IExpressionNode assignedNode, IDefinition definition);
+
+    BinaryOperatorNodeBase getGeneratedTypeCheck(ExpressionNodeBase leftOperand, ExpressionNodeBase rightOperand);
 }
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/JSEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/JSEmitter.java
index 292746c..6ba481c 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/JSEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/JSEmitter.java
@@ -68,6 +68,7 @@ import org.apache.royale.compiler.internal.codegen.js.jx.WhileLoopEmitter;
 import org.apache.royale.compiler.internal.codegen.js.jx.WithEmitter;
 import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleDocEmitter;
 import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitterTokens;
+import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
 import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
 import org.apache.royale.compiler.internal.semantics.SemanticUtils;
 import org.apache.royale.compiler.internal.tree.as.*;
@@ -103,6 +104,7 @@ import org.apache.royale.compiler.tree.as.IWithNode;
 import com.google.debugging.sourcemap.FilePosition;
 
 import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
+import org.apache.royale.compiler.utils.DefinitionUtils;
 import org.apache.royale.compiler.utils.NativeUtils;
 
 /**
@@ -767,6 +769,10 @@ public class JSEmitter extends ASEmitter implements IJSEmitter
             getModel().needLanguage = true;
             return;
         }
+        //special case for 'rewritten' multicatch support - we avoid coercion because it is assured via an injected 'is' check immediately prior to declaration/assignment
+        if (DefinitionUtils.isRewrittenMultiCatchParam(assignedDef)) {
+            avoidCoercion = true;
+        }
         if (coercionStart == null
                 && !avoidCoercion
                 && assignedTypeDef !=null
@@ -930,4 +936,10 @@ public class JSEmitter extends ASEmitter implements IJSEmitter
 		}
     }
 
+    public BinaryOperatorNodeBase getGeneratedTypeCheck(ExpressionNodeBase leftOperand, ExpressionNodeBase rightOperand) {
+        //default to using 'instanceof' for compiler-generated type-checks, because it is native JS
+        BinaryOperatorInstanceOfNode check = new BinaryOperatorInstanceOfNode(null,leftOperand, rightOperand);
+        return check;
+    }
+
 }
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/CatchEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/CatchEmitter.java
index 12cb8bc..f9ed090 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/CatchEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/CatchEmitter.java
@@ -47,6 +47,10 @@ public class CatchEmitter extends JSSubEmitter implements
         startMapping(node, paramNode);
         writeToken(ASEmitterTokens.PAREN_CLOSE);
         endMapping(node);
+        if (TryEmitter.ROYALE_MULTI_CATCH_ERROR_NAME.equals(paramNode.getName())) {
+            //it is probably already obvious, but let's be explicit:
+            write("/* implicit multi-catch wrapper */ ");
+        }
         getWalker().walk(node.getStatementContentsNode());
     }
 }
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/TryEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/TryEmitter.java
index 943ba73..39cdfb5 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/TryEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/TryEmitter.java
@@ -21,10 +21,21 @@ package org.apache.royale.compiler.internal.codegen.js.jx;
 
 import org.apache.royale.compiler.codegen.ISubEmitter;
 import org.apache.royale.compiler.codegen.js.IJSEmitter;
+import org.apache.royale.compiler.constants.IASLanguageConstants;
 import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
 import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter;
-import org.apache.royale.compiler.tree.as.ITerminalNode;
-import org.apache.royale.compiler.tree.as.ITryNode;
+import org.apache.royale.compiler.internal.parsing.as.ASToken;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.internal.scopes.ASScope;
+import org.apache.royale.compiler.internal.scopes.CatchScope;
+import org.apache.royale.compiler.internal.semantics.PostProcessStep;
+import org.apache.royale.compiler.internal.tree.as.*;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.scopes.IASScope;
+import org.apache.royale.compiler.tree.as.*;
+
+import java.util.Collection;
+import java.util.EnumSet;
 
 public class TryEmitter extends JSSubEmitter implements
         ISubEmitter<ITryNode>
@@ -41,10 +52,14 @@ public class TryEmitter extends JSSubEmitter implements
         writeToken(ASEmitterTokens.TRY);
         endMapping(node);
         getWalker().walk(node.getStatementContentsNode());
-        for (int i = 0; i < node.getCatchNodeCount(); i++)
-        {
-            getWalker().walk(node.getCatchNode(i));
+        if (node.getCatchNodeCount() >= 1) {
+            if (node.getCatchNodeCount() == 1) {
+                getWalker().walk(node.getCatchNode(0));
+            } else {
+                getWalker().walk(codeGenMultipleCatchSupport(node));
+            }
         }
+
         ITerminalNode fnode = node.getFinallyNode();
         if (fnode != null)
         {
@@ -55,4 +70,216 @@ public class TryEmitter extends JSSubEmitter implements
             getWalker().walk(fnode);
         }
     }
+
+
+    //Everything below here is specific to Multi-Catch support
+    //--------------------------------------------------------
+    public static final String ROYALE_MULTI_CATCH_ERROR_NAME = "$$royaleMultiCatchErr";
+
+    private final EnumSet<PostProcessStep> postProcess = EnumSet.of(
+            PostProcessStep.POPULATE_SCOPE);
+
+    /**
+     * Create a new implementation of the multiple catch sequence to support the typed nature of the original
+     * implementation in the untyped JS runtime. Multiple catches of type catch (e:ErrorType) are expressed as
+     * if (caughtErr is ErrorType) { var e:ErrorType = caughtErr; ... original catch contents } else... other catch clauses else... throw caughtError
+     * @param node The original Try node to generate JS multi-catch support for
+     * @return a new Catch node that has the internally generated alternate implementation for multi-catch
+     */
+    private ICatchNode codeGenMultipleCatchSupport(ITryNode node) {
+        int catchCount = node.getCatchNodeCount();
+        boolean hasCatchAll = false;
+        IdentifierNode royaleErr = new IdentifierNode(ROYALE_MULTI_CATCH_ERROR_NAME);
+        IdentifierNode ErrorClassIdentifier = new IdentifierNode("Error");
+        ParameterNode argumentNode = new ParameterNode(royaleErr, ErrorClassIdentifier);
+        argumentNode.addChild(royaleErr);
+        argumentNode.addChild(royaleErr);
+        CatchNode wrapper = new CatchNode(argumentNode);
+        wrapper.setParent((NodeBase) node);
+        argumentNode.setParent(wrapper);
+        BlockNode wrapperBlock = wrapper.getContentsNode();
+        wrapperBlock.setContainerType(IContainerNode.ContainerType.BRACES);
+        wrapperBlock.setParent(wrapper);
+        wrapper.setSourcePath(node.getSourcePath());
+        wrapper.setLine(node.getEndLine());
+        wrapper.setEndLine(node.getEndLine());
+        wrapper.setColumn(node.getEndColumn()+1);
+        wrapper.setEndColumn(node.getEndColumn()+1);
+        IfNode ifNode = new IfNode(null);
+
+        for (int i = 0; i < catchCount; i++)
+        {
+            BaseStatementNode rewrittenCatch;
+            ICatchNode childCatch = node.getCatchNode(i);
+            int childChildren = childCatch.getStatementContentsNode().getChildCount();
+            ParameterNode catchParam = (ParameterNode)childCatch.getCatchParameterNode();
+
+            if (catchParam.getTypeNode().resolve(getProject()).equals(getProject().getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE))) {
+                hasCatchAll = true;
+                //if we are at first catch node when we encounter this, just short-circuit and return it, 'nothing else matters'
+                if (i == 0) {
+                    return childCatch;
+                }
+            }
+
+            if (hasCatchAll) {
+                TerminalNode terminalNode = createTerminalCatchAllNode(catchParam, royaleErr, (CatchNode) childCatch);
+                ifNode.addBranch(terminalNode);
+                rewrittenCatch = terminalNode;
+
+            } else {
+                ConditionalNode conditionalNode = createConditionalNode(catchParam, royaleErr, (CatchNode) childCatch);
+                ifNode.addBranch(conditionalNode);
+
+                conditionalNode.setSourceLocation(childCatch);
+                rewrittenCatch = conditionalNode;
+            }
+
+            for (int j = 0; j < childChildren; j++) {
+                rewrittenCatch.getContentsNode().addChild((NodeBase) childCatch.getStatementContentsNode().getChild(j));
+            }
+            if (hasCatchAll) {
+                //no point continuing, any following catch clauses will never execute, 'nothing else matters'
+                break;
+            }
+        }
+        if (!hasCatchAll) { //then we need to re-throw the original top-level error, as an 'uncaught' error
+            TerminalNode terminalNode = createTerminalThrowNode(royaleErr);
+            ifNode.addBranch(terminalNode);
+        }
+        wrapperBlock.addChild(ifNode);
+        wrapper.runPostProcess(postProcess,((NodeBase) node).getASScope());
+        return wrapper;
+    }
+
+    private void addVarStartToRewrittenCatch(ParameterNode parameterNode,  IdentifierNode assignedValue, BaseStatementNode parentNode, PseudoCatchBlock content, CatchNode originalCatch) {
+        content.setParent(parentNode);
+        content.setContainerType(IContainerNode.ContainerType.BRACES);
+        content.setSourceLocation(originalCatch.getContentsNode());
+        IdentifierNode name = new IdentifierNode(parameterNode.getName());
+        name.setSourceLocation(parameterNode.getNameExpressionNode());
+        ExpressionNodeBase type = parameterNode.getTypeNode().copyForInitializer(originalCatch);
+        
+        VariableNode varNode = new VariableNode(name,type);
+        varNode.setAssignedValue(null, assignedValue);
+
+        content.addChild(varNode);
+    }
+
+    private ConditionalNode createConditionalNode(ParameterNode parameterNode,IdentifierNode hoistedError, CatchNode originalCatch) {
+        // [else] if (hoistedError is parameterNodeClass) {
+        // var parameterNodeName = $$royaleMultiCatchErr;
+        // (followed by)...original catch clause contents
+        // }
+        PseudoCatchParam conditionalNode = new PseudoCatchParam((CatchScope) originalCatch.getScope());
+        BinaryOperatorNodeBase check = getEmitter().getGeneratedTypeCheck(hoistedError, parameterNode.getTypeNode());
+        check.setSourceLocation(parameterNode);
+        conditionalNode.setConditionalExpression(check);
+        check.setParent(conditionalNode);
+
+        addVarStartToRewrittenCatch(parameterNode, hoistedError, conditionalNode, (PseudoCatchBlock)conditionalNode.getContentsNode(), originalCatch );
+
+        return conditionalNode;
+    }
+
+
+    private TerminalNode createTerminalThrowNode(IdentifierNode hoistedError) {
+        // else { throw hoistedError; }
+        TerminalNode terminalNode = new TerminalNode(getElseToken());
+        //throw hoistedError (from ROYALE_MULTI_CATCH_ERROR_NAME)
+        ThrowNode throwNode = new ThrowNode(null);
+        //clone the Identifier to avoid issues with parenting chains
+        IdentifierNode localCopy = new IdentifierNode(hoistedError.getName());
+        localCopy.setSourceLocation(hoistedError);
+        throwNode.setStatementExpression(localCopy);
+        BlockNode contents = terminalNode.getContentsNode();
+        contents.setContainerType(IContainerNode.ContainerType.BRACES);
+        contents.setParent(terminalNode);
+        contents.addChild(throwNode);
+        return terminalNode;
+    }
+
+    private TerminalNode createTerminalCatchAllNode(ParameterNode parameterNode,IdentifierNode hoistedError, CatchNode originalCatch) {
+        //  else {
+        //  var parameterNodeName = $$royaleMultiCatchErr;
+        //  (followed by)...original catch clause contents
+        // }
+        PseudoCatchAllParam terminalNode = new PseudoCatchAllParam((CatchScope) originalCatch.getScope());
+
+        addVarStartToRewrittenCatch(parameterNode, hoistedError, terminalNode, (PseudoCatchBlock)terminalNode.getContentsNode(), originalCatch );
+
+        terminalNode.setSourceLocation(originalCatch);
+        return terminalNode;
+    }
+
+    static ASToken getElseToken(){
+        return new ASToken(ASTokenTypes.TOKEN_KEYWORD_ELSE, -1,-1, -1, -1, "else");
+    }
+}
+
+
+/**
+ * The following mainly exists because the original catch scope allows for multiple 'same name' catch parameter definitions
+ * that would otherwise clash if they were hoisted to the containing scope.
+ * This serves to simulate the same thing for the rewritten catch parameter definitions.
+ */
+class PseudoCatchBlock extends BlockNode implements IScopedNode {
+
+    protected ASScope scope;
+
+    public PseudoCatchBlock(CatchScope catchScope){
+        super();
+        scope = catchScope;
+
+    }
+
+    public ASScope getASScope(){
+        return scope;
+    }
+
+    @Override
+    public IASScope getScope(){
+        return scope;
+    }
+
+    @Override
+    protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems)
+    {
+        if (set.contains(PostProcessStep.POPULATE_SCOPE) ||
+                set.contains(PostProcessStep.RECONNECT_DEFINITIONS))
+        {
+            if (this.scope == null) {
+                this.scope = new CatchScope(scope);
+            }
+            this.scope.setContainingScope(scope);
+        }
+
+        super.analyze(set, this.scope, problems);
+    }
+
+    @Override
+    public void getAllImports(Collection<String> imports)
+    {
+        getContainingScope().getAllImports(imports);
+    }
+
+    @Override
+    public void getAllImportNodes(Collection<IImportNode> imports)
+    {
+        getContainingScope().getAllImportNodes(imports);
+    }
+}
+
+class PseudoCatchParam extends ConditionalNode{
+    public PseudoCatchParam(CatchScope scope){
+        super(null);
+        this.contentsNode = new PseudoCatchBlock(scope);
+    }
+}
+
+class PseudoCatchAllParam extends TerminalNode{
+    public PseudoCatchAllParam(CatchScope scope){
+        super(TryEmitter.getElseToken());
+        this.contentsNode = new PseudoCatchBlock(scope);
+    }
 }
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/royale/JSRoyaleEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/royale/JSRoyaleEmitter.java
index 6cfd566..10bc04b 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/royale/JSRoyaleEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/royale/JSRoyaleEmitter.java
@@ -1649,4 +1649,12 @@ public class JSRoyaleEmitter extends JSGoogEmitter implements IJSRoyaleEmitter
         }
     }
 
+    @Override
+    public BinaryOperatorNodeBase getGeneratedTypeCheck(ExpressionNodeBase leftOperand, ExpressionNodeBase rightOperand) {
+        //using 'is' in this case for compiler-generated type-checks, because it is more robust with the framework support
+        BinaryOperatorIsNode check = new BinaryOperatorIsNode(null,leftOperand, rightOperand);
+        getModel().needLanguage = true;
+        return check;
+    }
+
 }
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/utils/DefinitionUtils.java b/compiler-jx/src/main/java/org/apache/royale/compiler/utils/DefinitionUtils.java
index 7da8710..dfb52bd 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/utils/DefinitionUtils.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/utils/DefinitionUtils.java
@@ -22,6 +22,9 @@ package org.apache.royale.compiler.utils;
 import org.apache.royale.compiler.definitions.IClassDefinition;
 import org.apache.royale.compiler.definitions.IDefinition;
 import org.apache.royale.compiler.definitions.IInterfaceDefinition;
+import org.apache.royale.compiler.internal.codegen.js.jx.TryEmitter;
+import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
+import org.apache.royale.compiler.internal.scopes.CatchScope;
 import org.apache.royale.compiler.projects.ICompilerProject;
 import org.apache.royale.compiler.tree.as.IDocumentableDefinitionNode;
 
@@ -61,5 +64,17 @@ public class DefinitionUtils
         return ret;
     }
 
+    /**
+     * Utility method for checking if an assigned definition represents the rewritten part of a
+     * multi-catch sequence - currently needed to avoid implicit coercions.
+     * This check ultimately relies on the TryEmitter.ROYALE_MULTI_CATCH_ERROR_NAME naming scheme.
+     */
+    public static final boolean isRewrittenMultiCatchParam(IDefinition definition) {
+        return (definition instanceof ParameterDefinition &&
+                definition.getContainingScope() instanceof CatchScope &&
+                definition.getBaseName().equals(TryEmitter.ROYALE_MULTI_CATCH_ERROR_NAME)
+                );
+    }
+
 
 }
diff --git a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/goog/TestGoogStatements.java b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/goog/TestGoogStatements.java
index b768192..94a11c3 100644
--- a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/goog/TestGoogStatements.java
+++ b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/goog/TestGoogStatements.java
@@ -29,6 +29,7 @@ import org.apache.royale.compiler.tree.as.IIfNode;
 import org.apache.royale.compiler.tree.as.ISwitchNode;
 import org.apache.royale.compiler.tree.as.ITryNode;
 import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -230,12 +231,11 @@ public class TestGoogStatements extends TestStatements
     @Test
     public void testVisitTry_Catch_Catch_Finally()
     {
-        // TODO (erikdebruin) handle multiple 'catch' statements (FW in Wiki)
         ITryNode node = (ITryNode) getNode(
-                "try { a; } catch (e:Error) { b; } catch (f:Error) { c; } finally { d; }",
+                "try { a; } catch (e:ReferenceError) { b; } catch (f:Error) { c; } finally { d; }",
                 ITryNode.class);
         asBlockWalker.visitTry(node);
-        assertOut("try {\n\ta;\n} catch (e) {\n\tb;\n} catch (f) {\n\tc;\n} finally {\n\td;\n}");
+        assertOut("try {\n\ta;\n} catch ($$royaleMultiCatchErr) /* implicit multi-catch wrapper */ {\n\tif ($$royaleMultiCatchErr instanceof ReferenceError) {\n\t\tvar /** @type {ReferenceError} */ e = $$royaleMultiCatchErr;\n\t\tb;\n\t} else if ($$royaleMultiCatchErr instanceof Error) {\n\t\tvar /** @type {Error} */ f = $$royaleMultiCatchErr;\n\t\tc;\n\t} else {\n\t\tthrow $$royaleMultiCatchErr;\n\t}\n} finally {\n\td;\n}");
     }
 
     @Override
diff --git a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/royale/TestRoyaleStatements.java b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/royale/TestRoyaleStatements.java
index 8684665..46d66de 100644
--- a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/royale/TestRoyaleStatements.java
+++ b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/royale/TestRoyaleStatements.java
@@ -37,6 +37,7 @@ import org.apache.royale.compiler.tree.as.ITryNode;
 import org.apache.royale.compiler.tree.as.IVariableNode;
 import org.apache.royale.compiler.tree.as.IWhileLoopNode;
 import org.apache.royale.compiler.tree.as.IWithNode;
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -435,12 +436,11 @@ public class TestRoyaleStatements extends TestGoogStatements
     @Test
     public void testVisitTry_Catch_Catch_Finally()
     {
-        // TODO (erikdebruin) handle multiple 'catch' statements (FW in Wiki)
         ITryNode node = (ITryNode) getNode(
-                "try { a; } catch (e:Error) { b; } catch (f:Error) { c; } finally { d; }",
+                "try { a; } catch (e:ReferenceError) { b; } catch (f:Error) { c; } finally { d; }",
                 ITryNode.class);
         asBlockWalker.visitTry(node);
-        assertOut("try {\n  a;\n} catch (e) {\n  b;\n} catch (f) {\n  c;\n} finally {\n  d;\n}");
+        assertOut("try {\n  a;\n} catch ($$royaleMultiCatchErr) /* implicit multi-catch wrapper */ {\n  if (org.apache.royale.utils.Language.is($$royaleMultiCatchErr, ReferenceError)) {\n    var /** @type {ReferenceError} */ e = $$royaleMultiCatchErr;\n    b;\n  } else if (org.apache.royale.utils.Language.is($$royaleMultiCatchErr, Error)) {\n    var /** @type {Error} */ f = $$royaleMultiCatchErr;\n    c;\n  } else {\n    throw $$royaleMultiCatchErr;\n  }\n} finally {\n  d;\n}");
     }
 
     @Override
diff --git a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
index 0e306d1..8d3b492 100644
--- a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
+++ b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
@@ -28,6 +28,10 @@ import org.apache.royale.compiler.tree.as.ITryNode;
 import org.apache.royale.compiler.tree.as.IVariableNode;
 import org.apache.royale.compiler.tree.as.IWithNode;
 
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class TestSourceMapStatements extends SourceMapTestBase
@@ -344,26 +348,28 @@ public class TestSourceMapStatements extends SourceMapTestBase
     public void testVisitTry_Catch_Catch_Finally()
     {
         ITryNode node = (ITryNode) getNode(
-                "try { a; } catch (e:Error) { b; } catch (f:Error) { c; } finally { d; }",
+                "try { a; } catch (e:ReferenceError) { b; } catch (f:Error) { c; } finally { d; }",
                 ITryNode.class);
         asBlockWalker.visitTry(node);
-        //try {\n  a;\n} catch (e) {\n  b;\n} catch (f) {\n  c;\n} finally {\n  d;\n}
+        //try {\n  a;\n} catch ($$royaleMultiCatchErr) /* implicit multi-catch wrapper */ {\n  if (org.apache.royale.utils.Language.is($$royaleMultiCatchErr, ReferenceError)) {\n    var /** @type {ReferenceError} */ e = $$royaleMultiCatchErr;\n    b;\n  } else if (org.apache.royale.utils.Language.is($$royaleMultiCatchErr, Error)) {\n    var /** @type {Error} */ f = $$royaleMultiCatchErr;\n    c;\n  } else {\n    throw $$royaleMultiCatchErr;\n  }\n} finally {\n  d;\n}
         assertMapping(node, 0, 0, 0, 0, 0, 4);     // try
         assertMapping(node, 0, 4, 0, 4, 0, 5);     // {
         assertMapping(node, 0, 9, 2, 0, 2, 1);     // }
-        assertMapping(node, 0, 11, 2, 1, 2, 9);    // catch(
-        assertMapping(node, 0, 18, 2, 9, 2, 10);   // e
-        assertMapping(node, 0, 25, 2, 10, 2, 12);  // )
-        assertMapping(node, 0, 27, 2, 12, 2, 13);  // {
-        assertMapping(node, 0, 32, 4, 0, 4, 1);    // }
-        assertMapping(node, 0, 34, 4, 1, 4, 9);    // catch(
-        assertMapping(node, 0, 41, 4, 9, 4, 10);   // f
-        assertMapping(node, 0, 48, 4, 10, 4, 12);  // )
-        assertMapping(node, 0, 50, 4, 12, 4, 13);  // {
-        assertMapping(node, 0, 55, 6, 0, 6, 1);    // }
-        assertMapping(node, 0, 57, 6, 1, 6, 10);   // finally
-        assertMapping(node, 0, 65, 6, 10, 6, 11);  // {
-        assertMapping(node, 0, 70, 8, 0, 8, 1);    // }
+        assertMapping(node, 0, 11, 3, 2, 3, 6);    // catch( --> if (
+        assertMapping(node, 0, 18, 4, 38, 4, 39);  // e
+        assertMapping(node, 0, 20, 3, 65, 3, 79);  // ReferenceError
+        assertMapping(node, 0, 34, 3, 80, 3, 82);  // )
+        assertMapping(node, 0, 36, 3, 82, 3, 83);  // {
+        assertMapping(node, 0, 41, 6, 2, 6, 3);    // }
+        assertMapping(node, 0, 43, 6, 4, 6, 13);   // catch( --> else if (
+        assertMapping(node, 0, 50, 7, 29, 7, 30);  // f
+        assertMapping(node, 0, 52, 6, 72, 6, 77);  // Error
+        assertMapping(node, 0, 57, 6, 78, 6, 80);  // )
+        assertMapping(node, 0, 59, 6, 80, 6, 81);  // {
+        assertMapping(node, 0, 64, 9, 2, 9, 3);    // }
+        assertMapping(node, 0, 66, 12, 1, 12, 10); // finally
+        assertMapping(node, 0, 74, 12, 10, 12, 11);// {
+        assertMapping(node, 0, 79, 14, 0, 14, 1);  // }
     }
 
     @Test