Package com.redhat.ceylon.compiler.java.codegen

Source Code of com.redhat.ceylon.compiler.java.codegen.StatementTransformer$ExistsCond

/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.  See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA  02110-1301, USA.
*/

package com.redhat.ceylon.compiler.java.codegen;

import static com.sun.tools.javac.code.Flags.FINAL;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Set;

import com.redhat.ceylon.compiler.java.codegen.Naming.CName;
import com.redhat.ceylon.compiler.java.codegen.Naming.Substitution;
import com.redhat.ceylon.compiler.java.codegen.Naming.Suffix;
import com.redhat.ceylon.compiler.java.codegen.Naming.SyntheticName;
import com.redhat.ceylon.compiler.java.codegen.Naming.Unfix;
import com.redhat.ceylon.compiler.java.codegen.recovery.HasErrorException;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.ConditionScope;
import com.redhat.ceylon.compiler.typechecker.model.ControlBlock;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.model.Util;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.CaseClause;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Expression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.SpecifierOrInitializerExpression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.SwitchStatement;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.main.OptionName;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCBreak;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCConditional;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Options;

/**
* This transformer deals with statements only
*/
public class StatementTransformer extends AbstractTransformer {

    // Used to hold the name of the variable associated with the fail-block if the innermost for-loop
    // Is null if we're currently in a while-loop or not in any loop at all
    private Name currentForFailVariable = null;
   
    /**
     * If false generating plain {@code return;} statements is OK.
     * If true then generate {@code return null;} statements instead of
     * expressionless {@code return;}.
     */
    boolean noExpressionlessReturn = false;

    private final Set<Optimization> disabledOptimizations;
   
    public static StatementTransformer getInstance(Context context) {
        StatementTransformer trans = context.get(StatementTransformer.class);
        if (trans == null) {
            trans = new StatementTransformer(context);
            context.put(StatementTransformer.class, trans);
        }
        return trans;
    }

    private StatementTransformer(Context context) {
        super(context);
        Options options = context.get(Options.optionsKey);
        if (options.isSet(OptionName.CEYLONDISABLEOPT)) {
            disabledOptimizations = EnumSet.allOf(Optimization.class);
        } else if (options.isSet(OptionName.CEYLONDISABLEOPT_CUSTOM)) {
            disabledOptimizations = new HashSet<Optimization>();
            for (String name : options.get(OptionName.CEYLONDISABLEOPT_CUSTOM).split(",")) {
                disabledOptimizations.add(Optimization.valueOf(name));
            }
        } else {
            disabledOptimizations = EnumSet.noneOf(Optimization.class);
        }
       
    }

    public JCBlock transform(Tree.Block block) {
        return block == null ? null : at(block).Block(0, transformBlock(block));
    }
   
    public List<JCStatement> transformBlock(Tree.Block block) {
        if (block == null) {
            return List.<JCStatement>nil();
        }
        at(block);
        CeylonVisitor v = gen().visitor;
        final ListBuffer<JCTree> prevDefs = v.defs;
        final boolean prevInInitializer = v.inInitializer;
        final ClassDefinitionBuilder prevClassBuilder = v.classBuilder;
        List<JCStatement> result;
        try {
            v.defs = new ListBuffer<JCTree>();
            v.inInitializer = false;
            v.classBuilder = current();
            for (Tree.Statement stmt : block.getStatements()) {
                HasErrorException error = errors().getFirstErrorBlock(stmt);
                if (error == null) {
                    stmt.visit(v);
                } else {
                    v.append(this.makeThrowUnresolvedCompilationError(error));
                    break;
                }
            }
            result = (List<JCStatement>)v.getResult().toList();
        } finally {
            v.classBuilder = prevClassBuilder;
            v.inInitializer = prevInInitializer;
            v.defs = prevDefs;
            // Close Substitutions which were scoped to this block
            Scope scope = block.getScope();
            while (scope instanceof ConditionScope) {
                scope = scope.getScope();
            }
            naming.closeScopedSubstitutions(scope);
        }
        return result;
    }
   
    /**
     * Helper for constructing {@code throw AssertionException()} statements
     * @author tom
     */
    class AssertionExceptionMessageBuilder {
       
        private JCExpression expr;
       
        private JCExpression cat(JCExpression str1, JCExpression str2) {
            if (str2 == null) {
                return str1;
            }
            return make().Binary(JCTree.PLUS, str1, str2);
        }
       
        private JCExpression cat(JCExpression strExpr, String literal) {
            return cat(strExpr, make().Literal(literal));
        }
       
        private JCExpression newline() {
            return make().Apply(null,
                    makeQualIdent(makeIdent(syms().systemType), "lineSeparator"),
                    List.<JCExpression>nil());
        }
       
        public AssertionExceptionMessageBuilder(JCExpression expr) {
            this.expr = expr;
        }
       
        public AssertionExceptionMessageBuilder prependAssertionDoc(Tree.Assertion ass) {
            return prependAssertionDoc(getDocAnnotationText(ass));
        }
       
        public AssertionExceptionMessageBuilder prependAssertionDoc(String docText) {
            JCExpression p = make().Literal("Assertion failed");
            if (docText != null) {
                p = cat(p, ": " + docText);
            }
            this.expr = cat(p, expr);
            return this;
        }
       
        private AssertionExceptionMessageBuilder appendCondition(String state, String sourceCode) {
            JCExpression m = cat(newline(), make().Literal("\t" + state+ " "));
            if (expr != null) {
                expr = cat(expr, m);
            } else {
                expr = m;
            }
            expr = cat(expr, make().Literal(sourceCode));
           
            return this;
        }
       
        public AssertionExceptionMessageBuilder appendViolatedCondition(String sourceCode) {
            return appendCondition("violated", sourceCode);
        }
       
        public AssertionExceptionMessageBuilder appendViolatedCondition(Tree.Condition condition) {
            return appendViolatedCondition(getSourceCode(condition));
        }
       
        public AssertionExceptionMessageBuilder appendUnviolatedCondition(Tree.Condition condition) {
            return appendCondition("unviolated", getSourceCode(condition));
        }
       
        public AssertionExceptionMessageBuilder appendUntestedCondition(Tree.Condition condition) {
            return appendCondition("untested", getSourceCode(condition));
        }
       
        public JCExpression build() {
            return expr;
        }
    }
   
    abstract class CondList {
        protected final Tree.Block thenPart;
        protected final java.util.List<Tree.Condition> conditions;
        public CondList(java.util.List<Tree.Condition> conditions, Tree.Block thenPart) {
            this.conditions = conditions;
            this.thenPart = thenPart;
        }
       
        protected List<JCStatement> transformList(java.util.List<Tree.Condition> conditions) {
            Tree.Condition condition = conditions.get(0);
            at(condition);
            if (conditions.size() == 1) {
                return transformInnermost(condition);
            } else {
                return transformIntermediate(condition, conditions.subList(1, conditions.size()));
            }
        }

        protected abstract List<JCStatement> transformInnermost(Tree.Condition condition);
       
        protected List<JCStatement> transformIntermediate(Tree.Condition condition, java.util.List<Tree.Condition> rest) {
            return transformList(rest);
        }
       
        public abstract List<JCStatement> getResult();
    }
   
    abstract class BlockCondList extends CondList {

        public BlockCondList(java.util.List<Tree.Condition> conditions,
                Tree.Block thenPart) {
            super(conditions, thenPart);
        }
       
        @Override
        protected final List<JCStatement> transformInnermost(Tree.Condition condition) {
            Cond transformedCond = transformCondition(condition, thenPart);
            // Note: The innermost test happens outside the substitution scope
            JCExpression test = transformedCond.makeTest();
            java.util.List<Tree.Condition> rest = Collections.<Tree.Condition>emptyList();
            JCStatement elseBlock = transformInnermostElse(transformedCond, rest);
            Substitution subs = getSubstitution(transformedCond);
            List<JCStatement> stmts = transformCommonResultDecl(transformedCond, List.<JCStatement>nil());
            stmts = stmts.appendList(transformInnermostThen(transformedCond));
            stmts = transformCommon(transformedCond, rest, test,
                    stmts, elseBlock);
            if (subs != null) {
                subs.close();
            }
            return stmts;
        }
       
        protected abstract List<JCStatement> transformInnermostThen(Cond transformedCond);

        protected abstract JCStatement transformInnermostElse(Cond transformedCond, java.util.List<Tree.Condition> rest);

        protected Substitution getSubstitution(Cond cond) {
            Substitution subs;
            if (cond.hasResultDecl()) {
                subs = naming.substituteAlias(cond.getVariable().getDeclarationModel());
            } else {
                subs = null;
            }
            return subs;
        }
       
        @Override
        protected List<JCStatement> transformIntermediate(Tree.Condition condition, java.util.List<Tree.Condition> rest) {
            Cond intermediate = transformCondition(condition, null);
            JCExpression test = intermediate.makeTest();
            Substitution subs = getSubstitution(intermediate);
            List<JCStatement> stmts = transformList(rest);
            stmts = transformCommonResultDecl(intermediate, stmts);
            JCStatement intermediateElse = transformIntermediateElse(intermediate, rest);
            stmts = transformCommon(intermediate, rest, test,
                    stmts, intermediateElse);
            if (subs != null) {
                subs.close();
            }
            return stmts;
        }

        protected abstract JCStatement transformIntermediateElse(Cond transformedCond, java.util.List<Tree.Condition> rest);
        protected abstract List<JCStatement> transformCommon(Cond transformedCond, java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, JCStatement elseBlock);
        protected abstract List<JCStatement> transformCommonResultDecl(Cond cond, List<JCStatement> stmts);
    }
   
    class IfCondList extends BlockCondList {

        final ListBuffer<JCStatement> varDecls = ListBuffer.lb();
        final SyntheticName ifVar = naming.temp("if");
        private LinkedHashMap<Cond, CName> unassignedResultVars = new LinkedHashMap<Cond, CName>();
        private JCBlock thenBlock;
        private Tree.Block elsePart;
       
        public IfCondList(java.util.List<Tree.Condition> conditions, Tree.Block thenPart,
                Tree.Block elsePart) {
            super(conditions, thenPart);
            this.elsePart = elsePart;
        }    

        /**
         * If the "if" statement has > 1 condition and an else block, then
         * we need to use one set of nested "if"s to do the narrowing and
         * determine whether the overall condition is satisfied, and a separate
         * if/else block to actually hold the transformed blocks.
         */
        private boolean isDeferred() {
            return conditions.size() > 1 && elsePart != null;
        }
       
        @Override
        protected JCBreak transformIntermediateElse(Cond transformedCond, java.util.List<Tree.Condition> rest) {
            return null;
        }

        @Override
        protected List<JCStatement> transformInnermostThen(Cond transformedCond) {
            List<JCStatement> stmts;
            if (definitelyNotSatisfied(conditions)
                    && !thenPart.getDefinitelyReturns()
                    && (elsePart != null && elsePart.getDefinitelyReturns())) {
                stmts = List.<JCStatement>of(makeFlowAppeaser(conditions.get(0)));
            } else if (isDeferred()) {
                stmts = List.<JCStatement>of(make().Exec(make().Assign(ifVar.makeIdent(), makeBoolean(true))));
                thenBlock = makeThenBlock(transformedCond, thenPart, null);
            } else {
                stmts = makeThenBlock(transformedCond, thenPart, null).getStatements();
            }
            return stmts;
        }

        @Override
        protected JCStatement transformInnermostElse(Cond transformedCond, java.util.List<Tree.Condition> rest) {
            JCBlock elseBlock = null;
            if (!isDeferred()) {
                elseBlock = transform(this.elsePart);
            }
            return elseBlock;
        }
       
        @Override
        protected List<JCStatement> transformCommon(Cond transformedCond,
                java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, JCStatement elseBlock) {
            JCStatement testVarDecl = transformedCond.makeTestVarDecl(0, false);
            if (testVarDecl != null) {
                varDecls.prepend(testVarDecl);
            }
            JCStatement elsePart;
            if (isDeferred()) {
                List<JCStatement> assignDefault = List.<JCStatement>nil();
                for (Cond unassigned : unassignedResultVars.keySet()) {
                    assignDefault = assignDefault.append(
                            make().Exec(make().Assign(unassignedResultVars.get(unassigned).makeIdent(),
                            ((SpecialFormCond<?>)unassigned).makeDefaultExpr())));
                }
                elsePart = assignDefault.isEmpty() ? null : make().Block(0, assignDefault);
            } else {
                elsePart = elseBlock;
            }
            stmts = List.<JCStatement>of(make().If(
                    test,
                    make().Block(0, stmts),
                    elsePart));
            return stmts;
        }

        protected List<JCStatement> transformCommonResultDecl(
                Cond transformedCond, List<JCStatement> stmts) {
            if (transformedCond.hasResultDecl()) {
                JCVariableDecl resultVarDecl = make().VarDef(make().Modifiers(Flags.FINAL),
                        transformedCond.getVariableName().asName(),
                        transformedCond.makeTypeExpr(),
                        isDeferred() ? null : transformedCond.makeResultExpr());
                if (isDeferred()) {
                    unassignedResultVars.put(transformedCond,
                            transformedCond.getVariableName());
                    varDecls.prepend(resultVarDecl);
                    stmts = stmts.prepend(make().Exec(make().Assign(transformedCond.getVariableName().makeIdent(), transformedCond.makeResultExpr())));
                } else {
                    stmts = stmts.prepend(resultVarDecl);
                }
            }
            return stmts;
        }
       
        @Override
        public List<JCStatement> getResult() {
            List<JCStatement> stmts = transformList(conditions);
            if (definitelySatisfied(conditions)
                    && thenPart.getDefinitelyReturns()
                    && (elsePart == null || !elsePart.getDefinitelyReturns())) {
                stmts = stmts.append(makeFlowAppeaser(conditions.get(0)));
            }
            ListBuffer<JCStatement> result = ListBuffer.lb();
            if (isDeferred()) {
                result.append(makeVar(ifVar, make().Type(syms().booleanType), makeBoolean(false)));
            }
            result.appendList(varDecls);
            result.appendList(stmts);
            if (isDeferred()) {
                result.append(make().If(ifVar.makeIdent(), thenBlock, StatementTransformer.this.transform(elsePart)));
            }
            return result.toList();  
        }

        
    }
   
    private boolean definitelySatisfiedOrNot(java.util.List<Tree.Condition> conditions, boolean satisfied) {
        if (conditions.size() != 1) {
            return false;
        }
        Tree.Condition condition = conditions.get(0);
        if (!(condition instanceof Tree.BooleanCondition)) {
            return false;
        }
        Tree.Term term = ((Tree.BooleanCondition)condition).getExpression().getTerm();
        if (!(term instanceof Tree.BaseMemberExpression)) {
            return false;
        }
        Declaration declaration = ((Tree.BaseMemberExpression)term).getDeclaration();
        return declaration instanceof Value
                && satisfied ? isBooleanTrue(declaration) : isBooleanFalse(declaration);
    }
   
    boolean definitelySatisfied(java.util.List<Tree.Condition> conditions) {
        return definitelySatisfiedOrNot(conditions, true);
    }
   
    boolean definitelyNotSatisfied(java.util.List<Tree.Condition> conditions) {
        return definitelySatisfiedOrNot(conditions, false);
    }
   
    /**
     * Sometimes we need something to appease javacs flow analysis.
     */
    JCStatement makeFlowAppeaser(Node node) {
        at(node);
        return make().Throw(make().NewClass(null, List.<JCExpression>nil(),
                make().Type(syms().errorType),
                List.<JCExpression>of(make().Literal("Ceylon flow error")),
                        null));
    }
   
    List<JCStatement> transform(Tree.IfStatement stmt) {
        Tree.Block thenPart = stmt.getIfClause().getBlock();
        Tree.Block elsePart = stmt.getElseClause() != null ? stmt.getElseClause().getBlock() : null;
        java.util.List<Tree.Condition> conditions = stmt.getIfClause().getConditionList().getConditions();
        return new IfCondList(conditions, thenPart, elsePart).getResult();
    }

    private JCBlock makeThenBlock(Cond cond, Tree.Block thenPart, Substitution subs) {
        List<JCStatement> blockStmts = statementGen().transformBlock(thenPart);
        if (subs != null) {
            // The variable holding the result for the code inside the code block
            blockStmts = blockStmts.prepend(at(cond.getCondition()).VarDef(make().Modifiers(FINAL), names().fromString(subs.substituted),
                    cond.makeTypeExpr(), cond.makeResultExpr()));
        }
        JCBlock thenBlock = at(cond.getCondition()).Block(0, blockStmts);
        return thenBlock;
    }
   
    List<JCStatement> transform(Tree.WhileStatement stmt) {
        Name tempForFailVariable = currentForFailVariable;
        currentForFailVariable = null;
        final List<JCStatement> res;
        res =  new WhileCondList(stmt.getWhileClause()).getResult();
        currentForFailVariable = tempForFailVariable;
       
        return res;
    }
   
    class WhileCondList extends BlockCondList {

        private final ListBuffer<JCStatement> varDecls = ListBuffer.lb();
        private final Name label;
        public WhileCondList(Tree.WhileClause whileClause) {
            super(whileClause.getConditionList().getConditions(), whileClause.getBlock());
            this.label = getLabel(whileClause.getControlBlock());
        }
       
        @Override
        protected JCBreak transformIntermediateElse(Cond transformedCond, java.util.List<Tree.Condition> rest) {
            return make().Break(label);
        }

        @Override
        protected List<JCStatement> transformInnermostThen(Cond transformedCond) {
            return makeThenBlock(transformedCond, thenPart, null).getStatements();  
        }

        @Override
        protected JCStatement transformInnermostElse(Cond transformedCond, java.util.List<Tree.Condition> rest) {
            return make().Break(label);
        }
       
