/* * 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.Set; import com.redhat.ceylon.common.BooleanUtil; import com.redhat.ceylon.compiler.java.codegen.AbstractTransformer.BoxingStrategy; 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.SyntheticName; import com.redhat.ceylon.compiler.java.codegen.recovery.HasErrorException; import com.redhat.ceylon.compiler.typechecker.tree.CustomTree; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Break; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CaseClause; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Condition; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ConditionList; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Continue; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Expression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ForStatement; import com.redhat.ceylon.compiler.typechecker.tree.Tree.RangeOp; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Return; import com.redhat.ceylon.compiler.typechecker.tree.Tree.SpecifierOrInitializerExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Statement; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Switched; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term; import com.redhat.ceylon.compiler.typechecker.tree.Tree.Variable; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.model.loader.NamingBase.Suffix; import com.redhat.ceylon.model.loader.NamingBase.Unfix; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.ConditionScope; import com.redhat.ceylon.model.typechecker.model.ControlBlock; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.ModelUtil; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; import com.redhat.ceylon.model.typechecker.model.Value; 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.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; private final Transformer<JCStatement, Tree.Return> defaultReturnTransformer = new DefaultReturnTransformer(); private Transformer<JCStatement, Tree.Return> returnTransformer = defaultReturnTransformer; private final Transformer<List<JCStatement>, Tree.Break> defaultBreakTransformer = new DefaultBreakTransformer(); private final Transformer<JCStatement, Tree.Continue> defaultContinueTransformer = new DefaultContinueTransformer(); private Transformer<JCStatement, Tree.Continue> continueTransformer = defaultContinueTransformer; 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) { return transformBlock(block, false); } public List<JCStatement> transformBlock(Tree.Block block, boolean revertRet) { 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(); java.util.Iterator<Statement> statements = block.getStatements().iterator(); while (statements.hasNext()) { Tree.Statement stmt = statements.next(); Transformer<JCStatement, Return> returnTransformer; if (revertRet && stmt instanceof Tree.Declaration) { returnTransformer = returnTransformer(defaultReturnTransformer); } else { returnTransformer = this.returnTransformer; } try { HasErrorException error = errors().getFirstErrorBlock(stmt); if (error == null) { stmt.visit(v); } else { v.append(this.makeThrowUnresolvedCompilationError(error)); break; } } finally { returnTransformer(returnTransformer); } } 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 Node 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; } public CondList(java.util.List<Tree.Condition> conditions, Tree.Expression thenPart) { this.conditions = conditions; this.thenPart = thenPart; } protected Cond getConditionTransformer(Tree.Condition cond) { return getConditionTransformer(cond, null); } protected Cond getConditionTransformer(Tree.Condition cond, Tree.Variable elseVariable) { if (cond instanceof Tree.IsCondition) { Tree.IsCondition is = (Tree.IsCondition)cond; IsVarTrans var = new IsVarTrans(is.getVariable()); IsVarTrans elseVar = (elseVariable != null) ? new IsVarTrans(elseVariable, var.getTestVariableName()) : null; return new IsCond(is, var, elseVar); } else if (cond instanceof Tree.ExistsCondition) { Tree.ExistsCondition exists = (Tree.ExistsCondition)cond; ExistsVarTrans var = new ExistsVarTrans(exists.getVariable()); ExistsVarTrans elseVar = (elseVariable != null) ? new ExistsVarTrans(elseVariable, var.getTestVariableName()) : null; return new ExistsCond(exists, var, elseVar); } else if (cond instanceof Tree.NonemptyCondition) { Tree.NonemptyCondition nonempty = (Tree.NonemptyCondition)cond; NonemptyVarTrans var = new NonemptyVarTrans(nonempty.getVariable()); NonemptyVarTrans elseVar = (elseVariable != null) ? new NonemptyVarTrans(elseVariable, var.getTestVariableName()) : null; return new NonemptyCond(nonempty, var, elseVar); } else if (cond instanceof Tree.BooleanCondition) { return new BooleanCond((Tree.BooleanCondition)cond); } throw BugException.unhandledNodeCase(cond); } 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 { /* Name of the variable in which to store the result of the blocks if thenPart is a Tree.Expression */ protected final String tmpVar; /* The outer expression that we are evaluating, to get boxing/erasure info from */ protected final Tree.Term outerExpression; /* The expected type that we are evaluating, to get nullable info from */ protected final Type expectedType; public BlockCondList(java.util.List<Tree.Condition> conditions, Tree.Block thenPart) { super(conditions, thenPart); tmpVar = null; outerExpression = null; expectedType = null; } public BlockCondList(java.util.List<Tree.Condition> conditions, Tree.Expression thenPart, String tmpVar, Tree.Term outerExpression, Type expectedType) { super(conditions, thenPart); this.tmpVar = tmpVar; this.outerExpression = outerExpression; this.expectedType = expectedType; } @Override protected final List<JCStatement> transformInnermost(Tree.Condition condition) { Cond transformedCond = getConditionTransformer(condition); // Note: The innermost test happens outside the substitution scope JCExpression test = transformedCond.makeTest(); List<JCStatement> elseStmts; java.util.List<Tree.Condition> rest = Collections.<Tree.Condition>emptyList(); if (transformedCond.getElseVarTrans() != null) { List<Substitution> subs = getSubstitutions(transformedCond.getElseVarTrans()); elseStmts = transformInnermostElse(transformedCond, rest); elseStmts = transformCommonResultDecl(transformedCond.getElseVarTrans(), elseStmts); closeSubstitutions(subs); } else { elseStmts = transformInnermostElse(transformedCond, rest); } List<Substitution> subs = getSubstitutions(transformedCond.getVarTrans()); List<JCStatement> stmts = transformInnermostThen(transformedCond); stmts = transformCommonResultDecl(transformedCond.getVarTrans(), stmts); closeSubstitutions(subs); stmts = transformCommon(transformedCond, rest, test, stmts, elseStmts); return stmts; } protected List<Substitution> getSubstitutions(VarTrans vartrans) { if (vartrans.hasResultDecl()) { List<Substitution> subs = List.nil(); List<VarDefBuilder> vars = vartrans.getVarDefBuilders(); for (VarDefBuilder v : vars) { subs = subs.append(naming.substituteAlias(v.var.getDeclarationModel())); } return subs; } return null; } protected void closeSubstitutions(List<Substitution> subs) { if (subs != null) { for (Substitution s : subs) { s.close(); } } } @Override protected List<JCStatement> transformIntermediate(Tree.Condition condition, java.util.List<Tree.Condition> rest) { Cond intermediate = getConditionTransformer(condition); JCExpression test = intermediate.makeTest(); List<Substitution> subs = getSubstitutions(intermediate.getVarTrans()); List<JCStatement> stmts = transformList(rest); stmts = transformCommonResultDecl(intermediate.getVarTrans(), stmts); List<JCStatement> intermediateElse = transformIntermediateElse(intermediate, rest); stmts = transformCommon(intermediate, rest, test, stmts, intermediateElse); closeSubstitutions(subs); return stmts; } protected JCStatement makeDefaultAssignment(Type type, CName name) { return make().Exec(make().Assign(name.makeIdent(), makeDefaultExprForType(type))); } protected abstract List<JCStatement> transformInnermostThen(Cond cond); protected abstract List<JCStatement> transformInnermostElse(Cond cond, java.util.List<Tree.Condition> rest); protected abstract List<JCStatement> transformIntermediateElse(Cond cond, java.util.List<Tree.Condition> rest); protected abstract List<JCStatement> transformCommon(Cond cond, java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, List<JCStatement> elseStmts); protected abstract List<JCStatement> transformCommonResultDecl(VarTrans var, List<JCStatement> stmts); } class IfCondList extends BlockCondList { final ListBuffer<JCStatement> varDecls = ListBuffer.lb(); final SyntheticName ifVar = naming.temp("if"); private List<JCStatement> unassignedResultVars = List.nil(); private JCBlock thenBlock; private Tree.Variable elseVar; private Node elsePart; public IfCondList(java.util.List<Tree.Condition> conditions, Tree.Block thenPart, Tree.Variable elseVar, Tree.Block elsePart) { super(conditions, thenPart); this.elseVar = elseVar; this.elsePart = elsePart; } public IfCondList(java.util.List<Tree.Condition> conditions, Tree.Expression thenPart, Tree.Variable elseVar, Tree.Expression elsePart, String tmpVar, Tree.Term outerExpression, Type expectedType) { super(conditions, thenPart, tmpVar, outerExpression, expectedType); this.elseVar = elseVar; 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 Cond getConditionTransformer(Condition cond) { return getConditionTransformer(cond, elseVar); } @Override protected List<JCStatement> transformIntermediateElse(Cond cond, java.util.List<Tree.Condition> rest) { return null; } private boolean isThenDefinitelyReturns(){ return isDefinitelyReturns(thenPart); } private boolean isElseDefinitelyReturns(){ return isDefinitelyReturns(elsePart); } private boolean isDefinitelyReturns(Node thenPart){ if(thenPart instanceof Tree.Block) return ((Tree.Block)thenPart).getDefinitelyReturns(); else if(thenPart instanceof Tree.Expression) return false; // I guess? else return false; } @Override protected List<JCStatement> transformInnermostThen(Cond cond) { List<JCStatement> stmts; if (definitelyNotSatisfied(conditions) && !isThenDefinitelyReturns() && (elsePart != null && isElseDefinitelyReturns())) { 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(cond, thenPart, null, tmpVar, outerExpression, expectedType); } else { stmts = makeThenBlock(cond, thenPart, null, tmpVar, outerExpression, expectedType).getStatements(); } return stmts; } @Override protected List<JCStatement> transformInnermostElse(Cond cond, java.util.List<Tree.Condition> rest) { List<JCStatement> stmts = null; if (elsePart != null && !isDeferred()) { if (this.elsePart instanceof Tree.Block) { stmts = transformBlock((Tree.Block)this.elsePart); } else if (this.elsePart instanceof Tree.Expression) { stmts = evaluateAndAssign(tmpVar, (Tree.Expression)this.elsePart, outerExpression, expectedType); } else { stmts = List.<JCStatement>of(make().Exec(makeErroneous(thenPart, "Only block or expression allowed"))); } } return stmts; } @Override protected List<JCStatement> transformCommon(Cond cond, java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, List<JCStatement> elseStmts) { JCStatement testVarDecl = cond.getVarTrans().makeTestVarDecl(0, false); if (testVarDecl == null && cond.getElseVarTrans() != null) { testVarDecl = cond.getElseVarTrans().makeTestVarDecl(0, false); } if (testVarDecl != null) { varDecls.prepend(testVarDecl); } if (isDeferred()) { elseStmts = unassignedResultVars.isEmpty() ? null : unassignedResultVars; } stmts = List.<JCStatement>of(make().If( test, make().Block(0, stmts), makeElseBlock(elseStmts))); return stmts; } protected List<JCStatement> transformCommonResultDecl( VarTrans vartrans, List<JCStatement> stmts) { if (vartrans.hasResultDecl()) { List<VarDefBuilder> vars = vartrans.getVarDefBuilders(); for (VarDefBuilder vdb : vars) { if (isDeferred()) { unassignedResultVars = unassignedResultVars.prepend(make().Exec(vdb.buildDefaultAssign())); varDecls.prepend(vdb.buildDefOnly()); stmts = stmts.prepend(make().Exec(vdb.buildAssign())); } else { stmts = stmts.prepend(vdb.build()); } } } return stmts; } @Override public List<JCStatement> getResult() { List<JCStatement> stmts = transformList(conditions); if (definitelySatisfied(conditions) && isThenDefinitelyReturns() && (elsePart == null || !isElseDefinitelyReturns())) { 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()) { JCBlock elseBlock; if(elsePart instanceof Tree.Block) elseBlock = StatementTransformer.this.transform((Tree.Block)elsePart); else if(elsePart instanceof Tree.Expression) elseBlock = at(elsePart).Block(0, evaluateAndAssign(tmpVar, (Tree.Expression)elsePart, outerExpression, expectedType)); else if(elsePart == null) elseBlock = null; else elseBlock = at(elsePart).Block(0, List.<JCStatement>of(make().Exec(makeErroneous(thenPart, "Only block or expression allowed")))); result.append(make().If(ifVar.makeIdent(), thenBlock, elseBlock)); } 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(); Tree.Variable elseVar = (stmt.getElseClause() != null) ? stmt.getElseClause().getVariable() : null; return transformIf(conditions, thenPart, elseVar, elsePart); } List<JCStatement> transformIf(java.util.List<Condition> conditions, Tree.Block thenPart, Tree.Variable elseVar, Tree.Block elsePart) { return new IfCondList(conditions, thenPart, elseVar, elsePart).getResult(); } List<JCStatement> transformIf(java.util.List<Condition> conditions, Tree.Expression thenPart, Tree.Variable elseVar, Tree.Expression elsePart, String tmpVar, Tree.Term outerExpression, Type expectedType) { return new IfCondList(conditions, thenPart, elseVar, elsePart, tmpVar, outerExpression, expectedType).getResult(); } private List<JCStatement> evaluateAndAssign(String tmpVar, Tree.Expression expr, Tree.Term outerExpression, Type expectedType){ at(expr); if(expectedType == null) expectedType = outerExpression.getTypeModel(); if (!expectedType.getDeclaration().isAnonymous()) { expectedType = typeFact().denotableType(expectedType); } BoxingStrategy boxingStrategy = CodegenUtil.getBoxingStrategy(outerExpression); return List.<JCStatement>of(make().Exec(make().Assign( makeUnquotedIdent(tmpVar), expressionGen().transformExpression(expr, boxingStrategy, expectedType)))); } private JCBlock makeThenBlock(Cond cond, Node thenPart, Substitution subs, String tmpVar, Tree.Term outerExpression, Type expectedType) { List<JCStatement> blockStmts; if(thenPart instanceof Tree.Block) blockStmts = statementGen().transformBlock((Tree.Block)thenPart); else if(thenPart instanceof Tree.Expression){ blockStmts = evaluateAndAssign(tmpVar, (Tree.Expression)thenPart, outerExpression, expectedType); }else if(thenPart == null){ blockStmts = List.<JCStatement>nil(); }else{ blockStmts = List.<JCStatement>of(make().Exec(makeErroneous(thenPart, "Only block or expression allowed"))); } 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.getVarTrans().makeTypeExpr(), cond.getVarTrans().makeResultExpr())); } JCBlock thenBlock = at(cond.getCondition()).Block(0, blockStmts); return thenBlock; } protected JCStatement makeElseBlock(List<JCStatement> stmts) { if (stmts != null) { return make().Block(0, stmts); } else { return null; } } 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 List<JCStatement> transformIntermediateElse(Cond cond, java.util.List<Tree.Condition> rest) { return List.<JCStatement>of(make().Break(label)); } @Override protected List<JCStatement> transformInnermostThen(Cond cond) { return makeThenBlock(cond, thenPart, null, null /* while is not an expression yet */, null, null).getStatements(); } @Override protected List<JCStatement> transformInnermostElse(Cond cond, java.util.List<Tree.Condition> rest) { return List.<JCStatement>of(make().Break(label)); } @Override protected List<JCStatement> transformCommon(Cond cond, java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, List<JCStatement> elseStmts) { if (cond.getVarTrans().makeTestVarDecl(0, false) != null) { varDecls.append(cond.getVarTrans().makeTestVarDecl(0, false)); } stmts = List.<JCStatement>of(make().If( test, make().Block(0, stmts), makeElseBlock(elseStmts))); return stmts; } protected List<JCStatement> transformCommonResultDecl( VarTrans vartrans, List<JCStatement> stmts) { if (vartrans.hasResultDecl()) { List<VarDefBuilder> vars = vartrans.getVarDefBuilders(); for (VarDefBuilder v : vars) { stmts = stmts.prepend(v.build()); } } 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 List<JCStatement> unassignedResultVars = List.nil(); public AssertCondList(Tree.Assertion ass) { super(ass.getConditionList().getConditions(), (Tree.Block)null); this.ass = ass; } /** Determines whether there's more than one Condition in the ConditionList */ private boolean isMulti() { return this.conditions.size() > 1; } protected List<Substitution> getSubstitutions(VarTrans vartrans) { List<Substitution> subs = super.getSubstitutions(vartrans); if (subs == null) { return subs; } List<VarDefBuilder> vars = vartrans.getVarDefBuilders(); Set<Scope> scopes = new HashSet<Scope>(); for (VarDefBuilder v : vars) { Scope scope = v.var.getScope().getScope(); while (scope instanceof ConditionScope) { scope = scope.getScope(); } scopes.add(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 v.name(); } for (Scope scope : scopes) { for (Substitution s : subs) { s.scopeClose(scope); } } return subs; } @Override protected List<JCStatement> transformCommon(Cond cond, java.util.List<Tree.Condition> rest, JCExpression test, List<JCStatement> stmts, List<JCStatement> elseStmts) { if (isMulti()) { elseStmts = elseStmts.prependList(unassignedResultVars); } stmts = List.<JCStatement>of(make().If( test, make().Block(0, stmts), makeElseBlock(elseStmts))); JCStatement testVarDecl = cond.getVarTrans().makeTestVarDecl(0, true); if (testVarDecl != null) { stmts = stmts.prepend(testVarDecl); } return stmts; } protected List<JCStatement> transformCommonResultDecl(VarTrans vartrans, List<JCStatement> stmts) { if (vartrans.hasResultDecl()) { List<VarDefBuilder> vars = vartrans.getVarDefBuilders(); for (VarDefBuilder v : vars) { unassignedResultVars = unassignedResultVars.prepend(make().Exec(v.buildDefaultAssign())); (Decl.getNonConditionScope(ass.getScope()) instanceof ClassOrInterface && v.var.getDeclarationModel().isCaptured() ? fieldDecls : varDecls).append(v.buildDefOnly()); stmts = stmts.prepend(make().Exec(v.buildAssign())); } } 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 List<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 List.<JCStatement>of( make().Exec(make().Assign(messageSb.makeIdent(), msg.build()))); } @Override protected List<JCStatement> transformInnermostThen(Cond cond) { return List.nil(); } @Override protected List<JCStatement> transformInnermostElse(Cond cond, java.util.List<Tree.Condition> rest) { if (!isMulti()) { return List.<JCStatement>of(makeThrowAssertionFailure(cond.getCondition())); } 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 List<JCStatement> transformIntermediateElse(Cond cond, java.util.List<Tree.Condition> rest) { return transformCommonElse(cond, rest); } } interface VarTrans { public Tree.Variable getVariable(); public List<VarDefBuilder> getVarDefBuilders(); public CName getVariableName(); public CName getTestVariableName(); public Tree.Expression getExpression(); public boolean hasResultDecl(); public boolean hasAliasedVariable(); public boolean isDestructure(); public JCExpression makeTypeExpr(); public JCExpression makeResultExpr(); public Type getType(); public Type getResultType(); public JCStatement makeTestVarDecl(int flags, boolean init); } abstract class BaseVarTransImpl implements VarTrans { protected final Type toType; private final boolean toTypeBoxed; private final Tree.Expression specifierExpr; private final Tree.Statement varOrDes; private final CName testVarName; private final List<VarDefBuilder> vdBuilders; private CName variableName; BaseVarTransImpl(Tree.Statement varOrDes) { this(varOrDes, naming.syntheticDestructure(varOrDes).alias()); } BaseVarTransImpl(Tree.Statement varOrDes, CName testVarName) { this.specifierExpr = getDestructureExpression(varOrDes); this.varOrDes = varOrDes; this.testVarName = testVarName; Tree.Type type; if (varOrDes instanceof Tree.Variable) { Tree.Variable var = (Tree.Variable)varOrDes; type = var.getType(); toTypeBoxed = !CodegenUtil.isUnBoxed(var.getDeclarationModel()); } else if (varOrDes instanceof Tree.Destructure) { type = ((Tree.Destructure)varOrDes).getType(); toTypeBoxed = false; } else { throw BugException.unhandledCase(varOrDes); } if (type != null) { this.toType = type.getTypeModel(); } else { this.toType = specifierExpr.getTypeModel(); } vdBuilders = transformDestructure(varOrDes, getTestVariableName().makeIdent(), getResultType(), true); } @Override public final Tree.Variable getVariable() { return (Tree.Variable)varOrDes; } @Override public List<VarDefBuilder> getVarDefBuilders() { return vdBuilders; } @Override public final boolean isDestructure() { return varOrDes instanceof Tree.Destructure; } @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(getVariable().getDeclarationModel()).capture(); return variableName; } @Override public final CName getTestVariableName() { return testVarName; } @Override public final Tree.Expression getExpression() { return specifierExpr; } @Override public boolean hasResultDecl() { return true; } @Override public boolean hasAliasedVariable() { return !(getVariable().getType() instanceof Tree.SyntheticVariable) || naming.isSubstituted(getVariable().getDeclarationModel().getOriginalDeclaration()); } @Override public final Type getType() { return toType; } @Override public final JCExpression makeTypeExpr() { return makeJavaType(toType, (toTypeBoxed) ? AbstractTransformer.JT_NO_PRIMITIVES : 0); } @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), testVarName.asName(), makeResultType(), init ? makeNull() : null); } protected JCExpression makeResultType() { return makeJavaType(getResultType(), JT_NO_PRIMITIVES); } @Override public Type getResultType() { Type exprType = getExpression().getTypeModel(); if (isOptional(exprType)) { exprType = typeFact().getDefiniteType(exprType); } return exprType; } } class IsVarTrans extends BaseVarTransImpl { private IsVarTrans(Tree.Variable var) { super(var); } private IsVarTrans(Tree.Variable var, CName testVarName) { super(var, testVarName); } @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().getNothingType()) && ! 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(getExpression().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 protected JCExpression makeResultType() { at(getVariable()); return make().Type(syms().objectType); } @Override public Type getResultType() { return typeFact().getObjectType(); } @Override public JCExpression makeResultExpr() { at(getVariable()); JCExpression expr = getTestVariableName().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(getVariable()).TypeCast(rawToTypeExpr, expr); if ((getVariable().getDeclarationModel().getUnboxed() == true) && canUnbox(toType)) { expr = unboxType(expr, toType); } } return expr; } } class ExistsVarTrans extends BaseVarTransImpl { private ExistsVarTrans(Tree.Statement varOrDes) { super(varOrDes); } private ExistsVarTrans(Tree.Statement varOrDes, CName testVarName) { super(varOrDes, testVarName); } @Override public JCExpression makeResultExpr() { BoxingStrategy boxing; if (isDestructure()) { boxing = BoxingStrategy.BOXED; } else { Value decl = getVariable().getDeclarationModel(); boxing = CodegenUtil.getBoxingStrategy(decl); } return expressionGen().applyErasureAndBoxing(getTestVariableName().makeIdent(), getResultType(), willEraseToObject(toType), true, boxing, toType, 0); } } class NonemptyVarTrans extends BaseVarTransImpl { private NonemptyVarTrans(Tree.Statement varOrDes) { super(varOrDes); } private NonemptyVarTrans(Tree.Statement varOrDes, CName testVarName) { super(varOrDes, testVarName); } @Override public JCExpression makeResultExpr() { Type exprType = getExpression().getTypeModel(); if (isOptional(exprType)) { exprType = typeFact().getDefiniteType(exprType); } Type expectedType = getVariable().getDeclarationModel().getType(); return expressionGen().applyErasureAndBoxing(getTestVariableName().makeIdent(), exprType, false, true, BoxingStrategy.BOXED, expectedType, ExpressionTransformer.EXPR_DOWN_CAST); } } interface Cond { public Tree.Condition getCondition(); public VarTrans getVarTrans(); public VarTrans getElseVarTrans(); public JCExpression makeTest(); } abstract class SpecialFormCond<C extends Tree.Condition, V extends VarTrans> implements Cond { protected final C cond; protected final V var; protected final V elseVar; protected final boolean negate; SpecialFormCond(C cond, V var, V elseVar, boolean negate) { this.cond = cond; this.var = var; this.elseVar = elseVar; this.negate = negate; } @Override public final C getCondition() { return cond; } @Override public final V getVarTrans() { return var; } @Override public final V getElseVarTrans() { return elseVar; } } class IsCond extends SpecialFormCond<Tree.IsCondition, IsVarTrans> { private IsCond(Tree.IsCondition isdecl, IsVarTrans var, IsVarTrans elseVar) { super(isdecl, var, elseVar, isdecl.getNot()); } @Override public JCExpression makeTest() { Type 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 Type specifierType = negate ? expressionType : getOptionalTypeForInteropIfAllowed(cond.getType().getTypeModel(), expressionType, var.getExpression()); // no need to cast for erasure here JCExpression expr = expressionGen().transformExpression(var.getExpression(), BoxingStrategy.BOXED, specifierType); at(cond); // Assign the expression to test to the temporary variable boolean useTempVar = !var.isErasedToObjectOptimization() && !var.isNothingOptimization(); if (elseVar != null) { useTempVar = useTempVar || (!elseVar.isErasedToObjectOptimization() && !elseVar.isNothingOptimization()); } if (useTempVar) { expr = make().Assign(var.getTestVariableName().makeIdent(), expr); } // Test on the tmpVar in the following condition expr = makeOptimizedTypeTest(expr, var.isErasedToObjectOptimization() ? var.getVariableName() : var.getTestVariableName(), // 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; } } class ExistsCond extends SpecialFormCond<Tree.ExistsCondition, ExistsVarTrans> { private ExistsCond(Tree.ExistsCondition exists, ExistsVarTrans var, ExistsVarTrans elseVar) { super(exists, var, elseVar, exists.getNot()); } @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 Type specifierType = var.getExpression().getTypeModel(); if(!typeFact().isOptionalType(specifierType)){ specifierType = typeFact().getOptionalType(specifierType); } JCExpression expr = expressionGen().transformExpression(var.getExpression(), BoxingStrategy.BOXED, specifierType); at(cond); // Assign the expression to test to the temporary variable expr = make().Assign(var.getTestVariableName().makeIdent(), expr); // Test on the tmpVar in the following condition expr = make().Binary(JCTree.NE, expr, makeNull()); if (negate) { expr = make().Unary(JCTree.NOT, expr); } return expr; } } class NonemptyCond extends SpecialFormCond<Tree.NonemptyCondition, NonemptyVarTrans> { private NonemptyCond(Tree.NonemptyCondition nonempty, NonemptyVarTrans var, NonemptyVarTrans elseVar) { super(nonempty, var, elseVar, nonempty.getNot()); } @Override public JCExpression makeTest() { // no need to cast for erasure here JCExpression expr = expressionGen().transformExpression(var.getExpression()); at(cond); // Assign the expression to test to the temporary variable expr = make().Assign(var.getTestVariableName().makeIdent(), expr); // Test on the tmpVar in the following condition expr = makeNonEmptyTest(expr); if (negate) { expr = make().Unary(JCTree.NOT, expr); } return expr; } } class BooleanCond implements Cond { private final Tree.BooleanCondition cond; private final VarTrans var; private BooleanCond(Tree.BooleanCondition booleanCondition) { super(); this.cond = booleanCondition; this.var = new VarTrans() { @Override public Variable getVariable() { return null; } @Override public List<VarDefBuilder> getVarDefBuilders() { return null; } @Override public final boolean isDestructure() { return false; } @Override public CName getVariableName() { return null; } @Override public CName getTestVariableName() { return null; } @Override public Expression getExpression() { return null; } @Override public boolean hasResultDecl() { return false; } @Override public boolean hasAliasedVariable() { return false; } @Override public JCExpression makeTypeExpr() { return null; } @Override public JCExpression makeResultExpr() { return null; } @Override public Type getType() { return null; } @Override public Type getResultType() { return null; } @Override public JCStatement makeTestVarDecl(int flags, boolean init) { return null; } }; } @Override public JCExpression makeTest() { at(cond); return expressionGen().transformExpression(cond.getExpression(), BoxingStrategy.UNBOXED, typeFact().getBooleanType()); } @Override public Tree.Condition getCondition() { return cond; } @Override public VarTrans getVarTrans() { return var; } @Override public VarTrans getElseVarTrans() { return var; } } 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 Type 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(); Type primaryType = qme.getPrimary().getTypeModel(); Type 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()); Type charType = typeFact().getCharacterType(); boolean elemBoxed = BooleanUtil.isFalse(getElementOrKeyVariable().getDeclarationModel().getUnboxed()); JCExpression elemNameExpr = naming.makeQuotedIdent(Naming.getVariableName(getElementOrKeyVariable())); if (elemBoxed) { elemNameExpr = unboxType(elemNameExpr, charType); } 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(elemNameExpr))))); JCExpression typeExpr = makeJavaType(charType, elemBoxed ? JT_NO_PRIMITIVES : 0); JCExpression codePointAtCallExpr = make().Apply(null, naming.makeQualIdent(stringName.makeIdent(), "codePointAt"), List.<JCExpression>of(indexName.makeIdent())); if (elemBoxed) { codePointAtCallExpr = boxType(codePointAtCallExpr, charType); } transformedBlock = transformedBlock.prepend(makeVar(FINAL, Naming.getVariableName(getElementOrKeyVariable()), typeExpr, codePointAtCallExpr)); 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 Type 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; protected final SyntheticName stepName; IndexedAccessIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step, Type elementType, String indexableName) { this(stmt, baseIterable, step, elementType, indexableName, "length", "i"); } IndexedAccessIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step, Type elementType, String indexableName, String lengthName, String indexName) { super(stmt); this.baseIterable = baseIterable; this.step = step; if (step != null) { stepName = naming.alias("step"); } else { stepName = null; } 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 length = java.lang.reflect.Array.getLength(array); JCExpression lengthExpr = makeLengthExpr(); if (lengthExpr != null) { result.add(makeVar(FINAL, lengthName, makeIndexType(), lengthExpr)); } // int step = ... if (this.step != null) { JCExpression stepExpr = makeStepExpr(); result.add(makeVar(FINAL, stepName, makeIndexType(), stepExpr)); result.add( make().If( stepCheck(stepName), makeThrowAssertionException( new AssertionExceptionMessageBuilder(null) .appendViolatedCondition("step > 0") .prependAssertionDoc("step size must be greater than zero") .build()), null)); } // 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) { Tree.ValueIterator valIter = (Tree.ValueIterator)forIterator; JCStatement variable = transformVariable(valIter.getVariable(), elementGet, elementType, isIndexedAccessBoxed()).build(); // Prepend to the block transformedBlock = transformedBlock.prepend(variable); } else if (forIterator instanceof Tree.PatternIterator) { Tree.PatternIterator patIter = (Tree.PatternIterator)forIterator; Tree.KeyValuePattern pat = (Tree.KeyValuePattern)patIter.getPattern(); // FIXME DESCTRUCTURE SyntheticName entryName = naming.alias("entry"); JCStatement entryVariable = makeVar(FINAL, entryName, makeJavaType(typeFact().getEntryType(typeFact().getAnythingType(), typeFact().getAnythingType()), JT_RAW), elementGet); Type entryType = elementType.getSupertype(typeFact().getEntryDeclaration()); Type keyType = entryType.getTypeArgumentList().get(0); Tree.Variable keyVar = ((Tree.VariablePattern)pat.getKey()).getVariable(); String keyName = Naming.getVariableName(keyVar); Boolean keyUnboxed = keyVar.getDeclarationModel().getUnboxed(); BoxingStrategy keyBoxStrat = CodegenUtil.getBoxingStrategy(keyVar.getDeclarationModel()); JCStatement keyVariable = makeVar(FINAL, keyName, makeJavaType(keyType, BooleanUtil.isFalse(keyUnboxed) ? JT_NO_PRIMITIVES : 0), expressionGen().applyErasureAndBoxing( make().Apply(null, naming.makeQualIdent(entryName.makeIdent(), "getKey"), List.<JCExpression>nil()), typeFact().getAnythingType(), true, keyBoxStrat, keyType)); Type valueType = entryType.getTypeArgumentList().get(1); Tree.Variable valueVar = ((Tree.VariablePattern)pat.getValue()).getVariable(); String valueName = Naming.getVariableName(valueVar); Boolean valueUnboxed = keyVar.getDeclarationModel().getUnboxed(); BoxingStrategy valueBoxStrat = CodegenUtil.getBoxingStrategy(valueVar.getDeclarationModel()); JCStatement valueVariable = makeVar(FINAL, valueName, makeJavaType(valueType, BooleanUtil.isFalse(valueUnboxed) ? JT_NO_PRIMITIVES : 0), expressionGen().applyErasureAndBoxing( make().Apply(null, naming.makeQualIdent(entryName.makeIdent(), "getItem"), List.<JCExpression>nil()), typeFact().getAnythingType(), true, valueBoxStrat, valueType)); // Prepend to the block transformedBlock = transformedBlock.prepend(valueVariable); transformedBlock = transformedBlock.prepend(keyVariable); transformedBlock = transformedBlock.prepend(entryVariable); } else { throw BugException.unhandledCase(forIterator); } 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 JCBinary stepCheck(final SyntheticName stepName) { return make().Binary(JCTree.LE, stepName.makeIdent(), make().Literal(0)); } protected JCExpression makeIndexType() { return make().Type(syms().intType); } /** Makes the expression for incrementing the index */ protected JCExpression makeStepExpr() { Type intType = typeFact().getIntegerType(); 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(); /** To determine if the makeIndexedAccess() returns a boxed value or not */ protected abstract boolean isIndexedAccessBoxed(); /** 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, Type arrayType) { super(stmt, baseIterable, step, typeFact().getArrayElementType(arrayType), "array"); unboxed = typeFact().getArrayType(typeFact().getBooleanType()).isExactly(arrayType) || typeFact().getArrayType(typeFact().getByteType()).isExactly(arrayType) || typeFact().getArrayType(typeFact().getIntegerType()).isExactly(arrayType) || typeFact().getArrayType(typeFact().getCharacterType()).isExactly(arrayType) || typeFact().getArrayType(typeFact().getFloatType()).isExactly(arrayType) || typeFact().getArrayType(typeFact().getStringType()).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() { Type 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; } else if (isCeylonString(elementType)) { elementGet = utilInvocation().getStringArray( 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().getObjectType(); typeErased = true; exprBoxed = true; } elementGet = expressionGen().applyErasureAndBoxing( elementGet, gotType, typeErased, exprBoxed, CodegenUtil.getBoxingStrategy(getElementOrKeyVariable().getDeclarationModel()), elementType, 0); return elementGet; } @Override protected boolean isIndexedAccessBoxed() { return getElementOrKeyVariable().getDeclarationModel().getUnboxed() == false; } } /** * 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 Type javaArrayType; JavaArrayIterationOptimization(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step, Type elementType, Type 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()); } @Override protected boolean isIndexedAccessBoxed() { return isOptional(elementType); } } private ForStatementTransformation arrayIteration(Tree.ForStatement stmt, Tree.Term baseIterable, Tree.Term step) { Type ceylonArrayType = baseIterable.getTypeModel(); Type 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, Type ofType) { Type 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 SpanOpIterationOptimization} if that optimization applies * to the given {@code for} statement, otherwise null. * @param stmt The for statement * @return a {@link SpanOpIterationOptimization} 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)'"); } com.sun.tools.javac.code.Type type; Type integerType = typeFact().getIntegerType(); Type characterType = typeFact().getCharacterType(); 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 increment == null ? new SpanOpIterationOptimization(stmt, range, increment, type) : new SpanOpWithStepIterationOptimization(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", "length", "i"); this.start = start; this.length = length; // TODO If length if < 0 we need to not loop at all } @Override protected JCBinary stepCheck(final SyntheticName stepName) { return make().Binary(JCTree.AND, make().Binary(JCTree.GT, lengthName.makeIdent(), make().Literal(0)), super.stepCheck(stepName)); } @Override protected JCExpression makeIndexableType() { return makeJavaType(this.elementType); } @Override protected JCExpression makeIndexable() { return expressionGen().transformExpression(start, BoxingStrategy.UNBOXED, length.getTypeModel()); } protected JCExpression makeIndexType() { return make().Type(syms().longType); } protected JCExpression makeIndexInit() { return make().Literal(0); } @Override protected JCExpression makeIndexedAccess() { if (elementType.isExactly(typeFact().getIntegerType())) { if (step == null) { return make().Binary(JCTree.PLUS, indexName.makeIdent(), indexableName.makeIdent()); } else{ return make().Conditional(make().Binary(JCTree.EQ, stepName.makeIdent(), make().Literal(1L)), make().Binary(JCTree.PLUS, indexName.makeIdent(), indexableName.makeIdent()), make().Apply(null, naming.makeSelect(makeJavaType(elementType, JT_NO_PRIMITIVES), "neighbour"), List.<JCExpression>of( indexableName.makeIdent(), indexName.makeIdent()))); } } else {// must be character if (step == null) { return make().Apply(null, naming.makeSelect(makeJavaType(elementType, JT_NO_PRIMITIVES), "neighbour"), List.<JCExpression>of( indexableName.makeIdent(), indexName.makeIdent())); } else { return make().Conditional(make().Binary(JCTree.EQ, stepName.makeIdent(), make().Literal(1L)), make().Apply(null, naming.makeSelect(makeJavaType(elementType, JT_NO_PRIMITIVES), "codepoint"), List.<JCExpression>of( make().Binary(JCTree.PLUS, indexName.makeIdent(), indexableName.makeIdent()))), make().Apply(null, naming.makeSelect(makeJavaType(elementType, JT_NO_PRIMITIVES), "neighbour"), List.<JCExpression>of( indexableName.makeIdent(), indexName.makeIdent()))); } } } @Override protected boolean isIndexedAccessBoxed() { return false; } @Override protected JCExpression makeLengthExpr() { JCExpression result = expressionGen().transformExpression(length, BoxingStrategy.UNBOXED, length.getTypeModel()); //if (isCeylonCharacter(elementType)) { // This cannot be correct! // result = make().TypeCast(syms().intType, result); //} return result; } protected JCExpression makeStepExpr() { return expressionGen().transformExpression(step, BoxingStrategy.UNBOXED, elementType); } protected JCExpression makeIncrement(SyntheticName stepName) { if (stepName == null) { return make().Unary(JCTree.POSTINC, indexName.makeIdent()); } else { return make().Assign(indexName.makeIdent(), make().Conditional(make().Binary(JCTree.EQ, stepName.makeIdent(), make().Literal(1L)), make().Binary(JCTree.PLUS, indexName.makeIdent(), make().Literal(1L)), make().Apply(null, naming.makeSelect(make().Type(syms().ceylonIntegerType), "neighbour"), List.<JCExpression>of( indexName.makeIdent(), stepName.makeIdent())))); } } } 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; Type iteratedType = typeFact().getIteratedType(op.getTypeModel()); if (iteratedType.isExactly(typeFact().getIntegerType())) { } else if (iteratedType.isExactly(typeFact().getCharacterType())) { } 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; protected final Name failVar; ForStatementTransformation(Tree.ForStatement stmt) { this.stmt = stmt; this.label = getLabel(stmt.getForClause().getControlBlock()); if (needsFailVar()) { // boolean $doforelse$X = true; failVar = naming.aliasName("doforelse"); } else { failVar = null; } } 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 forIterator = getForIterator(); if (forIterator instanceof Tree.ValueIterator) { return ((Tree.ValueIterator)forIterator).getVariable(); } else if (forIterator instanceof Tree.PatternIterator) { Tree.PatternIterator patIter = (Tree.PatternIterator)forIterator; Tree.Pattern pat = patIter.getPattern(); // FIXME DESCTRUCTURE return ((Tree.VariablePattern)((Tree.KeyValuePattern)pat).getKey()).getVariable(); } return null; } protected final Tree.Variable getValueVariable() { Tree.ForIterator forIterator = getForIterator(); if (forIterator instanceof Tree.PatternIterator) { Tree.PatternIterator patIter = (Tree.PatternIterator)forIterator; Tree.Pattern pat = patIter.getPattern(); // FIXME DESCTRUCTURE return ((Tree.VariablePattern)((Tree.KeyValuePattern)pat).getKey()).getVariable(); } 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), failVar, 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 forIterator = stmt.getForClause().getForIterator(); List<JCStatement> itemDecls = List.nil(); final Naming.SyntheticName iteratorVarName; if (forIterator instanceof Tree.ValueIterator) { Tree.Variable variable = ((Tree.ValueIterator) forIterator).getVariable(); JCVariableDecl varExpr = transformVariable(variable, elem_name.makeIdent()).build(); itemDecls = itemDecls.append(varExpr); iteratorVarName = naming.synthetic(variable.getDeclarationModel()).suffixedBy(Suffix.$iterator$).alias(); } else if (forIterator instanceof Tree.PatternIterator) { Tree.PatternIterator patIter = (Tree.PatternIterator)forIterator; Tree.Pattern pat = patIter.getPattern(); List<VarDefBuilder> varsDefs = transformPattern(pat, elem_name.makeIdent()); for (VarDefBuilder vdb : varsDefs) { itemDecls = itemDecls.append(vdb.build()); } iteratorVarName = elem_name.suffixedBy(Suffix.$iterator$); } else { throw BugException.unhandledNodeCase(forIterator); } Tree.Expression specifierExpression = forIterator.getSpecifierExpression().getExpression(); Type sequenceElementType = typeFact().getIteratedType(specifierExpression.getTypeModel()); Type sequenceType = specifierExpression.getTypeModel().getSupertype(typeFact().getIterableDeclaration()); Type expectedIterableType = typeFact().isNonemptyIterableType(sequenceType) ? typeFact().getNonemptyIterableType(sequenceElementType) : typeFact().getIterableType(sequenceElementType); // ceylon.language.Iterator<T> $V$iter$X = ITERABLE.getIterator(); JCExpression containment = expressionGen().transformExpression(specifierExpression, BoxingStrategy.BOXED, expectedIterableType); 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, forIterator.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, Type iterableType, Type iteratedType, JCExpression iterableExpr, List<JCStatement> itemDecls, List<JCStatement> bodyStmts, boolean allowArrayOpt, boolean allowArraySeqOpt) { Type 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; Type 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().getAnythingType()), 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 Span<Integer>} * or {@code Span<Character>} which avoids allocating a {@code Span} and * using an {@code Iterator} like * {@link #ForStatementTransformation} but instead outputs a C-style * {@code while} loop. Because a Span 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 {@code for (i in first..last) ... }, * where {@code first}, {@code last} are * expressions (not necessarily literals or compile-time constants). * See {@link SpanOpWithStepIterationOptimization} for optimization of * statements like {@code for (i in (first..last).by(step)) ... } * * <p>The transformation is complicated by:</p> * <ul> * <li>Not knowing at compile-time whether {@code lhs < rhs}, which * complicates 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 SpanOpIterationOptimization extends ForStatementTransformation { public static final String OPT_NAME = "RangeOpIteration"; protected final Tree.RangeOp span; protected final Tree.Term first; protected final Tree.Term last; protected final Tree.Term step;// if null then increment is +/-1 protected final com.sun.tools.javac.code.Type type; protected final Type pt; /** the name of the statement label for continue jumps */ protected final SyntheticName continueName = naming.temp("continue"); /** the variable holding the Span.first */ protected final SyntheticName firstName = naming.temp("first"); /** the variable holding the Span.last */ protected final SyntheticName lastName = naming.temp("last"); /** whether this is an increasing span */ protected final SyntheticName increasingName = naming.temp("increasing"); /** the increment +/-1 for iteration with a step */ protected final SyntheticName incrementName = naming.temp("incr"); /** f: whether this is the first iteration */ protected final SyntheticName fName = naming.temp("f"); /** the current element */ protected final SyntheticName elementName = naming.temp("element"); /** The (aliased) iteration variable, that is the {@code i} in {@code for (i in first..last) */ protected final SyntheticName varname; public SpanOpIterationOptimization( Tree.ForStatement stmt, Tree.RangeOp range, Tree.Term step, com.sun.tools.javac.code.Type type) { super(stmt); this.span = range; this.first = range.getLeftTerm(); this.last = range.getRightTerm(); this.step = step; this.type = type; if (isCharacterSpan()) { this.pt = typeFact().getCharacterType(); } else if (isIntegerSpan()) { this.pt = typeFact().getIntegerType(); } else { throw new BugException(range, "unhandled Range type: " + type.tag); } varname = naming.alias(getVariable().getIdentifier().getText()); } protected final boolean isIntegerSpan() { return type.tag == syms().longType.tag; } protected final boolean isCharacterSpan() { return type.tag == syms().intType.tag; } protected Tree.Variable getVariable() { return ((Tree.ValueIterator)stmt.getForClause().getForIterator()).getVariable(); } protected JCExpression makeType(boolean boxed) { return makeJavaType(pt, boxed ? JT_NO_PRIMITIVES : 0); } protected Type getType() { return pt; } @Override protected ListBuffer<JCStatement> transformForClause() { ListBuffer<JCStatement> result = ListBuffer.<JCStatement>lb(); at(span); // Generate variable decls we'll need prelude(result); /* We're transforming a Ceylon for clause to a Java while clause and we want to execute POST-STATEMENTS before the while CONDITION is tested. That means we need to change how we will transform continue statements occurring in the TRANSFORMED-BODY, so we still execute those POST-STATEMENTS statements. We generate this: do { PRE-STATEMENTS label: do { TRANSFORMED-BODY } while(false); POST-STATEMENTS } while(CONDITION); and continues in that body becomes a break: label */ Tree.ControlClause prevForclause = currentForClause; currentForClause = stmt.getForClause(); Transformer<JCStatement, Continue> currentContinueTransformer = continueTransformer; continueTransformer = new Transformer<JCTree.JCStatement, Tree.Continue>() { @Override public JCStatement transform(Continue tree) { return at(tree).Break(continueName.asName()); } }; List<JCStatement> blockStatements = List.<JCStatement>of(make().Labelled( continueName.asName(), // The inner do {...} while (false) used purely as a jump target for continues make().DoLoop( make().Block(0, transformBlock(getBlock())), make().Literal(false) ))); continueTransformer = currentContinueTransformer; currentForClause = prevForclause; // Now add the PRE- and POST-STATEMENTS blockStatements = decorateBlock(blockStatements); // The actual loop result.append(make().Labelled(this.label, makeLoop(blockStatements))); return result; } /** * Appends statements to appear before the loop, * typically adding variables */ protected void prelude(ListBuffer<JCStatement> result) { // Note: Must invoke lhs, rhs and increment in the correct order! // long start = <lhs> at(first); result.append(makeVar(FINAL, firstName, makeType(false), expressionGen().transformExpression(first, BoxingStrategy.UNBOXED, getType()))); // long end = <rhs> at(last); result.append(makeVar(FINAL, lastName, makeType(false), expressionGen().transformExpression(last, BoxingStrategy.UNBOXED, getType()))); at(span); result.append(makeVar(FINAL, increasingName, make().Type(syms().booleanType), makeIncreasingExpr())); if (isCharacterSpan()) { /* Because Character.successor and .predecessor can throw when a Span is constructed, we need to replicate the following logic here: Boolean recursive = first.offsetSign(first.successor) > 0 && last.predecessor.offsetSign(last) > 0; */ at(span); result.append(makeVar(naming.temp(), make().Type(syms().booleanType), make().Binary(JCTree.AND, make().Binary(JCTree.GT, make().Apply(null, naming.makeQualIdent(make().QualIdent(syms().ceylonCharacterType.tsym), "offsetSign"), List.<JCExpression>of( firstName.makeIdent(), make().Apply(null, naming.makeQualIdent(make().QualIdent(syms().ceylonCharacterType.tsym), "getSuccessor"), List.<JCExpression>of(firstName.makeIdent())))), make().Literal(0L)), make().Binary(JCTree.GT, make().Apply(null, naming.makeQualIdent(make().QualIdent(syms().ceylonCharacterType.tsym), "offsetSign"), List.<JCExpression>of( make().Apply(null, naming.makeQualIdent(make().QualIdent(syms().ceylonCharacterType.tsym), "getPredecessor"), List.<JCExpression>of(lastName.makeIdent())), lastName.makeIdent())), make().Literal(0L)) ) )); } at(span); result.append(makeVar(FINAL, incrementName, make().Type(syms().longType), makeIncrementExpr())); result.append(makeVar(varname, makeType(false), firstName.makeIdent())); result.append(makeVar(elementName, makeType(false), firstName.makeIdent())); result.append(makeVar(fName, make().Type(syms().booleanType), make().Literal(false))); } /** The initial value of the {@code long $increment} variable */ protected JCExpression makeIncrementExpr() { return at(span).Conditional( increasingName.makeIdent(), makeIncreasingIncrement(), makeDecreasingIncrement()); } /** The initial value of the {@code boolean $increasing} variable */ protected JCExpression makeIncreasingExpr() { JCBinary incrExpr = at(span).Binary(JCTree.GE, make().Apply(null, naming.makeSelect(makeType(true), "offset"), List.<JCExpression>of(lastName.makeIdent(), firstName.makeIdent())), make().Literal(0)); return incrExpr; } /** The expression for an increasing increment */ protected JCExpression makeIncreasingIncrement() { if (isCharacterSpan()) { // long incr = start < end ? 1 : -1 return make().Literal(1); } else if (isIntegerSpan()) { return make().Literal(1L); } else { return makeErroneous(span, "unhandled Span type: " + type.tag); } } /** The expression for a decreasing increment */ protected JCExpression makeDecreasingIncrement() { if (isCharacterSpan()) { // long incr = start < end ? 1 : -1 return make().Literal(-1); } else if (isIntegerSpan()) { return make().Literal(-1L); } else { return makeErroneous(span, "unhandled Span type: " + type.tag); } } /** Prepend statements to the transformed block */ protected List<JCStatement> decorateBlock( List<JCStatement> blockStatements) { blockStatements = blockStatements.prepend(makeDeclareOrAssignTheirLoopVar()); blockStatements = blockStatements.prepend(make().Exec(makeAssignOurVar())); blockStatements = blockStatements.prepend( make().Exec(make().Assign(fName.makeIdent(), make().Literal(true)))); blockStatements = blockStatements.prepend( make().If(fName.makeIdent(), make().Exec(makeIncrementElement()), null)); return blockStatements; } /** Declare or assign the loop variable {@code i = <whatever>} */ protected JCStatement makeDeclareOrAssignTheirLoopVar() { boolean elemBoxed = BooleanUtil.isFalse(getVariable().getDeclarationModel().getUnboxed()); JCExpression elemInit = varname.makeIdent(); if (elemBoxed) { elemInit = boxType(elemInit, getType()); } return make().VarDef(make().Modifiers(FINAL), names().fromString(Naming.getVariableName(getVariable())), makeType(elemBoxed), elemInit); } protected final JCExpression makeAssignOurVar() { return make().Assign(varname.makeIdent(), elementName.makeIdent()); } /** * The expression used to increment {@code $element} */ protected JCExpression makeIncrementElement() { JCExpression stepExpr; if (isCharacterSpan()) { stepExpr = at(span).Assign(elementName.makeIdent(), make().Apply(null, naming.makeSelect(makeType(true), "neighbour"), List.<JCExpression>of(elementName.makeIdent(), incrementName.makeIdent()))); } else { stepExpr = at(span).Assignop(JCTree.PLUS_ASG, elementName.makeIdent(), incrementName.makeIdent()); } return stepExpr; } protected JCStatement makeLoop( List<JCStatement> blockStatements) { return make().DoLoop(make().Block(0, blockStatements), makeLoopCondition(varname)); } /** The expression used for the condition on the {@code while} loop */ protected JCExpression makeLoopCondition(SyntheticName varname) { JCExpression cond = at(span).Conditional(increasingName.makeIdent(), make().Binary(JCTree.NE, make().Binary(JCTree.MINUS, varname.makeIdent(), lastName.makeIdent()), makeZero()), make().Binary(JCTree.NE, make().Binary(JCTree.MINUS, varname.makeIdent(), lastName.makeIdent()), makeZero())); return cond; } protected JCExpression makeZero() { if (isCharacterSpan()) { return make().Literal(0); } else if (isIntegerSpan()) { return make().Literal(0L); } else { return makeErroneous(span, "unhandled Range type: " + type.tag); } } } /** * Optimization for statements like * {@code for (i in (lhs..rhs).by(increment)) ... } * This differs somewhat from the case without an increment because the * semantics of the iterator we're effectively inlining is quite different. * Annoyingly {@code Span.by} returns {@code this} when {@code step==1}, * which is behaviour we have to replicate. That means that the * {@code for} loop has to be structurally the same as the * step-less {@code for} loop. */ class SpanOpWithStepIterationOptimization extends SpanOpIterationOptimization { /** The variable holding the Span.by(step) */ protected final SyntheticName stepName = naming.temp("step"); public SpanOpWithStepIterationOptimization(ForStatement stmt, RangeOp range, Term increment, com.sun.tools.javac.code.Type type) { super(stmt, range, increment, type); } @Override protected void prelude(ListBuffer<JCStatement> result) { // $step = increment; result.append(at(step).VarDef(make().Modifiers(FINAL), stepName.asName(), make().Type(syms().longType), expressionGen().transformExpression(step, BoxingStrategy.UNBOXED, getType()))); // if ($step <= 0) throw Exception("step size must be greater than zero"); result.append(at(step).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)); super.prelude(result); } @Override protected final List<JCStatement> decorateBlock( List<JCStatement> blockStatements) { // i = $i blockStatements = blockStatements.prepend(makeDeclareOrAssignTheirLoopVar()); // $i = $n blockStatements = blockStatements.prepend(make().Exec(makeAssignOurVar())); // $f = true; blockStatements = blockStatements.prepend( make().Exec(make().Assign(fName.makeIdent(), make().Literal(true)))); // if ($f && step == 1) n=neighbour($i, $step); blockStatements = blockStatements.prepend( make().If( make().Binary(JCTree.AND, fName.makeIdent(), make().Binary(JCTree.EQ, stepName.makeIdent(), make().Literal(1L))), make().Exec(makeIncrementElement()), null)); if (!getBlock().getDefinitelyReturns()) { // if (step != 1) $n=neighbour($i, $step) blockStatements = blockStatements.append( make().If( make().Binary(JCTree.NE, stepName.makeIdent(), make().Literal(1L)), make().Exec(makeIncrementElement()), null)); } return blockStatements; } @Override protected JCStatement makeLoop( List<JCStatement> blockStatements) { // loop condition is on varname if step == 1 otherwise on n return make().DoLoop(make().Block(0, blockStatements), make().Conditional(make().Binary(JCTree.EQ, stepName.makeIdent(), make().Literal(1L)), makeLoopCondition(varname), makeLoopCondition(elementName))); } @Override protected JCExpression makeIncrementExpr() { JCConditional l = make().Conditional( increasingName.makeIdent(), makeIncreasingIncrement(), super.makeDecreasingIncrement()); return unitStep(stepName, l, at(span).Conditional( increasingName.makeIdent(), makeIncreasingIncrement(), makeDecreasingIncrement())); } @Override protected JCExpression makeIncreasingExpr() { return unitStep(stepName, super.makeIncreasingExpr(), at(span).Binary(JCTree.GE, make().Apply(null, naming.makeSelect(makeType(true), "offsetSign"), List.<JCExpression>of(lastName.makeIdent(), firstName.makeIdent())), make().Literal(0))); } private JCExpression unitStep(Naming.SyntheticName by, JCExpression withoutBy, JCExpression withBy) { if (by == null) { return withoutBy; } else { return make().Conditional(make().Binary(JCTree.EQ, by.makeIdent(), make().Literal(1)), withoutBy, withBy); } } @Override protected JCExpression makeIncreasingIncrement() { // long incr = increasing ? by : -by; return stepName.makeIdent(); } @Override protected JCExpression makeDecreasingIncrement() { // long incr = increasing ? by : -by; return make().Unary(JCTree.NEG, stepName.makeIdent()); } @Override protected JCExpression makeLoopCondition(SyntheticName varname) { JCExpression cond = unitStep(stepName, super.makeLoopCondition(varname), at(span).Conditional(increasingName.makeIdent(), make().Binary(JCTree.AND, make().Binary(JCTree.LE, make().Apply(null, naming.makeSelect(makeType(true), "offsetSign"), List.<JCExpression>of(varname.makeIdent(), lastName.makeIdent())), makeZero()), make().Binary(JCTree.GE, make().Apply(null, naming.makeSelect(makeType(true), "offsetSign"), List.<JCExpression>of(varname.makeIdent(), firstName.makeIdent())), makeZero()) ), make().Binary(JCTree.AND, make().Binary(JCTree.GE, make().Apply(null, naming.makeSelect(makeType(true), "offsetSign"), List.<JCExpression>of(varname.makeIdent(), lastName.makeIdent())), makeZero()), make().Binary(JCTree.LE, make().Apply(null, naming.makeSelect(makeType(true), "offsetSign"), List.<JCExpression>of(varname.makeIdent(), firstName.makeIdent())), makeZero()) ) ) ); return cond; } @Override protected JCExpression makeIncrementElement() { JCExpression stepExpr = unitStep(stepName, super.makeIncrementElement(), make().Assign(elementName.makeIdent(), at(step).Apply(null, naming.makeSelect(makeType(true), "neighbour"), List.<JCExpression>of(elementName.makeIdent(), incrementName.makeIdent())))); return stepExpr; } } /** * <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 Type 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, Type 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()) { JCExpression valueExpr = makeDefaultExprForType(type); JCExpression typeExpr; if (value.getUnboxed() == false) { typeExpr = makeJavaType(type, AbstractTransformer.JT_NO_PRIMITIVES); valueExpr = boxType(valueExpr, type); } else { typeExpr = makeJavaType(type); } return make().VarDef( make().Modifiers(modifiers & ~FINAL, annots), outerAlias.asName(), typeExpr, valueExpr); } } /** * 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, (value.getUnboxed() == false) ? AbstractTransformer.JT_NO_PRIMITIVES : 0), 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())); Type 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); if (CodegenUtil.getBoxingStrategy(decl.getDeclarationModel()) == BoxingStrategy.BOXED && canUnbox(t)) { initialValue = boxType(initialValue, t); } } List<JCAnnotation> annots = List.<JCAnnotation>nil(); int modifiers = transformLocalFieldDeclFlags(decl); JCExpression typeExpr = makeJavaType(decl.getDeclarationModel(), t, modifiers); result.append(at(decl.getIdentifier()).VarDef(at(decl.getIdentifier()).Modifiers(modifiers, annots), attrName, typeExpr, initialValue)); JCStatement outerSubs = openOuterSubstitutionIfNeeded( decl.getDeclarationModel(), t, annots, modifiers); if (outerSubs != null) { result.append(outerSubs); } } return result.toList(); } JCStatement openOuterSubstitutionIfNeeded( Value value, Type 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; return defaultBreakTransformer.transform(stmt); } class DefaultBreakTransformer implements Transformer<List<JCStatement>, Tree.Break> { @Override public List<JCStatement> transform(Break stmt) { // 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 continueTransformer.transform(stmt); } class DefaultContinueTransformer implements Transformer<JCStatement, Tree.Continue> { @Override public JCStatement transform(Continue stmt) { return at(stmt).Continue(getLabel(stmt)); } } Transformer<JCStatement, Tree.Return> returnTransformer(Transformer<JCStatement, Tree.Return> tx) { Transformer<JCStatement, Tree.Return> result = this.returnTransformer; this.returnTransformer = tx; return result; } JCStatement transform(Tree.Return ret) { return returnTransformer.transform(ret); } class ConstructorReturnTransformer implements Transformer<JCStatement, Tree.Return> { private final Name label; public ConstructorReturnTransformer(Name label) { this.label = label; } public JCStatement transform(Tree.Return ret) { // Remove the inner substitutions for any deferred values specified // in the control block closeInnerSubstituionsForSpecifiedValues(currentForClause); at(ret); return at(ret).Break(label); } } class DefaultReturnTransformer implements Transformer<JCStatement, Tree.Return> { public 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 Function 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 Type 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().getDestroyableType().isSupertypeOf(resExpr.getTypeModel()); Type resVarType = resExpr.getTypeModel(); Type resVarExpectedType = isDestroyable ? typeFact().getDestroyableType() : typeFact().getObtainableType(); // 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().getThrowableType(), 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().getThrowableType(), 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().getThrowableType(), 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) { Type 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(); Type 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(Type exceptionType) { if (exceptionType.isTypeAlias()) { return isOrContainsError(exceptionType.resolveAliases()); } else if (exceptionType.isUnion()) { for (Type t : exceptionType.getCaseTypes()) { if (isOrContainsError(t)) { return true; } } return false; } else if (exceptionType.isIntersection()) { for (Type t : exceptionType.getSatisfiedTypes()) { if (isOrContainsError(t)) { return true; } } return false; } else { TypeDeclaration declaration = exceptionType.getDeclaration(); 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) { Type 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(); Type 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, supertype), 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 Type intersectionOfCatchClauseTypes( java.util.List<Tree.CatchClause> catchClauses) { Type result = typeFact().getNothingType(); for (Tree.CatchClause catchClause : catchClauses) { Type pt = catchClause.getCatchVariable().getVariable().getType().getTypeModel(); result = ModelUtil.unionType(result, exceptionSupertype(pt), typeFact()); } if (typeFact().isUnion(result)) { return result.getSupertype(typeFact().getThrowableDeclaration()); } return result; } private Type exceptionSupertype(Type pt) { Type result = typeFact().getNothingType(); if (typeFact().isUnion(pt)) { for (Type t : pt.getCaseTypes()) { result = ModelUtil.unionType(result, exceptionSupertype(t), typeFact()); } } else if (typeFact().isIntersection(pt)) { for (Type t : pt.getSatisfiedTypes()) { if (t.isSubtypeOf(typeFact().getThrowableType())) { result = ModelUtil.unionType(result, exceptionSupertype(t), typeFact()); } } } else if (pt.isSubtypeOf(typeFact().getThrowableType())) { 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; } protected JCBlock transformElseClauseBlock(Tree.ElseClause elseClause, String tmpVar, Tree.Term outerExpression, Type expectedType){ return make().Block(0, transformElseClause(elseClause, tmpVar, outerExpression, expectedType)); } protected List<JCStatement> transformElseClause(Tree.ElseClause elseClause, String tmpVar, Tree.Term outerExpression, Type expectedType){ if(elseClause.getBlock() != null) return transformBlock(elseClause.getBlock()); if(elseClause.getExpression() != null) return evaluateAndAssign(tmpVar, elseClause.getExpression(), outerExpression, expectedType); return List.<JCStatement>of(make().Exec(makeErroneous(elseClause, "Only block or expression allowed"))); } protected JCBlock transformCaseClauseBlock(Tree.CaseClause caseClause, String tmpVar, Tree.Term outerExpression, Type expectedType){ return make().Block(0, transformCaseClause(caseClause, tmpVar, outerExpression, expectedType)); } protected List<JCStatement> transformCaseClause(Tree.CaseClause caseClause, String tmpVar, Tree.Term outerExpression, Type expectedType){ int p = unblock(); List<JCStatement> result; if(caseClause.getBlock() != null) result = transformBlock(caseClause.getBlock()); else if(caseClause.getExpression() != null) result = evaluateAndAssign(tmpVar, caseClause.getExpression(), outerExpression, expectedType); else result = List.<JCStatement>of(make().Exec(makeErroneous(caseClause, "Only block or expression allowed"))); _at(p); block(); return result; } protected Type switchExpressionType(Tree.SwitchClause switchClause) { Switched sw = switchClause.getSwitched(); if (sw.getExpression() != null) { return sw.getExpression().getTypeModel(); } else if (sw.getVariable() != null) { return sw.getVariable().getType().getTypeModel(); } throw new BugException("Switch should have expression or variable"); } protected Boolean switchExpressionUnboxed(Tree.SwitchClause switchClause) { Switched sw = switchClause.getSwitched(); if (sw.getExpression() != null) { return sw.getExpression().getUnboxed(); } else if (sw.getVariable() != null) { return sw.getVariable().getDeclarationModel().getUnboxed(); } throw new BugException("Switch should have expression or variable"); } abstract class SwitchTransformation { public SwitchTransformation() { } protected boolean hasVariable(Tree.SwitchClause switchClause) { return switchClause.getSwitched().getVariable() != null; } protected Expression getSwitchExpression(Tree.SwitchClause switchClause) { Switched sw = switchClause.getSwitched(); if (sw.getExpression() != null) { return sw.getExpression(); } else if (sw.getVariable() != null) { return sw.getVariable().getSpecifierExpression().getExpression(); } throw new BugException("Switch should have expression or variable"); } protected Type getSwitchExpressionType(Tree.SwitchClause switchClause) { return switchExpressionType(switchClause); } protected Boolean getSwitchExpressionUnboxed(Tree.SwitchClause switchClause) { return switchExpressionUnboxed(switchClause); } protected Type getDefiniteSwitchExpressionType(Tree.SwitchClause switchClause) { return typeFact().getDefiniteType(getSwitchExpressionType(switchClause)); } protected java.util.List<CaseClause> getCaseClauses(Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList) { return caseList.getCaseClauses(); } protected JCStatement transformElse(Naming.SyntheticName selectorAlias, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType, boolean primitiveSelector) { Tree.ElseClause elseClause = caseList.getElseClause(); if (elseClause != null) { if (elseClause.getVariable() != null && selectorAlias != null) { at(elseClause); // Use the type of the variable, which is more precise than the type we test for. Type varType = elseClause.getVariable().getDeclarationModel().getType(); String name = elseClause.getVariable().getIdentifier().getText(); TypedDeclaration varDecl = elseClause.getVariable().getDeclarationModel(); Naming.SyntheticName tmpVarName = selectorAlias; Name substVarName; List<JCStatement> stats; if(primitiveSelector){ substVarName = tmpVarName.asName(); stats = List.<JCStatement> nil(); }else{ 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(elseClause).TypeCast(rawToTypeExpr, tmpVarName.makeIdent()); JCExpression toTypeExpr; if (isCeylonBasicType(varType) && BooleanUtil.isTrue(varDecl.getUnboxed())) { toTypeExpr = makeJavaType(varType); tmpVarExpr = unboxType(tmpVarExpr, varType); } else { toTypeExpr = makeJavaType(varType, JT_NO_PRIMITIVES); if (BooleanUtil.isTrue(varDecl.getUnboxed())) { tmpVarExpr = boxType(tmpVarExpr, varType); } else if (varDecl.getOriginalDeclaration() != null && BooleanUtil.isTrue(varDecl.getOriginalDeclaration().getUnboxed())) { tmpVarExpr = boxType(tmpVarName.makeIdent(), varType); } } // The variable holding the result for the code inside the code block JCVariableDecl decl2 = at(elseClause).VarDef(make().Modifiers(FINAL), substVarName, toTypeExpr, tmpVarExpr); stats = List.<JCStatement> of(decl2); } // Prepare for variable substitution in the following code block Substitution prevSubst = naming.addVariableSubst(varDecl, substVarName.toString()); stats = stats.appendList(transformElseClause(elseClause, tmpVar, outerExpression, expectedType)); JCBlock block = at(elseClause).Block(0, stats); // Deactivate the above variable substitution prevSubst.close(); return block; } else { return transformElseClauseBlock(elseClause, tmpVar, outerExpression, expectedType); } } 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. if(outerExpression != null){ // Actually this only works for statements. For expressions we're not allowed to throw, // we get a verifier error at runtime otherwise: https://github.com/ceylon/ceylon-compiler/issues/2276 List<JCStatement> stmts = List.<JCStatement>of(make().Exec(make().Assign( makeUnquotedIdent(tmpVar), expressionGen().applyErasureAndBoxing(makeDefaultExprForType(expectedType), expectedType, !canUnbox(expectedType), CodegenUtil.getBoxingStrategy(outerExpression), expectedType)))); stmts = stmts.prepend(make().Exec(utilInvocation().rethrow(makeNewEnumeratedTypeError(exhasutedExhaustiveSwitch)))); return make().Block(0L, stmts); }else{ return makeThrowEnumeratedTypeError(); } } } protected JCStatement makeThrowEnumeratedTypeError() { return make().Throw(makeNewEnumeratedTypeError(exhasutedExhaustiveSwitch)); } String exhasutedExhaustiveSwitch = "Supposedly exhaustive switch was not exhaustive"; public abstract JCStatement transformSwitch(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType); protected boolean isDefinitelyReturns(CaseClause caseClause) { if(caseClause.getBlock() != null) return caseClause.getBlock().getDefinitelyReturns(); // expressions don't return return false; } } JCExpression makeNewEnumeratedTypeError(String msg) { return make().NewClass(null, List.<JCExpression>nil(), makeIdent(syms().ceylonEnumeratedTypeErrorType), List.<JCExpression>of(make().Literal( msg)), null); } /** * 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() { } @Override public JCStatement transformSwitch(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType) { JCExpression switchExpr = expressionGen().transformExpression( getSwitchExpression(switchClause), BoxingStrategy.UNBOXED, getSwitchExpressionType(switchClause)); JCVariableDecl selector; if (hasVariable(switchClause)) { String name = switchClause.getSwitched().getVariable().getIdentifier().getText(); selector = makeVar(name, makeJavaType(getSwitchExpressionType(switchClause)), switchExpr); JCStatement sw = transformSwitch(switchClause, caseList, tmpVar, outerExpression, expectedType, naming.makeQuotedIdent(name)); return at(node).Block(0, List.of(selector, sw)); } else { return transformSwitch(switchClause, caseList, tmpVar, outerExpression, expectedType, switchExpr); } } JCStatement transformSwitch(Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType, JCExpression switchExpr) { Name label = names().fromString("switch_" + gen().visitor.lv.getSwitchId(switchClause)); ListBuffer<JCCase> cases = ListBuffer.<JCCase>lb(); for (Tree.CaseClause caseClause : getCaseClauses(switchClause, caseList)) { 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 = transformCaseClauseBlock(caseClause, tmpVar, outerExpression, expectedType); List<JCStatement> stmts = List.<JCStatement>nil(); if (!isDefinitelyReturns(caseClause)) { stmts = stmts.prepend(make().Break(label)); } stmts = stmts.prepend(block); cases.add(make().Case(transformCaseExpr(term), stmts)); } Naming.SyntheticName elseSelectorAlias = null; if(caseList.getElseClause() != null && caseList.getElseClause().getVariable() != null){ Value elseVar = caseList.getElseClause().getVariable().getDeclarationModel(); if (hasVariable(switchClause)) { Type switchVarType = switchClause.getSwitched().getVariable().getDeclarationModel().getType(); Type elseVarType = elseVar.getType(); if(!elseVarType.isExactly(switchVarType)){ elseSelectorAlias = naming.synthetic(elseVar); } } else if (CodegenUtil.getBoxingStrategy(elseVar) != CodegenUtil.getBoxingStrategy(elseVar.getOriginalDeclaration())) { elseSelectorAlias = naming.synthetic(elseVar); } } cases.add(make().Case(null, List.of(transformElse(elseSelectorAlias, caseList, tmpVar, outerExpression, expectedType, false)))); 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(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType) { Type switchExpressionType = getSwitchExpressionType(switchClause); Type switchDefiniteExpressionType = getDefiniteSwitchExpressionType(switchClause); JCExpression selectorExpr = expressionGen().transformExpression(getSwitchExpression(switchClause), BoxingStrategy.BOXED, getSwitchExpressionType(switchClause)); JCVariableDecl selector; JCIdent ident; if (hasVariable(switchClause)) { String name = switchClause.getSwitched().getVariable().getIdentifier().getText(); selector = makeVar(name, makeJavaType(switchExpressionType), selectorExpr); ident = naming.makeQuotedIdent(name); } else { Naming.SyntheticName selectorAlias = naming.alias("sel"); selector = makeVar(selectorAlias, makeJavaType(switchExpressionType), selectorExpr); ident = selectorAlias.makeIdent(); } // Make a switch out of the non-null cases JCStatement switch_ = new Switch().transformSwitch(switchClause, caseList, tmpVar, outerExpression, expectedType, expressionGen().applyErasureAndBoxing(ident, switchDefiniteExpressionType, true, BoxingStrategy.UNBOXED, switchDefiniteExpressionType)); // Now wrap it with a null test JCIf ifElse = null; for (Tree.CaseClause caseClause : getCaseClauses(switchClause, caseList)) { Tree.Term term = getSingletonNullCase(caseClause); if (term != null) { ifElse = make().If(make().Binary(JCTree.EQ, ident, makeNull()), transformCaseClauseBlock(caseClause, tmpVar, outerExpression, expectedType), make().Block(0, List.<JCStatement>of(switch_))); break; } } return at(node).Block(0, List.of(selector, ifElse)); } } /** * The default switch transformation which produces a * {@code if/else if} chain. */ class IfElseChain extends SwitchTransformation { @Override protected java.util.List<Tree.CaseClause> getCaseClauses(Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList) { // 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(switchClause, caseList); 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$); // if one of the match expressions is null that // test must be first to avoid NPE. Tree.CaseClause containsNull = null; outer: 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(switchClause)); } else if (item instanceof Tree.MatchCase) { // will be primitive equality test for (Tree.Expression expr : ((Tree.MatchCase) item).getExpressionList().getExpressions()) { if (isNull(expr.getTypeModel())) { containsNull = clause; continue outer; } } 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; } } if (containsNull != null) { cheap.add(0, containsNull); } return cheap; } @Override public JCStatement transformSwitch(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType) { Naming.SyntheticName selectorAlias = naming.alias("sel"); JCStatement last; final BoxingStrategy bs; final JCExpression selectorType; Type switchExpressionType = getSwitchExpressionType(switchClause); Boolean switchUnboxed = getSwitchExpressionUnboxed(switchClause); boolean allMatches = isSwitchAllMatchCases(caseList); boolean primitiveSelector = allMatches && isCeylonBasicType(switchExpressionType) && BooleanUtil.isNotFalse(switchUnboxed); if (primitiveSelector) { bs = BoxingStrategy.UNBOXED; selectorType = makeJavaType(switchExpressionType); } else { bs = BoxingStrategy.BOXED; selectorType = makeJavaType(switchExpressionType, JT_NO_PRIMITIVES|JT_RAW); } last = transformElse(selectorAlias, caseList, tmpVar, outerExpression, expectedType, primitiveSelector); JCExpression selectorExpr = expressionGen().transformExpression(getSwitchExpression(switchClause), bs, switchExpressionType); JCVariableDecl selector = makeVar(selectorAlias, selectorType, selectorExpr); java.util.List<Tree.CaseClause> caseClauses = getCaseClauses(switchClause, caseList); 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, tmpVar, outerExpression, expectedType, (Tree.IsCase)caseItem, last, switchExpressionType); } 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, switchClause, caseClause, tmpVar,outerExpression, expectedType, (Tree.MatchCase)caseItem, last, switchExpressionType, primitiveSelector); } else { return make().Exec(makeErroneous(caseItem, "compiler bug: unknown switch case clause: "+caseItem)); } } return at(node).Block(0, List.of(selector, last)); } } private boolean isJavaSwitchableType(Type type, Boolean switchUnboxed) { return BooleanUtil.isNotFalse(switchUnboxed) && (type.isExactly(typeFact().getCharacterType()) || type.isExactly(typeFact().getStringType())) || 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) { return transform(stmt, stmt.getSwitchClause(), stmt.getSwitchCaseList(), null, null, null); } JCStatement transform(Node node, Tree.SwitchClause switchClause, Tree.SwitchCaseList caseList, String tmpVar, Tree.Term outerExpression, Type expectedType) { at(switchClause); block(); SwitchTransformation transformation = null; Type exprType = switchExpressionType(switchClause); Boolean switchUnboxed = switchExpressionUnboxed(switchClause); // Are we switching with just String literal or Character literal match cases? if (isJavaSwitchableType(exprType, switchUnboxed)) { boolean canUseSwitch = true; caseStmts: for (Tree.CaseClause clause : caseList.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? Type definiteType = typeFact().getDefiniteType(exprType); if (isJavaSwitchableType(definiteType, switchUnboxed)) { boolean canUseIfElseSwitch = true; boolean hasSingletonNullCase = false; caseStmts: for (Tree.CaseClause clause : caseList.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(); } JCStatement result = transformation.transformSwitch(node, switchClause, caseList, tmpVar, outerExpression, expectedType); unblock(); return result; } private boolean isSwitchAllMatchCases(Tree.SwitchCaseList caseList) { for (Tree.CaseClause caseClause : caseList.getCaseClauses()) { if (!(caseClause.getCaseItem() instanceof Tree.MatchCase)) { return false; } } return true; } private JCStatement transformCaseMatch(Naming.SyntheticName selectorAlias, Tree.SwitchClause switchClause, Tree.CaseClause caseClause, String tmpVar, Tree.Term outerExpression, Type expectedType, Tree.MatchCase matchCase, JCStatement last, Type 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 if (isNull(term.getTypeModel())) { // ensure we do any null check as the first operation in the ||-ed expression tests = make().Binary(JCTree.OR, test, tests); } else { tests = make().Binary(JCTree.OR, tests, test); } } Substitution prevSubst = null; if (switchClause.getSwitched().getVariable() != null) { // Prepare for variable substitution in the following code block prevSubst = naming.addVariableSubst(switchClause.getSwitched().getVariable().getDeclarationModel(), selectorAlias.toString()); } JCBlock block = transformCaseClauseBlock(caseClause, tmpVar, outerExpression, expectedType); if (prevSubst != null) { // Deactivate the above variable substitution prevSubst.close(); } 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, String tmpVar, Tree.Term outerExpression, Type expectedType, Tree.IsCase isCase, JCStatement last, Type expressionType) { at(isCase); // Use the type of the variable, which is more precise than the type we test for. Type varType = isCase.getVariable().getDeclarationModel().getType(); Type 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(); TypedDeclaration varDecl = isCase.getVariable().getDeclarationModel(); 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 (isCeylonBasicType(varType) && varDecl.getUnboxed() == true) { 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(varDecl , substVarName.toString()); List<JCStatement> stats = List.<JCStatement> of(decl2); stats = stats.appendList(transformCaseClause(caseClause, tmpVar, outerExpression, expectedType)); 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()); } public List<JCVariableDecl> transformVariableOrDestructure(Tree.Statement varOrDes) { List<JCVariableDecl> vars = List.<JCVariableDecl>nil(); if (varOrDes instanceof Tree.Variable) { Tree.Variable var = (Tree.Variable)varOrDes; Expression expr = var.getSpecifierExpression().getExpression(); BoxingStrategy boxingStrategy = CodegenUtil.getBoxingStrategy(var.getDeclarationModel()); JCExpression init = expressionGen().transformExpression(expr, boxingStrategy, var.getType().getTypeModel()); vars = vars.append(transformVariable(var, init, expr.getTypeModel(), boxingStrategy == BoxingStrategy.BOXED).build()); } else if (varOrDes instanceof Tree.Destructure) { Tree.Destructure des = (Tree.Destructure)varOrDes; vars = vars.appendList(transform(des)); } else { throw BugException.unhandledCase(varOrDes); } return vars; } /** * Transforms a Ceylon destructuring assignment to Java code. * @param stmt The Ceylon destructure * @return The Java tree */ List<JCVariableDecl> transform(Tree.Destructure stmt) { List<JCVariableDecl> result = List.nil(); // Create temp var to hold result of expression Tree.Pattern pat = stmt.getPattern(); Naming.SyntheticName tmpVarName = naming.synthetic(pat); Expression destExpr = stmt.getSpecifierExpression().getExpression(); JCExpression typeExpr = makeJavaType(destExpr.getTypeModel()); JCExpression expr = expressionGen().transformExpression(destExpr); at(stmt); JCVariableDecl tmpVar = makeVar(Flags.FINAL, tmpVarName, typeExpr, expr); result = result.append(tmpVar); // Now add the destructured variables List<JCVariableDecl> vars = VarDefBuilder.buildAll(transformPattern(pat, tmpVarName.makeIdent())); result = result.appendList(vars); return result; } static class VarDefBuilder { private final ExpressionTransformer gen; private final Variable var; private final JCExpression initExpr; private SyntheticName name; private boolean built; public VarDefBuilder(ExpressionTransformer gen, Variable var, JCExpression initExpr) { this.gen = gen; this.var = var; this.initExpr = initExpr; } private Type model() { return var.getType().getTypeModel(); } private SyntheticName name() { if (name == null) { name = gen.naming.substituted(var.getDeclarationModel()).capture(); } return name; } private JCExpression type() { BoxingStrategy boxingStrategy = CodegenUtil.getBoxingStrategy(var.getDeclarationModel()); return gen.makeJavaType(model(), (boxingStrategy == BoxingStrategy.BOXED) ? JT_NO_PRIMITIVES : 0); } private JCExpression expr() { if (built) { throw new BugException(var, "Variable expression can only be used once"); } return initExpr; } Substitution alias() { return gen.naming.substituteAlias(var.getDeclarationModel()); } JCVariableDecl buildInternal() { gen.at(var); JCVariableDecl def = gen.makeVar(Flags.FINAL, name(), type(), expr()); return def; } JCVariableDecl build() { JCVariableDecl def = buildInternal(); built = true; return def; } JCVariableDecl buildDefOnly() { gen.at(var); JCVariableDecl def = gen.makeVar(Flags.FINAL, name(), type(), null); return def; } JCVariableDecl buildField() { gen.at(var); JCVariableDecl def = gen.makeVar(Flags.PRIVATE, name(), type(), null); return def; } JCVariableDecl buildFromField() { gen.at(var); JCVariableDecl def = gen.makeVar(Flags.FINAL, name(), type(), name().makeIdentWithThis()); return def; } JCAssign buildAssign() { gen.at(var); JCAssign def = gen.make().Assign(name().makeIdent(), expr()); built = true; return def; } JCAssign buildDefaultAssign() { gen.at(var); JCAssign def = gen.make().Assign(name().makeIdent(), gen.makeDefaultExprForType(model())); return def; } static List<JCVariableDecl> buildAll(List<VarDefBuilder> vars) { List<JCVariableDecl> result = List.nil(); for (VarDefBuilder vdb : vars) { result = result.append(vdb.build()); } return result; } @Override public String toString() { return "VarDefBuilder [build()=" + buildInternal() + "]"; } } List<VarDefBuilder> transformDestructure(Tree.Statement stmt, JCExpression varAccessExpr, Type exprType, boolean exprBoxed) { List<VarDefBuilder> result = List.nil(); if (stmt instanceof Tree.Variable) { result = result.append(transformVariable((Tree.Variable)stmt, varAccessExpr, exprType, exprBoxed)); } else if (stmt instanceof Tree.Destructure) { result = result.appendList(transformPattern(((Tree.Destructure)stmt).getPattern(), varAccessExpr)); } else { throw BugException.unhandledCase(stmt); } return result; } List<VarDefBuilder> transformPattern(Tree.Pattern pat, JCExpression varAccessExpr) { List<VarDefBuilder> result = List.nil(); if (pat instanceof Tree.TuplePattern) { // For a Tuple we get the value of each of its items and assign it to a local value int idx = 0; Tree.TuplePattern tuple = (Tree.TuplePattern)pat; for (Tree.Pattern p : tuple.getPatterns()) { JCExpression idxExpr = makeInteger(idx++); Type ot = typeFact().getObjectType(); JCExpression fullGetExpr; if (varAccessExpr != null) { JCExpression seqVarAccessExpr = make().TypeCast(makeJavaType(typeFact().getSequenceType(ot), JT_RAW), varAccessExpr); JCExpression tupleAccessExpr; if (isVariadicVariable(p)) { tupleAccessExpr = makeQualIdent(seqVarAccessExpr, "skip"); } else { tupleAccessExpr = makeQualIdent(seqVarAccessExpr, "getFromFirst"); } fullGetExpr = make().Apply(null, tupleAccessExpr, List.of(idxExpr)); if (isVariadicVariable(p)) { fullGetExpr = make().Apply(null, makeQualIdent(fullGetExpr, "sequence"), List.<JCExpression>nil()); } } else { fullGetExpr = null; } result = result.appendList(transformPattern(p, fullGetExpr)); } } else if (pat instanceof Tree.KeyValuePattern) { // For an Entry we create two local values, one for the key and one for the value Tree.KeyValuePattern entry = (Tree.KeyValuePattern)pat; Type ot = typeFact().getObjectType(); JCExpression getItemExpr; if (varAccessExpr != null) { JCExpression entryVarAccessExpr = make().TypeCast(makeJavaType(typeFact().getEntryType(ot , ot), JT_RAW), varAccessExpr); JCExpression getKeyExpr = make().Apply(null, makeQualIdent(entryVarAccessExpr, "getKey"), List.<JCExpression>nil()); result = result.appendList(transformPattern(entry.getKey(), getKeyExpr)); getItemExpr = make().Apply(null, makeQualIdent(entryVarAccessExpr, "getItem"), List.<JCExpression>nil()); } else { getItemExpr = null; } result = result.appendList(transformPattern(entry.getValue(), getItemExpr)); } else if (pat instanceof Tree.VariablePattern) { Tree.VariablePattern var = (Tree.VariablePattern)pat; result = result.append(transformVariable(var.getVariable(), varAccessExpr)); } else { throw BugException.unhandledCase(pat); } return result; } private boolean isVariadicVariable(Tree.Pattern pat) { if (pat instanceof Tree.VariablePattern) { Tree.VariablePattern var = (Tree.VariablePattern)pat; return var.getVariable().getType() instanceof Tree.SequencedType; } return false; } VarDefBuilder transformVariable(Variable var, JCExpression initExpr) { return transformVariable(var, initExpr, typeFact().getObjectType(), true); } VarDefBuilder transformVariable(Variable var, JCExpression initExpr, Type exprType, boolean exprBoxed) { BoxingStrategy boxingStrategy = CodegenUtil.getBoxingStrategy(var.getDeclarationModel()); JCExpression expr = initExpr; if (expr != null) { Type type; if (var.getType().getTypeModel().getDeclaration().isAnonymous()) { type = var.getType().getTypeModel(); } else { type = simplifyType(typeFact().denotableType(var.getType().getTypeModel())); } expr = expressionGen().applyErasureAndBoxing( expr, exprType, false, exprBoxed, boxingStrategy, type, ExpressionTransformer.EXPR_DOWN_CAST); } return new VarDefBuilder(expressionGen(), var, expr); } Expression getDestructureExpression(Tree.Statement varOrDes) { Tree.SpecifierExpression specExpr; if (varOrDes instanceof Tree.Variable) { specExpr = ((Tree.Variable)varOrDes).getSpecifierExpression(); } else if (varOrDes instanceof Tree.Destructure) { specExpr = ((Tree.Destructure)varOrDes).getSpecifierExpression(); } else { throw BugException.unhandledCase(varOrDes); } return (specExpr != null) ? specExpr.getExpression() : null; } public JCTree transform(CustomTree.GuardedVariable that) { BoxingStrategy boxingStrategy = CodegenUtil.getBoxingStrategy(that.getDeclarationModel()); Tree.Expression expr = that.getSpecifierExpression().getExpression(); Type fromType = expr.getTypeModel(); Value newValue = that.getDeclarationModel(); Type toType = newValue.getType(); Tree.ConditionList conditionList = that.getConditionList(); Tree.Condition condition = conditionList.getConditions().get(0); JCExpression val = expressionGen().transformExpression(expr); at(that); if(condition instanceof Tree.IsCondition){ if(!willEraseToObject(toType)){ // 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 val = make().TypeCast(rawToTypeExpr, val); if (CodegenUtil.isUnBoxed(newValue) && canUnbox(toType)) { val = unboxType(val, toType); } } }else if(condition instanceof Tree.ExistsCondition){ Type exprType = fromType; if (isOptional(exprType)) { exprType = typeFact().getDefiniteType(exprType); } val = expressionGen().applyErasureAndBoxing(val, exprType, CodegenUtil.hasTypeErased(expr), true, CodegenUtil.hasUntrustedType(expr), boxingStrategy, toType, 0); }else if(condition instanceof Tree.NonemptyCondition){ Type exprType = fromType; if (isOptional(exprType)) { exprType = typeFact().getDefiniteType(exprType); } val = expressionGen().applyErasureAndBoxing(val, exprType, false, true, BoxingStrategy.BOXED, toType, ExpressionTransformer.EXPR_DOWN_CAST); } SyntheticName alias = naming.alias(that.getIdentifier().getText()); Substitution subst = naming.addVariableSubst(newValue, alias.getName()); // FIXME: this is rubbish, but the same rubbish from assert. it's most likely wrong there too Scope scope = that.getScope().getScope(); while (scope instanceof ConditionScope) { scope = scope.getScope(); } subst.scopeClose(scope); JCExpression varType = makeJavaType(toType); return make().VarDef(make().Modifiers(FINAL), alias.asName(), varType, val); } }