        @Override
        protected List<JCStatement> transformCommon(Cond transformedCond,
                java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, JCStatement elseBlock) {
            if (transformedCond.makeTestVarDecl(0, false) != null) {
                varDecls.append(transformedCond.makeTestVarDecl(0, false));
            }
            JCStatement elsePart = elseBlock;
            stmts = List.<JCStatement>of(make().If(
                    test,
                    make().Block(0, stmts),
                    elsePart));
            return stmts;
        }

        protected List<JCStatement> transformCommonResultDecl(
                Cond transformedCond, List<JCStatement> stmts) {
            if (transformedCond.hasResultDecl()) {
                JCVariableDecl resultVarDecl = make().VarDef(make().Modifiers(Flags.FINAL),
                        transformedCond.getVariableName().asName(),
                        transformedCond.makeTypeExpr(),
                        transformedCond.makeResultExpr());
                stmts = stmts.prepend(resultVarDecl);
            }
            return stmts;
        }
       
        @Override
        public List<JCStatement> getResult() {
            List<JCStatement> stmts = transformList(conditions);
            ListBuffer<JCStatement> loopStmts = ListBuffer.lb();
            loopStmts.appendList(varDecls);
            loopStmts.appendList(stmts);
            List<JCStatement> result = List.nil();
            if (definitelySatisfied(conditions)) {
                BreakVisitor v = new BreakVisitor();
                thenPart.visit(v);
                if (!v.breaks) {
                    result = result.prepend(makeFlowAppeaser(conditions.get(0)));
                }
            }
           
            result = result.prepend(
                    make().Labelled(label, make().WhileLoop(makeBoolean(true),
                    make().Block(0, loopStmts.toList()))));
            return result;
        }

    }
   
    class BreakVisitor extends Visitor {
        private boolean breaks = false;
       
       
        public void visit(Tree.WhileStatement stmt) {
            // We're not interested in breaks in that loop
        }
       
        public void visit(Tree.ForStatement stmt) {
            // We're not interested in breaks in that loop
        }
       
        public void visit(Tree.Declaration stmt) {
            // We're not interested in breaks in loops within that declaration
        }
       
        public void visit(Tree.Break stmt) {
            breaks = true;
        }
    }
   
    List<JCStatement> transform(Tree.Assertion ass) {
        return new AssertCondList(ass).getResult();
    }
   
    class AssertCondList extends BlockCondList {
        private final Tree.Assertion ass;
        private final ListBuffer<JCStatement> varDecls = ListBuffer.lb();
        private final ListBuffer<JCStatement> fieldDecls = ListBuffer.lb();
        private final SyntheticName messageSb = naming.temp("assert");
        private LinkedHashMap<Cond, CName> unassignedResultVars = new LinkedHashMap<Cond, CName>();
       
        public AssertCondList(Tree.Assertion ass) {
            super(ass.getConditionList().getConditions(), null);
            this.ass = ass;
        }
       
        /** Determines whether there's more than one Condition in the ConditionList */
        private boolean isMulti() {
            return this.conditions.size() > 1;
        }
       
        protected Substitution getSubstitution(Cond cond) {
            Substitution subs = super.getSubstitution(cond);
            if (subs == null) {
                return subs;
            }
            Scope scope = cond.getVariable().getScope().getScope();
            while (scope instanceof ConditionScope) {
                scope = scope.getScope();
            }
            subs.scopeClose(scope);
            // make sure we get a variable name now, and that it doesn't change over time, because
            // we will need this variable name in transformCommonResultDecl(), which declares it,
            // and it runs after we process inner conditions, and if we are an assert, inner conditions
            // may declare new substitutions, which do not close until the end of the outer scope,
            // so if we use substitutions we will get substituted names, rather than the name we should
            // be declaring. See https://github.com/ceylon/ceylon-compiler/issues/1532
            cond.getVariableName();
            return subs;
        }
       
        @Override
        protected List<JCStatement> transformCommon(Cond cond,
                java.util.List<Tree.Condition> rest,
                JCExpression test, List<JCStatement> stmts, JCStatement elseBlock) {
           
            if (isMulti()) {
                List<JCStatement> elseStmts = ((JCBlock)elseBlock).getStatements();
                for (Cond unassigned : unassignedResultVars.keySet()) {
                    elseStmts = elseStmts.prepend(
                            make().Exec(make().Assign(unassignedResultVars.get(unassigned).makeIdent(),
                            ((SpecialFormCond<?>)unassigned).makeDefaultExpr())));
                }
                elseBlock = make().Block(0, elseStmts);
            }
            stmts = List.<JCStatement>of(make().If(
                    test,
                    make().Block(0, stmts),
                    elseBlock));
           
            JCStatement testVarDecl = cond.makeTestVarDecl(0, true);
            if (testVarDecl != null) {
                stmts = stmts.prepend(testVarDecl);
            }
            return stmts;
        }

        protected List<JCStatement> transformCommonResultDecl(Cond cond,
                List<JCStatement> stmts) {
            if (cond.hasResultDecl()) {
                JCVariableDecl resultVarDecl = make().VarDef(make().Modifiers(Flags.FINAL),
                        cond.getVariableName().asName(),
                        cond.makeTypeExpr(),
                        null);
                unassignedResultVars.put(cond, cond.getVariableName());
                (Decl.getNonConditionScope(cond.getCondition().getScope()) instanceof ClassOrInterface
                        && cond.getVariable().getDeclarationModel().isCaptured() ? fieldDecls : varDecls).append(resultVarDecl);
                stmts = stmts.prepend(make().Exec(make().Assign(cond.getVariableName().makeIdent(), cond.makeResultExpr())));
            }
            return stmts;
        }
       
        @Override
        public List<JCStatement> getResult() {
            if (definitelyNotSatisfied(conditions)) {
                return List.<JCTree.JCStatement>of(makeThrowAssertionFailure(conditions.get(0)));
            }
            List<JCStatement> stmts = transformList(conditions);
            at(this.ass);
            ListBuffer<JCStatement> result = ListBuffer.lb();
            if (isMulti()) {
                result.append(makeVar(messageSb, make().Type(syms().stringType), makeNull()));
            }
            result.appendList(varDecls);
            current().defs((List)fieldDecls.toList());
            result.appendList(stmts);
            JCExpression message = new AssertionExceptionMessageBuilder(
                    messageSb.makeIdent()).prependAssertionDoc(ass).build();
            JCThrow throw_ = makeThrowAssertionException(message);
            if (isMulti()) {
                result.append(make().If(
                        make().Binary(JCTree.NE, messageSb.makeIdent(), makeNull()),
                        throw_, null));
            }
            return result.toList();
        }

        private JCStatement transformCommonElse(Cond cond, java.util.List<Tree.Condition> rest) {
            if (!isMulti()) {
                return null;
            }
            AssertionExceptionMessageBuilder msg = new AssertionExceptionMessageBuilder(null);
            boolean seen = false;
            for (Tree.Condition condition : this.conditions) {
                if (cond.getCondition() == condition) {
                    msg.appendViolatedCondition(condition);
                    seen = true;
                    continue;
                }
                if (seen) {
                    msg.appendUntestedCondition(condition);
                } else {
                    msg.appendUnviolatedCondition(condition);
                }
            }
            return make().Block(0, List.<JCStatement>of(
                    make().Exec(make().Assign(messageSb.makeIdent(), msg.build()))));
        }
       
        @Override
        protected List<JCStatement> transformInnermostThen(Cond cond) {
            return List.nil();
        }

        @Override
        protected JCStatement transformInnermostElse(Cond cond, java.util.List<Tree.Condition> rest) {
            if (!isMulti()) {
                Tree.Condition condition = cond.getCondition();
                return makeThrowAssertionFailure(condition);
            }
            return transformCommonElse(cond, rest);
        }

        private JCStatement makeThrowAssertionFailure(Tree.Condition condition) {
            at(condition);
            AssertionExceptionMessageBuilder msg = new AssertionExceptionMessageBuilder(null);
            msg.appendViolatedCondition(condition);
            msg.prependAssertionDoc(ass);
            return makeThrowAssertionException(msg.build());
        }
       
        @Override
        protected JCStatement transformIntermediateElse(Cond cond, java.util.List<Tree.Condition> rest) {
            return transformCommonElse(cond, rest);
        }
    }
   
    interface Cond {
       
        public Tree.Condition getCondition();
       
        public Tree.Variable getVariable();
       
        public CName getVariableName();
       
        public boolean hasResultDecl();
        public boolean hasAliasedVariable();
       
        public JCExpression makeTypeExpr();
       
        public JCExpression makeResultExpr();
   
        public JCStatement makeTestVarDecl(int flags, boolean init);
   
        public JCExpression makeTest();
    }
   
    abstract class SpecialFormCond<C extends Tree.Condition> implements Cond {
        protected final C cond;
        protected final ProducedType toType;
        protected final Tree.Expression specifierExpr;
        protected final Naming.SyntheticName testVar;
        protected final Tree.Variable variable;
        private CName variableName;
        SpecialFormCond(
                C cond,
                ProducedType toType,
                Tree.Expression specifierExpr,
                Tree.Variable variable) {
            this.cond = cond;
            this.toType = toType;
            this.specifierExpr = specifierExpr;
            this.testVar = naming.alias(variable.getIdentifier().getText());
            this.variable = variable;
        }
       
        @Override
        public final C getCondition() {
            return cond;
        }
       
        @Override
        public final Tree.Variable getVariable() {
            return variable;
        }
       
        @Override
        public final CName getVariableName() {
            // make sure once we get a variable it never changes name, because the whole code
            // generation of Cond depends on being able to call this method multiple times and
            // get the same result. See https://github.com/ceylon/ceylon-compiler/issues/1532
            if(variableName == null)
                variableName = naming.substituted(variable.getDeclarationModel()).capture();
            return variableName;
        }
       
        @Override
        public boolean hasResultDecl() {
            return true;
        }
       
        @Override
        public boolean hasAliasedVariable() {
            return !(getVariable().getType() instanceof Tree.SyntheticVariable);
        }
       
        @Override
        public final JCExpression makeTypeExpr() {
            return makeJavaType(toType);
        }

        /**
         * Generates a default value for result variables.
         * When transforming {@code if/else if} the variable holding the
         * type-narrowed variable must be declared final so it can be captured
         * but in the else blocks we don't have anything we can safely
         * initialise it with in the model. So we generate default values
         * here, which cannot actually be seen from the ceylon code.
         * @return
         */
        protected JCExpression makeDefaultExpr() {
            at(cond);
            return makeDefaultExprForType(toType);
        }
       
        @Override
        public JCStatement makeTestVarDecl(int flags, boolean init) {
            // Temporary variable holding the result of the expression/variable to test
            return make().VarDef(make().Modifiers(flags), testVar.asName(), makeResultType(), init ? makeNull() : null);
        }

        protected abstract JCExpression makeResultType();
       
    }
   
    class IsCond extends SpecialFormCond<Tree.IsCondition> {
        /** Is this a negated is: {@code !is X} */
        private final boolean negate;
        private IsCond(Tree.IsCondition isdecl) {
            super(isdecl,
                    // use the type of the variable, which is more precise than the type we test for
                    isdecl.getVariable().getType().getTypeModel(),
                    isdecl.getVariable().getSpecifierExpression().getExpression(),
                    isdecl.getVariable());
            negate = isdecl.getNot();
        }
       
        @Override
        public boolean hasResultDecl() {
            return isErasedToObjectOptimization() || isNothingOptimization() ? false : super.hasResultDecl();
        }       

        /**
         * We can optimize "is Nothing x" (but not "is Nothing y = x")
         * because there can be no unboxing or typecasting of the result
         */
        private boolean isNothingOptimization() {
            return toType.isExactly(typeFact().getNothingDeclaration().getType())
                    && ! hasAliasedVariable();
        }
       
        /**
         * Optimization: if no typecast will be required, and there's no
         * aliasing then we don't need to declare a test var, and the result var
         * is simply the variable name.
         */
        private boolean isErasedToObjectOptimization() {
            return !typecastRequired()
                    && !hasAliasedVariable()
                    && !canUnbox(this.specifierExpr.getTypeModel());
        }
       
        /**
         * Optimization: if the type of the expression and toType
         * both erase to Object then we can avoid the typecast
         */
        private boolean typecastRequired() {
            // TODO: In fact it should be possible to avoid declaring a test
            // var this case, but it complicates the test when dealing with unions and intersections
            return !willEraseToObject(toType);
        }
       
        @Override
        public JCStatement makeTestVarDecl(int flags, boolean init) {
            // We can optimize "is Nothing x" (but not "is Nothing y = x")
            // because there can be no unboxing or typecasting of the result
            return isErasedToObjectOptimization() || isNothingOptimization() ? null : super.makeTestVarDecl(flags, init);
        }
       
        @Override
        public JCExpression makeTest() {
            ProducedType expressionType;
            if(cond.getVariable().getSpecifierExpression() != null)
                expressionType = cond.getVariable().getSpecifierExpression().getExpression().getTypeModel();
            else
                expressionType = cond.getVariable().getDeclarationModel().getOriginalDeclaration().getType();

            // make sure we do not insert null checks if we're going to allow testing for null
            ProducedType specifierType = negate ?
                    expressionType : getOptionalTypeForInteropIfAllowed(cond.getType().getTypeModel(), expressionType, specifierExpr);
            // no need to cast for erasure here
            JCExpression expr = expressionGen().transformExpression(specifierExpr, BoxingStrategy.BOXED, specifierType);
            at(cond);
            // Assign the expression to test to the temporary variable
            if (!isErasedToObjectOptimization() && !isNothingOptimization()) {
                expr = make().Assign(testVar.makeIdent(), expr);
            }
           
            // Test on the tmpVar in the following condition
            expr = makeOptimizedTypeTest(expr, isErasedToObjectOptimization() ? getVariableName() : testVar,
                    // only test the types we're testing for, not the type of
                    // the variable (which can be more precise)
                    cond.getType().getTypeModel(), expressionType);
            if (negate) {
                expr = make().Unary(JCTree.NOT, expr);
            }
            return expr;
        }
       
        @Override
        protected JCExpression makeResultType() {
            at(cond);
            return make().Type(syms().objectType);
        }
       
        @Override
        public JCExpression makeResultExpr() {
            at(cond);
            JCExpression expr = testVar.makeIdent();
           
            if (typecastRequired()) {
                // Want raw type for instanceof since it can't be used with generic types
                JCExpression rawToTypeExpr = makeJavaType(toType, JT_NO_PRIMITIVES | JT_RAW);
                // Substitute variable with the correct type to use in the rest of the code block
                expr = at(cond).TypeCast(rawToTypeExpr, expr);
                if (canUnbox(toType)) {
                    expr = unboxType(expr, toType);
                }
            }
            return expr;
        }

    }
   
    class ExistsCond extends SpecialFormCond<Tree.ExistsCondition> {

        private ExistsCond(Tree.ExistsCondition exists) {
            super(exists,
                    simplifyType(exists.getVariable().getType().getTypeModel()),
                    exists.getVariable().getSpecifierExpression().getExpression(),
                    exists.getVariable());
        }
       
        @Override
        public JCExpression makeResultExpr() {
            Value decl = this.cond.getVariable().getDeclarationModel();
            ProducedType exprType = this.specifierExpr.getTypeModel();
            if (isOptional(exprType)) {
                exprType = typeFact().getDefiniteType(exprType);
            }
            return expressionGen().applyErasureAndBoxing(testVar.makeIdent(),
                    exprType, willEraseToObject(decl.getType()), true,
                    CodegenUtil.getBoxingStrategy(decl),
                    decl.getType(), 0);
        }
       
        @Override
        protected JCExpression makeResultType() {
            ProducedType tmpVarType = specifierExpr.getTypeModel();
            return makeJavaType(tmpVarType, JT_NO_PRIMITIVES);
        }
       
        @Override
        public JCExpression makeTest() {
            // for the purpose of checking if something is null, we need it boxed and optional, otherwise
            // for some Java calls if we consider it non-optional we will get an unwanted null check
            ProducedType specifierType = this.specifierExpr.getTypeModel();
            if(!typeFact().isOptionalType(specifierType)){
                specifierType = typeFact().getOptionalType(specifierType);
            }
            JCExpression expr = expressionGen().transformExpression(specifierExpr, BoxingStrategy.BOXED, specifierType);
            at(cond);
            // Assign the expression to test to the temporary variable
            JCExpression firstTimeTestExpr = make().Assign(testVar.makeIdent(), expr);
            // Test on the tmpVar in the following condition
            return make().Binary(JCTree.NE, firstTimeTestExpr, makeNull());
        }
    }
   
    class NonemptyCond extends SpecialFormCond<Tree.NonemptyCondition> {

        private NonemptyCond(Tree.NonemptyCondition nonempty) {
            super(nonempty,
                    nonempty.getVariable().getType().getTypeModel(),
                    nonempty.getVariable().getSpecifierExpression().getExpression(),
                    nonempty.getVariable());
        }
       
        @Override
        protected JCExpression makeDefaultExpr() {
            return makeNull();
        }
       
        @Override
        public JCExpression makeResultExpr() {
            Value decl = this.cond.getVariable().getDeclarationModel();
            return expressionGen().applyErasureAndBoxing(testVar.makeIdent(),
                    typeFact().getDefiniteType(this.specifierExpr.getTypeModel()), false, true,
                    BoxingStrategy.BOXED,
                    decl.getType(),
                    ExpressionTransformer.EXPR_DOWN_CAST);
        }
       
        @Override
        protected JCExpression makeResultType() {
            ProducedType tmpVarType = specifierExpr.getTypeModel();
            return makeJavaType(tmpVarType, JT_NO_PRIMITIVES);
        }

        @Override
        public JCExpression makeTest() {
            // no need to cast for erasure here
            JCExpression expr = expressionGen().transformExpression(specifierExpr);
            at(cond);
            // Assign the expression to test to the temporary variable
            JCExpression firstTimeTestExpr = make().Assign(testVar.makeIdent(), expr);
            // Test on the tmpVar in the following condition
            return makeNonEmptyTest(firstTimeTestExpr);
        }
    }
   
    class BooleanCond implements Cond {
        private final Tree.BooleanCondition cond;
       
        private BooleanCond(Tree.BooleanCondition booleanCondition) {
            super();
            this.cond = booleanCondition;
        }

        @Override
        public JCStatement makeTestVarDecl(int flags, boolean init) {
            return null;
        }

        @Override
        public JCExpression makeTest() {
            at(cond);
            return expressionGen().transformExpression(cond.getExpression(),
                    BoxingStrategy.UNBOXED, typeFact().getBooleanDeclaration().getType());
        }

        @Override
        public boolean hasResultDecl() {
            return false;
        }

        @Override
        public Tree.Condition getCondition() {
            return cond;
        }

        @Override
        public Tree.Variable getVariable() {
            return null;
        }
       
        @Override
        public final CName getVariableName() {
            return null;
        }

        @Override
        public JCExpression makeTypeExpr() {
            return null;
        }

        @Override
        public JCExpression makeResultExpr() {
            return null;
        }
       
        @Override
        public boolean hasAliasedVariable() {
            return false;
        }

    }
   
    Cond transformCondition(Tree.Condition cond, Tree.Block thenPart) {
        if (cond instanceof Tree.IsCondition) {
            Tree.IsCondition is = (Tree.IsCondition)cond;
            return new IsCond(is);
        } else if (cond instanceof Tree.ExistsCondition) {
            Tree.ExistsCondition exists = (Tree.ExistsCondition)cond;
            return new ExistsCond(exists);
        } else if (cond instanceof Tree.NonemptyCondition) {
            Tree.NonemptyCondition nonempty = (Tree.NonemptyCondition)cond;
            return new NonemptyCond(nonempty);
        } else if (cond instanceof Tree.BooleanCondition) {
            return new BooleanCond((Tree.BooleanCondition)cond);
        }
        throw BugException.unhandledNodeCase(cond);
    }

    private String getDocAnnotationText(Tree.Assertion ass) {
        String docText = null;
        Tree.Annotation doc = getAnnotation(ass.getAnnotationList(), "doc");
        if (doc != null) {
            Tree.Expression expression = null;
            if (doc.getPositionalArgumentList() != null) {
                Tree.PositionalArgument arg = doc.getPositionalArgumentList().getPositionalArguments().get(0);
                if(arg instanceof Tree.ListedArgument)
                    expression = ((Tree.ListedArgument) arg).getExpression();
                else
                    throw new BugException(arg, "argument to doc annotation cannot be a spread argument or comprehension: " + arg);
            } else if (doc.getNamedArgumentList() != null) {
                Tree.SpecifiedArgument arg = (Tree.SpecifiedArgument)doc.getNamedArgumentList().getNamedArguments().get(0);
                expression = arg.getSpecifierExpression().getExpression();
            } else {
                // Impossible on a well-formed tree
                return null;
            }
            Tree.Literal literal = (Tree.Literal)expression.getTerm();
            docText = literal.getText();
        } else if (ass.getAnnotationList() != null
                && ass.getAnnotationList().getAnonymousAnnotation() != null) {
            docText = ass.getAnnotationList().getAnonymousAnnotation().getStringLiteral().getText();
        }
        return docText;
    }

    private Tree.Annotation getAnnotation(Tree.AnnotationList al, String name) {
        if (al!=null) {
            for (Tree.Annotation a: al.getAnnotations()) {
                Tree.BaseMemberExpression p = (Tree.BaseMemberExpression) a.getPrimary();
                if (p!=null) {
                    if ( p.getIdentifier().getText().equals(name) ) {
                        return a;
                    }
                }
            }
        }
        return null;
    }
   
    /**
     * Gets the source code corresponding to the given node
     */
    private String getSourceCode(Node node) {
        StringBuilder sb = new StringBuilder();
        DiagnosticSource source = new DiagnosticSource(gen().getFileObject(), Log.instance(gen().getContext()));
        int startLine = node.getToken().getLine();
        int endLine = node.getEndToken().getLine();
        for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
            int startPos = gen().getMap().getPosition(lineNumber, 1);
            String line = source.getLine(startPos);
            if (lineNumber == endLine) {
                line = line.substring(0,  node.getEndToken().getCharPositionInLine() + node.getEndToken().getText().length());
            }
            if (lineNumber == startLine) {
                line = line.substring(node.getToken().getCharPositionInLine());
            }
            sb.append(line).append("\n");
        }
        return sb.substring(0, sb.length() - 1);
    }

    private ProducedType actualType(Tree.TypedDeclaration decl) {
        return decl.getType().getTypeModel();
    }
   
    List<JCStatement> transform(Tree.ForStatement stmt) {
        Tree.Term iterableTerm = ExpressionTransformer.eliminateParens(stmt.getForClause().getForIterator().getSpecifierExpression().getExpression().getTerm());
        Tree.Term baseIterable = iterableTerm;
        Tree.Term step = null;
        if (iterableTerm instanceof Tree.InvocationExpression) {
            Tree.InvocationExpression invocation = (Tree.InvocationExpression)iterableTerm;
            if (invocation.getPrimary() instanceof Tree.QualifiedMemberExpression) {
                Tree.QualifiedMemberExpression qme = (Tree.QualifiedMemberExpression)invocation.getPrimary();
                ProducedType primaryType = qme.getPrimary().getTypeModel();
                ProducedType iterableType = primaryType.getSupertype(typeFact().getIterableDeclaration());
                if (iterableType != null) {
                    if ("by".equals(qme.getIdentifier().getText())) {
                        if (invocation.getPositionalArgumentList() != null) {
                            Tree.PositionalArgument positionalArgument = invocation.getPositionalArgumentList().getPositionalArguments().get(0);
                            if (positionalArgument instanceof Tree.ListedArgument) {
                                step = ((Tree.ListedArgument)positionalArgument).getExpression().getTerm();
                                baseIterable = ExpressionTransformer.eliminateParens(qme.getPrimary());
                            }
                        } else if (invocation.getNamedArgumentList() != null) {
                            Tree.NamedArgument positionalArgument = invocation.getNamedArgumentList().getNamedArguments().get(0);
                            if (positionalArgument instanceof Tree.SpecifiedArgument) {
                                step = ((Tree.SpecifiedArgument)positionalArgument).getSpecifierExpression().getExpression().getTerm();
                                baseIterable = ExpressionTransformer.eliminateParens(qme.getPrimary());
                            }
                        }
                    }
                }
            }
        }
       
       
        ForStatementTransformation transformation;
       
        transformation = stringIteration(stmt, baseIterable, step);
        if (transformation == null) {
            transformation = arrayIteration(stmt, baseIterable, step);
        }
        if (transformation == null) {
            transformation = segmentOpIteration(stmt, baseIterable, step);
        }
        if (transformation == null) {
            transformation = spanOpIteration(stmt);
        }
        if (transformation == null) {
            transformation = new ForStatementTransformation(stmt);
        }
        return transformation.transform();
    }
   
    /**
     * Loop transformation when the iterated expression is statically known
     * to be a {@code String}.
     * <pre>
        java.lang.String s = ITERABLE.value;
        int sz = s.codePointCount(0, s.length());
        for (int index = 0; index < sz; ) {
            int ITEM = s.codePointAt(index);
            index+= java.lang.Character.charCount(ITEM);
           
        }
       </pre>
      
     */
    class StringIterationOptimization extends ForStatementTransformation {

        private Tree.Term baseIterable;

        StringIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step) {
            super(stmt);
            this.baseIterable = baseIterable;
        }
       
        protected ListBuffer<JCStatement> transformForClause() {
            ListBuffer<JCStatement> stmts = ListBuffer.<JCStatement>lb();
           
            SyntheticName stringName = naming.alias("s");
            stmts.append(makeVar(stringName, make().Type(syms().stringType),
                    expressionGen().transformExpression(baseIterable, BoxingStrategy.UNBOXED, baseIterable.getTypeModel())));
           
            SyntheticName lengthName = naming.alias("length");
            stmts.append(makeVar(lengthName, make().Type(syms().intType),
               
                                make().Apply(null,
                                        makeQualIdent(stringName.makeIdent(), "length"),
                                        List.<JCExpression>nil())));
           
            SyntheticName indexName = naming.alias("index");
           
            List<JCStatement> transformedBlock = transformBlock(getBlock());
            transformedBlock = transformedBlock.prepend(make().Exec(
                    make().Assignop(JCTree.PLUS_ASG, indexName.makeIdent(),
                        make().Apply(null,
                                naming.makeQualIdent(make().Type(syms().characterObjectType), "charCount"),
                                List.<JCExpression>of(naming.makeQuotedIdent(Naming.getVariableName(getElementOrKeyVariable())))))));
            transformedBlock = transformedBlock.prepend(makeVar(FINAL,
                    Naming.getVariableName(getElementOrKeyVariable()),
                    make().Type(syms().intType),
                    make().Apply(null,
                            naming.makeQualIdent(stringName.makeIdent(), "codePointAt"),
                            List.<JCExpression>of(indexName.makeIdent()))));
           
            JCStatement block = make().Block(0, transformedBlock);
           
           
            JCForLoop loop = make().ForLoop(
                    List.<JCStatement>of(makeVar(indexName, make().Type(syms().intType), make().Literal(0))),
                    make().Binary(JCTree.LT, indexName.makeIdent(), lengthName.makeIdent()),
                    List.<JCExpressionStatement>nil(),
                    block);
            stmts.add(make().Labelled(this.label, loop));
           
            return stmts;
        }
    }
   
    private ForStatementTransformation stringIteration(Tree.ForStatement stmt,
            Tree.Term baseIterable, Tree.Term step) {
        if (step == null &&
                baseIterable.getTypeModel().getSupertype(typeFact().getStringDeclaration()) != null) {
            return new StringIterationOptimization(stmt, baseIterable, step);
        }
        return null;
    }

    /**
     * Optimized transformation for a {@code for} loop where the iterable is
     * statically known to be immutable and support efficient indexed access,
     * and therefore can be
     * iterated using a C-style {@code for}.
     */
    abstract class IndexedAccessIterationOptimization extends ForStatementTransformation {
       
        protected final ProducedType elementType;
        /** The name of the indexable variable */
        protected final SyntheticName indexableName;
        /** The name of the length variable */
        protected final SyntheticName lengthName;
        /** The name of the index variable */
        protected final SyntheticName indexName;
        protected final Tree.Term baseIterable;
        protected final Tree.Term step;
       
        IndexedAccessIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step, ProducedType elementType, String indexableName) {
            this(stmt, baseIterable, step, elementType, indexableName, "length", "i");
        }
       
        IndexedAccessIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step, ProducedType elementType, String indexableName, String lengthName, String indexName) {
            super(stmt);
            this.baseIterable = baseIterable;
            this.step = step;
            this.elementType = elementType;
            this.indexableName = naming.alias(indexableName);
            this.lengthName = naming.alias(lengthName);
            this.indexName = naming.alias(indexName);
        }
       
        protected final Tree.Term getIterable() {
            return baseIterable;
        }
       
        protected ListBuffer<JCStatement> transformForClause() {
            ListBuffer<JCStatement> result = ListBuffer.<JCStatement>lb();
           
            // java.lang.Object array = ITERABLE.toArray();
            result.add(makeVar(FINAL, indexableName,
                    makeIndexableType(),
                    makeIndexable()));
           
            // int step = ...
            final SyntheticName stepName;
            if (this.step != null) {
                JCExpression stepExpr = makeStepExpr();
                stepName = naming.alias("step");
                result.add(makeVar(FINAL, stepName,
                        makeIndexType(),
                        stepExpr));
                result.add(
                make().If(
                        make().Binary(JCTree.LE, stepName.makeIdent(), make().Literal(0)),
                        makeThrowAssertionException(
                                new AssertionExceptionMessageBuilder(null)
                                    .appendViolatedCondition("step > 0")
                                    .prependAssertionDoc("step size must be greater than zero")
                                    .build()),
                        null));
               
            } else {
                stepName = null;
            }
           
            // int length = java.lang.reflect.Array.getLength(array);
            JCExpression lengthExpr = makeLengthExpr();
            if (lengthExpr != null) {
                result.add(makeVar(FINAL, lengthName,
                        makeIndexType(),
                        lengthExpr));
            }
           
            // int i = 0;
            JCStatement iVar = makeVar(indexName,
                    makeIndexType(),
                    makeIndexInit());
            // i < length;
            JCExpression iCond = makeCondition();
            // i++
            JCExpression iIncr = makeIncrement(stepName);
           
            Tree.ControlClause prevControlClause = currentForClause;
            currentForClause = stmt.getForClause();
            List<JCStatement> transformedBlock = transformBlock(getBlock());
            currentForClause = prevControlClause;
           
            // FOO element = java.lang.reflect.Array.get(array, i);
            JCExpression elementGet = makeIndexedAccess();
           
            Tree.ForIterator forIterator = getForIterator();
            if (forIterator instanceof Tree.ValueIterator) {
                String varName = Naming.getVariableName(((Tree.ValueIterator)forIterator).getVariable());
                JCStatement variable = makeVar(FINAL,
                        varName,
                        makeJavaType(elementType),
                        elementGet);
                // Prepend to the block
                transformedBlock = transformedBlock.prepend(variable);
            } else if (forIterator instanceof Tree.KeyValueIterator) {
                SyntheticName entryName = naming.alias("entry");
                JCStatement entryVariable = makeVar(FINAL, entryName,
                        makeJavaType(typeFact().getEntryType(typeFact().getAnythingDeclaration().getType(), typeFact().getAnythingDeclaration().getType()), JT_RAW),
                        elementGet);
                ProducedType entryType = elementType.getSupertype(typeFact().getEntryDeclaration());
                ProducedType keyType = entryType.getTypeArgumentList().get(0);
                String keyName = Naming.getVariableName(((Tree.KeyValueIterator)forIterator).getKeyVariable());
                JCStatement keyVariable = makeVar(FINAL,
                        keyName,
                        makeJavaType(keyType),
                        expressionGen().applyErasureAndBoxing(
                                make().Apply(null, naming.makeQualIdent(entryName.makeIdent(), "getKey"), List.<JCExpression>nil()),
                                typeFact().getAnythingDeclaration().getType(), true, BoxingStrategy.UNBOXED, keyType));
                ProducedType valueType = entryType.getTypeArgumentList().get(1);
                String valueName = Naming.getVariableName(((Tree.KeyValueIterator)forIterator).getValueVariable());
                JCStatement valueVariable = makeVar(FINAL,
                        valueName,
                        makeJavaType(valueType),
                        expressionGen().applyErasureAndBoxing(
                                make().Apply(null, naming.makeQualIdent(entryName.makeIdent(), "getItem"), List.<JCExpression>nil()),
                                typeFact().getAnythingDeclaration().getType(), true, BoxingStrategy.UNBOXED, valueType));
                // Prepend to the block
                transformedBlock = transformedBlock.prepend(valueVariable);
                transformedBlock = transformedBlock.prepend(keyVariable);
                transformedBlock = transformedBlock.prepend(entryVariable);
            }
           
            JCStatement block = make().Block(0, transformedBlock);
            result.add(make().Labelled(this.label, make().ForLoop(
                    List.<JCStatement>of(iVar),
                    iCond,
                    List.<JCExpressionStatement>of(make().Exec(iIncr)),
                    block)));
            return result;
        }

        protected JCExpression makeIndexType() {
            return make().Type(syms().intType);
        }

        /** Makes the expression for incrementing the index */
        protected JCExpression makeStepExpr() {
            ProducedType intType = typeFact().getIntegerDeclaration().getType();
            intType.setUnderlyingType("int");
            return expressionGen().transformExpression(step, BoxingStrategy.UNBOXED,
                    intType);
        }
       
        protected JCExpression makeIncrement(SyntheticName stepName) {
            if (stepName == null) {
                return make().Unary(JCTree.POSTINC, indexName.makeIdent());
            } else {
                return make().Assignop(JCTree.PLUS_ASG, indexName.makeIdent(), stepName.makeIdent());
            }
        }
       
        protected JCExpression makeCondition() {
            return make().Binary(JCTree.LT, indexName.makeIdent(), lengthName.makeIdent());
        }
       
        protected abstract JCExpression makeIndexableType();
       
        protected JCExpression makeIndexInit() {
            return make().Literal(0);
        }
       
        /** Makes the expression for the accessing the indexable at the current index */
        protected abstract JCExpression makeIndexedAccess();
       
        /** Makes the expression for the length of the iteration */
        protected abstract JCExpression makeLengthExpr();
       
        /** Makes the expression for the thing to be iterated over */
        protected abstract JCExpression makeIndexable();
    }
   
    /**
     * Optimized transformation for a {@code for} loop where the iterable is
     * statically known to be an {@code Array}, and therefore can be
     * iterated using a C-style {@code for}.
     */
    class ArrayIterationOptimization extends IndexedAccessIterationOptimization {
       
        public static final String OPT_NAME = "ArrayIterationStatic";
        private final boolean unboxed;
       
        ArrayIterationOptimization(Tree.ForStatement stmt,
                Tree.Term baseIterable, Tree.Term step,
                ProducedType arrayType) {
            super(stmt, baseIterable, step, typeFact().getArrayElementType(arrayType), "array");
            unboxed = typeFact().getArrayType(typeFact().getBooleanDeclaration().getType()).isExactly(arrayType)
                    || typeFact().getArrayType(typeFact().getByteDeclaration().getType()).isExactly(arrayType)
                    || typeFact().getArrayType(typeFact().getIntegerDeclaration().getType()).isExactly(arrayType)
                    || typeFact().getArrayType(typeFact().getCharacterDeclaration().getType()).isExactly(arrayType)
                    || typeFact().getArrayType(typeFact().getFloatDeclaration().getType()).isExactly(arrayType);
        }
       
        @Override
        protected JCExpression makeIndexableType() {
            if(unboxed)
                return make().Type(syms().objectType);
            return makeJavaType(typeFact().getArrayType(elementType));
        }
       
        @Override
        protected JCExpression makeIndexable() {
            JCExpression iterableExpr = expressionGen().transformExpression(getIterable());
            if(unboxed)
                return make().Apply(null,
                                    naming.makeQualIdent(iterableExpr, "toArray"),
                                    List.<JCExpression>nil());
            return iterableExpr;
        }
       
        protected JCExpression makeCondition() {
            return make().Binary(JCTree.LT, indexName.makeIdent(), lengthName.makeIdent());
        }
       
        @Override
        protected JCExpression makeLengthExpr() {
            if(unboxed)
                return utilInvocation().arrayLength(indexableName.makeIdent());
            else
                return make().TypeCast(make().Type(syms().intType),
                                       make().Apply(null,
                                                    naming.makeQualIdent(indexableName.makeIdent(), "getSize"),
                                                    List.<JCExpression>nil()));
        }
       
        @Override
        protected JCExpression makeIndexedAccess() {
            ProducedType gotType = null;
            JCExpression elementGet = null;
            boolean typeErased = false;
            boolean exprBoxed = false;
            if (isCeylonBoolean(elementType)) {
                elementGet = utilInvocation().getBooleanArray(
                        indexableName.makeIdent(), indexName.makeIdent());
                gotType = elementType;
            } else if (isCeylonFloat(elementType)) {
                elementGet = utilInvocation().getFloatArray(
                        indexableName.makeIdent(), indexName.makeIdent());
                gotType = elementType;
            } else if (isCeylonInteger(elementType)) {
                elementGet = utilInvocation().getIntegerArray(
                        indexableName.makeIdent(), indexName.makeIdent());
                gotType = elementType;
            } else if (isCeylonCharacter(elementType)) {
                elementGet = utilInvocation().getCharacterArray(
                        indexableName.makeIdent(), indexName.makeIdent());
                gotType = elementType;
            } else if (isCeylonByte(elementType)) {
                elementGet = utilInvocation().getByteArray(
                        indexableName.makeIdent(), indexName.makeIdent());
                gotType = elementType;
            }
           
            if(elementGet == null){
                elementGet = make().Apply(null,
                                          naming.makeQualIdent(indexableName.makeIdent(), "unsafeItem"),
                                          List.<JCExpression>of(indexName.makeIdent()));
                gotType = typeFact().getObjectDeclaration().getType();
                typeErased = true;
                exprBoxed = true;
            }
            elementGet = expressionGen().applyErasureAndBoxing(
                    elementGet, gotType, typeErased, exprBoxed,
                    CodegenUtil.getBoxingStrategy(getElementOrKeyVariable().getDeclarationModel()),
                    elementType, 0);
            return elementGet;
        }
    }
   
    /**
     * Optimized transformation for a {@code for} loop where the iterable is
     * statically known to be a Java array (int[], long[] Object[] etc)
     * and therefore can be
     * iterated using a C-style {@code for}.
     */
    class JavaArrayIterationOptimization extends IndexedAccessIterationOptimization {
       
        public static final String OPT_NAME = "JavaArrayIterationStatic";
        /** this is the IntArray, ObjectArray or whatever */
        private final ProducedType javaArrayType;
       
        JavaArrayIterationOptimization(Tree.ForStatement stmt,
                Tree.Term baseIterable, Tree.Term step,
                ProducedType elementType, ProducedType javaArrayType) {
            super(stmt, baseIterable, step, elementType, "array");
            this.javaArrayType = javaArrayType;
        }
       
        @Override
        protected JCExpression makeIndexableType() {
            return makeJavaType(javaArrayType);
        }
       
        @Override
        protected JCExpression makeIndexable() {
            Tree.QualifiedMemberExpression expr = (Tree.QualifiedMemberExpression)getIterable();
           
            return expressionGen().transformExpression(expr.getPrimary());
        }
       
        protected JCExpression makeCondition() {
            JCExpression lengthExpr = naming.makeQualIdent(indexableName.makeIdent(), "length");
            return make().Binary(JCTree.LT, indexName.makeIdent(), lengthExpr);
        }
       
        @Override
        protected JCExpression makeLengthExpr() {
            return null;
        }
       
        @Override
        protected JCExpression makeIndexedAccess() {
            return make().Indexed(indexableName.makeIdent(), indexName.makeIdent());
        }
    }
   
    private ForStatementTransformation arrayIteration(Tree.ForStatement stmt,
            Tree.Term baseIterable,
            Tree.Term step) {
       
        ProducedType ceylonArrayType = baseIterable.getTypeModel();
        ProducedType elementType = typeFact().getArrayElementType(ceylonArrayType);
        if (elementType == null) {
            // Check for "for (x in javaArray.iterable)" where javaArray is e.g. IntArray
           
            if (baseIterable instanceof Tree.QualifiedMemberExpression) {
                Tree.QualifiedMemberExpression expr = (Tree.QualifiedMemberExpression)baseIterable;
                if ("iterable".equals(expr.getIdentifier().getText())) {
                    if (Decl.isJavaArray(expr.getPrimary().getTypeModel().getDeclaration())) {// What's this for
                        if (isOptimizationDisabled(stmt, Optimization.JavaArrayIterationStatic)) {
                            return optimizationDisabled(stmt, Optimization.JavaArrayIterationStatic);
                        }
                        elementType = typeFact().getIteratedType(ceylonArrayType);
                        return new JavaArrayIterationOptimization(stmt, baseIterable, step, elementType, expr.getPrimary().getTypeModel());
                    }
                }
            }
        } else {
            if (isOptimizationRequired(stmt, Optimization.JavaArrayIterationStatic)) {
                return optimizationFailed(stmt, Optimization.JavaArrayIterationStatic, "iterable expression wasn't of form javaArray.array");
            }
            if (isOptimizationDisabled(stmt, Optimization.ArrayIterationStatic)) {
                return optimizationDisabled(stmt, Optimization.ArrayIterationStatic);
            }
            // it's an Ceylon Array
            return new ArrayIterationOptimization(stmt, baseIterable, step, ceylonArrayType);
        }
        if (isOptimizationRequired(stmt, Optimization.JavaArrayIterationStatic)) {
            return optimizationFailed(stmt, Optimization.JavaArrayIterationStatic, "iterable expression wasn't of form javaArray.iterable");
        }
        if (isOptimizationRequired(stmt, Optimization.ArrayIterationStatic)) {
            return optimizationFailed(stmt, Optimization.ArrayIterationStatic, "static type of iterable in for statement is not Array");
        }
        return null;
    }

    private boolean isSpanOf(Tree.RangeOp range, ProducedType ofType) {
        ProducedType rangeType = range.getTypeModel();
        return typeFact().getSpanType(ofType).isExactly(rangeType);
    }


    /**
     * Returns null but logs an error with the given reason if an optimization
     * asserted using {@code @requireOptimization["optName"]} couldn't be
     * performed.
     *
     * @param stmt The thing with the {@code @requireOptimization} compiler
     * annotation
     * @param optName The name of the optimization
     * @param reason The reason the optimization could not be used
     * @return null
     */
    private <T,S extends Tree.StatementOrArgument> T optimizationFailed(S stmt, Optimization optName, String reason) {
        return optimizationFailed(stmt, new Optimization[]{optName}, reason);
    }
    private <T,S extends Tree.StatementOrArgument> T optimizationFailed(S stmt, Optimization[] optNames, String reason) {
        for (Optimization optName : optNames) {
            if (CodegenUtil.hasCompilerAnnotationWithArgument(stmt,
                            "requireOptimization", optName.toString())) {
                log.error(getPosition(stmt), "ceylon.optim.failed", optName, reason);
            }
        }
        return null;
    }
   
    /**
     * Returns null but logs an error the given optimization
     */
    private <T,S extends Tree.StatementOrArgument> T optimizationDisabled(S stmt, Optimization optName) {
        return optimizationFailed(stmt, optName,
                "optimization explicitly disabled by @disableOptimization");
    }
   
    /**
     * Determines whether the given optimization has been disabled on the
     * given statement.
     * @param stmt The thing with the {@code @requireOptimization} compiler
     * annotation.
     * @param optName The name of the optimization
     * @return
     */
    private boolean isOptimizationDisabled(Tree.StatementOrArgument stmt, Optimization optName) {
        return this.disabledOptimizations.contains(optName)
                || CodegenUtil.hasCompilerAnnotationNoArgument(stmt, "disableOptimization")
                || CodegenUtil.hasCompilerAnnotationWithArgument(stmt,
                        "disableOptimization", optName.toString());
    }
   
    private boolean isOptimizationRequired(Tree.StatementOrArgument stmt, Optimization optName) {
        return optName == null ? CodegenUtil.hasCompilerAnnotationNoArgument(stmt, "requireOptimization")
                : CodegenUtil.hasCompilerAnnotationWithArgument(stmt,
                        "requireOptimization", optName.toString());
    }
   
    /**
     * Returns a {@link RangeOpIterationOptimization} if that optimization applies
     * to the given {@code for} statement, otherwise null.
     * @param stmt The for statement
     * @return a {@link RangeOpIterationOptimization} or null.
     */
    private ForStatementTransformation spanOpIteration(Tree.ForStatement stmt) {
        if (isOptimizationDisabled(stmt, Optimization.SpanOpIteration)) {
            return optimizationFailed(stmt, Optimization.SpanOpIteration,
                    "optimization explicitly disabled by @disableOptimization");
        }
       
        Tree.ForIterator iterator = stmt.getForClause().getForIterator();
        if (!(iterator instanceof Tree.ValueIterator)) {
            return optimizationFailed(stmt, Optimization.SpanOpIteration,
                    "optimization applies only to ValueIterators");
        }
        Tree.ValueIterator vi = (Tree.ValueIterator)iterator;
        Tree.SpecifierExpression specifier = vi.getSpecifierExpression();
        Tree.Term term = specifier.getExpression().getTerm();
        final Tree.Term increment;
        final Tree.RangeOp range;
        if (term instanceof Tree.RangeOp) {
            // So it's a for (i in (lhs..rhs)) { ... }
            increment = null;
            range = (Tree.RangeOp)term;
        } else if (term instanceof Tree.InvocationExpression) {
            Tree.InvocationExpression inv = (Tree.InvocationExpression)term;
            if (inv.getPrimary() instanceof Tree.QualifiedMemberExpression) {
                Tree.QualifiedMemberExpression prim = (Tree.QualifiedMemberExpression)inv.getPrimary();
                if ("by".equals(prim.getIdentifier().getText())
                        && prim.getPrimary() instanceof Tree.Expression
                        && (((Tree.Expression)(prim.getPrimary())).getTerm() instanceof Tree.RangeOp)) {
                    // So it's a for (i in (lhs..rhs).by(increment)) { ... }
                    range = (Tree.RangeOp)((Tree.Expression)(prim.getPrimary())).getTerm();                   
                    if (inv.getPositionalArgumentList() != null) {
                        Tree.PositionalArgument a = inv.getPositionalArgumentList().getPositionalArguments().get(0);
                        if(a instanceof Tree.ListedArgument)
                            increment = ((Tree.ListedArgument)a).getExpression().getTerm();
                        else
                            return optimizationFailed(stmt, Optimization.SpanOpIteration,
                                    "Unable to determine expression for argument to by(): appears spread or comprehension");
                    } else if (inv.getNamedArgumentList() != null) {
                        Tree.SpecifiedArgument sarg = null;
                        for (Tree.NamedArgument arg : inv.getNamedArgumentList().getNamedArguments()) {
                            if ("step".equals(arg.getIdentifier().getText())) {
                                if (arg instanceof Tree.SpecifiedArgument) {
                                    sarg = ((Tree.SpecifiedArgument)arg);
                                    break;
                                }
                                // TODO In theory we could support Tree.AttributeArgument too
                            }
                        }
                        if (sarg != null) {
                            increment = sarg.getSpecifierExpression().getExpression().getTerm();
                        } else {
                            return optimizationFailed(stmt, Optimization.SpanOpIteration,
                                    "Unable to determine expression for argument to by{}");
                        }
                    } else {
                        return optimizationFailed(stmt, Optimization.SpanOpIteration,
                                "Unable to get arguments to by()");
                    }
                } else {
                    return optimizationFailed(stmt, Optimization.SpanOpIteration,
                            "Only applies to Iterables of the form 'lhs..rhs' or '(lhs..rhs).by(step)'");
                }
            } else {
                return optimizationFailed(stmt, Optimization.SpanOpIteration,
                        "Only applies to Iterables of the form 'lhs..rhs' or '(lhs..rhs).by(step)'");
            }
        } else {
            return optimizationFailed(stmt, Optimization.SpanOpIteration,
                    "Only applies to Iterables of the form 'lhs..rhs' or '(lhs..rhs).by(step)'");
        }
       
        Type type;
        ProducedType integerType = typeFact().getIntegerDeclaration().getType();
        ProducedType characterType = typeFact().getCharacterDeclaration().getType();
        if (isSpanOf(range, integerType)) {
            type = syms().longType;
        } else if (isSpanOf(range, characterType)) {
            type = syms().intType;
        } else {
            return optimizationFailed(stmt, Optimization.SpanOpIteration, "The RangeOp doesn't produce a Range<Integer>/Range<Character>");
        }
        return new RangeOpIterationOptimization(stmt,
                range,
                increment, type);
    }
   
    /**
     * <p>Transformation of {@code for} loops over {@code Range<Integer>}
     * or {@code Range<Character>} which avoids allocating a {@code Range} and
     * using an {@code Iterator} like
     * {@link #ForStatementTransformation} but instead outputs a C-style
     * {@code for} loop.</p>
     */
    private class SegmentOpIteration extends IndexedAccessIterationOptimization {

        private final Tree.Term start;
        private final Tree.Term length;

        SegmentOpIteration(Tree.ForStatement stmt, Tree.SegmentOp op, Tree.Term step, Tree.Term start, Tree.Term length) {
            super(stmt, op, step, typeFact().getIteratedType(op.getTypeModel()), "start", "end", "i");
            this.start = start;
            this.length = length;
            // TODO If length if < 0 we need to not loop at all
        }

        @Override
        protected JCExpression makeIndexableType() {
            return makeJavaType(this.elementType);
        }
       
        @Override
        protected JCExpression makeIndexable() {
            return expressionGen().transformExpression(start, BoxingStrategy.UNBOXED, length.getTypeModel());
        }

        protected JCExpression makeIndexType() {
            return makeJavaType(this.elementType);
        }
       
        protected JCExpression makeIndexInit() {
            return indexableName.makeIdent();
        }
       
        @Override
        protected JCExpression makeIndexedAccess() {
            return indexName.makeIdent();
        }

        @Override
        protected JCExpression makeLengthExpr() {
            JCExpression result = make().Binary(JCTree.PLUS,
                    indexableName.makeIdent(),
                    make().Apply(null,
                            naming.makeQuotedFQIdent("java.lang.Math.max"),
                            List.<JCExpression>of(
                                    make().Literal(0L),
                                    expressionGen().transformExpression(length,
                                            BoxingStrategy.UNBOXED, length.getTypeModel()))));
            if (isCeylonCharacter(elementType)) {
                result = make().TypeCast(syms().intType, result);
            }
            return result;
        }
       
        protected JCExpression makeStepExpr() {
            return expressionGen().transformExpression(step, BoxingStrategy.UNBOXED,
                    elementType);
        }
       
    }
   
    private ForStatementTransformation segmentOpIteration(Tree.ForStatement stmt,
            Tree.Term baseIterable, Tree.Term step) {
        if (!(baseIterable instanceof Tree.SegmentOp)) {
            return optimizationFailed(stmt, Optimization.SegmentOpIteration,
                    "base iterable is no a segment op");
        }
       
       
        final Tree.SegmentOp op = (Tree.SegmentOp)baseIterable;
        ProducedType iteratedType = typeFact().getIteratedType(op.getTypeModel());
        if (iteratedType.isExactly(typeFact().getIntegerDeclaration().getType())) {
           
        } else if (iteratedType.isExactly(typeFact().getCharacterDeclaration().getType())) {
           
        } else {
            return optimizationFailed(stmt, Optimization.SegmentOpIteration,
                    "base iterable is neither Range<Integer> not Range<Character>");
        }
       
        Tree.Term start = op.getLeftTerm();
        Tree.Term length = op.getRightTerm();
       
        if (isOptimizationDisabled(stmt, Optimization.SegmentOpIteration)) {
            return optimizationDisabled(stmt, Optimization.SegmentOpIteration);
        }
       
        return new SegmentOpIteration(stmt, op, step, start, length);
    }
   
    private Tree.ControlClause currentForClause = null;
   
    class ForStatementTransformation {
       
        protected Tree.ForStatement stmt;
        protected final Name label;
       
        ForStatementTransformation(Tree.ForStatement stmt) {
            this.stmt = stmt;
            this.label = getLabel(stmt.getForClause().getControlBlock());
        }
       
        protected final Tree.ForIterator getForIterator() {
            Tree.ForIterator forIterator = stmt.getForClause().getForIterator();
            return forIterator;
        }
       
        protected Tree.Term getIterable() {
            return getForIterator().getSpecifierExpression().getExpression().getTerm();
        }
       
        protected final boolean isValueIterator() {
            return getForIterator() instanceof Tree.ValueIterator;
        }

        protected final Tree.Variable getElementOrKeyVariable() {
            Tree.ForIterator iterator = getForIterator();
            if (iterator instanceof Tree.ValueIterator) {
                return ((Tree.ValueIterator)iterator).getVariable();
            } else if (iterator instanceof Tree.KeyValueIterator) {
                return ((Tree.KeyValueIterator)iterator).getKeyVariable();
            }
            return null;
        }
       
        protected final Tree.Variable getValueVariable() {
            Tree.ForIterator iterator = getForIterator();
            if (iterator instanceof Tree.KeyValueIterator) {
                return ((Tree.KeyValueIterator)iterator).getKeyVariable();
            }
            return null;
        }
       
        protected List<JCStatement> transform() {
            at(stmt);
            ListBuffer<JCStatement> outer = ListBuffer.<JCStatement> lb();
            Name tempForFailVariable = currentForFailVariable;
            try {
                // Install the outer substitutions
                Iterable<Value> deferredSpecifiedInFor = stmt.getForClause().getControlBlock().getSpecifiedValues();
                if (deferredSpecifiedInFor != null) {
                    for (Value value : deferredSpecifiedInFor) {
                        DeferredSpecification ds  = StatementTransformer.this.deferredSpecifications.get(value);
                        ds.installOuterSubstitution();
                    }
                }

                if (needsFailVar()) {
                    // boolean $doforelse$X = true;
                    JCVariableDecl failtest_decl = make().VarDef(make().Modifiers(0), naming.aliasName("doforelse"), make().TypeIdent(TypeTags.BOOLEAN), make().Literal(TypeTags.BOOLEAN, 1));
                    outer.append(failtest_decl);
                    currentForFailVariable = failtest_decl.getName();
                } else {
                    currentForFailVariable = null;
                }
               
                outer.appendList(transformForClause());
               
                if (stmt.getElseClause() != null) {
                    // The user-supplied contents of fail block
                    List<JCStatement> failblock = transformBlock(stmt.getElseClause().getBlock());
                    // Close the inner substitutions of the else block
                    closeInnerSubstituionsForSpecifiedValues(stmt.getElseClause());
                    if (needsFailVar()) {
                        // if ($doforelse$X) ...
                        JCIdent failtest_id = at(stmt).Ident(currentForFailVariable);
                        outer.append(at(stmt).If(failtest_id, at(stmt).Block(0, failblock), null));
                    } else {
                        outer.appendList(failblock);
                    }
                }
               
                // Close the outer substitutions
                if (deferredSpecifiedInFor != null) {
                    for (Value value : deferredSpecifiedInFor) {
                        DeferredSpecification ds  = StatementTransformer.this.deferredSpecifications.get(value);
                        outer.append(ds.closeOuterSubstitution());
                    }
                }
           
            } finally {
                currentForFailVariable = tempForFailVariable;
            }
   
            return outer.toList();
        }

        private boolean needsFailVar() {
            return stmt.getExits() && stmt.getElseClause() != null;
        }

        protected ListBuffer<JCStatement> transformForClause() {
            Naming.SyntheticName elem_name = naming.alias("elem");
           
            Tree.ForIterator iterDecl = stmt.getForClause().getForIterator();
            Tree.Variable variable;
            Tree.Variable valueVariable;
            if (iterDecl instanceof Tree.ValueIterator) {
                variable = ((Tree.ValueIterator) iterDecl).getVariable();
                valueVariable = null;
            } else if (iterDecl instanceof Tree.KeyValueIterator) {
                variable = ((Tree.KeyValueIterator) iterDecl).getKeyVariable();
                valueVariable = ((Tree.KeyValueIterator) iterDecl).getValueVariable();
            } else {
                throw BugException.unhandledNodeCase(iterDecl);
            }
           
            final Naming.SyntheticName loopVarName = naming.synthetic(variable.getDeclarationModel());
            Tree.Expression specifierExpression = iterDecl.getSpecifierExpression().getExpression();
            ProducedType sequenceElementType;
            if(valueVariable == null)
                sequenceElementType = variable.getType().getTypeModel();
            else{
                // Entry<V1,V2>
                sequenceElementType = typeFact().getEntryType(variable.getType().getTypeModel(),
                        valueVariable.getType().getTypeModel());
            }
            ProducedType sequenceType = specifierExpression.getTypeModel().getSupertype(typeFact().getIterableDeclaration());
            ProducedType expectedIterableType = typeFact().isNonemptyIterableType(sequenceType)
                    ? typeFact().getNonemptyIterableType(sequenceElementType)
                    : typeFact().getIterableType(sequenceElementType);
            JCExpression castElem = at(stmt).TypeCast(makeJavaType(sequenceElementType, CeylonTransformer.JT_NO_PRIMITIVES), elem_name.makeIdent());
            List<JCAnnotation> annots = makeJavaTypeAnnotations(variable.getDeclarationModel());

            // ceylon.language.Iterator<T> $V$iter$X = ITERABLE.getIterator();
            JCExpression containment = expressionGen().transformExpression(specifierExpression, BoxingStrategy.BOXED, expectedIterableType);
           
            // final U n = $elem$X;
            // or
            // final U n = $elem$X.getKey();
            JCExpression loopVarInit;
            ProducedType loopVarType;
            if (valueVariable == null) {
                loopVarType = sequenceElementType;
                loopVarInit = castElem;
            } else {
                loopVarType = variable.getDeclarationModel().getType();
                loopVarInit = at(stmt).Apply(null, makeSelect(castElem, Naming.getGetterName("key")), List.<JCExpression> nil());
            }
            JCVariableDecl itemOrKeyDecl = at(stmt).VarDef(make().Modifiers(FINAL, annots), loopVarName.asName(), makeJavaType(loopVarType),
                    boxUnboxIfNecessary(loopVarInit, true, loopVarType, CodegenUtil.getBoxingStrategy(variable.getDeclarationModel())));
            final SyntheticName iteratorVarName = loopVarName.suffixedBy(Suffix.$iterator$).alias();
            List<JCStatement> itemDecls = List.<JCStatement> of(itemOrKeyDecl);

            if (valueVariable != null) {
                // final V n = $elem$X.getElement();
                ProducedType valueVarType = valueVariable.getDeclarationModel().getType();
                JCExpression valueVarTypeExpr = makeJavaType(valueVarType);
                JCExpression valueVarInitExpr = at(stmt).Apply(null, makeSelect(castElem, Naming.getGetterName("item")), List.<JCExpression> nil());
                String valueVarName = valueVariable.getIdentifier().getText();
                JCVariableDecl valueDecl = at(stmt).VarDef(make().Modifiers(FINAL, annots), names().fromString(valueVarName), valueVarTypeExpr,
                        boxUnboxIfNecessary(valueVarInitExpr, true, valueVarType, CodegenUtil.getBoxingStrategy(valueVariable.getDeclarationModel())));
                itemDecls = itemDecls.append(valueDecl);
            }

            Tree.ControlClause prevControlClause = currentForClause;
            currentForClause = stmt.getForClause();
            List<JCStatement> stmts = transformBlock(stmt.getForClause().getBlock());
            currentForClause = prevControlClause;
           
            return ListBuffer.<JCStatement>lb().appendList(transformIterableIteration(stmt,
                    this.label,
                    elem_name,
                    iteratorVarName,
                    iterDecl.getSpecifierExpression().getExpression().getTypeModel(),
                    sequenceElementType,
                    containment,
                    itemDecls,
                    stmts,
                    !isOptimizationDisabled(stmt, Optimization.ArrayIterationDynamic),
                    !isOptimizationDisabled(stmt, Optimization.TupleIterationDynamic)));
        }
       
        protected final Tree.Block getBlock() {
            return stmt.getForClause().getBlock();
        }
    }
   
    /**
     * The transformation of a ceylon {@code for} loop:
     *
     * <pre>
     *     java.lang.Object ITERATION_VAR_NAME;
     *     Iterator<ITERATOR_ELEMENT_TYPE> ITERATOR_VAR_NAME = ITERABLE.getIterator();
     *     while (
     *             !((ITERATION_VAR_NAME = ITERATOR_VAR_NAME.getNext()) instanceof ceylon.language.Finished;
     *         ) {
     *         ITEM_DECLS;
     *         BODY_STMTS;
     *     }
     * </pre>
     * @param label
     *
     * @param iterationVarName The iteration variable (which recieves the value of {@code Iterator.next()})
     * @param iteratorVarName The name of the {@code Iterator} variable
     * @param iteratorElementType The type argument of the {@code Iterator}
     * @param iterableExpr The {@code Iterable} expression
     * @param itemDecls variable declarations for the iteration variables which
     * begin the loop body (these depend on {@code iterationVarName} and may
     * typecast or destructure it). May be null.
     * @param bodyStmts Other statements in the loop body
     * @return
     */
    List<JCStatement> transformIterableIteration(Node node,
            Name label, Naming.SyntheticName iterationVarName,
            Naming.SyntheticName iteratorVarName,
            ProducedType iterableType, ProducedType iteratedType,
            JCExpression iterableExpr,
            List<JCStatement> itemDecls,
            List<JCStatement> bodyStmts,
            boolean allowArrayOpt, boolean allowArraySeqOpt) {
        ProducedType iteratorElementType = iteratedType;
        ListBuffer<JCStatement> result = ListBuffer.<JCStatement>lb();
       
        // TODO Only when the iterable *could be* an array (e.g. if static type is Iterable, but not if static type is Sequence)
        // TODO Need to use naming.Infix for the hidden members of Array
        boolean optForArray = allowArrayOpt && typeFact().getArrayType(iteratedType).isSubtypeOf(iterableType);
        boolean optForTuple = allowArraySeqOpt && typeFact().getTupleType(Collections.singletonList(iteratedType), true, false, -1).isSubtypeOf(iterableType);
       
        SyntheticName iterableName = optForArray || optForTuple ? naming.alias("iterable") : null;
        SyntheticName isArrayName = optForArray ? naming.alias("isArray") : null;
        SyntheticName isTupleName = optForTuple ? naming.alias("isTuple") : null;
        SyntheticName arrayName = optForArray || optForTuple ? naming.alias("array") : null;
        SyntheticName arrayIndex = optForArray || optForTuple ? naming.alias("i") : null;
        SyntheticName arrayLength = optForArray || optForTuple ? naming.alias("length") : null;
        if (optForArray || optForTuple) {
            result.append(makeVar(FINAL, iterableName, makeJavaType(typeFact().getIterableType(iteratorElementType)), iterableExpr));
        }
        if (optForArray) {
            result.append(makeVar(FINAL, isArrayName,
                    make().Type(syms().booleanType),
                    make().TypeTest(iterableName.makeIdent(),
                            makeJavaType(typeFact().getArrayType(iteratorElementType), JT_RAW))));
        }
        if (optForTuple) {
            result.append(makeVar(FINAL, isTupleName,
                    make().Type(syms().booleanType),
                    make().Binary(JCTree.AND,
                            make().TypeTest(iterableName.makeIdent(),
                                    make().QualIdent(syms().ceylonTupleType.tsym)),
                            make().Binary(JCTree.NE,
                                    make().Apply(null,
                                            naming.makeQualIdent(
                                                    make().TypeCast(make().QualIdent(syms().ceylonTupleType.tsym), iterableName.makeIdent()),
                                                    Unfix.$getArray$.toString()),
                                                List.<JCExpression>nil()),
                                    makeNull()))));
        }
       
        // java.lang.Object ELEM_NAME;
        JCVariableDecl elemDecl = makeVar(iterationVarName, make().Type(syms().objectType), optForArray || optForTuple ? makeNull() : null);
        result.append(elemDecl);
       
        SyntheticName iterName = iteratorVarName;
       
        ProducedType iteratorType = typeFact().getIteratorType(iteratorElementType);
        JCExpression iteratorTypeExpr = makeJavaType(iteratorType, CeylonTransformer.JT_TYPE_ARGUMENT);
       
        // ceylon.language.Iterator<T> LOOP_VAR_NAME$iter$X = ITERABLE.getIterator();
        // We don't need to unerase here as anything remotely a sequence will be erased to Iterable, which has getIterator()
        JCExpression getIter;
        if (optForArray || optForTuple) {
            at(node);
            result.append(makeVar(FINAL, arrayName, make().Type(syms().objectType), null));
            result.append(makeVar(arrayIndex, make().Type(syms().intType), make().Literal(0)));
            result.append(makeVar(FINAL, arrayLength, make().Type(syms().intType), null));
            ListBuffer<JCStatement> whenTuple = ListBuffer.<JCTree.JCStatement>lb();
            whenTuple.append(make().Exec(make().Assign(
                    arrayName.makeIdent(),
                    make().Apply(null,
                            naming.makeQualIdent(
                                    make().TypeCast(make().QualIdent(syms().ceylonTupleType.tsym), iterableName.makeIdent()),
                                    Unfix.$getArray$.toString()),
                            List.<JCExpression>nil()))));
            whenTuple.append(make().Exec(make().Assign(
                    arrayIndex.makeIdent(),
                    make().Apply(null,
                            naming.makeQualIdent(
                                    make().TypeCast(make().QualIdent(syms().ceylonTupleType.tsym), iterableName.makeIdent()),
                                    Unfix.$getFirst$.toString()),
                            List.<JCExpression>nil()))));
            whenTuple.append(make().Exec(make().Assign(
                    arrayLength.makeIdent(),
                    make().Binary(JCTree.PLUS, arrayIndex.makeIdent(), make().Apply(null,
                            naming.makeQualIdent(
                                    make().TypeCast(make().QualIdent(syms().ceylonTupleType.tsym), iterableName.makeIdent()),
                                    Unfix.$getLength$.toString()),
                            List.<JCExpression>nil())))));
           
            ListBuffer<JCStatement> whenArray = ListBuffer.<JCTree.JCStatement>lb();
            whenArray.append(make().Exec(make().Assign(
                    arrayName.makeIdent(),
                    make().Apply(null,
                            naming.makeQualIdent(
                                    make().TypeCast(makeJavaType(typeFact().getArrayType(typeFact().getAnythingDeclaration().getType()), JT_RAW), iterableName.makeIdent()),
                                    "toArray"),
                            List.<JCExpression>nil()))));
            whenArray.append(make().Exec(make().Assign(
                    arrayLength.makeIdent(),
                    make().Apply(null,
                            naming.makeQuotedFQIdent("com.redhat.ceylon.compiler.java.Util.arrayLength"),
                            List.<JCExpression>of(arrayName.makeIdent())))));
            ListBuffer<JCStatement> whenIterable = ListBuffer.<JCTree.JCStatement>lb();
            whenIterable.append(make().Exec(make().Assign(
                    arrayName.makeIdent(),
                    makeNull())));
            whenIterable.append(make().Exec(make().Assign(
                    arrayLength.makeIdent(),
                    make().Literal(0))));
            if (optForArray && optForTuple) {
                result.append(make().If(isTupleName.makeIdent(),
                        make().Block(0, whenTuple.toList()),
                        make().If(isArrayName.makeIdent(),
                            make().Block(0, whenArray.toList()),
                            make().Block(0, whenIterable.toList()))));
            } else {
                result.append(make().If((optForArray ? isArrayName : isTupleName).makeIdent(),
                        make().Block(0, (optForArray ? whenArray : whenTuple).toList()),
                        make().Block(0, whenIterable.toList())));
            }
           
            getIter = make().Conditional(
                    optForArray && optForTuple ? make().Binary(JCTree.OR, isTupleName.makeIdent(), isArrayName.makeIdent()): optForArray ? isArrayName.makeIdent() : isTupleName.makeIdent(),
                    makeNull(),
                    make().Apply(null, makeSelect(iterableName.makeIdent(), "iterator"), List.<JCExpression> nil()));
        } else {
            getIter = at(node).Apply(null, makeSelect(iterableExpr, "iterator"), List.<JCExpression> nil());
        }
        getIter = gen().expressionGen().applyErasureAndBoxing(getIter, iteratorType, true, BoxingStrategy.BOXED, iteratorType);
        JCVariableDecl iteratorDecl = at(node).VarDef(make().Modifiers(0), iterName.asName(), iteratorTypeExpr, getIter);
        // .ceylon.language.Iterator<T> LOOP_VAR_NAME$iter$X = ITERABLE.getIterator();
        result.append(iteratorDecl);
       
        ListBuffer<JCStatement> loopBody = ListBuffer.<JCStatement>lb();
       
        if(optForArray || optForTuple) {
            JCExpression cond;
            if (optForArray && optForTuple) {
                cond = make().Binary(JCTree.OR, isTupleName.makeIdent(), isArrayName.makeIdent());
            } else if (optForArray) {
                cond = isArrayName.makeIdent();
            } else {
                cond = isTupleName.makeIdent();
            }
            loopBody.append(make().If(cond,
                    make().Exec(make().Assign(iterationVarName.makeIdent(),
                            make().Apply(null,
                                    naming.makeQuotedFQIdent("com.redhat.ceylon.compiler.java.Util.getObjectArray"),
                                    List.<JCExpression>of(arrayName.makeIdent(),
                                            make().Unary(JCTree.POSTINC, arrayIndex.makeIdent()))))),
                    null));
        }
       
        if (itemDecls != null) {
            loopBody.appendList(itemDecls);
        }

        // The user-supplied contents of the loop
        loopBody.appendList(bodyStmts);
       
        // ELEM_NAME = LOOP_VAR_NAME$iter$X.next()
        JCExpression iter_elem = make().Apply(null, makeSelect(iterName.makeIdent(), "next"), List.<JCExpression> nil());
        JCExpression elem_assign = make().Assign(iterationVarName.makeIdent(), iter_elem);
        // !((ELEM_NAME = LOOP_VAR_NAME$iter$X.next()) instanceof Finished)
        JCExpression instof = make().TypeTest(elem_assign, makeIdent(syms().ceylonFinishedType));
        JCExpression loopCond = make().Unary(JCTree.NOT, instof);
        if (optForArray || optForTuple) {
            JCExpression cond;
            if (optForArray && optForTuple) {
                cond = make().Binary(JCTree.OR, isTupleName.makeIdent(), isArrayName.makeIdent());
            } else if (optForArray) {
                cond = isArrayName.makeIdent();
            } else {
                cond = isTupleName.makeIdent();
            }
            loopCond = make().Conditional(cond,
                    make().Binary(JCTree.LT, arrayIndex.makeIdent(), arrayLength.makeIdent()),
                    make().Unary(JCTree.NOT, instof));
        }
       
        // while (!(($elem$X = $V$iter$X.next()) instanceof Finished); ) {
        JCStatement whileLoop = at(node).WhileLoop(loopCond, at(node).Block(0, loopBody.toList()));
        if (label != null) {
            whileLoop = make().Labelled(label, whileLoop);
        }
        return result.append(whileLoop).toList();
    }
   
   
    /**
     * <p>Transformation of {@code for} loops over {@code Range<Integer>}
     * or {@code Range<Character>} which avoids allocating a {@code Range} and
     * using an {@code Iterator} like
     * {@link #ForStatementTransformation} but instead outputs a C-style
     * {@code for} loop. Because a Range is never empty we can also omit
     * code for handling {@code else} clauses of {@code for} statements when
     * we know the {@code for} block returns normally</p>
     *
     * <p>This is able to optimize statements like the following:</p>
     * <ul>
     * <li>{@code for (i in lhs..rhs) ... }</li>
     * <li>{@code for (i in (lhs..rhs).by(increment)) ... }</li>
     * <ul>
     * <p>where {@code lhs}, {@code rhs} and {@code increment} are
     * expressions (not necessarily literals or compile-time constants).</p>
     *
     * <p>Given a statement like {@code for (i in (lhs..rhs).by(increment) ...}
     * we generate something like this:</p>
     * <pre>
     *  long by$ = by;
     *  if (by$ <= 0) {
     *      throw new Exception(ceylon.language.String.instance("step size must be greater than zero"));
     *  }
     *  final long start$ = lhs;
     *  final long end$ = rhs;
     *  final boolean increasing$ = start <= end;
     *  final long inc$ = (increasing$ ? by$ : -by$);
     *  for (long i = start$; (increasing$ ? i-end$ <= 0 : i-end$ >= 0); i+=inc$) {
     *      USERBLOCK
     *  }
     * </pre>
     *
     * <p>In the case where we have a simple range with no {@code by()}
     * invocation then the test for negative step size is omitted.</p>
     *
     * <p>The transformation is complicated by:</p>
     * <ul>
     *   <li>Not knowing at compile-time whether {@code lhs < rhs}, which
     *       complicated the {@code for} termination condition</li>
     *   <li>Needing to worry about {@code int} or {@code long} overflow
     *       (hence the {@code i-end$ <= 0} rather than the more natural
     *       {@code i <= end}.</li>
     * </ul>
     */
    class RangeOpIterationOptimization extends ForStatementTransformation {
        public static final String OPT_NAME = "RangeOpIteration";
        private final Tree.RangeOp range;
        private final Tree.Term lhs;
        private final Tree.Term rhs;
        private final Tree.Term increment;// if null then increment is +/-1
        private final Type type;
        private final ProducedType pt;
        public RangeOpIterationOptimization(
                Tree.ForStatement stmt,
                Tree.RangeOp range,
                Tree.Term increment,
                Type type) {
            super(stmt);
            this.range = range;
            this.lhs = range.getLeftTerm();
            this.rhs = range.getRightTerm();
            this.increment = increment;
            this.type = type;
            if (type.tag == syms().intType.tag) {
                this.pt = typeFact().getCharacterDeclaration().getType();
            } else if (type.tag == syms().longType.tag) {
                this.pt = typeFact().getIntegerDeclaration().getType();
            } else {
                throw new BugException(range, "unhandled Range type: " + type.tag);
            }
        }
        private Tree.Variable getVariable() {
            return ((Tree.ValueIterator)stmt.getForClause().getForIterator()).getVariable();
        }
        private JCExpression makeType() {
            return make().Type(type);
        }
        private ProducedType getType() {
            return pt;
        }
        @Override
        protected ListBuffer<JCStatement> transformForClause() {
            ListBuffer<JCStatement> result = ListBuffer.<JCStatement>lb();
           
            // Note: Must invoke lhs, rhs and increment in the correct order!
            // long start = <lhs>
            SyntheticName start = naming.temp("start");
            result.append(make().VarDef(make().Modifiers(FINAL), start.asName(), makeType(),
                    expressionGen().transformExpression(lhs, BoxingStrategy.UNBOXED, getType())));
            // long end = <rhs>
            SyntheticName end = naming.temp("end");
            result.append(make().VarDef(make().Modifiers(FINAL), end.asName(), makeType(),
                    expressionGen().transformExpression(rhs, BoxingStrategy.UNBOXED, getType())));
           
            final SyntheticName by;
            if (increment != null) {
                by = naming.temp("by");
                // by = increment;
                result.append(make().VarDef(make().Modifiers(FINAL), by.asName(), makeType(),
                        expressionGen().transformExpression(increment, BoxingStrategy.UNBOXED, getType())));
                // if (by <= 0) throw Exception("step size must be greater than zero");
                result.append(make().If(
                        make().Binary(JCTree.LE, by.makeIdent(), make().Literal(0)),
                        makeThrowAssertionException(
                                new AssertionExceptionMessageBuilder(null)
                                    .appendViolatedCondition("step > 0")
                                    .prependAssertionDoc("step size must be greater than zero")
                                    .build()),
                        null));
            } else {
                by = null;
            }
           
            SyntheticName increasing = naming.temp("increasing");
            // boolean increasing = start < end;
            result.append(make().VarDef(make().Modifiers(FINAL), increasing.asName(), make().Type(syms().booleanType),
                    make().Binary(JCTree.LE, start.makeIdent(), end.makeIdent())));
           
            SyntheticName incr = naming.temp("incr");
           
            result.append(make().VarDef(make().Modifiers(FINAL), incr.asName(), makeType(),
                    make().Conditional(
                            increasing.makeIdent(),
                            makeIncreasingIncrement(by), makeDecreasingIncrement(by))));
           
            SyntheticName varname = naming.alias(getVariable().getIdentifier().getText());
            JCVariableDecl init = make().VarDef(make().Modifiers(0), varname.asName(), makeType(), start.makeIdent());
            Tree.ControlClause prevForclause = currentForClause;
            currentForClause = stmt.getForClause();
            List<JCStatement> blockStatements = transformBlock(getBlock());
            currentForClause = prevForclause;
            blockStatements = blockStatements.prepend(make().VarDef(make().Modifiers(FINAL),
                    names().fromString(Naming.getVariableName(getVariable())),
                    makeType(),
                    varname.makeIdent()));
           
            // for (long i = start; (increasing ? i -end <= 0 : i -end >= 0); i+=inc) {
            JCConditional cond = make().Conditional(increasing.makeIdent(),
                    make().Binary(JCTree.LE, make().Binary(JCTree.MINUS, varname.makeIdent(), end.makeIdent()), makeZero()),
                    make().Binary(JCTree.GE, make().Binary(JCTree.MINUS, varname.makeIdent(), end.makeIdent()), makeZero()));
            List<JCExpressionStatement> step = List.<JCExpressionStatement>of(make().Exec(make().Assignop(JCTree.PLUS_ASG, varname.makeIdent(), incr.makeIdent())));
            result.append(make().Labelled(this.label, make().ForLoop(
                    List.<JCStatement>of(init),
                    cond,
                    step,
                    make().Block(0, blockStatements))));
           
            return result;
        }
        private JCExpression makeIncreasingIncrement(SyntheticName by) {
            if (increment != null) {
                // long incr = increasing ? by : -by;
                return by.makeIdent();
            } else if (type.tag == syms().intType.tag) {
                // long incr = start < end ? 1 : -1
                return make().Literal(1);
            } else if (type.tag == syms().longType.tag) {
                return make().Literal(1L);
            } else {
                return makeErroneous(range, "unhandled Range type: " + type.tag);
            }
        }
        private JCExpression makeDecreasingIncrement(SyntheticName by) {
            if (increment != null) {
                // long incr = increasing ? by : -by;
                return make().Unary(JCTree.NEG, by.makeIdent());
            } else if (type.tag == syms().intType.tag) {
                // long incr = start < end ? 1 : -1
                return make().Literal(-1);
            } else if (type.tag == syms().longType.tag) {
                return make().Literal(-1L);
            } else {
                return makeErroneous(range, "unhandled Range type: " + type.tag);
            }
        }
        private JCExpression makeZero() {
            if (type.tag == syms().intType.tag) {
                return make().Literal(0);
            } else if (type.tag == syms().longType.tag) {
                return make().Literal(0L);
            } else {
                return makeErroneous(range, "unhandled Range type: " + type.tag);
            }
        }
    }

    /**
     * <p>In some cases (#1227) we can have a deferred specification of a
     * non-variable value <i>x</i> which is assigned in a control structure
     * (for/else) in such a way that javac cannot prove the transformed
     * variable is assigned exactly once.</p>
     *
     * <p>In these cases the transformation proceeds thusly:</p>
     * <ul>
     * <li>The AttributeDeclaration is transformed to a final var <tt>x</tt>
     * and a non-final var <tt>x$</tt> and the "outer substitution"
     * {@code x->x$} is installed.</li>
     * <li>SpecificationStatements to <i>x</i> within the control structure
     * are transformed assignments to <tt>x$</tt>. At these points we
     * also generate a final <tt>x$$</tt> and install an "inner substitution"
     * {@code x->x$$} so that captured references within the control
     * structure still work.</li>
     * <li>At end end of the else Blocks of for/else and at
     * Break, Continue, Throw and Return statements within the
     * control structure we remove the "inner substitution" {@code x->x$$}
     * <li>At the end of the control structure we assign <tt>x = x$</tt>
     * and remove the "outer substitution" {@code x->x$}</li>
     * </ul>
     */
    class DeferredSpecification {
       
        private final ProducedType type;
        private final long modifiers;
        private final Value value;
        private final List<JCAnnotation> annots;
        private SyntheticName outerAlias;
        private Naming.Substitution outerSubst = null;
        private SyntheticName innerAlias;
        private Naming.Substitution innerSubst = null;
       
        public DeferredSpecification(Value value, int modifiers, List<JCAnnotation> annots, ProducedType type) {
            this.value = value;
            this.modifiers = modifiers;
            this.annots = annots;
            this.type = type;
        }
       
        /**
         * Installs the "outer substitution" and
         * makes a non-{@code final} variable declaration for use where
         * the ceylon value is declared:
         * <pre>
         *     TYPE NAME$1 = DEFAULT_FOR_TYPE;
         * </pre>
         * The caller is expected to generate the corresponding
         * {@code final} declaration:
         * <pre>
         *     final TYPE NAME;
         * </pre>
         */
        public JCStatement openOuterSubstitution() {
            if (outerSubst != null || outerAlias != null) {
                throw new BugException("An Outer substitution (" + outerSubst + ") is already open");
            }
            this.outerAlias = naming.alias(value.getName());
            // TODO Annots
            try (SavedPosition pos = noPosition()) {
                return make().VarDef(
                        make().Modifiers(modifiers & ~FINAL, annots),
                        outerAlias.asName(),
                        makeJavaType(type),
                        makeDefaultExprForType(type));
            }
        }

        /**
         * Installs an outer substitution in Naming, we can't do this in openOuterSubstitution since that
         * would also make the substitution visible outside the "for" block. This is meant to be done when
         * we enter the "for" block.
         */
        public void installOuterSubstitution(){
            this.outerSubst = naming.addVariableSubst(value, outerAlias.getName());
        }
       
        /**
         * Installs the "inner substitution" and makes a final
         * variable declaration:
         * <pre>
         *   final TYPE NAME$2 = NAME$1;
         * </pre>
         * The caller is expected to generare the preceeding assignment:
         * <pre>
         *   NAME$1 = WHATEVER;
         * </pre>
         */
        public JCStatement openInnerSubstitution() {
            if (innerSubst != null || innerAlias != null) {
                throw new BugException("An inner substitution (" + innerSubst + ") is already open");
            }
            try (SavedPosition pos = noPosition()) {
                innerAlias = naming.alias(value.getName());
                JCStatement result = makeVar(
                        modifiers,
                        innerAlias.getName(),
                        makeJavaType(type),
                        naming.makeName(value, Naming.NA_IDENT));
                innerSubst = naming.addVariableSubst(value, innerAlias.getName());
                return result;
            }
        }
       
        /**
         * Removes the "inner substitution" installed by {@link #openInnerSubstitution()}
         */
        public void closeInnerSubstitution() {
            if (innerSubst == null || innerAlias == null) {
                throw new BugException("No inner substitution to close");
            }
            innerSubst.close();
            innerSubst = null;
            innerAlias = null;
        }
       
        /**
         * Removes the "outer substitution" and returns an assignment
         * to the {@code final} variable after the control structure
         * <pre>
         *     NAME = NAME$1;
         * </pre>
         */
        public JCStatement closeOuterSubstitution() {
            if (outerSubst == null) {
                throw new BugException("No outer substitution to close");
            }
            try (SavedPosition pos = noPosition()) {
                JCExpression alias = naming.makeName(value, Naming.NA_IDENT);
                outerSubst.close();
                outerSubst = null;
                JCExpression var = naming.makeName(value, Naming.NA_IDENT);
                return make().Exec(make().Assign(var, alias));
            }
        }
    }
   
    private HashMap<Value, DeferredSpecification> deferredSpecifications = new HashMap<Value, DeferredSpecification>();
   
    public DeferredSpecification getDeferredSpecification(Declaration value) {
        return deferredSpecifications.get(value);
    }
   
    /**
     * Removes the "inner substitutions" for any deferred values specified
     * in the given control block
     */
    private void closeInnerSubstituionsForSpecifiedValues(Tree.ControlClause contolClause) {
        if (contolClause != null) {
            ControlBlock controlBlock = contolClause.getControlBlock();
            java.util.Set<Value> assigned = controlBlock.getSpecifiedValues();
            if (assigned != null) {
                for (Value value : assigned) {
                    DeferredSpecification ds = statementGen().getDeferredSpecification(value);
                    if (ds != null) {
                        ds.closeInnerSubstitution();
                    }
                }
            }
        }
    }
   
    // FIXME There is a similar implementation in ClassGen!
    public List<JCStatement> transform(Tree.AttributeDeclaration decl) {
        ListBuffer<JCStatement> result = ListBuffer.<JCStatement> lb();
        // If the attribute is really from a parameter then don't generate a local variable
        Parameter parameter = CodegenUtil.findParamForDecl(decl);
        if (parameter == null) {
           
            final Name attrName = names().fromString(naming.substitute(decl.getDeclarationModel()));
           
            ProducedType t = decl.getDeclarationModel().getType();
           
            JCExpression initialValue = null;
            SpecifierOrInitializerExpression initOrSpec = decl.getSpecifierOrInitializerExpression();
            if (initOrSpec != null) {
                HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(initOrSpec.getExpression().getTerm());
                if (error != null) {
                    return List.<JCStatement>of(this.makeThrowUnresolvedCompilationError(error));
                }
                initialValue = expressionGen().transformExpression(initOrSpec.getExpression(),
                        CodegenUtil.getBoxingStrategy(decl.getDeclarationModel()),
                        decl.getDeclarationModel().getType());
            } else if (decl.getDeclarationModel().isVariable()) {
                // Java's definite initialization doesn't always work
                // so give variable attribute declarations without
                // initializers a default value. See #1153.
                initialValue = makeDefaultExprForType(t);
            }
           
            List<JCAnnotation> annots = makeJavaTypeAnnotations(decl.getDeclarationModel());
           
            int modifiers = transformLocalFieldDeclFlags(decl);
            result.append(at(decl).VarDef(at(decl).Modifiers(modifiers, annots), attrName, makeJavaType(t), initialValue));
           
            JCStatement outerSubs = openOuterSubstitutionIfNeeded(
                    decl.getDeclarationModel(), t, annots, modifiers);
            if (outerSubs != null) {
                result.append(outerSubs);
            }
        }
        return result.toList();
    }

    JCStatement openOuterSubstitutionIfNeeded(
            Value value, ProducedType t,
            List<JCAnnotation> annots, int modifiers) {
        JCStatement result = null;
        if (value.isSpecifiedInForElse()) {
            DeferredSpecification d = new DeferredSpecification(value, modifiers, annots, t);
            deferredSpecifications.put(value, d);
            result = d.openOuterSubstitution();
        }
        return result;
    }
   
    List<JCStatement> transform(Tree.Break stmt) {
        // break;
       
        // Remove the inner substitutions for any deferred values specified
        // in the control block
        closeInnerSubstituionsForSpecifiedValues(currentForClause);
       
        JCStatement brk = at(stmt).Break(getLabel(stmt));
   
        if (currentForFailVariable != null) {
            JCIdent failtest_id = at(stmt).Ident(currentForFailVariable);
            List<JCStatement> list = List.<JCStatement> of(at(stmt).Exec(at(stmt).Assign(failtest_id, make().Literal(TypeTags.BOOLEAN, 0))));
            list = list.append(brk);
            return list;
        } else {
            return List.<JCStatement> of(brk);
        }
    }

    JCStatement transform(Tree.Continue stmt) {
        // continue;
        return at(stmt).Continue(getLabel(stmt));
    }

    JCStatement transform(Tree.Return ret) {
        // Remove the inner substitutions for any deferred values specified
        // in the control block
        closeInnerSubstituionsForSpecifiedValues(currentForClause);
        Tree.Expression expr = ret.getExpression();
        JCExpression returnExpr = null;
        at(ret);
        if (expr != null) {
            boolean prevNoExpressionlessReturn = noExpressionlessReturn;
            try {
                noExpressionlessReturn = false;
                // we can cast to TypedDeclaration here because return with expressions are only in Method or Value
                TypedDeclaration declaration = (TypedDeclaration)ret.getDeclaration();
                returnExpr = expressionGen().transformExpression(declaration, expr.getTerm());
                // make sure all returns from hash are properly turned into ints
                returnExpr = convertToIntIfHashAttribute(declaration, returnExpr);
            } finally {
                noExpressionlessReturn = prevNoExpressionlessReturn;
            }
        } else if (noExpressionlessReturn) {
            returnExpr = makeNull();
        }
        return at(ret).Return(returnExpr);
    }

    public JCStatement transform(Tree.Throw t) {
        // Remove the inner substitutions for any deferred values specified
        // in the control block
        closeInnerSubstituionsForSpecifiedValues(currentForClause);
        at(t);
        Tree.Expression expr = t.getExpression();
        final JCExpression exception;
        if (expr == null) {// bare "throw;" stmt
            exception = make().NewClass(null, null,
                    makeIdent(syms().ceylonExceptionType),
                    List.<JCExpression>of(makeNull(), makeNull()),
                    null);
        } else {
            // we must unerase the exception to Throwable
            ProducedType exceptionType = expr.getTypeModel().getSupertype(t.getUnit().getThrowableDeclaration());
            exception = gen().expressionGen().transformExpression(expr, BoxingStrategy.UNBOXED, exceptionType);
        }
        return make().Throw(exception);
    }
   
    public JCStatement transform(Tree.TryCatchStatement t) {
        Tree.TryClause tryClause = t.getTryClause();
        at(tryClause);
       
        JCBlock tryBlock = transform(tryClause.getBlock());

        Tree.ResourceList resList = tryClause.getResourceList();
        if (resList != null) {
            ArrayList<Tree.Resource> resources = new ArrayList<Tree.Resource>(resList.getResources());
            Collections.reverse(resources);
            for (Tree.Resource res : resources) {
                List<JCStatement> stats = List.nil();
               
                Tree.Expression resExpr;
                String resVarName;
                if (res.getExpression() != null) {
                    resExpr = res.getExpression();
                    resVarName = naming.newTemp("try");
                } else if (res.getVariable() != null) {
                    Tree.Variable var = res.getVariable();
                    resExpr = var.getSpecifierExpression().getExpression();
                    resVarName = var.getIdentifier().getText();
                } else {
                    throw new BugException(res, "missing resource expression");
                }
                boolean isDestroyable = typeFact().getDestroyableDeclaration().getType().isSupertypeOf(resExpr.getTypeModel());
                ProducedType resVarType = resExpr.getTypeModel();
                ProducedType resVarExpectedType = isDestroyable
                        ? typeFact().getDestroyableDeclaration().getType()
                        : typeFact().getObtainableDeclaration().getType();
               
                // CloseableType $var = resource-expression
                JCExpression expr = expressionGen().transformExpression(resExpr);
                JCExpression javaType = makeJavaType(resVarType);
                JCVariableDecl var = makeVar(FINAL, resVarName, javaType, expr);
                stats = stats.append(var);
               
                // $var.open() /// ((Closeable)$var).open()
               
                if (!isDestroyable) {
                    JCExpression resVar0 = expressionGen().applyErasureAndBoxing(makeUnquotedIdent(resVarName), resVarType, true, BoxingStrategy.BOXED, resVarExpectedType);
                    JCMethodInvocation openCall = make().Apply(null, makeQualIdent(resVar0, "obtain"), List.<JCExpression>nil());
                    stats = stats.append(make().Exec(openCall));
                }
               
                // Exception $tpmex = null;
                String innerExTmpVarName = naming.newTemp("ex");
                JCExpression innerExType = makeJavaType(typeFact().getThrowableDeclaration().getType(), JT_CATCH);
                JCVariableDecl innerExTmpVar = makeVar(innerExTmpVarName, innerExType, makeNull());
                stats = stats.append(innerExTmpVar);
               
                // $tmpex = ex;
                List<JCStatement> innerCatchStats = List.nil();
                Name innerCatchVarName = naming.tempName("ex");
                JCAssign exTmpAssign = make().Assign(makeUnquotedIdent(innerExTmpVarName), make().Ident(innerCatchVarName));
                innerCatchStats = innerCatchStats.append(make().Exec(exTmpAssign));
               
                // throw ex;
                JCThrow innerCatchThrow = make().Throw(make().Ident(innerCatchVarName));
                innerCatchStats = innerCatchStats.append(innerCatchThrow);
                JCBlock innerCatchBlock = make().Block(0, innerCatchStats);
               
                // $var.close() /// ((Closeable)$var).close()
                JCExpression exarg = makeUnquotedIdent(innerExTmpVarName);
                JCExpression resVar1 = expressionGen().applyErasureAndBoxing(makeUnquotedIdent(resVarName), resVarType, true, BoxingStrategy.BOXED, resVarExpectedType);
                JCMethodInvocation closeCall = make().Apply(null, makeQualIdent(resVar1, isDestroyable ? "destroy" : "release"), List.<JCExpression>of(exarg));
                JCBlock closeTryBlock = make().Block(0, List.<JCStatement>of(make().Exec(closeCall)));
               
                // try { $var.close() } catch (Exception closex) { $tmpex.addSuppressed(closex); }
                Name closeCatchVarName = naming.tempName("closex");
                JCExpression closeCatchExType = makeJavaType(typeFact().getThrowableDeclaration().getType(), JT_CATCH);
                JCVariableDecl closeCatchVar = make().VarDef(make().Modifiers(Flags.FINAL), closeCatchVarName, closeCatchExType, null);
                JCExpression addarg = make().Ident(closeCatchVarName);
                JCMethodInvocation addSuppressedCall = make().Apply(null, makeQualIdent(makeUnquotedIdent(innerExTmpVarName), "addSuppressed"), List.<JCExpression>of(addarg));
                JCCatch closeCatch = make().Catch(closeCatchVar, make().Block(0, List.<JCStatement>of(make().Exec(addSuppressedCall))));
                JCTry closeTry = at(res).Try(closeTryBlock, List.<JCCatch>of(closeCatch), null);
               
                // $var.close() /// ((Closeable)$var).close()
                JCExpression exarg2 = makeUnquotedIdent(innerExTmpVarName);
                JCExpression resVar2 = expressionGen().applyErasureAndBoxing(makeUnquotedIdent(resVarName), resVarType, true, BoxingStrategy.BOXED, resVarExpectedType);
                JCMethodInvocation closeCall2 = make().Apply(null, makeQualIdent(resVar2, isDestroyable ? "destroy" : "release"), List.<JCExpression>of(exarg2));
               
                // if ($tmpex != null) { ... } else { ... }
                JCBinary closeCatchCond = make().Binary(JCTree.NE, makeUnquotedIdent(innerExTmpVarName), makeNull());
                JCIf closeCatchIf = make().If(closeCatchCond, closeTry, make().Exec(closeCall2));
   
                // try { .... } catch (Exception ex) { $tmpex=ex; throw ex; }
                // finally { try { $var.close() } catch (Exception closex) { } }
                JCExpression innerCatchExType = makeJavaType(typeFact().getThrowableDeclaration().getType(), JT_CATCH);
                JCVariableDecl innerCatchVar = make().VarDef(make().Modifiers(Flags.FINAL), innerCatchVarName, innerCatchExType, null);
                JCCatch innerCatch = make().Catch(innerCatchVar, innerCatchBlock);
                JCBlock innerFinallyBlock = make().Block(0, List.<JCStatement>of(closeCatchIf));
                JCTry innerTry = at(res).Try(tryBlock, List.<JCCatch>of(innerCatch), innerFinallyBlock);
                stats = stats.append(innerTry);
               
                tryBlock = at(res).Block(0, stats);
            }
        }
       
        final List<JCCatch> catches;
        if (usePolymorphicCatches(t.getCatchClauses())) {
            catches = transformCatchesPolymorphic(t.getCatchClauses());
        } else {
            catches = transformCatchesIfElseIf(t.getCatchClauses());
        }
       
        final JCBlock finallyBlock;
        Tree.FinallyClause finallyClause = t.getFinallyClause();
        if (finallyClause != null) {
            at(finallyClause);
            finallyBlock = transform(finallyClause.getBlock());
        } else {
            finallyBlock = null;
        }
       
        if (!catches.isEmpty() || finallyBlock != null) {
            return at(t).Try(tryBlock, catches, finallyBlock);
        } else {
            return tryBlock;
        }
       
       
    }

    /**
     * Determines whether the {@code CatchClause}s contain any
     * intersections or parameterised exception types. If so, the
     * we have to transform the {@code CatchClause}s using
     * {@link #transformCatchesIfElseIf(java.util.List)}, otherwise we can
     * let the JVM do the heavy lifting an use
     * {@link #transformCatchesPolymorphic(java.util.List)}.
     */
    boolean usePolymorphicCatches(Iterable<Tree.CatchClause> catchClauses) {
        for (Tree.CatchClause catchClause : catchClauses) {
            ProducedType type = catchClause.getCatchVariable().getVariable().getType().getTypeModel();
            if (isOrContainsError(type)) {
                return false;
            }
            if (type.getDeclaration().isParameterized()) {
                // e.g. E<T>
                return false;
            } else if (typeFact().isIntersection(type)) {
                // e.g. E&Interface
                return false;
            } else if (typeFact().isUnion(type)) {
                return false;
            }
        }
        return true;
    }
   
    /**
     * Transforms a list of {@code CatchClause}s to a corresponding list
     * of {@code JCCatch}.
     * @see #transformCatchesIfElseIf(java.util.List)
     */
    private List<JCCatch> transformCatchesPolymorphic(
            java.util.List<Tree.CatchClause> catchClauses) {
        final ListBuffer<JCCatch> catches = ListBuffer.<JCCatch>lb();
        for (Tree.CatchClause catchClause : catchClauses) {
            at(catchClause);
            Tree.Variable variable = catchClause.getCatchVariable().getVariable();
            ProducedType exceptionType = variable.getDeclarationModel().getType();
            JCExpression type = makeJavaType(exceptionType, JT_CATCH);
            JCVariableDecl param = make().VarDef(
                    make().Modifiers(Flags.FINAL),
                    names().fromString(variable.getIdentifier().getText()),
                    type, null);
            catches.add(make().Catch(param, transform(catchClause.getBlock())));
           
        }
        return catches.toList();
    }


    private boolean isOrContainsError(ProducedType exceptionType) {
        TypeDeclaration declaration = exceptionType.getDeclaration();
        if (declaration instanceof TypeAlias) {
            return isOrContainsError(exceptionType.resolveAliases());
        } else if (declaration instanceof UnionType) {
            for (ProducedType t : declaration.getCaseTypes()) {
                if (isOrContainsError(t)) {
                    return true;
                }
            }
            return false;
        } else if (declaration instanceof IntersectionType) {
            for (ProducedType t : declaration.getSatisfiedTypes()) {
                if (isOrContainsError(t)) {
                    return true;
                }
            }
            return false;
        } else {
            return "java.lang::Error".equals(declaration.getQualifiedNameString());
        }
    }

    /**
     * Transforms a list of {@code CatchClause}s to a single {@code JCCatch}
     * containing and if/else if chain for finding the appropriate catch block.
     * @see #transformCatchesPolymorphic(java.util.List)
     */
    private List<JCCatch> transformCatchesIfElseIf(
            java.util.List<Tree.CatchClause> catchClauses) {
        ProducedType supertype = intersectionOfCatchClauseTypes(catchClauses);
        JCExpression exceptionType = makeJavaType(supertype, JT_CATCH | JT_RAW);
        SyntheticName exceptionVar = naming.alias("exception");
        JCVariableDecl param = make().VarDef(
                make().Modifiers(Flags.FINAL),
                exceptionVar.asName(),
                exceptionType, null);
       
        ArrayList<Tree.CatchClause> reversed = new ArrayList<Tree.CatchClause>(catchClauses);
        Collections.reverse(reversed);
       
        JCStatement elsePart = make().Throw(exceptionVar.makeIdent());
       
        for (Tree.CatchClause catchClause : reversed) {
            Tree.Variable caughtVar = catchClause.getCatchVariable().getVariable();
            ProducedType caughtType = caughtVar.getType().getTypeModel();
            List<JCStatement> catchBlock = transformBlock(catchClause.getBlock());
            catchBlock = catchBlock.prepend(
                    makeVar(FINAL,
                        caughtVar.getIdentifier().getText(),
                        makeJavaType(caughtType),
                        expressionGen().applyErasureAndBoxing(exceptionVar.makeIdent(),
                                supertype, true, true, BoxingStrategy.BOXED, caughtType, 0)));
            elsePart = make().If(makeOptimizedTypeTest(null, exceptionVar, caughtType, caughtType),
                    make().Block(0, catchBlock),
                    elsePart);
        }
        return List.of(make().Catch(param, make().Block(0, List.<JCStatement>of(elsePart))));
    }
   
    /**
     * <p>When transforming {@code CatchClause}s using
     * {@link #transformCatchesIfElseIf(java.util.List)} we want the single
     * {@code catch} clause to have the most specific Java Exception type
     * applicable to all the Ceylon Exception types in the list.</p>
     *
     * <p><strong>Note:</strong> This method can return parameterised types
     * whose parameters are taken from their declaration (i.e. there's
     * no corresponding Java type parameter in scope). That should be OK,
     * because we're only interested in catching the raw type.</p>
     */
    private ProducedType intersectionOfCatchClauseTypes(
            java.util.List<Tree.CatchClause> catchClauses) {
        ProducedType result = typeFact().getNothingDeclaration().getType();
        for (Tree.CatchClause catchClause : catchClauses) {
            ProducedType pt = catchClause.getCatchVariable().getVariable().getType().getTypeModel();
            result = Util.unionType(result, exceptionSupertype(pt), typeFact());
        }
        if (typeFact().isUnion(result)) {
            return result.getSupertype(typeFact().getThrowableDeclaration());
        }
        return result;
    }

    private ProducedType exceptionSupertype(ProducedType pt) {
        ProducedType result = typeFact().getNothingDeclaration().getType();
        if (typeFact().isUnion(pt)) {
            for (ProducedType t : pt.getCaseTypes()) {
                result = Util.unionType(result, exceptionSupertype(t), typeFact());
            }
        } else if (typeFact().isIntersection(pt)) {
            for (ProducedType t : pt.getSatisfiedTypes()) {
                if (t.isSubtypeOf(typeFact().getThrowableDeclaration().getType())) {
                    result = Util.unionType(result, exceptionSupertype(t), typeFact());
                }
            }
        } else if (pt.isSubtypeOf(typeFact().getThrowableDeclaration().getType())) {
            if (pt.getDeclaration().isParameterized()) {
                // We do this to avoid ending up with a union ExG<Foo>|ExG<Bar>
                // when we're actually going to go raw, so ExG would be fine
                return pt.getDeclaration().getType();
            }
            return pt;
        }
        return result;
    }
   
    private int transformLocalFieldDeclFlags(Tree.AttributeDeclaration cdecl) {
        int result = 0;

        result |= cdecl.getDeclarationModel().isVariable() ? 0 : FINAL;

        return result;
    }
   
    abstract class SwitchTransformation {
        public SwitchTransformation() {
        }
        protected ProducedType getSwitchExpressionType(Tree.SwitchStatement stmt) {
            return stmt.getSwitchClause().getExpression().getTypeModel();
        }
        protected ProducedType getDefiniteSwitchExpressionType(Tree.SwitchStatement stmt) {
            return typeFact().getDefiniteType(getSwitchExpressionType(stmt));
        }
        protected java.util.List<CaseClause> getCaseClauses(Tree.SwitchStatement stmt) {
            return stmt.getSwitchCaseList().getCaseClauses();
        }
        protected JCStatement transformElse(Tree.SwitchCaseList caseList) {
            Tree.ElseClause elseClause = caseList.getElseClause();
            if (elseClause != null) {
                return StatementTransformer.this.transform(elseClause.getBlock());
            } else {
                // To avoid possible javac warnings about uninitialized vars we
                // need to have an 'else' clause, even if the ceylon code doesn't
                // require one.
                // This exception could be thrown for example if an enumerated
                // type is recompiled after having a subclass added, but the
                // switch is not recompiled.
                return makeThrowEnumeratedTypeError();
            }
        }
        protected JCStatement makeThrowEnumeratedTypeError() {
            return make().Throw(
                        make().NewClass(null, List.<JCExpression>nil(),
                                makeIdent(syms().ceylonEnumeratedTypeErrorType),
                                List.<JCExpression>of(make().Literal(
                                        "Supposedly exhaustive switch was not exhaustive")), null));
        }
        public abstract JCStatement transformSwitch(Tree.SwitchStatement stmt);
    }
    /**
     * Switch transformation which produces a Java {@code switch},
     * suitable for a switch whose cases are all String literals,
     * or all Character literals.
     */
    class Switch extends SwitchTransformation {
        public Switch() {
        }
        public JCStatement transformSwitch(Tree.SwitchStatement stmt) {
            JCExpression switchExpr = expressionGen().transformExpression(
                    stmt.getSwitchClause().getExpression(),
                    BoxingStrategy.UNBOXED,
                    getSwitchExpressionType(stmt));
            return transformSwitch(stmt, switchExpr);
        }
        JCStatement transformSwitch(Tree.SwitchStatement stmt,
                JCExpression switchExpr) {
            Name label = names().fromString("switch_" + gen().visitor.lv.getSwitchId(stmt.getSwitchClause()));
            ListBuffer<JCCase> cases = ListBuffer.<JCCase>lb();
            for (Tree.CaseClause caseClause : getCaseClauses(stmt)) {
                if (getSingletonNullCase(caseClause) != null) {
                    continue;
                }
                Tree.MatchCase match = (Tree.MatchCase)caseClause.getCaseItem();
               
                java.util.List<Tree.Expression> exprs = match.getExpressionList().getExpressions();
                for (int ii = 0; ii < exprs.size()-1; ii++) {
                    Tree.Term term = ExpressionTransformer.eliminateParens(exprs.get(ii).getTerm());
                    at(term);
                    cases.add(make().Case(transformCaseExpr(term),
                            List.<JCStatement>nil()));
                }
                Tree.Term term = exprs.get(exprs.size()-1).getTerm();
                JCBlock block = transform(caseClause.getBlock());
                List<JCStatement> stmts = List.<JCStatement>nil();
                if (!caseClause.getBlock().getDefinitelyReturns()) {
                    stmts = stmts.prepend(make().Break(label));
                }
                stmts = stmts.prepend(block);
                cases.add(make().Case(transformCaseExpr(term),
                        stmts));
            }
            cases.add(make().Case(null, List.of(transformElse(stmt.getSwitchCaseList()))));
           
           
            JCStatement last = make().Switch(switchExpr, cases.toList());
            last = make().Labelled(label, last);
            return last;
        }
        private JCExpression transformCaseExpr(Tree.Term term) {
            if (term instanceof Tree.BaseMemberExpression
                    && ((Tree.BaseMemberExpression)term).getDeclaration() instanceof Value
                    && ((Value)((Tree.BaseMemberExpression)term).getDeclaration()).isEnumValue()) {
                // A case(enumValue) must use the unqualified name
                return naming.makeName((Value)((Tree.BaseMemberExpression)term).getDeclaration(), Naming.NA_MEMBER);
            }
            return expressionGen().transformExpression(term,
                    BoxingStrategy.UNBOXED, term.getTypeModel());
        }
    }
    Tree.Term getSingletonNullCase(Tree.CaseClause caseClause) {
        Tree.CaseItem caseItem = caseClause.getCaseItem();
        if (caseItem instanceof Tree.MatchCase) {
            java.util.List<Expression> expressions = ((Tree.MatchCase)caseItem).getExpressionList().getExpressions();
            for (Tree.Expression expr : expressions) {
                Tree.Term term = ExpressionTransformer.eliminateParens(expr.getTerm());
                if (term instanceof Tree.BaseMemberExpression
                        && isNullValue(((Tree.BaseMemberExpression)term).getDeclaration())
                        && expressions.size() == 1) {
                    return term;
                }
            }
        }
        return null;
    }
   
    /**
     * Switch transformation which produces a Java
     * <code>if (selector == null) {...} else { switch() {...} }</code>,
     * suitable for a switch whose cases include a singleton case for null
     * (i.e. <code>case (null) {}</code>, but not <code>case("foo", null) {}</code>)
     * with the remaining cases are all String literals
     * or all Character literals.
     */
    class IfNullElseSwitch extends SwitchTransformation {
       
        @Override
        public JCStatement transformSwitch(SwitchStatement stmt) {
            JCExpression selectorExpr = expressionGen().transformExpression(stmt.getSwitchClause().getExpression(), BoxingStrategy.BOXED, getSwitchExpressionType(stmt));
            Naming.SyntheticName selectorAlias = naming.alias("sel");
            JCVariableDecl selector = makeVar(selectorAlias, makeJavaType(getSwitchExpressionType(stmt)), selectorExpr);
            // Make a switch out of the non-null cases
            JCStatement switch_ = new Switch().transformSwitch(stmt,
                    expressionGen().applyErasureAndBoxing(selectorAlias.makeIdent(),
                            getDefiniteSwitchExpressionType(stmt),
                            true,
                            BoxingStrategy.UNBOXED,
                            getDefiniteSwitchExpressionType(stmt)));
            // Now wrap it with a null test
            JCIf ifElse = null;
            for (Tree.CaseClause caseClause : getCaseClauses(stmt)) {
                Tree.Term term = getSingletonNullCase(caseClause);
                if (term != null) {
                    ifElse  = make().If(make().Binary(JCTree.EQ, selectorAlias.makeIdent(), makeNull()),
                            transform(caseClause.getBlock()),
                            make().Block(0, List.<JCStatement>of(switch_)));
                    break;
                }
            }
            return at(stmt).Block(0, List.of(selector, ifElse));
        }
       
    }
    /**
     * The default switch transformation which produces a
     * {@code if/else if} chain.
     */
    class IfElseChain extends SwitchTransformation {

        protected java.util.List<Tree.CaseClause> getCaseClauses(Tree.SwitchStatement stmt) {
            // If all the cases are "case (is ...)" we can try to avoid
            // the expense of testing reified is by putting all the cheap tests first
            // We respect the relative order of all the cheap cases and all
            // the expensive cases though on the basis that that might be a
            // hint
            // about which are more common
            java.util.List<CaseClause> list = super.getCaseClauses(stmt);
            java.util.ArrayList<CaseClause> cheap = new ArrayList<CaseClause>(list.size());
            int lastCheap = 0;
            // The dummy isn't actually used for anything, it just has to be non-null
            SyntheticName dummy = naming.synthetic(Naming.Unfix.$annotationSequence$);
            for (Tree.CaseClause clause : list) {
                Tree.CaseItem item = clause.getCaseItem();
                boolean isCheap;
                if (item instanceof Tree.IsCase) {
                    isCheap = isTypeTestCheap(null, dummy,
                            ((Tree.IsCase) item).getType().getTypeModel(),
                            getSwitchExpressionType(stmt));
                } else if (item instanceof Tree.MatchCase) {
                    // will be primitive equality test
                    isCheap = true;
                } else {
                    // should never get here, but we can just return the unsorted list
                    return list;
                }
                int index = isCheap ? lastCheap : cheap.size();
                cheap.add(index, clause);
                if (isCheap) {
                    lastCheap = index+1;
                }
            }
            return cheap;
        }
       
        @Override
        public JCStatement transformSwitch(SwitchStatement stmt) {
            JCStatement last = transformElse(stmt.getSwitchCaseList());
            final BoxingStrategy bs;
            final JCExpression selectorType;
            boolean allMatches = isSwitchAllMatchCases(stmt);
            boolean primitiveSelector = allMatches && isCeylonBasicType(getSwitchExpressionType(stmt));
            if (primitiveSelector) {
                bs = BoxingStrategy.UNBOXED;
                selectorType = makeJavaType(getSwitchExpressionType(stmt));
            } else {
                bs = BoxingStrategy.BOXED;
                if (allMatches && isCeylonBasicType(getDefiniteSwitchExpressionType(stmt))) {
                    selectorType = makeJavaType(getSwitchExpressionType(stmt));
                } else {
                    selectorType = make().Type(syms().objectType);
                }
            }
            JCExpression selectorExpr = expressionGen().transformExpression(stmt.getSwitchClause().getExpression(), bs, getSwitchExpressionType(stmt));
            Naming.SyntheticName selectorAlias = naming.alias("sel");
           
            JCVariableDecl selector = makeVar(selectorAlias, selectorType, selectorExpr);
           
            java.util.List<Tree.CaseClause> caseClauses = getCaseClauses(stmt);
            for (int ii = caseClauses.size() - 1; ii >= 0; ii--) {// reverse order
                Tree.CaseClause caseClause = caseClauses.get(ii);
                Tree.CaseItem caseItem = caseClause.getCaseItem();
                if (caseItem instanceof Tree.IsCase) {
                    last = transformCaseIs(selectorAlias, caseClause, (Tree.IsCase)caseItem, last, getSwitchExpressionType(stmt));
                } else if (caseItem instanceof Tree.SatisfiesCase) {
                    // TODO Support for 'case (satisfies ...)' is not implemented yet
                    return make().Exec(makeErroneous(caseItem, "compiler bug: switch/satisfies not implemented yet"));
                } else if (caseItem instanceof Tree.MatchCase) {
                    last = transformCaseMatch(selectorAlias, caseClause, (Tree.MatchCase)caseItem, last, getSwitchExpressionType(stmt), primitiveSelector);
                } else {
                    return make().Exec(makeErroneous(caseItem, "compiler bug: unknown switch case clause: "+caseItem));
                }
            }
            return at(stmt).Block(0, List.of(selector, last));
        }
       
    }
   
    private boolean isJavaSwitchableType(ProducedType type) {
        return type.isExactly(typeFact().getCharacterDeclaration().getType())
                || type.isExactly(typeFact().getStringDeclaration().getType())
                || isJavaEnumType(type);
    }
   
    /**
     * Transforms a Ceylon switch to a Java {@code if/else if} chain.
     * @param stmt The Ceylon switch
     * @return The Java tree
     */
    JCStatement transform(Tree.SwitchStatement stmt) {
        SwitchTransformation transformation = null;
        ProducedType exprType = stmt.getSwitchClause().getExpression().getTypeModel();
        // Are we switching with just String literal or Character literal match cases?
        if (isJavaSwitchableType(exprType)) {
            boolean canUseSwitch = true;
            caseStmts: for (Tree.CaseClause clause : stmt.getSwitchCaseList().getCaseClauses()) {
                if (clause.getCaseItem() instanceof Tree.MatchCase) {
                    java.util.List<Expression> caseExprs = ((Tree.MatchCase)clause.getCaseItem()).getExpressionList().getExpressions();
                    caseExpr: for (Tree.Expression expr : caseExprs) {
                        Tree.Term e = ExpressionTransformer.eliminateParens(expr);
                        if (e instanceof Tree.StringLiteral
                                || e instanceof Tree.CharLiteral) {
                            continue caseExpr;
                        } else if (e instanceof Tree.BaseMemberExpression
                                && ((Tree.BaseMemberExpression)e).getDeclaration() instanceof Value
                                && ((Value)((Tree.BaseMemberExpression)e).getDeclaration()).isEnumValue()) {
                            continue caseExpr;
                        } else {
                            canUseSwitch = false;
                            break caseStmts;
                        }
                    }
                } else {
                    canUseSwitch = false;
                    break caseStmts;
                }
            }
            if (canUseSwitch) {
                // yes, so use a Java Switch
                transformation = new Switch();
            }
        }
        if (transformation == null
                && isOptional(exprType)) {
            // Are we switching with just String literal or Character literal plus null
            // match cases?
            ProducedType definiteType = typeFact().getDefiniteType(exprType);
            if (isJavaSwitchableType(definiteType)) {
                boolean canUseIfElseSwitch = true;
                boolean hasSingletonNullCase = false;
                caseStmts: for (Tree.CaseClause clause : stmt.getSwitchCaseList().getCaseClauses()) {
                    if (clause.getCaseItem() instanceof Tree.MatchCase) {
                        if (getSingletonNullCase(clause) != null) {
                            hasSingletonNullCase = true;
                        }
                        java.util.List<Expression> caseExprs = ((Tree.MatchCase)clause.getCaseItem()).getExpressionList().getExpressions();
                        caseExpr: for (Tree.Expression expr : caseExprs) {
                            Tree.Term e = ExpressionTransformer.eliminateParens(expr);
                            if (e instanceof Tree.StringLiteral
                                    || e instanceof Tree.CharLiteral) {
                                continue caseExpr;
                            } else if (e instanceof Tree.BaseMemberExpression
                                    && isNullValue(((Tree.BaseMemberExpression)e).getDeclaration())
                                    && caseExprs.size() == 1) {
                                continue caseExpr;
                            } else if (e instanceof Tree.BaseMemberExpression
                                    && ((Tree.BaseMemberExpression)e).getDeclaration() instanceof Value
                                    && ((Value)((Tree.BaseMemberExpression)e).getDeclaration()).isEnumValue()) {
                                continue caseExpr;
                            } else {
                                canUseIfElseSwitch = false;
                                break caseStmts;
                            }
                        }
                    } else {
                        canUseIfElseSwitch = false;
                        break caseStmts;
                    }
                }
                canUseIfElseSwitch &= hasSingletonNullCase;
                if (canUseIfElseSwitch) {
                 // yes, so use a If
                    transformation = new IfNullElseSwitch();
                }
            }
        }
        // The default transformation
        if (transformation == null) {
            transformation = new IfElseChain();
        }
        return transformation.transformSwitch(stmt);
    }

    private boolean isSwitchAllMatchCases(SwitchStatement stmt) {
        for (Tree.CaseClause caseClause : stmt.getSwitchCaseList().getCaseClauses()) {
            if (!(caseClause.getCaseItem() instanceof Tree.MatchCase)) {
                return false;
            }
        }
        return true;
    }

    private JCStatement transformCaseMatch(Naming.SyntheticName selectorAlias,
            Tree.CaseClause caseClause, Tree.MatchCase matchCase,
            JCStatement last, ProducedType switchType, boolean primitiveSelector) {
        at(matchCase);
       
        JCExpression tests = null;
        java.util.List<Tree.Expression> expressions = matchCase.getExpressionList().getExpressions();
        for(Tree.Expression expr : expressions){
            Tree.Term term = ExpressionTransformer.eliminateParens(expr.getTerm());
            boolean unboxedEquality = primitiveSelector || isCeylonBasicType(typeFact().getDefiniteType(switchType));
            JCExpression transformedExpression = expressionGen().transformExpression(term,
                    unboxedEquality ? BoxingStrategy.UNBOXED: BoxingStrategy.BOXED,
                    term.getTypeModel());
            JCExpression test;
            if (term instanceof Tree.Literal || term instanceof Tree.NegativeOp) {
                if (unboxedEquality) {
                    if (term instanceof Tree.StringLiteral) {
                        test = make().Apply(null,
                                makeSelect(unboxType(selectorAlias.makeIdent(), term.getTypeModel()), "equals"), List.<JCExpression>of(transformedExpression));
                    } else {
                        test = make().Binary(JCTree.EQ,
                                primitiveSelector ? selectorAlias.makeIdent() : unboxType(selectorAlias.makeIdent(), term.getTypeModel()),
                                transformedExpression);
                    }
                } else {
                    test = make().Apply(null, makeSelect(selectorAlias.makeIdent(), "equals"), List.<JCExpression>of(transformedExpression));
                }
                if (isOptional(switchType)) {
                    test = make().Binary(JCTree.AND, make().Binary(JCTree.NE, selectorAlias.makeIdent(), makeNull()), test);
                }
            } else {
                JCExpression selectorExpr;
                if (!primitiveSelector && isCeylonBasicType(typeFact().getDefiniteType(switchType))) {
                    selectorExpr = unboxType(selectorAlias.makeIdent(), term.getTypeModel());
                } else {
                    selectorExpr = selectorAlias.makeIdent();
                }
                test = make().Binary(JCTree.EQ, selectorExpr, transformedExpression);
            }
            if(tests == null)
                tests = test;
            else
                tests = make().Binary(JCTree.OR, tests, test);
        }
        JCBlock block = transform(caseClause.getBlock());
        return at(caseClause).If(tests, block, last);
    }

    /**
     * Transform a "case(is ...)"
     * @param selectorAlias
     * @param caseClause
     * @param isCase
     * @param last
     * @return
     */
    private JCStatement transformCaseIs(Naming.SyntheticName selectorAlias,
            Tree.CaseClause caseClause, Tree.IsCase isCase,
            JCStatement last, ProducedType expressionType) {
        at(isCase);
        // Use the type of the variable, which is more precise than the type we test for.
        ProducedType varType = isCase.getVariable().getDeclarationModel().getType();
        ProducedType caseType = isCase.getType().getTypeModel();
        // note: There's no point using makeOptimizedTypeTest() because cases are disjoint
        // anyway and the cheap cases get evaluated first.
        JCExpression cond = makeTypeTest(null, selectorAlias, caseType , expressionType);
       
        String name = isCase.getVariable().getIdentifier().getText();

        Naming.SyntheticName tmpVarName = selectorAlias;
        Name substVarName = naming.aliasName(name);

        // Want raw type for instanceof since it can't be used with generic types
        JCExpression rawToTypeExpr = makeJavaType(varType, JT_NO_PRIMITIVES | JT_RAW);

        // Substitute variable with the correct type to use in the rest of the code block
       
        JCExpression tmpVarExpr = at(isCase).TypeCast(rawToTypeExpr, tmpVarName.makeIdent());
        JCExpression toTypeExpr;
        if (!willEraseToObject(varType)) {
            toTypeExpr = makeJavaType(varType);
            tmpVarExpr = unboxType(tmpVarExpr, varType);
        } else {
            toTypeExpr = makeJavaType(varType, JT_NO_PRIMITIVES);
        }
       
        // The variable holding the result for the code inside the code block
        JCVariableDecl decl2 = at(isCase).VarDef(make().Modifiers(FINAL), substVarName, toTypeExpr, tmpVarExpr);

        // Prepare for variable substitution in the following code block
        Substitution prevSubst = naming.addVariableSubst(isCase.getVariable().getDeclarationModel(), substVarName.toString());

        List<JCStatement> stats = List.<JCStatement> of(decl2);
        stats = stats.appendList(transformBlock(caseClause.getBlock()));
        JCBlock block = at(isCase).Block(0, stats);

        // Deactivate the above variable substitution
        prevSubst.close();

        last = make().If(cond, block, last);
        return last;
    }
   
    private Name getLabel(Tree.Directive dir) {
        Scope scope = dir.getScope();
        while (!(scope instanceof Package)) {
            if (scope instanceof ControlBlock) {
                Integer loopId = gen().visitor.lv.getLoopId((ControlBlock)scope);
                if (loopId != null) {
                    return names().fromString("loop_"+loopId);
                }
            }
            scope = scope.getContainer();
        }
        throw new BugException(dir, "failed to find label");
    }
   
    public Name getLabel(Tree.Break brk) {
        return getLabel((Tree.Directive)brk);
    }
   
    public Name getLabel(Tree.Continue cont) {
        return getLabel((Tree.Directive)cont);
    }
   
    public Name getLabel(Tree.WhileClause loop) {
        return getLabel(loop.getControlBlock());
    }
   
    private Name getLabel(ControlBlock block) {
        Integer i = gen().visitor.lv.getLoopId(block);
        return names().fromString("loop_"+i);
    }
   
    public Name getLabel(Tree.ForClause loop) {
        return getLabel(loop.getControlBlock());
    }
   
}
TOP

Related Classes of com.redhat.ceylon.compiler.java.codegen.StatementTransformer$ExistsCond

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.