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

Source Code of com.redhat.ceylon.compiler.java.codegen.ExpressionTransformer

/*
* 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.redhat.ceylon.compiler.typechecker.tree.Util.hasUncheckedNulls;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

import com.redhat.ceylon.compiler.java.codegen.Invocation.TransformedInvocationPrimary;
import com.redhat.ceylon.compiler.java.codegen.Naming.DeclNameFlag;
import com.redhat.ceylon.compiler.java.codegen.Naming.Prefix;
import com.redhat.ceylon.compiler.java.codegen.Naming.Substitution;
import com.redhat.ceylon.compiler.java.codegen.Naming.Suffix;
import com.redhat.ceylon.compiler.java.codegen.Naming.SyntheticName;
import com.redhat.ceylon.compiler.java.codegen.Operators.AssignmentOperatorTranslation;
import com.redhat.ceylon.compiler.java.codegen.Operators.OperatorTranslation;
import com.redhat.ceylon.compiler.java.codegen.Operators.OptimisationStrategy;
import com.redhat.ceylon.compiler.java.codegen.StatementTransformer.Cond;
import com.redhat.ceylon.compiler.java.codegen.StatementTransformer.CondList;
import com.redhat.ceylon.compiler.java.codegen.recovery.HasErrorException;
import com.redhat.ceylon.compiler.loader.model.FieldValue;
import com.redhat.ceylon.compiler.typechecker.analyzer.Util;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.Generic;
import com.redhat.ceylon.compiler.typechecker.model.Interface;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.NothingType;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ParameterList;
import com.redhat.ceylon.compiler.typechecker.model.ProducedReference;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.ProducedTypedReference;
import com.redhat.ceylon.compiler.typechecker.model.Referenceable;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Expression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ForComprehensionClause;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.InitialComprehensionClause;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.SequenceEnumeration;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Convert;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;

/**
* This transformer deals with expressions only
*/
public class ExpressionTransformer extends AbstractTransformer {

    // flags for transformExpression
    /**
     * This implies inclusion of the JT_SATISFIES flags when
     * constructing the type for a variance typecast.
     */
    public static final int EXPR_FOR_COMPANION = 1;
    /**
     * The expected type has type parameters
     * (so an extra typecast to the raw type will be required)
     */
    public static final int EXPR_EXPECTED_TYPE_NOT_RAW = 1 << 1;
    /**
     * The expected type has type parameters with {@code satisfies}
     * constraints (which may be erased, and thus a type cast may be required
     * irrespective of the presence of type arguments)
     */
    public static final int EXPR_EXPECTED_TYPE_HAS_CONSTRAINED_TYPE_PARAMETERS = 1 << 2;
    /**
     * Seems to be used when the expected and expression
     * types have no supertype in common.
     */
    public static final int EXPR_DOWN_CAST = 1 << 3;
    /**
     * Use this when the expression being passed contains code to check for nulls coming from Java
     */
    public static final int EXPR_HAS_NULL_CHECK_FENCE = 1 << 4;
    /**
     * This implies inclusion of the JT_COMPANION flags when
     * constructing the type casts.
     */
    public static final int EXPR_WANTS_COMPANION = 1 << 5;

    /**
     * The expected type has type parameters with {@code satisfies}
     * constraints which have a covariant type parameter that is used by other
     * type parameter bounds, so we will always generate types where it is fixed rather
     * than using wildcards and in some cases we need to cast to raw
     */
    public static final int EXPR_EXPECTED_TYPE_HAS_DEPENDENT_COVARIANT_TYPE_PARAMETERS = 1 << 6;

   
    /**
     * Usually if a {@code long} to {@code int}, {@code short} or {@code byte}
     * conversion is required we use a Util invocation so that a runtime check
     * is performed.
     *
     * In some circumstances (e.g. annotations) we need to use a
     * real typecast.
     */
    public static final int EXPR_UNSAFE_PRIMITIVE_TYPECAST_OK = 1 << 7;

    /**
     * Use this when the expression is to be passed to a Java field/setter/parameter which does not require null-safety
     */
    public static final int EXPR_TARGET_ACCEPTS_NULL = 1 << 8;

    static{
        // only there to make sure this class is initialised before the enums defined in it, otherwise we
        // get an initialisation error
        Operators.init();
    }
   
    private boolean inStatement = false;
    private boolean withinInvocation = false;
    private boolean withinSyntheticClassBody = false;
    /** The transformation for spread method references involves nested invocations of
     * {@link #transformSpreadOperator(com.redhat.ceylon.compiler.typechecker.tree.Tree.QualifiedMemberExpression, TermTransformer)}
     * and what we generate depends on the invocation. This field will be odd on
     * the "outer" invocation (which generates the outer part of the tree)
     * and even on the "inner" invocation (which generates the inner part of the tree)
     */
    private Tree.QualifiedMemberExpression spreading = null;
    private Naming.SyntheticName memberPrimary = null;
    private ClassOrInterface withinSuperInvocation = null;
    private ClassOrInterface withinDefaultParameterExpression = null;
   
    public static ExpressionTransformer getInstance(Context context) {
        ExpressionTransformer trans = context.get(ExpressionTransformer.class);
        if (trans == null) {
            trans = new ExpressionTransformer(context);
            context.put(ExpressionTransformer.class, trans);
        }
        return trans;
    }

  private ExpressionTransformer(Context context) {
        super(context);
    }

    // Statement expressions
   
    public JCStatement transform(Tree.ExpressionStatement tree) {
        // ExpressionStatements do not return any value, therefore we don't care about the type of the expressions.
        inStatement = true;
        JCStatement result;
        HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(tree.getExpression());
        if (error != null) {
            result = this.makeThrowUnresolvedCompilationError(error);
        } else {
            result = at(tree).Exec(transformExpression(tree.getExpression(), BoxingStrategy.INDIFFERENT, null));
        }
        inStatement = false;
        return result;
    }
   
    public JCStatement transform(Tree.SpecifierStatement op) {
        // SpecifierStatement do not return any value, therefore we don't care about the type of the expressions.
        inStatement = true;
        JCStatement  result;
        HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(op.getBaseMemberExpression());
        if (error != null) {
            result = this.makeThrowUnresolvedCompilationError(error);
        } else if ((error = errors().getFirstExpressionErrorAndMarkBrokenness(op.getSpecifierExpression().getExpression())) != null) {
            result = this.makeThrowUnresolvedCompilationError(error);
        } else {
            result = at(op).Exec(transformAssignment(op, op.getBaseMemberExpression(), op.getSpecifierExpression().getExpression()));
        }
        inStatement = false;
        return result;
    }
   
    public JCExpression transform(Tree.SpecifierOrInitializerExpression expr,
            BoxingStrategy boxing, ProducedType expectedType) {
        return transformExpression(expr.getExpression(), boxing, expectedType);
    }
   
    //
    // Any sort of expression
   
    JCExpression transformExpression(final TypedDeclaration declaration, final Tree.Term expr) {
        // make sure we use the best declaration for boxing and type
        ProducedTypedReference typedRef = getTypedReference(declaration);
        ProducedTypedReference nonWideningTypedRef = nonWideningTypeDecl(typedRef);
        ProducedType nonWideningType = nonWideningType(typedRef, nonWideningTypedRef);
        // If this is a return statement in a MPL method we want to know
        // the non-widening type of the innermost callable
        if (declaration instanceof Functional
                && Decl.isMpl((Functional)declaration)) {
            for (int i = ((Functional)declaration).getParameterLists().size(); i > 1; i--) {
                nonWideningType = getReturnTypeOfCallable(nonWideningType);
            }
        }
        // respect the refining definition of optionality
        nonWideningType = propagateOptionality(declaration.getType(), nonWideningType);
        BoxingStrategy boxing = CodegenUtil.getBoxingStrategy(nonWideningTypedRef.getDeclaration());
        return transformExpression(expr, boxing, nonWideningType);
    }

    private ProducedType propagateOptionality(ProducedType type, ProducedType nonWideningType) {
        if(!isNull(type)){
            if(isOptional(type)){
                if(!isOptional(nonWideningType)){
                    return typeFact().getOptionalType(nonWideningType);
                }
            }else{
                if(isOptional(nonWideningType)){
                    return typeFact().getDefiniteType(nonWideningType);
                }
            }
        }
        return nonWideningType;
    }
   
    JCExpression transformExpression(final Tree.Term expr) {
        return transformExpression(expr, BoxingStrategy.BOXED, expr.getTypeModel());
    }

    JCExpression transformExpression(final Tree.Term expr, BoxingStrategy boxingStrategy, ProducedType expectedType) {
        return transformExpression(expr, boxingStrategy, expectedType, 0);
    }
   
    JCExpression transformExpression(final Tree.Term expr, BoxingStrategy boxingStrategy,
            ProducedType expectedType, int flags) {
        if (expr == null) {
            return null;
        }
       
        at(expr);
        if (inStatement && boxingStrategy != BoxingStrategy.INDIFFERENT) {
            // We're not directly inside the ExpressionStatement anymore
            inStatement = false;
        }
       
        // Cope with things like ((expr))
        // FIXME: shouldn't that be in the visitor?
        Tree.Term term = expr;
        while (term instanceof Tree.Expression) {
            term = ((Tree.Expression)term).getTerm();
        }
       
        JCExpression result;
        if(term instanceof Tree.SequenceEnumeration){
            // special case to be able to pass expected type to sequences
            result = transform((Tree.SequenceEnumeration)term, expectedType);
        }else if(term instanceof Tree.DefaultOp){
            // special case to be able to pass expected type to else op
            result = transform((Tree.DefaultOp)term, expectedType);
        }else{
            CeylonVisitor v = gen().visitor;
            final ListBuffer<JCTree> prevDefs = v.defs;
            final boolean prevInInitializer = v.inInitializer;
            final ClassDefinitionBuilder prevClassBuilder = v.classBuilder;
            try {
                v.defs = new ListBuffer<JCTree>();
                v.inInitializer = false;
                v.classBuilder = gen().current();
                term.visit(v);
                if (v.hasResult()) {
                    result = v.getSingleResult();
                    if (result == null) {
                        throw new BugException(term, "visitor yielded multiple results");
                    }
                } else {
                    throw new BugException(term, "visitor didn't yield any result");
                }
            } catch (BugException e) {
                result = e.makeErroneous(this, expr);
            } finally {
                v.classBuilder = prevClassBuilder;
                v.inInitializer = prevInInitializer;
                v.defs = prevDefs;
            }
        }

        if ((flags & EXPR_TARGET_ACCEPTS_NULL) == 0
                && expectedType != null
                && hasUncheckedNulls(expr)
                && expectedType.isSubtypeOf(typeFact().getObjectDeclaration().getType())) {
            result = utilInvocation().checkNull(result);
            flags |= EXPR_HAS_NULL_CHECK_FENCE;
        }
        result = applyErasureAndBoxing(result, expr, boxingStrategy, expectedType, flags);

        return result;
    }
   
    JCExpression transform(Tree.FunctionArgument functionArg, ProducedType expectedType) {
        Method model = functionArg.getDeclarationModel();
        List<JCStatement> body;
        boolean prevNoExpressionlessReturn = statementGen().noExpressionlessReturn;
        boolean prevSyntheticClassBody = expressionGen().withinSyntheticClassBody(true);
        try {
            statementGen().noExpressionlessReturn = isAnything(model.getType());
            if (functionArg.getBlock() != null) {
                body = statementGen().transformBlock(functionArg.getBlock());
                if (!functionArg.getBlock().getDefinitelyReturns()) {
                    if (isAnything(model.getType())) {
                        body = body.append(make().Return(makeNull()));
                    } else {
                        body = body.append(make().Return(makeErroneous(functionArg.getBlock(), "compiler bug: non-void method does not definitely return")));
                    }
                }
            } else {
                Tree.Expression expr = functionArg.getExpression();
                JCExpression transExpr = expressionGen().transformExpression(expr);
                JCReturn returnStat = make().Return(transExpr);
                body = List.<JCStatement>of(returnStat);
            }
        } finally {
            expressionGen().withinSyntheticClassBody(prevSyntheticClassBody);
            statementGen().noExpressionlessReturn = prevNoExpressionlessReturn;
        }

        ProducedType callableType = functionArg.getTypeModel();
        CallableBuilder callableBuilder = CallableBuilder.methodArgument(gen(),
                callableType,
                Collections.singletonList(functionArg.getParameterLists().get(0)),
                classGen().transformMplBody(functionArg.getParameterLists(), model, body));
       
        JCExpression result = callableBuilder.build();
        result = applyErasureAndBoxing(result, callableType, true, BoxingStrategy.BOXED, expectedType);
        return result;
    }
   
    //
    // Boxing and erasure of expressions
   
    private JCExpression applyErasureAndBoxing(JCExpression result, Tree.Term expr, BoxingStrategy boxingStrategy,
            ProducedType expectedType) {
        return applyErasureAndBoxing(result, expr, boxingStrategy, expectedType, 0);
    }
   
    private JCExpression applyErasureAndBoxing(JCExpression result, Tree.Term expr, BoxingStrategy boxingStrategy,
                ProducedType expectedType, int flags) {
        ProducedType exprType = expr.getTypeModel();
        if ((flags & EXPR_HAS_NULL_CHECK_FENCE) != 0) {
            exprType = getNonNullType(exprType);
        } else if (hasUncheckedNulls(expr) && !isOptional(exprType)) {
            exprType = typeFact().getOptionalType(exprType);
        }
        boolean exprBoxed = !CodegenUtil.isUnBoxed(expr);
        boolean exprErased = CodegenUtil.hasTypeErased(expr);
        boolean exprUntrustedType = CodegenUtil.hasUntrustedType(expr);
        return applyErasureAndBoxing(result, exprType, exprErased, exprBoxed, exprUntrustedType, boxingStrategy, expectedType, flags);
    }
   
    JCExpression applyErasureAndBoxing(JCExpression result, ProducedType exprType,
            boolean exprBoxed,
            BoxingStrategy boxingStrategy, ProducedType expectedType) {
        return applyErasureAndBoxing(result, exprType, false, exprBoxed, boxingStrategy, expectedType, 0);
    }
   
    JCExpression applyErasureAndBoxing(JCExpression result, ProducedType exprType,
            boolean exprErased, boolean exprBoxed,
            BoxingStrategy boxingStrategy, ProducedType expectedType,
            int flags) {
        return applyErasureAndBoxing(result, exprType, exprErased, exprBoxed, false, boxingStrategy, expectedType, flags);
    }
   
    JCExpression applyErasureAndBoxing(JCExpression result, ProducedType exprType,
            boolean exprErased, boolean exprBoxed, boolean exprUntrustedType,
            BoxingStrategy boxingStrategy, ProducedType expectedType,
            int flags) {
       
        if(exprType != null)
            exprType = exprType.resolveAliases();
        if(expectedType != null)
            expectedType = expectedType.resolveAliases();
        boolean canCast = false;

        if (expectedType != null
                // don't add cast to an erased type
                && !willEraseToObject(expectedType)) {

            // only try to cast boxed types, no point otherwise
            if(exprBoxed){

                boolean expectedTypeIsNotRaw = (flags & EXPR_EXPECTED_TYPE_NOT_RAW) != 0;
                boolean expectedTypeHasConstrainedTypeParameters = (flags & EXPR_EXPECTED_TYPE_HAS_CONSTRAINED_TYPE_PARAMETERS) != 0;
                boolean expectedTypeHasDependentCovariantTypeParameters = (flags & EXPR_EXPECTED_TYPE_HAS_DEPENDENT_COVARIANT_TYPE_PARAMETERS) != 0;
                boolean downCast = (flags & EXPR_DOWN_CAST) != 0;
                int companionFlags = (flags & EXPR_WANTS_COMPANION) != 0 ? AbstractTransformer.JT_COMPANION : 0;

                // special case for returning Null expressions
                if (isNull(exprType)){
                    // don't add cast for null
                    if(!isNullValue(exprType)
                            // include a cast even for null for interop and disambiguating bw overloads and null values
                            // of different types using the "of" operator
                            || downCast){
                        // in some cases we may have an instance of Null, which is of type java.lang.Object, being
                        // returned in a context where we expect a String? (aka ceylon.language.String) so even though
                        // the instance at hand will really be null, we need a up-cast to it
                        JCExpression targetType = makeJavaType(expectedType, AbstractTransformer.JT_RAW | companionFlags);
                        result = make().TypeCast(targetType, result);
                    }
                }else if(exprType.getDeclaration() instanceof NothingType){
                    // type param erasure
                    JCExpression targetType = makeJavaType(expectedType,
                            AbstractTransformer.JT_RAW | AbstractTransformer.JT_NO_PRIMITIVES | companionFlags);
                    result = make().TypeCast(targetType, result);
                }else if(// expression was forcibly erased
                         exprErased
                         // expression type cannot be trusted to be true, most probably because we had to satisfy Java type parameter
                         // bounds that are different from what we think the expression type should be
                         || exprUntrustedType
                         // if we have a covariant type parameter which is dependent and whose type arg contains erased type parameters
                         // we need a raw cast because it will be fixed rather than using a wildcard and there's a good chance
                         // we can't use proper subtyping rules to assign to it
                         // see https://github.com/ceylon/ceylon-compiler/issues/1557
                         || expectedTypeHasDependentCovariantTypeParameters
                         // some type parameter somewhere needs a cast
                         || needsCast(exprType, expectedType, expectedTypeIsNotRaw, expectedTypeHasConstrainedTypeParameters, downCast)
                         // if the exprType is raw and the expected type isn't
                         || (exprType.isRaw() && (expectedTypeIsNotRaw || !isTurnedToRaw(expectedType)))){

                    // save this before we simplify it because we lose that flag doing so
                    boolean exprIsRaw = exprType.isRaw();
                    boolean expectedTypeIsRaw = isTurnedToRaw(expectedType) && !expectedTypeIsNotRaw;

                    // simplify the type
                    // (without the underlying type, because the cast is always to a non-primitive)
                    exprType = simplifyType(expectedType).withoutUnderlyingType();

                    // We will need a raw cast if the expected type has type parameters,
                    // unless the expr is already raw
                    if (!exprIsRaw && hasTypeParameters(expectedType)) {
                        JCExpression rawType = makeJavaType(expectedType,
                                AbstractTransformer.JT_TYPE_ARGUMENT | AbstractTransformer.JT_RAW | companionFlags);
                        result = make().TypeCast(rawType, result);
                        // expr is now raw
                        exprIsRaw = true;
                        // let's not add another downcast if we got a cast: one is enough
                        downCast = false;
                        // same for forced erasure
                        exprErased = false;
                        exprUntrustedType = false;
                    }

                    // if the expr is not raw, we need a cast
                    // if the expr is raw:
                    //  don't even try making an actual cast if there are bounded type parameters in play, because going raw is much safer
                    //  also don't try making the cast if the expected type is raw because anything goes
                    boolean needsTypedCast = !exprIsRaw
                            || (!expectedTypeHasConstrainedTypeParameters
                                    && !expectedTypeHasDependentCovariantTypeParameters
                                    && !expectedTypeIsRaw);
                    if(needsTypedCast
                            // make sure that downcasts get at least one cast
                            || downCast
                            // same for forced erasure
                            || exprUntrustedType){
                        // forced erasure may require a previous cast to Object if we were not able to insert a raw cast
                        // because for instance Sequential<String> cannot be cast forcibly to Empty because Java is so smart
                        // it figures out that there's no intersection between the two types, but we know better
                        if(exprUntrustedType){
                            result = make().TypeCast(syms().objectType, result);
                        }
                        // Do the actual cast
                        JCExpression targetType = makeJavaType(expectedType,
                                AbstractTransformer.JT_TYPE_ARGUMENT | companionFlags);
                        result = make().TypeCast(targetType, result);
                    }
                }else
                    canCast = true;
            }else
                canCast = true;
        }

        // we must do the boxing after the cast to the proper type
        JCExpression ret = boxUnboxIfNecessary(result, exprBoxed, exprType, boxingStrategy);
       
        // very special case for nothing that we need to "unbox" to a primitive type
        if(exprType != null
                && exprType.getDeclaration() instanceof NothingType
                && boxingStrategy == BoxingStrategy.UNBOXED){
            // in this case we have to use the expected type
            ret = unboxType(ret, expectedType);
        }
       
        // now check if we need variance casts
        if (canCast) {
            ret = applyVarianceCasts(ret, exprType, exprBoxed, boxingStrategy, expectedType, flags);
        }
        ret = applySelfTypeCasts(ret, exprType, exprBoxed, boxingStrategy, expectedType);
        ret = applyJavaTypeConversions(ret, exprType, expectedType, boxingStrategy, exprBoxed, flags);
        return ret;
    }

    boolean needsCast(ProducedType exprType, ProducedType expectedType,
                              boolean expectedTypeNotRaw,
                              boolean expectedTypeHasConstrainedTypeParameters,
                              boolean downCast) {
        // error handling
        if(exprType == null)
            return false;
        // make sure we work on definite types
        exprType = simplifyType(exprType);
        expectedType = simplifyType(expectedType);
        // abort if both types are the same
        if(exprType.isExactly(expectedType)){
            // unless the expected type is parameterised with bounds because in that case we can't
            // really trust the expected type
            if(!expectedTypeHasConstrainedTypeParameters)
                return false;
        }

        // now see about erasure
        boolean eraseExprType = willEraseToObject(exprType);
        boolean eraseExpectedType = willEraseToObject(expectedType);
       
        // if we erase expected type we need no cast
        if(eraseExpectedType){
            // unless the expected type is parameterised with bounds that erasure to Object can't possibly satisfy
            if(!expectedTypeHasConstrainedTypeParameters)
                return false;
        }
        // if we erase the expr type we need a cast
        if(eraseExprType)
            return true;
       
        // find their common type
        ProducedType commonType = exprType.getSupertype(expectedType.getDeclaration());
       
        if(commonType == null || !(commonType.getDeclaration() instanceof ClassOrInterface)){
            // we did not find any common type, but we may be downcasting, in which case we need a cast
            return downCast;
        }
       
        // some times we can lose info due to an erased type parameter somewhere in the inheritance graph
        if(lostTypeParameterInInheritance(exprType, commonType))
            return true;
       
        if(!expectedTypeNotRaw){
            // the truth is that we don't really know if the expected type is raw or not, that flag only gets set
            // if we know for sure that the expected type is NOT raw. if it's false we've no idea but we can check:
            if(isTurnedToRaw(expectedType)){
                return false;
            }
            // if the expected type is exactly the common type, they must have the same erasure
            // note that we don't do that test if we know the expected type is not raw, because
            // the common type could be erased
            if(commonType.isExactly(expectedType))
                return false;
        }
        //special case for Callable because only the first type param exists in Java, the rest is completely suppressed
        boolean isCallable = isCeylonCallable(commonType);
       
        // now see if the type parameters match
        java.util.List<ProducedType> commonTypeArgs = commonType.getTypeArgumentList();
        java.util.List<TypeParameter> commonTps = commonType.getDeclaration().getTypeParameters();
        java.util.List<ProducedType> expectedTypeArgs = expectedType.getTypeArgumentList();
        java.util.List<TypeParameter> expectedTps = expectedType.getDeclaration().getTypeParameters();
        // check that we got them all otherwise we just don't know
        if(commonTypeArgs.size() != expectedTypeArgs.size())
            return false;
        for(int i=0,n=commonTypeArgs.size(); i < n ; i++){
            // apply the same logic to each type param: see if they would require a raw cast
            ProducedType commonTypeArg = commonTypeArgs.get(i);
            ProducedType expectedTypeArg = expectedTypeArgs.get(i);
           
            if (hasDependentTypeParameters(commonTps, commonTps.get(i))
                    || hasDependentTypeParameters(expectedTps, expectedTps.get(i))) {
                // In this case makeJavaType() will have made the Java decl
                // invariant in this type argument, so we will need a type cast
                // if the type parameters are not identical:
                if (!simplifyType(commonTypeArg).isExactly(simplifyType(expectedTypeArg))) {
                    return true;
                }
            }
           
            if(needsCast(commonTypeArg, expectedTypeArg, expectedTypeNotRaw,
                         expectedTypeHasConstrainedTypeParameters,
                         downCast))
                return true;
            // stop after the first one for Callable
            if(isCallable)
                break;
        }
        return false;
    }

    private boolean lostTypeParameterInInheritance(ProducedType exprType, ProducedType commonType) {
        if(exprType.getDeclaration() instanceof ClassOrInterface == false
                || commonType.getDeclaration() instanceof ClassOrInterface == false)
            return false;
        ClassOrInterface exprDecl = (ClassOrInterface) exprType.getDeclaration();
        ClassOrInterface commonDecl = (ClassOrInterface) commonType.getDeclaration();
        // do not search interfaces if the common declaration is a class, because interfaces cannot be subtypes of a class
        boolean searchInterfaces = commonDecl instanceof Interface;
        return lostTypeParameterInInheritance(exprDecl, commonDecl, searchInterfaces, false);
    }

    private boolean lostTypeParameterInInheritance(ClassOrInterface exprDecl, ClassOrInterface commonDecl, boolean searchInterfaces, boolean lostTypeParameter) {
        // stop if we found the common decl
        if(Decl.equal(exprDecl, commonDecl))
            return lostTypeParameter;
        if(searchInterfaces){
            // find a match in interfaces
            for(ProducedType pt : exprDecl.getSatisfiedTypes()){
                // FIXME: this is very heavy-handed because we consider that once we've lost a type parameter we've lost them all
                // but we could optimise this by checking:
                // 1/ which type parameter we've really lost
                // 2/ if the type parameters we're passing to our super type actually depend in any way from type parameters we've lost
                boolean lostTypeParameter2 = lostTypeParameter || isTurnedToRaw(pt);
                pt = simplifyType(pt);
                // it has to be an interface
                Interface interf = (Interface) pt.getDeclaration();
                if(lostTypeParameterInInheritance(interf, commonDecl, searchInterfaces, lostTypeParameter2))
                    return true;
            }
        }
        // search for super classes
        ProducedType extendedType = exprDecl.getExtendedType();
        if(extendedType != null){
            // FIXME: see above
            boolean lostTypeParameter2 = lostTypeParameter || isTurnedToRaw(extendedType);
            extendedType = simplifyType(extendedType);
            // it has to be a Class
            Class extendedTypeDeclaration = (Class) extendedType.getDeclaration();
            // looks like Object's superclass is Object, so stop right there
            if(extendedTypeDeclaration != typeFact().getObjectDeclaration())
                return lostTypeParameterInInheritance(extendedTypeDeclaration, commonDecl, searchInterfaces, lostTypeParameter2);
        }
        // didn't find it
        return false;
    }

    private boolean hasTypeParameters(ProducedType type) {
        if (!type.getTypeArgumentList().isEmpty()) {
            return true;
        }
        if (type.getCaseTypes() != null) {
            for (ProducedType ct : type.getCaseTypes()) {
                if (hasTypeParameters(ct)) {
                    return true;
                }
            }
        }
        return false;
    }

    private JCExpression applyVarianceCasts(JCExpression result, ProducedType exprType,
            boolean exprBoxed,
            BoxingStrategy boxingStrategy, ProducedType expectedType, int flags) {
        // unboxed types certainly don't need casting for variance
        if(exprBoxed || boxingStrategy == BoxingStrategy.BOXED){
            VarianceCastResult varianceCastResult = getVarianceCastResult(expectedType, exprType);
            if(varianceCastResult != null){
                result = applyVarianceCasts(result, expectedType, varianceCastResult, flags);
            }
        }
        return result;
    }

    private JCExpression applyVarianceCasts(JCExpression result, ProducedType expectedType, VarianceCastResult varianceCastResult,
            int flags) {
        // Types with variance types need a type cast, let's start with a raw cast to get rid
        // of Java's type system constraint (javac doesn't grok multiple implementations of the same
        // interface with different type params, which the JVM allows)
        int forCompanionMask = (flags & EXPR_FOR_COMPANION) != 0 ? JT_SATISFIES : 0;
        int wantsCompanionMask = (flags & EXPR_WANTS_COMPANION) != 0 ? JT_COMPANION : 0;
        JCExpression targetType = makeJavaType(expectedType, AbstractTransformer.JT_RAW | wantsCompanionMask);
        // do not change exprType here since this is just a Java workaround
        result = make().TypeCast(targetType, result);
        // now, because a raw cast is losing a lot of info, can we do better?
        if(varianceCastResult.isBetterCastAvailable()){
            // let's recast that to something finer than a raw cast
            targetType = makeJavaType(varianceCastResult.castType, AbstractTransformer.JT_TYPE_ARGUMENT | wantsCompanionMask | forCompanionMask);
            result = make().TypeCast(targetType, result);
        }
        return result;
    }
   
    private JCExpression applySelfTypeCasts(JCExpression result, ProducedType exprType,
            boolean exprBoxed,
            BoxingStrategy boxingStrategy, ProducedType expectedType) {
        if (expectedType == null) {
            return result;
        }
        final ProducedType selfType = exprType.getDeclaration().getSelfType();
        if (selfType != null) {
            if (selfType.isExactly(exprType) // self-type within its own scope
                    || !exprType.isExactly(expectedType)) {
                final ProducedType castType = findTypeArgument(exprType, selfType.getDeclaration());
                // the fact that the original expr was or not boxed doesn't mean the current result is boxed or not
                // as boxing transformations occur before this method
                boolean resultBoxed = boxingStrategy == BoxingStrategy.BOXED
                        || (boxingStrategy == BoxingStrategy.INDIFFERENT && exprBoxed);
                JCExpression targetType = makeJavaType(castType, resultBoxed ? AbstractTransformer.JT_TYPE_ARGUMENT : 0);
                result = make().TypeCast(targetType, result);
            }
        }
        return result;
    }

    private ProducedType findTypeArgument(ProducedType type, TypeDeclaration declaration) {
        if(type == null)
            return null;
        ProducedType typeArgument = type.getTypeArguments().get(declaration);
        if(typeArgument != null)
            return typeArgument;
        return findTypeArgument(type.getQualifyingType(), declaration);
    }

    private JCExpression applyJavaTypeConversions(JCExpression ret, ProducedType exprType, ProducedType expectedType,
            BoxingStrategy boxingStrategy, boolean exprBoxed, int flags) {
        if(exprType == null || boxingStrategy != BoxingStrategy.UNBOXED)
            return ret;
        ProducedType definiteExprType = simplifyType(exprType);
        if(definiteExprType == null)
            return ret;
        // ignore the underlying type of the expr type if it was boxed, since we must have unboxed it to
        // something with no underlying type first
        String convertFrom = exprBoxed ? null : definiteExprType.getUnderlyingType();

        ProducedType definiteExpectedType = null;
        String convertTo = null;
        if (expectedType != null) {
            definiteExpectedType = simplifyType(expectedType);
            convertTo = definiteExpectedType.getUnderlyingType();
        }
        // check for identity conversion
        if (convertFrom != null && convertFrom.equals(convertTo)) {
            return ret;
        }
        if (isCeylonByte(definiteExpectedType) && isCeylonInteger(exprType)) {
            if ((flags & EXPR_UNSAFE_PRIMITIVE_TYPECAST_OK) == 0) {
                if(ret instanceof JCTree.JCUnary){
                    JCTree.JCUnary unary = (JCTree.JCUnary)ret;
                    if(unary.getTag() == JCTree.NEG
                            && unary.arg instanceof JCTree.JCLiteral){
                        Object value = ((JCTree.JCLiteral)unary.arg).value;
                        if(value instanceof Integer){
                            int val = (Integer)value;
                            // if it fits let's just leave it
                            if(val >= 0 && val <= -Byte.MIN_VALUE){
                                // in the case of -128 to 127 we don't need to cast to byte by using an int literal, but only for
                                // assignment, not for method calls, so it's simpler to always cast
                                return make().TypeCast(syms().byteType, ret);
                            }
                        }
                    }
                }
            }
            ret = make().TypeCast(syms().byteType, ret);
        } else {
            if (convertTo != null) {
                if(convertTo.equals("short")) {
                    if ((flags & EXPR_UNSAFE_PRIMITIVE_TYPECAST_OK) == 0) {
                        ret = utilInvocation().toShort(ret);
                    } else {
                        ret = make().TypeCast(syms().shortType, ret);
                    }
                } else if(convertTo.equals("int")) {
                    if ((flags & EXPR_UNSAFE_PRIMITIVE_TYPECAST_OK) == 0) {
                        ret = utilInvocation().toInt(ret);
                    } else {
                        ret = make().TypeCast(syms().intType, ret);
                    }
                } else if(convertTo.equals("float")) {
                    ret = make().TypeCast(syms().floatType, ret);
                } else if(convertTo.equals("char")) {
                    ret = make().TypeCast(syms().charType, ret);
                }
            }
        }
        return ret;
    }
   
    private final class InvocationTermTransformer implements TermTransformer {
        private final Invocation invocation;
        private final CallBuilder callBuilder;
       
        private InvocationTermTransformer(
                Invocation invocation,
                CallBuilder callBuilder) {
            this.invocation = invocation;
            this.callBuilder = callBuilder;
        }

        @Override
        public JCExpression transform(JCExpression primaryExpr, String selector) {
            TransformedInvocationPrimary transformedPrimary = invocation.transformPrimary(primaryExpr, selector);
            callBuilder.argumentsAndTypes(transformArgumentList(invocation, transformedPrimary, callBuilder));
            JCExpression resultExpr;
            if (invocation instanceof NamedArgumentInvocation) {
                resultExpr = transformNamedArgumentInvocationOrInstantiation((NamedArgumentInvocation)invocation, callBuilder, transformedPrimary);
            } else {
                resultExpr = transformPositionalInvocationOrInstantiation(invocation, callBuilder, transformedPrimary);
            }
            return resultExpr;
        }
    }

    private static class VarianceCastResult {
        ProducedType castType;
       
        VarianceCastResult(ProducedType castType){
            this.castType = castType;
        }
       
        private VarianceCastResult(){}
       
        boolean isBetterCastAvailable(){
            return castType != null;
        }
    }
   
    private static final VarianceCastResult RawCastVarianceResult = new VarianceCastResult();

    private VarianceCastResult getVarianceCastResult(ProducedType expectedType, ProducedType exprType) {
        // exactly the same type, doesn't need casting
        if(exprType.isExactly(expectedType))
            return null;
        // if we're not trying to put it into an interface, there's no need
        if(!(expectedType.getDeclaration() instanceof Interface))
            return null;
        // the interface must have type arguments, otherwise we can't use raw types
        if(expectedType.getTypeArguments().isEmpty())
            return null;
        // see if any of those type arguments has variance
        boolean hasVariance = false;
        for(TypeParameter t : expectedType.getTypeArguments().keySet()){
            if(expectedType.isContravariant(t) || expectedType.isCovariant(t)){
                hasVariance = true;
                break;
            }
        }
        if(!hasVariance)
            return null;
        // see if we're inheriting the interface twice with different type parameters
        java.util.List<ProducedType> satisfiedTypes = new LinkedList<ProducedType>();
        for(ProducedType superType : exprType.getSupertypes()){
            if(Decl.equal(superType.getDeclaration(), expectedType.getDeclaration()))
                satisfiedTypes.add(superType);
        }
        // discard the supertypes that have the same erasure
        for(int i=0;i<satisfiedTypes.size();i++){
            ProducedType pt = satisfiedTypes.get(i);
            for(int j=i+1;j<satisfiedTypes.size();j++){
                ProducedType other = satisfiedTypes.get(j);
                if(pt.isExactly(other) || haveSameErasure(pt, other)){
                    satisfiedTypes.remove(j);
                    break;
                }
            }
        }
        // we need at least two instantiations
        if(satisfiedTypes.size() <= 1)
            return null;
        boolean needsCast = false;
        // we need at least one that differs
        for(ProducedType superType : satisfiedTypes){
            if(!exprType.isExactly(superType)){
                needsCast = true;
                break;
            }
        }
        // no cast needed if they are all the same type
        if(!needsCast)
            return null;
        // find the better cast match
        for(ProducedType superType : satisfiedTypes){
            if(expectedType.isExactly(superType))
                return new VarianceCastResult(superType);
        }
        // nothing better than a raw cast (Stef: not sure that can happen)
        return RawCastVarianceResult;
    }

    private boolean haveSameErasure(ProducedType pt, ProducedType other) {
        TypeDeclaration decl1 = pt.getDeclaration();
        TypeDeclaration decl2 = other.getDeclaration();
        if(decl1 == null || decl2 == null)
            return false;
        // do we erase both to object?
        boolean erased1 = willEraseToObject(pt);
        boolean erased2 = willEraseToObject(other);
        if(erased1)
            return erased2;
        if(erased2)
            return false;
        // declarations must be the same
        // (use simplifyType() so we ignore the difference between T and T?)
        if (!simplifyType(pt).getDeclaration().equals(simplifyType(other).getDeclaration())) {
            return false;
        }
        // now see their type arguments
        java.util.List<ProducedType> tal1 = pt.getTypeArgumentList();
        java.util.List<ProducedType> tal2 = other.getTypeArgumentList();
        if(tal1.size() != tal2.size())
            return false;
        for(int i=0;i<tal1.size();i++){
            if(!haveSameErasure(tal1.get(i), tal2.get(i)))
                return false;
        }
        // all the same
        return true;
    }
   
    //
    // Literals
   

    JCExpression ceylonLiteral(String s) {
        JCLiteral lit = make().Literal(s);
        return lit;
    }

    static String literalValue(Tree.StringLiteral string) {
        return string.getText();
    }
   
    static String literalValue(Tree.QuotedLiteral string) {
        return string.getText().substring(1, string.getText().length()-1);
    }
   
    static int literalValue(Tree.CharLiteral ch) {
        // codePoint is at index 1 because the text is `X` (including quotation marks, so we skip them)
        return ch.getText().codePointAt(1);
    }
   
    static double literalValue(Tree.FloatLiteral literal) throws ErroneousException {
        double value = Double.parseDouble(literal.getText());
        // Don't need to handle the negative infinity and negative zero cases
        // because Ceylon Float literals have no sign
        if (value == Double.POSITIVE_INFINITY) {
            throw new ErroneousException(literal, "literal so large it is indistinguishable from infinity: "+ literal.getText() + " (use infinity)");
        } else if (value == 0.0 && !literal.getText().equals("0.0")) {
            throw new ErroneousException(literal, "literal so small it is indistinguishable from zero: " + literal.getText() + " (use 0.0)");
        }
        return value;
    }
   
    static long literalValue(Tree.NaturalLiteral literal) throws ErroneousException {
        return literalValue(literal, literal.getText());
    }
   
    static private long literalValue(Tree.NaturalLiteral literal, String text) throws ErroneousException {
        if(text.startsWith("#")){
            return literalValue(literal, 16, "invalid hexadecimal literal: " + text + " has more than 64 bits");
        }
        if(text.startsWith("$")){
            return literalValue(literal, 2, "invalid binary literal: " + text + " has more than 64 bits");
        }
        try {
            return Long.parseLong(text);
        } catch (NumberFormatException e) {
            throw new ErroneousException(literal, "literal outside representable range: " + text + " is too large to be represented as an Integer");
        }
       
    }
   
    private static long literalValue(Tree.NaturalLiteral literal, int radix, String error) throws ErroneousException{
        String value = literal.getText().substring(1);
        try{
            return Convert.string2long(value, radix);
        }catch(NumberFormatException x){
            throw new ErroneousException(literal, error);
        }
    }
   
    static Long literalValue(Tree.NegativeOp op) throws ErroneousException {
        if (op.getTerm() instanceof Tree.NaturalLiteral) {
            // To cope with -9223372036854775808 we can't just parse the
            // number separately from the sign
            String lit = op.getTerm().getText();
            if (!lit.startsWith("#") && !lit.startsWith("$")) {
                return literalValue((Tree.NaturalLiteral)op.getTerm(), "-" + lit);
            }
        }
        return null;
    }
   
    public JCExpression transform(Tree.StringLiteral string) {
        at(string);
        return ceylonLiteral(string.getText());
    }

    public JCExpression transform(Tree.QuotedLiteral string) {
        at(string);
        return ceylonLiteral(literalValue(string));
    }
   
    public JCExpression transform(Tree.CharLiteral lit) {
        return make().Literal(TypeTags.INT, literalValue(lit));
    }

    public JCExpression transform(Tree.FloatLiteral lit) {
        try {
            return make().Literal(literalValue(lit));
        } catch (ErroneousException e) {
            // We should never get here since the error should have been
            // reported by the UnsupportedVisitor and the containing statement
            // replaced with a throw.
            return e.makeErroneous(this);
        }
    }
   
    public JCExpression transform(Tree.NaturalLiteral lit) {
        try {
            at(lit);
            return make().Literal(literalValue(lit));
        } catch (ErroneousException e) {
            // We should never get here since the error should have been
            // reported by the UnsupportedVisitor and the containing statement
            // replaced with a throw.
            return e.makeErroneous(this);
        }
    }
   
    JCExpression transform(Tree.Literal literal) {
        if (literal instanceof Tree.StringLiteral) {
            return transform((Tree.StringLiteral)literal);
        } else if (literal instanceof Tree.NaturalLiteral) {
            return transform((Tree.NaturalLiteral)literal);
        } else if (literal instanceof Tree.CharLiteral) {
            return transform((Tree.CharLiteral)literal);
        } else if (literal instanceof Tree.FloatLiteral) {
            return transform((Tree.FloatLiteral)literal);
        } else if (literal instanceof Tree.QuotedLiteral) {
            return transform((Tree.QuotedLiteral)literal);
        }
        throw BugException.unhandledNodeCase(literal);
    }

    public JCTree transform(Tree.PackageLiteral expr) {
        at(expr);
       
        Package pkg = (Package) expr.getImportPath().getModel();
        return makePackageLiteralCall(pkg);
    }

    public JCTree transform(Tree.ModuleLiteral expr) {
        at(expr);

        Module mod = (Module) expr.getImportPath().getModel();
        return makeModuleLiteralCall(mod);
    }

    public JCTree transform(Tree.MemberLiteral expr) {
        at(expr);
       
        Declaration declaration = expr.getDeclaration();
        if(declaration == null)
            return makeErroneous(expr, "compiler bug: missing declaration");
        if(declaration.isToplevel()){
            return makeTopLevelValueOrFunctionLiteral(expr);
        }else if(expr.getWantsDeclaration()){
            return makeMemberValueOrFunctionDeclarationLiteral(expr, declaration);
        }else{
            // get its produced ref
            ProducedReference producedReference = expr.getTarget();
            // it's a member we get from its container type
            ProducedType containerType = producedReference.getQualifyingType();
            // if we have no container type it means we have an object member
            boolean objectMember = containerType == null;
           
            JCExpression memberCall;

            if(objectMember){
                // We don't care about the type args for the cast, nor for the reified container expr, because
                // we take the real reified container type from the container instance, and that one has the type
                // arguments
                containerType = ((Class)declaration.getContainer()).getType();
            }
            JCExpression typeCall = makeTypeLiteralCall(containerType, false, expr.getTypeModel());
            // make sure we cast it to ClassOrInterface
            TypeDeclaration classOrInterfaceDeclaration = (TypeDeclaration) typeFact().getLanguageModuleModelDeclaration("ClassOrInterface");
            JCExpression classOrInterfaceTypeExpr = makeJavaType(
                    classOrInterfaceDeclaration.getProducedReference(null, Arrays.asList(containerType)).getType());

            typeCall = make().TypeCast(classOrInterfaceTypeExpr, typeCall);
            // we will need a TD for the container
            // Note that we don't use Basic for the container for object members, because that's not how we represent
            // anonymous types.
            JCExpression reifiedContainerExpr = makeReifiedTypeArgument(containerType);
           
            // make a raw call and cast
            if(declaration instanceof Method){
                // we need to get types for each type argument
                JCExpression closedTypesExpr;
                if(expr.getTypeArgumentList() != null)
                    closedTypesExpr = getClosedTypesSequential(expr.getTypeArgumentList().getTypeModels());
                else
                    closedTypesExpr = null;
                // we also need type descriptors for ret and args
                ProducedType callableType = producedReference.getFullType();
                JCExpression reifiedReturnTypeExpr = makeReifiedTypeArgument(typeFact().getCallableReturnType(callableType));
                JCExpression reifiedArgumentsExpr = makeReifiedTypeArgument(typeFact().getCallableTuple(callableType));
                List<JCExpression> arguments;
                if(closedTypesExpr != null)
                    arguments = List.of(reifiedContainerExpr, reifiedReturnTypeExpr, reifiedArgumentsExpr,
                            ceylonLiteral(declaration.getName()), closedTypesExpr);
                else
                    arguments = List.of(reifiedContainerExpr, reifiedReturnTypeExpr, reifiedArgumentsExpr,
                            ceylonLiteral(declaration.getName()));
                memberCall = make().Apply(null, makeSelect(typeCall, "getMethod"), arguments);
            }else if(declaration instanceof Value){
                JCExpression reifiedGetExpr = makeReifiedTypeArgument(producedReference.getType());
                String getterName = "getAttribute";
                ProducedType ptype;
                if(!((Value)declaration).isVariable())
                    ptype = typeFact().getNothingDeclaration().getType();
                else
                    ptype = producedReference.getType();

                JCExpression reifiedSetExpr = makeReifiedTypeArgument(ptype);
                memberCall = make().Apply(null, makeSelect(typeCall, getterName), List.of(reifiedContainerExpr, reifiedGetExpr, reifiedSetExpr,
                        ceylonLiteral(declaration.getName())));
            }else{
                return makeErroneous(expr, "Unsupported member type: "+declaration);
            }
            if(objectMember){
                // now get the instance and bind it
                // I don't think we need any expected type since objects can't be erased
                JCExpression object = transformExpression(expr.getObjectExpression());
                // reset the location after we transformed the expression
                memberCall = at(expr).Apply(null, makeSelect(memberCall, "bind"), List.of(object));
            }
            // cast the member call because we invoke it with no Java generics
            memberCall = make().TypeCast(makeJavaType(expr.getTypeModel(), JT_RAW | JT_NO_PRIMITIVES), memberCall);
            memberCall = make().TypeCast(makeJavaType(expr.getTypeModel(), JT_NO_PRIMITIVES), memberCall);
            return memberCall;
        }
    }

    JCExpression makeMemberValueOrFunctionDeclarationLiteral(Node node, Declaration declaration) {
        return makeMemberValueOrFunctionDeclarationLiteral(node, declaration, true);
    }
   
    JCExpression makeMemberValueOrFunctionDeclarationLiteral(Node node, Declaration declaration, boolean f) {
        // it's a member we get from its container declaration
        if(declaration.getContainer() instanceof ClassOrInterface == false)
            return makeErroneous(node, "compiler bug: " + declaration.getContainer() + " is not a supported type parameter container");
       
        ClassOrInterface container = (ClassOrInterface) declaration.getContainer();
        // use the generated class to get to the declaration literal
        JCExpression metamodelCall = makeTypeDeclarationLiteral(container);
        JCExpression metamodelCast = makeJavaType(typeFact().getLanguageModuleDeclarationTypeDeclaration("ClassOrInterfaceDeclaration").getType(), JT_NO_PRIMITIVES);
        metamodelCall = make().TypeCast(metamodelCast, metamodelCall);

        String memberClassName;
        if(declaration instanceof Class)
            memberClassName = "ClassDeclaration";
        else if(declaration instanceof Interface)
            memberClassName = "InterfaceDeclaration";
        else if(declaration instanceof Method)
            memberClassName = "FunctionDeclaration";
        else if(declaration instanceof Value){
            memberClassName = "ValueDeclaration";
        }else{
            return makeErroneous(node, "compiler bug: " + declaration + " is not a supported declaration literal");
        }
        TypeDeclaration metamodelDecl = (TypeDeclaration) typeFact().getLanguageModuleDeclarationDeclaration(memberClassName);
        JCExpression memberType = makeJavaType(metamodelDecl.getType());
        JCExpression reifiedMemberType = makeReifiedTypeArgument(metamodelDecl.getType());
        JCExpression memberCall = make().Apply(List.of(memberType),
                                               makeSelect(metamodelCall, f ? "getMemberDeclaration" : "getDeclaredMemberDeclaration"),
                                               List.of(reifiedMemberType, ceylonLiteral(declaration.getName())));
        return memberCall;
    }

    private JCExpression makeTopLevelValueOrFunctionDeclarationLiteral(Declaration declaration) {
        // toplevel method or attribute: we need to fetch them from their module/package
        Package pkg = Decl.getPackageContainer(declaration.getContainer());

        // get the package
        JCExpression packageCall = makePackageLiteralCall(pkg);
       
        // now get the toplevel
        String getter = Decl.isMethod(declaration) ? "getFunction" : "getValue";
        JCExpression toplevelCall = make().Apply(null, makeSelect(packageCall, getter),
                                                 List.<JCExpression>of(ceylonLiteral(declaration.getName())));
       
        return toplevelCall;
    }
   
    private JCTree makeTopLevelValueOrFunctionLiteral(Tree.MemberLiteral expr) {
        Declaration declaration = expr.getDeclaration();
        JCExpression toplevelCall = makeTopLevelValueOrFunctionDeclarationLiteral(declaration);
       
        if(!expr.getWantsDeclaration()){
            ListBuffer<JCExpression> closedTypeArgs = new ListBuffer<JCExpression>();
            // expr is of type Function<Type,Arguments> or Value<Get,Set> so we can get its type like that
            JCExpression reifiedType = makeReifiedTypeArgument(expr.getTypeModel().getTypeArgumentList().get(0));
            closedTypeArgs.append(reifiedType);
            if(Decl.isMethod(declaration)){
                // expr is of type Function<Type,Arguments> so we can get its arguments type like that
                ProducedType argumentsType = typeFact().getCallableTuple(expr.getTypeModel());
                JCExpression reifiedArguments = makeReifiedTypeArgument(argumentsType);
                closedTypeArgs.append(reifiedArguments);
                if(expr.getTypeArgumentList() != null){
                    JCExpression closedTypesExpr = getClosedTypesSequential(expr.getTypeArgumentList().getTypeModels());
                    // must apply it
                    closedTypeArgs.append(closedTypesExpr);
                }
            }else{
                JCExpression reifiedSet;
                ProducedType ptype;
                if(!((Value)declaration).isVariable())
                    ptype = typeFact().getNothingDeclaration().getType();
                else
                    ptype = expr.getTypeModel().getTypeArgumentList().get(0);
               
                reifiedSet = makeReifiedTypeArgument(ptype);
                closedTypeArgs.append(reifiedSet);
            }
            toplevelCall = make().Apply(null, makeSelect(toplevelCall, "apply"), closedTypeArgs.toList());
            // add cast
            ProducedType exprType = expr.getTypeModel().resolveAliases();
            JCExpression typeClass = makeJavaType(exprType, JT_NO_PRIMITIVES);
            JCExpression rawTypeClass = makeJavaType(exprType, JT_NO_PRIMITIVES | JT_RAW);
            return make().TypeCast(typeClass, make().TypeCast(rawTypeClass, toplevelCall));
        }
        return toplevelCall;
    }

    private JCExpression makePackageLiteralCall(Package pkg) {
        // get the module
        Module module = pkg.getModule();
        JCExpression moduleCall = makeModuleLiteralCall(module);
       
        // now get the package
        return make().Apply(null, makeSelect(moduleCall, "findPackage"),
                                             List.<JCExpression>of(ceylonLiteral(pkg.getNameAsString())));
    }

    private JCExpression makeModuleLiteralCall(Module module) {
        JCExpression modulesGetIdent = naming.makeFQIdent("ceylon", "language", "meta", "modules_", "get_");
        JCExpression modulesGet = make().Apply(null, modulesGetIdent, List.<JCExpression>nil());
        JCExpression call;
        if(module.isDefault()){
            call = make().Apply(null, makeSelect(modulesGet, "getDefault"), List.<JCExpression>nil());
        }else{
            call = make().Apply(null, makeSelect(modulesGet, "find"),
                                      List.<JCExpression>of(ceylonLiteral(module.getNameAsString()),
                                                            ceylonLiteral(module.getVersion())));
        }
        // make sure we handle missing modules gracefully
        String version = module.getVersion();
        return makeMetamodelInvocation("checkModule", List.of(call, ceylonLiteral(module.getNameAsString()), version == null ? makeNull() : ceylonLiteral(version)), null);
    }

    private JCExpression getClosedTypesSequential(java.util.List<ProducedType> typeModels) {
        ListBuffer<JCExpression> closedTypes = new ListBuffer<JCExpression>();
        for (ProducedType producedType : typeModels) {
            closedTypes.add(makeTypeLiteralCall(producedType));
        }
        ProducedType elementType = typeFact().getMetamodelTypeDeclaration().getProducedType(null, Arrays.asList(typeFact().getAnythingDeclaration().getType()));
        // now wrap into a sequential
        return makeSequence(closedTypes.toList(), elementType, CeylonTransformer.JT_CLASS_NEW);
    }

    private JCExpression makeTypeLiteralCall(ProducedType producedType) {
        JCExpression typeLiteralIdent = naming.makeFQIdent("ceylon", "language", "meta", "typeLiteral_", "typeLiteral");
        JCExpression reifiedTypeArgument = makeReifiedTypeArgument(producedType.resolveAliases());
        // note that we don't pass it a Java type argument since it's not used
        return make().Apply(null, typeLiteralIdent, List.of(reifiedTypeArgument));
    }

    JCExpression makeTypeLiteralCall(ProducedType type, boolean addCast, ProducedType exprType) {
        // construct a call to typeLiteral<T>() and cast if required
        JCExpression call = makeTypeLiteralCall(type);
        if(addCast){
            // if we have a type that is not nothingType and not Type, we need to cast
            exprType = exprType.resolveAliases();
            TypeDeclaration typeDeclaration = exprType.getDeclaration();
            if(typeDeclaration instanceof UnionType == false
                    && !exprType.isExactly(typeFact().getMetamodelNothingTypeDeclaration().getType())
                    && !exprType.isExactly(typeFact().getMetamodelTypeDeclaration().getType())){
                JCExpression typeClass = makeJavaType(exprType, JT_NO_PRIMITIVES);
                return make().TypeCast(typeClass, call);
            }
        }
        return call;
    }

    public JCTree transform(Tree.TypeLiteral expr) {
        at(expr);
        if(!expr.getWantsDeclaration()){
            return makeTypeLiteralCall(expr.getType().getTypeModel(), true, expr.getTypeModel());
        }else if(expr.getDeclaration() instanceof TypeParameter){
            // we must get it from its container
            TypeParameter declaration = (TypeParameter)expr.getDeclaration();
            Node node = expr;
            return makeTypeParameterDeclaration(node, declaration);
        }else if(expr.getDeclaration() instanceof ClassOrInterface
                 || expr.getDeclaration() instanceof TypeAlias){
            // use the generated class to get to the declaration literal
            JCExpression metamodelCall = makeTypeDeclarationLiteral((TypeDeclaration) expr.getDeclaration());
            ProducedType exprType = expr.getTypeModel().resolveAliases();
            // now cast if required
            if(!exprType.isExactly(((TypeDeclaration)typeFact().getLanguageModuleDeclarationDeclaration("NestableDeclaration")).getType())){
                JCExpression type = makeJavaType(exprType, JT_NO_PRIMITIVES);
                return make().TypeCast(type, metamodelCall);
            }
            return metamodelCall;
        }else{
            return makeErroneous(expr, "compiler bug: " + expr.getDeclaration() + " is an unsupported declaration type");
        }
    }

    /**
     * Makes an expression equivalent to the result of {@code `given T`}
     * @param node
     * @param declaration
     * @return
     */
    JCExpression makeTypeParameterDeclaration(Node node,
            TypeParameter declaration) {
        Scope container = declaration.getContainer();
        if(container instanceof Declaration){
            JCExpression containerExpr;
            Declaration containerDeclaration = (Declaration) container;
            if(containerDeclaration instanceof ClassOrInterface
                    || containerDeclaration instanceof TypeAlias){
                JCExpression metamodelCall = makeTypeDeclarationLiteral((TypeDeclaration) containerDeclaration);
                JCExpression metamodelCast = makeJavaType(typeFact().getLanguageModuleDeclarationTypeDeclaration("GenericDeclaration").getType(), JT_NO_PRIMITIVES);
                containerExpr = make().TypeCast(metamodelCast, metamodelCall);
            }else if(containerDeclaration.isToplevel()) {
                containerExpr = makeTopLevelValueOrFunctionDeclarationLiteral(containerDeclaration);
            }else{
                containerExpr = makeMemberValueOrFunctionDeclarationLiteral(node, containerDeclaration);
            }
            // now it must be a ClassOrInterfaceDeclaration or a FunctionDeclaration, both of which have the method we need
            return at(node).Apply(null, makeSelect(containerExpr, "getTypeParameterDeclaration"), List.of(ceylonLiteral(declaration.getName())));
        }else{
            return makeErroneous(node, "compiler bug: " + container + " is not a supported type parameter container");
        }
    }

    JCExpression makeTypeDeclarationLiteral(TypeDeclaration declaration) {
        JCExpression classLiteral = makeUnerasedClassLiteral(declaration);
        return makeMetamodelInvocation("getOrCreateMetamodel", List.of(classLiteral), null);
    }

    public JCExpression transformStringExpression(Tree.StringTemplate expr) {
        at(expr);
        JCExpression builder;
        builder = make().NewClass(null, null, naming.makeFQIdent("java","lang","StringBuilder"), List.<JCExpression>nil(), null);

        java.util.List<Tree.StringLiteral> literals = expr.getStringLiterals();
        java.util.List<Tree.Expression> expressions = expr.getExpressions();
        for (int ii = 0; ii < literals.size(); ii += 1) {
            Tree.StringLiteral literal = literals.get(ii);
            if (!literal.getText().isEmpty()) {// ignore empty string literals
                at(literal);
                builder = make().Apply(null, makeSelect(builder, "append"), List.<JCExpression>of(transform(literal)));
            }
            if (ii == expressions.size()) {
                // The loop condition includes the last literal, so break out
                // after that because we've already exhausted all the expressions
                break;
            }
            Tree.Expression expression = expressions.get(ii);
            at(expression);
            // Here in both cases we don't need a type cast for erasure
            if (isCeylonBasicType(expression.getTypeModel())) {// TODO: Test should be erases to String, long, int, boolean, char, byte, float, double
                // If erases to a Java primitive just call append, don't box it just to call format.
                String method = isCeylonCharacter(expression.getTypeModel()) ? "appendCodePoint" : "append";
                builder = make().Apply(null, makeSelect(builder, method), List.<JCExpression>of(transformExpression(expression, BoxingStrategy.UNBOXED, null)));
            } else {
                JCMethodInvocation formatted = make().Apply(null, makeSelect(transformExpression(expression), "toString"), List.<JCExpression>nil());
                builder = make().Apply(null, makeSelect(builder, "append"), List.<JCExpression>of(formatted));
            }
        }

        return make().Apply(null, makeSelect(builder, "toString"), List.<JCExpression>nil());
    }

    public JCExpression transform(Tree.SequenceEnumeration value) {
        return transform(value, null);
    }
   
    private JCExpression transform(Tree.SequenceEnumeration value, ProducedType expectedType) {
        at(value);
        if (value.getSequencedArgument() != null) {
            Tree.SequencedArgument sequencedArgument = value.getSequencedArgument();
            java.util.List<Tree.PositionalArgument> list = sequencedArgument.getPositionalArguments();
            if(list.isEmpty())
                return makeErroneous(value, "compiler bug: empty iterable literal with sequenced arguments: "+value);
            ProducedType seqElemType = typeFact().getIteratedType(value.getTypeModel());
            seqElemType = wrapInOptionalForInterop(seqElemType, expectedType, containsUncheckedNulls(list));
            ProducedType absentType = typeFact().getIteratedAbsentType(value.getTypeModel());
            return makeLazyIterable(sequencedArgument, seqElemType, absentType, 0);
        } else {
            return makeEmpty();
        }
    }

    private boolean containsUncheckedNulls(java.util.List<Tree.PositionalArgument> list) {
        for(Tree.PositionalArgument arg : list){
            if(arg instanceof Tree.ListedArgument){
                if(containsUncheckedNulls(((Tree.ListedArgument) arg).getExpression()))
                    return true;
            }else if(arg instanceof Tree.Comprehension){
                if(containsUncheckedNulls((Tree.Comprehension)arg))
                    return true;
            }else if(arg instanceof Tree.SpreadArgument){
                if(containsUncheckedNulls(((Tree.SpreadArgument) arg).getExpression()))
                    return true;
            }
        }
        return false;
    }

    private boolean containsUncheckedNulls(Tree.Term term){
        if(term instanceof Tree.Expression){
            return containsUncheckedNulls(((Tree.Expression) term).getTerm());
        }else if(term instanceof Tree.SequenceEnumeration){
            Tree.SequencedArgument sequencedArgument = ((Tree.SequenceEnumeration) term).getSequencedArgument();
            if(sequencedArgument == null)
                return false;
            return containsUncheckedNulls(sequencedArgument.getPositionalArguments());
        }else
            return com.redhat.ceylon.compiler.typechecker.tree.Util.hasUncheckedNulls(term);
    }
   
    private boolean containsUncheckedNulls(Tree.Comprehension comp) {
        Tree.ComprehensionClause clause = comp.getInitialComprehensionClause();
        while(clause instanceof Tree.ExpressionComprehensionClause == false){
            if(clause instanceof Tree.ForComprehensionClause)
                clause = ((Tree.ForComprehensionClause) clause).getComprehensionClause();
            else if(clause instanceof Tree.IfComprehensionClause)
                clause = ((Tree.IfComprehensionClause) clause).getComprehensionClause();
            else
                return false;// error recovery
        }
        if(clause instanceof Tree.ExpressionComprehensionClause)
            return containsUncheckedNulls(((Tree.ExpressionComprehensionClause) clause).getExpression());
        return false;
    }

    public JCExpression transform(Tree.Tuple value) {
        Tree.SequencedArgument sequencedArgument = value.getSequencedArgument();
        if(sequencedArgument != null){
            java.util.List<Tree.PositionalArgument> args = sequencedArgument.getPositionalArguments();
            return makeTuple(value.getTypeModel(), args);
        }
        // nothing in there
        return makeEmpty();
    }

    private JCExpression sequentialEmptiness(JCExpression sequential,
            ProducedType expectedType, ProducedType sequentialType) {
        int flags = 0;
        // make sure we detect that we're downcasting a sequential into a sequence if we know the comprehension is non-empty
        if(expectedType.getSupertype(typeFact().getSequenceDeclaration()) != null)
            flags = EXPR_DOWN_CAST;
        return applyErasureAndBoxing(sequential, sequentialType, false, true, BoxingStrategy.BOXED, expectedType, flags);
    }
   
    public JCExpression comprehensionAsSequential(Tree.Comprehension comprehension, ProducedType expectedType) {
        JCExpression sequential = iterableToSequential(transformComprehension(comprehension));
        ProducedType elementType = comprehension.getInitialComprehensionClause().getTypeModel();
        ProducedType sequentialType = typeFact().getSequentialType(elementType);
        return sequentialEmptiness(sequential, expectedType, sequentialType);
    }
   
    private JCExpression makeTuple(ProducedType tupleType, java.util.List<Tree.PositionalArgument> expressions) {
        if (typeFact().isEmptyType(tupleType)) {
            return makeEmpty();// A tuple terminated by empty
        }
       
        JCExpression tail = null;
        List<JCExpression> elems = List.<JCExpression>nil();
        for (int i = 0; i < expressions.size(); i++) {
            Tree.PositionalArgument expr = expressions.get(i);
            if (expr instanceof Tree.ListedArgument) {
                JCExpression elem = transformExpression(((Tree.ListedArgument) expr).getExpression());
                elems = elems.append(elem);
            } else if (expr instanceof Tree.SpreadArgument) {
                Tree.SpreadArgument spreadExpr = (Tree.SpreadArgument) expr;
                // make sure we get a spread part of the right type
                ProducedType spreadType = spreadExpr.getExpression().getTypeModel();
                ProducedType sequentialSpreadType = spreadType.getSupertype(typeFact().getSequentialDeclaration());
                if(sequentialSpreadType != null){
                    tail = transformExpression(spreadExpr.getExpression(), BoxingStrategy.BOXED, sequentialSpreadType);
                }else {
                    // must at least be an Iterable then
                    ProducedType iterableSpreadType = spreadType.getSupertype(typeFact().getIterableDeclaration());
                    tail = transformExpression(spreadExpr.getExpression(), BoxingStrategy.BOXED, iterableSpreadType);
                    tail = utilInvocation().sequentialOf(makeReifiedTypeArgument(typeFact().getIteratedType(iterableSpreadType)), tail);
                    ProducedType elementType = typeFact().getIteratedType(spreadExpr.getTypeModel());
                    ProducedType sequentialType = typeFact().getSequentialType(elementType);
                    ProducedType expectedType = spreadExpr.getTypeModel();
                    if (typeFact().isNonemptyIterableType(spreadExpr.getTypeModel())) {
                        expectedType = typeFact().getSequenceType(elementType);
                    } else if (typeFact().isIterableType(spreadExpr.getTypeModel())) {
                        expectedType = typeFact().getSequentialType(elementType);
                    }
                    tail = sequentialEmptiness(tail, expectedType, sequentialType);
                }
            } else if (expr instanceof Tree.Comprehension) {
                Tree.Comprehension comp = (Tree.Comprehension) expr;
                ProducedType elementType = expr.getTypeModel();
                ProducedType expectedType = comp.getInitialComprehensionClause().getPossiblyEmpty()
                        ? typeFact().getSequentialType(elementType)
                        : typeFact().getSequenceType(elementType);
                tail = comprehensionAsSequential(comp, expectedType);
            } else {
                return makeErroneous(expr, "compiler bug: " + expr.getNodeType() + " is not a supported tuple argument");
            }
        }
       
        if (!elems.isEmpty()) {
            JCExpression reifiedTypeArg = makeReifiedTypeArgument(tupleType.getTypeArgumentList().get(0));
            List<JCExpression> args = List.<JCExpression>of(reifiedTypeArg);
            args = args.append(make().NewArray(make().Type(syms().objectType), List.<JCExpression>nil(), elems));
            if (tail != null) {
                args = args.append(tail);
            }
            JCExpression typeExpr = makeJavaType(tupleType, JT_TYPE_ARGUMENT);
            /* Tuple.instance(reifiedElement, new Object[]{elem, elem, elem}, tail) */
            return make().TypeCast(typeExpr, make().Apply(
                    List.<JCExpression>nil(),
                    naming.makeQualIdent(make().QualIdent(syms().ceylonTupleType.tsym), "instance"),
                    args));
        } else {
            return tail;
        }
    }
   
    public JCTree transform(Tree.This expr) {
        at(expr);
        if (needDollarThis(expr.getScope())) {
            return naming.makeQuotedThis();
        }
        if (isWithinSyntheticClassBody()) {
            return naming.makeQualifiedThis(makeJavaType(expr.getTypeModel()));
        }
        return naming.makeThis();
    }

    public JCTree transform(Tree.Super expr) {
        throw new BugException(expr, "unreachable");
    }

    public JCTree transform(Tree.Outer expr) {
        at(expr);
       
        ProducedType outerClass = com.redhat.ceylon.compiler.typechecker.model.Util.getOuterClassOrInterface(expr.getScope());
        return makeOuterExpr(outerClass);
    }

    JCExpression makeOuterExpr(ProducedType outerClass) {
        final TypeDeclaration outerDeclaration = outerClass.getDeclaration();
        if (outerDeclaration instanceof Interface) {
            return makeQualifiedDollarThis(outerClass);
        }
        return naming.makeQualifiedThis(makeJavaType(outerClass));
    }

    //
    // Unary and Binary operators that can be overridden
   
    //
    // Unary operators

    public JCExpression transform(Tree.NotOp op) {
        // No need for an erasure cast since Term must be Boolean and we never need to erase that
        JCExpression term = transformExpression(op.getTerm(), CodegenUtil.getBoxingStrategy(op), null);
        JCUnary jcu = at(op).Unary(JCTree.NOT, term);
        return jcu;
    }

    public JCExpression transform(Tree.OfOp op) {
        if (op.getTerm() instanceof Tree.Super) {
            // This should be unreachable
            throw new BugException(op, "unreachable");
        }
        ProducedType expectedType = op.getType().getTypeModel();
        return transformExpression(op.getTerm(), CodegenUtil.getBoxingStrategy(op), expectedType, EXPR_DOWN_CAST);
    }

    public JCExpression transform(Tree.IsOp op) {
        // make sure we do not insert null checks if we're going to allow testing for null
        ProducedType expectedType = getOptionalTypeForInteropIfAllowed(op.getType().getTypeModel(), op.getTerm().getTypeModel(), op.getTerm());
        // we don't need any erasure type cast for an "is" test
        JCExpression expression = transformExpression(op.getTerm(), BoxingStrategy.BOXED, expectedType);
        at(op);
        Naming.SyntheticName varName = naming.temp();
        JCExpression test = makeOptimizedTypeTest(null, varName, op.getType().getTypeModel(), op.getTerm().getTypeModel());
        return makeLetExpr(varName, List.<JCStatement>nil(), make().Type(syms().objectType), expression, test);
    }

    public JCTree transform(Tree.Nonempty op) {
        // we don't need any erasure type cast for a "nonempty" test
        JCExpression expression = transformExpression(op.getTerm());
        at(op);
        Naming.SyntheticName varName = naming.temp();
        JCExpression test = makeNonEmptyTest(varName.makeIdent());
        return makeLetExpr(varName, List.<JCStatement>nil(), make().Type(syms().objectType), expression, test);
    }

    public JCTree transform(Tree.Exists op) {
        // for the purpose of checking if something is null, we need it boxed and optional, otherwise
        // for some Java calls if we consider it non-optional we will get an unwanted null check
        ProducedType termType = op.getTerm().getTypeModel();
        if(!typeFact().isOptionalType(termType)){
            termType = typeFact().getOptionalType(termType);
        }
        JCExpression expression = transformExpression(op.getTerm(), BoxingStrategy.BOXED, termType);
        at(op);
        return  make().Binary(JCTree.NE, expression, makeNull());
    }

    public JCExpression transform(Tree.PositiveOp op) {
        return transformOverridableUnaryOperator(op, op.getUnit().getInvertableDeclaration());
    }

    public JCExpression transform(Tree.NegativeOp op) {
        at(op);
        if (op.getTerm() instanceof Tree.NaturalLiteral) {
            try {
                Long l = literalValue(op);
                if (l != null) {
                    return make().Literal(l);
                }
            } catch (ErroneousException e) {
                // We should never get here since the error should have been
                // reported by the UnsupportedVisitor and the containing statement
                // replaced with a throw.
                return e.makeErroneous(this);
            }
        }
        if(op.getTerm() instanceof Tree.QualifiedMemberExpression){
            JCExpression ret = checkForByteLiterals((Tree.QualifiedMemberExpression)op.getTerm());
            if(ret != null)
                return at(op).Unary(JCTree.NEG, ret);
        }
        return transformOverridableUnaryOperator(op, op.getUnit().getInvertableDeclaration());
    }

    public JCExpression transform(Tree.UnaryOperatorExpression op) {
        return transformOverridableUnaryOperator(op, (ProducedType)null);
    }

    private JCExpression transformOverridableUnaryOperator(Tree.UnaryOperatorExpression op, Interface compoundType) {
        ProducedType leftType = getSupertype(op.getTerm(), compoundType);
        return transformOverridableUnaryOperator(op, leftType);
    }
   
    private JCExpression transformOverridableUnaryOperator(Tree.UnaryOperatorExpression op, ProducedType expectedType) {
        at(op);
        Tree.Term term = op.getTerm();

        OperatorTranslation operator = Operators.getOperator(op.getClass());
        if (operator == null) {
            return makeErroneous(op, "compiler bug: " + op.getClass() + " is an unhandled operator class");
        }

        JCExpression ret;
        if(operator.getOptimisationStrategy(op, this).useJavaOperator()){
            // optimisation for unboxed types
            JCExpression expr = transformExpression(term, BoxingStrategy.UNBOXED, expectedType);
            // unary + is essentially a NOOP
            if(operator == OperatorTranslation.UNARY_POSITIVE)
                return expr;
            ret = make().Unary(operator.javacOperator, expr);
            ret = unAutoPromote(ret, op.getTypeModel());
        } else {
            if(operator == OperatorTranslation.UNARY_POSITIVE) {
                // +x is essentiall a NOOP, but in this case the expected type
                // is the self type of Invertible, so use the type of op
                return transformExpression(term, BoxingStrategy.BOXED, op.getTypeModel());
            }
            ret = make().Apply(null, makeSelect(transformExpression(term, BoxingStrategy.BOXED, expectedType),
                    Naming.getGetterName(operator.ceylonMethod)), List.<JCExpression> nil());
        }
        return ret;
    }

    //
    // Binary operators
   
    public JCExpression transform(Tree.NotEqualOp op) {
        OperatorTranslation operator = Operators.OperatorTranslation.BINARY_EQUAL;
        OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(op, this);
       
        // we want it unboxed only if the operator is optimised
        // we don't care about the left erased type, since equals() is on Object
        JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), null);
        // we don't care about the right erased type, since equals() is on Object
        JCExpression expr = transformOverridableBinaryOperator(op.getRightTerm(), null, operator, optimisationStrategy, left, op.getTypeModel());
        return at(op).Unary(JCTree.NOT, expr);
    }

    public JCExpression transform(Tree.EqualOp op) {
        // we don't care about the left/right type since they're both Object
        return transformOverridableBinaryOperator(op, null, null);
    }

    public JCExpression transform(Tree.SegmentOp op) {
        // we need to get the range bound type
        final ProducedType type = getTypeArgument(getSupertype(op.getLeftTerm(), op.getUnit().getEnumerableDeclaration()));
        JCExpression startExpr = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, type);
        JCExpression lengthExpr = transformExpression(op.getRightTerm(), BoxingStrategy.UNBOXED, typeFact().getIntegerDeclaration().getType());
        return make().Apply(List.<JCExpression>of(makeJavaType(type, JT_TYPE_ARGUMENT)),
                naming.makeLanguageFunction("measure"),
                List.<JCExpression>of(makeReifiedTypeArgument(type), startExpr, lengthExpr));
    }

    public JCExpression transform(Tree.RangeOp op) {
        // we need to get the range bound type
        final ProducedType type = getTypeArgument(getSupertype(op.getLeftTerm(), op.getUnit().getEnumerableDeclaration()));
        JCExpression lower = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, type);
        JCExpression upper = transformExpression(op.getRightTerm(), BoxingStrategy.BOXED, type);
        return make().Apply(List.<JCExpression>of(makeJavaType(type, JT_TYPE_ARGUMENT)),
                naming.makeLanguageFunction("span"),
                List.<JCExpression>of(makeReifiedTypeArgument(type), lower, upper));
    }

    public JCExpression transform(Tree.EntryOp op) {
        // no erasure cast needed for both terms
        JCExpression key = transformExpression(op.getLeftTerm());
        JCExpression elem = transformExpression(op.getRightTerm());
        ProducedType leftType = op.getLeftTerm().getTypeModel();
        ProducedType rightType = op.getRightTerm().getTypeModel();
        ProducedType entryType = typeFact().getEntryType(leftType, rightType);
        JCExpression typeExpr = makeJavaType(entryType, CeylonTransformer.JT_CLASS_NEW);
        return at(op).NewClass(null, null, typeExpr , List.<JCExpression> of(makeReifiedTypeArgument(leftType), makeReifiedTypeArgument(rightType), key, elem), null);
    }

    public JCExpression transform(Tree.DefaultOp op, ProducedType expectedType) {
        JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, typeFact().getOptionalType(op.getTypeModel()));
        // make sure we do not insert null checks if we're going to allow testing for null
        ProducedType rightExpectedType = getOptionalTypeForInteropIfAllowed(expectedType, op.getTypeModel(), op.getRightTerm());
        JCExpression right = transformExpression(op.getRightTerm(), BoxingStrategy.BOXED, rightExpectedType);
        Naming.SyntheticName varName = naming.temp();
        JCExpression varIdent = varName.makeIdent();
        JCExpression test = at(op).Binary(JCTree.NE, varIdent, makeNull());
        JCExpression cond = make().Conditional(test , varIdent, right);
        JCExpression typeExpr = makeJavaType(op.getTypeModel(), JT_NO_PRIMITIVES);
        return makeLetExpr(varName, null, typeExpr, left, cond);
    }

    public JCTree transform(Tree.ThenOp op) {
        JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.UNBOXED, typeFact().getBooleanDeclaration().getType());
        JCExpression right = transformExpression(op.getRightTerm(), CodegenUtil.getBoxingStrategy(op), op.getTypeModel());
        return make().Conditional(left , right, makeNull());
    }
   
    public JCTree transform(Tree.InOp op) {
        JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, typeFact().getObjectDeclaration().getType());
        JCExpression right = transformExpression(op.getRightTerm(), BoxingStrategy.BOXED, op.getRightTerm().getTypeModel()
            .getSupertype(typeFact().getCategoryDeclaration()));
        Naming.SyntheticName varName = naming.temp();
        JCExpression varIdent = varName.makeIdent();
        JCExpression contains = at(op).Apply(null, makeSelect(right, "contains"), List.<JCExpression> of(varIdent));
        JCExpression typeExpr = makeJavaType(op.getLeftTerm().getTypeModel(), JT_NO_PRIMITIVES);
        return makeLetExpr(varName, null, typeExpr, left, contains);
    }

    // Logical operators
   
    public JCExpression transform(Tree.LogicalOp op) {
        OperatorTranslation operator = Operators.getOperator(op.getClass());
        if(operator == null){
            return makeErroneous(op, "compiler bug: " + op.getNodeType() + " is not a supported logical operator");
        }
        // Both terms are Booleans and can't be erased to anything
        JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.UNBOXED, null);
        return transformLogicalOp(op, operator, left, op.getRightTerm());
    }

    private JCExpression transformLogicalOp(Node op, OperatorTranslation operator,
            JCExpression left, Tree.Term rightTerm) {
        // Both terms are Booleans and can't be erased to anything
        JCExpression right = transformExpression(rightTerm, BoxingStrategy.UNBOXED, null);

        return at(op).Binary(operator.javacOperator, left, right);
    }

    // Comparison operators
   
    public JCExpression transform(Tree.IdenticalOp op){
        // The only thing which might be unboxed is boolean, and we can follow the rules of == for optimising it,
        // which are simple and require that both types be booleans to be unboxed, otherwise they must be boxed
        OptimisationStrategy optimisationStrategy = OperatorTranslation.BINARY_EQUAL.getOptimisationStrategy(op, this);
        JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), null);
        JCExpression right = transformExpression(op.getRightTerm(), optimisationStrategy.getBoxingStrategy(), null);
        return at(op).Binary(JCTree.EQ, left, right);
    }
   
    public JCExpression transform(Tree.ComparisonOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getComparableDeclaration());
    }

    public JCExpression transform(Tree.CompareOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getComparableDeclaration());
    }

    public JCExpression transform(Tree.WithinOp op) {
        Tree.Term middle = op.getTerm();
        ProducedType middleType = middle.getTypeModel();
       
        Tree.Bound lowerBound = op.getLowerBound();
        OperatorTranslation lowerOp = Operators.getOperator(lowerBound instanceof Tree.OpenBound ? Tree.SmallerOp.class : Tree.SmallAsOp.class);
       
        Tree.Bound upperBound = op.getUpperBound();
        OperatorTranslation upperOp = Operators.getOperator(upperBound instanceof Tree.OpenBound ? Tree.SmallerOp.class : Tree.SmallAsOp.class);
       
        // If any of the terms is optimizable, then use optimized
        OptimisationStrategy opt;
        if (upperOp.isTermOptimisable(lowerBound.getTerm(), this) == OptimisationStrategy.OPTIMISE
                || upperOp.isTermOptimisable(middle, this) == OptimisationStrategy.OPTIMISE
                || upperOp.isTermOptimisable(upperBound.getTerm(), this) == OptimisationStrategy.OPTIMISE) {
            opt = OptimisationStrategy.OPTIMISE;
        } else {
            opt = OptimisationStrategy.NONE;
        }
       
        SyntheticName middleName = naming.alias("middle");
        List<JCStatement> vars = List.<JCStatement>of(makeVar(middleName,
                makeJavaType(middleType, opt.getBoxingStrategy() == BoxingStrategy.UNBOXED ? 0 : JT_NO_PRIMITIVES),
                transformExpression(middle, opt.getBoxingStrategy(), null)));
       
        JCExpression lower = transformBound(middleName, lowerOp, opt, middle, lowerBound, false);
        JCExpression upper = transformBound(middleName, upperOp, opt, middle, upperBound, true);
        at(op);
        OperatorTranslation andOp = Operators.getOperator(Tree.AndOp.class);
        OptimisationStrategy optimisationStrategy = OptimisationStrategy.OPTIMISE;
        return make().LetExpr(vars, transformOverridableBinaryOperator(andOp, optimisationStrategy, lower, upper, null, op.getTypeModel()));
    }

    public JCExpression transformBound(SyntheticName middle, final OperatorTranslation operator, final OptimisationStrategy optimisationStrategy, Tree.Term middleTerm, Tree.Bound bound, boolean isUpper) {
        ;
        final JCExpression left;
        final JCExpression right;
        if (isUpper) {
            left = middle.makeIdent();
            right = transformExpression(bound.getTerm(), optimisationStrategy.getBoxingStrategy(), null);
           
        } else {
            left = transformExpression(bound.getTerm(), optimisationStrategy.getBoxingStrategy(), null);
            right = middle.makeIdent();
        }
        at(bound);
        return transformOverridableBinaryOperator(operator, optimisationStrategy, left, right, null, bound.getTypeModel());
    }

    public JCExpression transform(Tree.ScaleOp op) {
        OperatorTranslation operator = Operators.getOperator(Tree.ScaleOp.class);
        Tree.Term scalableTerm = op.getRightTerm();
        ProducedType scalableTermType = getSupertype(scalableTerm, typeFact().getScalableDeclaration());
        SyntheticName scaleableName = naming.alias("scalable");
        JCVariableDecl scaleable = makeVar(scaleableName,
                makeJavaType(scalableTermType, JT_NO_PRIMITIVES),
                transformExpression(scalableTerm, BoxingStrategy.BOXED, scalableTermType));
       
        Tree.Term scaleTerm = op.getLeftTerm();
        SyntheticName scaleName = naming.alias("scale");
        ProducedType scaleType = getTypeArgument(scalableTermType, 0);
        JCExpression scaleValue;
        if (isCeylonInteger(scaleTerm.getTypeModel())
                && isCeylonFloat(scaleType)) {
            // Disgusting coercion
            scaleValue = transformExpression(scaleTerm, BoxingStrategy.UNBOXED, scalableTerm.getTypeModel());
            scaleValue = boxType(scaleValue, typeFact().getFloatDeclaration().getType());
        } else {
            scaleValue = transformExpression(scaleTerm, BoxingStrategy.BOXED, scaleType);
        }
        JCVariableDecl scale = makeVar(scaleName,
                makeJavaType(scaleType, JT_NO_PRIMITIVES),
                scaleValue);
       
        at(op);
        return make().LetExpr(List.<JCStatement>of(scale, scaleable),
                transformOverridableBinaryOperator(operator, OptimisationStrategy.NONE, scaleableName.makeIdent(), scaleName.makeIdent(), null, op.getTypeModel()));
    }
   
    // Arithmetic operators
   
    public JCExpression transform(Tree.ArithmeticOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getNumericDeclaration());
    }
   
    public JCExpression transform(Tree.SumOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getSummableDeclaration());
    }

    public JCExpression transform(Tree.DifferenceOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getInvertableDeclaration());
    }

    public JCExpression transform(Tree.RemainderOp op) {
        return transformOverridableBinaryOperator(op, op.getUnit().getIntegralDeclaration());
    }
   
    public JCExpression transform(Tree.PowerOp op) {
        if (Strategy.inlinePowerAsMultiplication(op)) {
            try {
                Long power = getIntegerLiteralPower(op);
                if(power != null)
                    return transformOptimizedIntegerPower(op.getLeftTerm(), power);
            } catch (ErroneousException e) {
                // fall through and let the default transformation handle this
            }
        }
        return transformOverridableBinaryOperator(op, op.getUnit().getExponentiableDeclaration(), 1);
    }

    /**
     * Returns the literal value of the power in the given power expression,
     * or null if the power is not an integer literal (or negation of an
     * integer literal)
     * @throws ErroneousException
     */
    static java.lang.Long getIntegerLiteralPower(Tree.PowerOp op)
            throws ErroneousException {
        java.lang.Long power;
        Tree.Term term = Util.unwrapExpressionUntilTerm(op.getRightTerm());
        if (term instanceof Tree.NaturalLiteral) {
            power = literalValue((Tree.NaturalLiteral)term);
        } else if (term instanceof Tree.NegativeOp &&
                ((Tree.NegativeOp)term).getTerm() instanceof Tree.NaturalLiteral) {
            power = literalValue((Tree.NegativeOp)term);
        } else {
            power = null;
        }
        return power;
    }
   
    private JCExpression transformOptimizedIntegerPower(Tree.Term base,
            Long power) {
        JCExpression baseExpr = transformExpression(base, BoxingStrategy.UNBOXED, base.getTypeModel());
        if (power == 1) {
            return baseExpr;
        }
        SyntheticName baseAlias = naming.alias("base");
        JCExpression multiplications = baseAlias.makeIdent();
        while (power > 1) {
            power--;
            multiplications = make().Binary(JCTree.MUL, multiplications, baseAlias.makeIdent());
        }
        return make().LetExpr(makeVar(baseAlias,
                    makeJavaType(base.getTypeModel()),
                    baseExpr),
                multiplications);
    }

    public JCExpression transform(Tree.BitwiseOp op) {
        ProducedType leftType = getSupertype(op.getLeftTerm(), typeFact().getSetDeclaration());
        ProducedType rightType = getSupertype(op.getRightTerm(), typeFact().getSetDeclaration());
      return transformOverridableBinaryOperator(op, leftType, rightType);
    }   

    // Overridable binary operators
   

    private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op, Interface compoundType) {
        return transformOverridableBinaryOperator(op, compoundType, 0);
    }
   
    private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op, Interface compoundType, int typeArgumentToUse) {
        ProducedType leftType = getSupertype(op.getLeftTerm(), compoundType);
        // the right type always only depends on the LHS so let's not try to find it on the right side because it may
        // be undecidable: https://github.com/ceylon/ceylon-compiler/issues/1535
        ProducedType rightType = getTypeArgument(leftType, typeArgumentToUse);
        // we do have a special case which is when the LHS is Float and RHS is Integer and the typechecker allowed coercion
        if(getSupertype(op.getLeftTerm(), typeFact().getFloatDeclaration()) != null
                && getSupertype(op.getRightTerm(), typeFact().getIntegerDeclaration()) != null){
            // keep the RHS type then, since floats are not integers, the whole thing is resolved in the Java impl of Float with
            // special hidden operator methods
            rightType = typeFact().getIntegerDeclaration().getType();
            // Also keep the LHS type as Float since it's final and the special methods wouldn't be found in any supertype of compountType
            leftType = typeFact().getFloatDeclaration().getType();
        }
        return transformOverridableBinaryOperator(op, leftType, rightType);
    }

    private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op, ProducedType leftType, ProducedType rightType) {
        OperatorTranslation operator = Operators.getOperator(op.getClass());
        if (operator == null) {
            return makeErroneous(op, "compiler bug: " + op.getClass() +" is an unhandled operator");
        }
        OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(op, this);

        at(op);
        JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), leftType);
        JCExpression right = transformExpression(op.getRightTerm(), optimisationStrategy.getBoxingStrategy(), rightType);
        return transformOverridableBinaryOperator(operator, optimisationStrategy, left, right, op.getRightTerm(), op.getTypeModel());
    }

    private JCExpression transformOverridableBinaryOperator(Tree.Term rightTerm, ProducedType rightType,
            OperatorTranslation operator, OptimisationStrategy optimisationStrategy,
            JCExpression left, ProducedType expectedType) {
        JCExpression right = transformExpression(rightTerm, optimisationStrategy.getBoxingStrategy(), rightType);
        return transformOverridableBinaryOperator(operator, optimisationStrategy, left, right, rightTerm, expectedType);
    }

    private JCExpression transformOverridableBinaryOperator(OperatorTranslation originalOperator,
            OptimisationStrategy optimisationStrategy,
            JCExpression left, JCExpression right,
            Tree.Term rightTerm, ProducedType expectedType) {
        JCExpression result = null;
       
        // optimise if we can
        if(optimisationStrategy.useJavaOperator()){
            result = make().Binary(originalOperator.javacOperator, left, right);
            if (rightTerm != null) {
                result = unAutoPromote(result, expectedType);
            }
            return result;
        }

        boolean loseComparison =
                originalOperator == OperatorTranslation.BINARY_SMALLER
                || originalOperator == OperatorTranslation.BINARY_SMALL_AS
                || originalOperator == OperatorTranslation.BINARY_LARGER
                || originalOperator == OperatorTranslation.BINARY_LARGE_AS;

        // for comparisons we need to invoke compare()
        OperatorTranslation actualOperator = originalOperator;
        if (loseComparison) {
            actualOperator = Operators.OperatorTranslation.BINARY_COMPARE;
        }

        List<JCExpression> args = List.of(right);
        List<JCExpression> typeArgs = null;
       
        // Set operators need reified generics
        if(originalOperator == OperatorTranslation.BINARY_UNION
                || originalOperator == OperatorTranslation.BINARY_INTERSECTION
                || originalOperator == OperatorTranslation.BINARY_COMPLEMENT){
            ProducedType otherSetElementType = typeFact().getIteratedType(rightTerm.getTypeModel());
            args = args.prepend(makeReifiedTypeArgument(otherSetElementType));
            typeArgs = List.<JCExpression>of(makeJavaType(otherSetElementType, JT_TYPE_ARGUMENT));
        }
       
        result = make().Apply(typeArgs, makeSelect(left, actualOperator.ceylonMethod), args);

        if (loseComparison) {
            // We cheat slightly bu using == instead of equals, but since those values
            // don't override equals the effect is the same
            result = make().Binary(originalOperator.javacValueOperator, result, makeLanguageValue(originalOperator.ceylonValue));
        }

        return result;
    }

    //
    // Operator-Assignment expressions

    public JCExpression transform(final Tree.ArithmeticAssignmentOp op){
        final AssignmentOperatorTranslation operator = Operators.getAssignmentOperator(op.getClass());
        if(operator == null){
            return makeErroneous(op, "compiler bug: "+op.getNodeType() + " is not a supported arithmetic assignment operator");
        }

        // see if we can optimise it
        if(op.getUnboxed() && CodegenUtil.isDirectAccessVariable(op.getLeftTerm())){
            return optimiseAssignmentOperator(op, operator);
        }
       
        // we can use unboxed types if both operands are unboxed
        final boolean boxResult = !op.getUnboxed();
       
        // find the proper type
        Interface compoundType = op.getUnit().getNumericDeclaration();
        if(op instanceof Tree.AddAssignOp){
            compoundType = op.getUnit().getSummableDeclaration();
        }else if(op instanceof Tree.SubtractAssignOp){
            compoundType = op.getUnit().getInvertableDeclaration();
        }else if(op instanceof Tree.RemainderAssignOp){
            compoundType = op.getUnit().getIntegralDeclaration();
        }
       
        final ProducedType leftType = getSupertype(op.getLeftTerm(), compoundType);
        final ProducedType resultType = getMostPreciseType(op.getLeftTerm(), getTypeArgument(leftType, 0));
        // Normally we don't look at the RHS type because it can lead to unknown types, but if we want to extract its
        // underlying type we have to, and we deal with any eventual unknown type. Presumably unknown types will not have
        // any useful underlying type anyways.
        // Note  that looking at the RHS allows us to not have the issue of using the LHS type wrongly for the RHS type when
        // the LHS type is Float and the RHS type is Integer with implicit Float coercion
        ProducedType rightSupertype = getSupertype(op.getRightTerm(), compoundType);
        if (rightSupertype == null || rightSupertype.isUnknown()) {
            // supertype could be null if, e.g. right type is Nothing
            rightSupertype = leftType;
        }
        ProducedType rightTypeArgument = getTypeArgument(rightSupertype);
        if(rightTypeArgument == null || rightTypeArgument.isUnknown())
            rightTypeArgument = getTypeArgument(leftType);
        final ProducedType rightType = getMostPreciseType(op.getLeftTerm(), rightTypeArgument);

        // we work on boxed types
        return transformAssignAndReturnOperation(op, op.getLeftTerm(), boxResult,
                leftType, resultType,
                new AssignAndReturnOperationFactory(){
            @Override
            public JCExpression getNewValue(JCExpression previousValue) {
                // make this call: previousValue OP RHS
                JCExpression ret = transformOverridableBinaryOperator(op.getRightTerm(), rightType,
                        operator.binaryOperator,
                        boxResult ? OptimisationStrategy.NONE : OptimisationStrategy.OPTIMISE,
                        previousValue, op.getTypeModel());
                return ret;
            }
        });
    }

    public JCExpression transform(final Tree.BitwiseAssignmentOp op){
        final AssignmentOperatorTranslation operator = Operators.getAssignmentOperator(op.getClass());
        if(operator == null){
            return makeErroneous(op, "compiler bug: "+op.getNodeType() +" is not a supported bitwise assignment operator");
        }
     
        ProducedType valueType = op.getLeftTerm().getTypeModel();
        final ProducedType rightType = getSupertype(op.getRightTerm(), typeFact().getSetDeclaration());
       
        return transformAssignAndReturnOperation(op, op.getLeftTerm(), false, valueType, valueType, new AssignAndReturnOperationFactory() {
            @Override
            public JCExpression getNewValue(JCExpression previousValue) {
              JCExpression result = transformOverridableBinaryOperator(op.getRightTerm(), rightType, operator.binaryOperator, OptimisationStrategy.NONE, previousValue, op.getTypeModel());
              return result;
            }
        });
    }

    public JCExpression transform(final Tree.LogicalAssignmentOp op){
        final AssignmentOperatorTranslation operator = Operators.getAssignmentOperator(op.getClass());
        if(operator == null){
            return makeErroneous(op, "compiler bug: "+op.getNodeType() + " is not a supported logical assignment operator");
        }
       
        // optimise if we can
        if(CodegenUtil.isDirectAccessVariable(op.getLeftTerm())){
            return optimiseAssignmentOperator(op, operator);
        }
       
        ProducedType valueType = op.getLeftTerm().getTypeModel();
        // we work on unboxed types
        return transformAssignAndReturnOperation(op, op.getLeftTerm(), false,
                valueType, valueType, new AssignAndReturnOperationFactory(){
            @Override
            public JCExpression getNewValue(JCExpression previousValue) {
                // make this call: previousValue OP RHS
                return transformLogicalOp(op, operator.binaryOperator,
                        previousValue, op.getRightTerm());
            }
        });
    }

    private JCExpression optimiseAssignmentOperator(final Tree.AssignmentOp op, final AssignmentOperatorTranslation operator) {
        // we don't care about their types since they're unboxed and we know it
        JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.UNBOXED, null);
        JCExpression right = transformExpression(op.getRightTerm(), BoxingStrategy.UNBOXED, null);
        return at(op).Assignop(operator.javacOperator, left, right);
    }

    // Postfix operator
   
    public JCExpression transform(Tree.PostfixOperatorExpression expr) {
        OperatorTranslation operator = Operators.getOperator(expr.getClass());
        if(operator == null){
            return makeErroneous(expr, "compiler bug "+expr.getNodeType() + " is not yet supported");
        }
       
        OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(expr, this);
        boolean canOptimise = optimisationStrategy.useJavaOperator();
       
        // only fully optimise if we don't have to access the getter/setter
        if(canOptimise && CodegenUtil.isDirectAccessVariable(expr.getTerm())){
            JCExpression term = transformExpression(expr.getTerm(), BoxingStrategy.UNBOXED, expr.getTypeModel());
            return at(expr).Unary(operator.javacOperator, term);
        }
       
        Tree.Term term = Util.unwrapExpressionUntilTerm(expr.getTerm());
       
        Interface compoundType = expr.getUnit().getOrdinalDeclaration();
        ProducedType valueType = getSupertype(expr.getTerm(), compoundType);
        ProducedType returnType = getMostPreciseType(term, getTypeArgument(valueType, 0));

        List<JCVariableDecl> decls = List.nil();
        List<JCStatement> stats = List.nil();
        JCExpression result = null;
        // we can optimise that case a bit sometimes
        boolean boxResult = !canOptimise;

        // attr++
        // (let $tmp = attr; attr = $tmp.getSuccessor(); $tmp;)
        if(term instanceof Tree.BaseMemberExpression){
            JCExpression getter = transform((Tree.BaseMemberExpression)term, null);
            at(expr);
            // Type $tmp = attr
            JCExpression exprType = makeJavaType(returnType, boxResult ? JT_NO_PRIMITIVES : 0);
            Name varName = naming.tempName("op");
            // make sure we box the results if necessary
            getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, returnType);
            JCVariableDecl tmpVar = make().VarDef(make().Modifiers(0), varName, exprType, getter);
            decls = decls.prepend(tmpVar);

            // attr = $tmp.getSuccessor()
            JCExpression successor;
            if(canOptimise){
                // use +1/-1 if we can optimise a bit
                successor = make().Binary(operator == OperatorTranslation.UNARY_POSTFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
                        make().Ident(varName), makeInteger(1));
                successor = unAutoPromote(successor, returnType);
            }else{
                successor = make().Apply(null,
                                         makeSelect(make().Ident(varName), operator.ceylonMethod),
                                         List.<JCExpression>nil());
                // make sure the result is boxed if necessary, the result of successor/predecessor is always boxed
                successor = boxUnboxIfNecessary(successor, true, term.getTypeModel(), CodegenUtil.getBoxingStrategy(term));
            }
            JCExpression assignment = transformAssignment(expr, term, successor);
            stats = stats.prepend(at(expr).Exec(assignment));

            // $tmp
            result = make().Ident(varName);
        }
        else if(term instanceof Tree.QualifiedMemberExpression){
            // e.attr++
            // (let $tmpE = e, $tmpV = $tmpE.attr; $tmpE.attr = $tmpV.getSuccessor(); $tmpV;)
            Tree.QualifiedMemberExpression qualified = (Tree.QualifiedMemberExpression) term;
            boolean isSuper = isSuperOrSuperOf(qualified.getPrimary());
            boolean isPackage = Util.unwrapExpressionUntilTerm(qualified.getPrimary()) instanceof Tree.Package;
            // transform the primary, this will get us a boxed primary
            JCExpression e = transformQualifiedMemberPrimary(qualified);
            at(expr);
           
            // Type $tmpE = e
            JCExpression exprType = makeJavaType(qualified.getTarget().getQualifyingType(), JT_NO_PRIMITIVES);
            Name varEName = naming.tempName("opE");
            JCVariableDecl tmpEVar = make().VarDef(make().Modifiers(0), varEName, exprType, e);

            // Type $tmpV = $tmpE.attr
            JCExpression attrType = makeJavaType(returnType, boxResult ? JT_NO_PRIMITIVES : 0);
            Name varVName = naming.tempName("opV");
            JCExpression getter;
            if (isSuper) {
                getter = transformMemberExpression(qualified, transformSuper(qualified), null);
            } else if (isPackage) {
                getter = transformMemberExpression(qualified, null, null);
            } else {
                getter = transformMemberExpression(qualified, make().Ident(varEName), null);
            }
            // make sure we box the results if necessary
            getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, returnType);
            JCVariableDecl tmpVVar = make().VarDef(make().Modifiers(0), varVName, attrType, getter);

            decls = decls.prepend(tmpVVar);
            if (!isSuper && !isPackage) {
                // define all the variables
                decls = decls.prepend(tmpEVar);
            }
           
            // $tmpE.attr = $tmpV.getSuccessor()
            JCExpression successor;
            if(canOptimise){
                // use +1/-1 if we can optimise a bit
                successor = make().Binary(operator == OperatorTranslation.UNARY_POSTFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
                        make().Ident(varVName), makeInteger(1));
                successor = unAutoPromote(successor, returnType);
            }else{
                successor = make().Apply(null,
                                         makeSelect(make().Ident(varVName), operator.ceylonMethod),
                                         List.<JCExpression>nil());
                //  make sure the result is boxed if necessary, the result of successor/predecessor is always boxed
                successor = boxUnboxIfNecessary(successor, true, term.getTypeModel(), CodegenUtil.getBoxingStrategy(term));
            }
            JCExpression assignment = transformAssignment(expr, term, isSuper ? transformSuper(qualified) : make().Ident(varEName), successor);
            stats = stats.prepend(at(expr).Exec(assignment));
           
            // $tmpV
            result = make().Ident(varVName);
        }else{
            return makeErroneous(term, "compiler bug: " + term.getNodeType() + " is not supported yet");
        }
        // e?.attr++ is probably not legal
        // a[i]++ is not for M1 but will be:
        // (let $tmpA = a, $tmpI = i, $tmpV = $tmpA.item($tmpI); $tmpA.setItem($tmpI, $tmpV.getSuccessor()); $tmpV;)
        // a?[i]++ is probably not legal
        // a[i1..i1]++ and a[i1...]++ are probably not legal
        // a[].attr++ and a[].e.attr++ are probably not legal

        return make().LetExpr(decls, stats, result);
    }
   
    // Prefix operator
   
    public JCExpression transform(final Tree.PrefixOperatorExpression expr) {
        final OperatorTranslation operator = Operators.getOperator(expr.getClass());
        if(operator == null){
            return makeErroneous(expr, "compiler bug: "+expr.getNodeType() + " is not supported yet");
        }
       
        OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(expr, this);
        final boolean canOptimise = optimisationStrategy.useJavaOperator();
       
        Tree.Term term = expr.getTerm();
        // only fully optimise if we don't have to access the getter/setter
        if(canOptimise && CodegenUtil.isDirectAccessVariable(term)){
            JCExpression jcTerm = transformExpression(term, BoxingStrategy.UNBOXED, expr.getTypeModel());
            return at(expr).Unary(operator.javacOperator, jcTerm);
        }

        Interface compoundType = expr.getUnit().getOrdinalDeclaration();
        ProducedType valueType = getSupertype(term, compoundType);
        final ProducedType returnType = getMostPreciseType(term, getTypeArgument(valueType, 0));
       
        // we work on boxed types unless we could have optimised
        return transformAssignAndReturnOperation(expr, term, !canOptimise,
                valueType, returnType, new AssignAndReturnOperationFactory(){
            @Override
            public JCExpression getNewValue(JCExpression previousValue) {
                // use +1/-1 if we can optimise a bit
                if(canOptimise){
                    JCExpression ret = make().Binary(operator == OperatorTranslation.UNARY_PREFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
                            previousValue, makeInteger(1));
                    ret = unAutoPromote(ret, returnType);
                    return ret;
                }
                // make this call: previousValue.getSuccessor() or previousValue.getPredecessor()
                return make().Apply(null, makeSelect(previousValue, operator.ceylonMethod), List.<JCExpression>nil());
            }
        });
    }
   
    //
    // Function to deal with expressions that have side-effects
   
    private interface AssignAndReturnOperationFactory {
        JCExpression getNewValue(JCExpression previousValue);
    }
   
    private JCExpression transformAssignAndReturnOperation(Node operator, Tree.Term term,
            boolean boxResult, ProducedType valueType, ProducedType returnType,
            AssignAndReturnOperationFactory factory){
       
        List<JCVariableDecl> decls = List.nil();
        List<JCStatement> stats = List.nil();
        JCExpression result = null;
        // attr
        // (let $tmp = OP(attr); attr = $tmp; $tmp)
        if(term instanceof Tree.BaseMemberExpression){
            JCExpression getter = transform((Tree.BaseMemberExpression)term, null);
            at(operator);
            // Type $tmp = OP(attr);
            JCExpression exprType = makeJavaType(returnType, boxResult ? JT_NO_PRIMITIVES : 0);
            Name varName = naming.tempName("op");
            // make sure we box the results if necessary
            getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, valueType);
            JCExpression newValue = factory.getNewValue(getter);
            // no need to box/unbox here since newValue and $tmpV share the same boxing type
            JCVariableDecl tmpVar = make().VarDef(make().Modifiers(0), varName, exprType, newValue);
            decls = decls.prepend(tmpVar);

            // attr = $tmp
            // make sure the result is unboxed if necessary, $tmp may be boxed
            JCExpression value = make().Ident(varName);
            value = boxUnboxIfNecessary(value, boxResult, term.getTypeModel(), CodegenUtil.getBoxingStrategy(term));
            JCExpression assignment = transformAssignment(operator, term, value);
            stats = stats.prepend(at(operator).Exec(assignment));
           
            // $tmp
            // return, with the box type we asked for
            result = make().Ident(varName);
        }
        else if(term instanceof Tree.QualifiedMemberExpression){
            // e.attr
            // (let $tmpE = e, $tmpV = OP($tmpE.attr); $tmpE.attr = $tmpV; $tmpV;)
            Tree.QualifiedMemberExpression qualified = (Tree.QualifiedMemberExpression) term;
            boolean isSuper = isSuperOrSuperOf(qualified.getPrimary());
            // transform the primary, this will get us a boxed primary
            JCExpression e = transformQualifiedMemberPrimary(qualified);
            at(operator);
           
            // Type $tmpE = e
            JCExpression exprType = makeJavaType(qualified.getTarget().getQualifyingType(), JT_NO_PRIMITIVES);
            Name varEName = naming.tempName("opE");
            JCVariableDecl tmpEVar = make().VarDef(make().Modifiers(0), varEName, exprType, e);

            // Type $tmpV = OP($tmpE.attr)
            JCExpression attrType = makeJavaType(returnType, boxResult ? JT_NO_PRIMITIVES : 0);
            Name varVName = naming.tempName("opV");
            JCExpression getter = transformMemberExpression(qualified, isSuper ? transformSuper(qualified) : make().Ident(varEName), null);
            // make sure we box the results if necessary
            getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, valueType);
            JCExpression newValue = factory.getNewValue(getter);
            // no need to box/unbox here since newValue and $tmpV share the same boxing type
            JCVariableDecl tmpVVar = make().VarDef(make().Modifiers(0), varVName, attrType, newValue);

            // define all the variables
            decls = decls.prepend(tmpVVar);
            if (!isSuper) {
                decls = decls.prepend(tmpEVar);
            }
           
            // $tmpE.attr = $tmpV
            // make sure $tmpV is unboxed if necessary
            JCExpression value = make().Ident(varVName);
            value = boxUnboxIfNecessary(value, boxResult, term.getTypeModel(), CodegenUtil.getBoxingStrategy(term));
            JCExpression assignment = transformAssignment(operator, term, isSuper ? transformSuper(qualified) : make().Ident(varEName), value);
            stats = stats.prepend(at(operator).Exec(assignment));
           
            // $tmpV
            // return, with the box type we asked for
            result = make().Ident(varVName);
        }else{
            return makeErroneous(operator, "compiler bug: " + term.getNodeType() + " is not a supported assign and return operator");
        }
        // OP(e?.attr) is probably not legal
        // OP(a[i]) is not for M1 but will be:
        // (let $tmpA = a, $tmpI = i, $tmpV = OP($tmpA.item($tmpI)); $tmpA.setItem($tmpI, $tmpV); $tmpV;)
        // OP(a?[i]) is probably not legal
        // OP(a[i1..i1]) and OP(a[i1...]) are probably not legal
        // OP(a[].attr) and OP(a[].e.attr) are probably not legal

        return make().LetExpr(decls, stats, result);
    }


    public JCExpression transform(Tree.Parameter param) {
        // Transform the expression marking that we're inside a defaulted parameter for $this-handling
        //needDollarThis  = true;
        JCExpression expr;
        at(param);
       
        if (Strategy.hasDefaultParameterValueMethod(param.getParameterModel())) {
            Tree.SpecifierOrInitializerExpression spec = Decl.getDefaultArgument(param);
            Scope container = param.getParameterModel().getModel().getContainer();
            boolean classParameter = container instanceof ClassOrInterface;
            ClassOrInterface oldWithinDefaultParameterExpression = withinDefaultParameterExpression;
            if(classParameter)
                withinDefaultParameterExpression((ClassOrInterface) container);
            if (param instanceof Tree.FunctionalParameterDeclaration) {
                Tree.FunctionalParameterDeclaration fpTree = (Tree.FunctionalParameterDeclaration) param;
                Tree.SpecifierExpression lazy = (Tree.SpecifierExpression)spec;
                Method fp = (Method)fpTree.getParameterModel().getModel();
               
                expr = CallableBuilder.anonymous(gen(), lazy.getExpression(),
                        ((Tree.MethodDeclaration)fpTree.getTypedDeclaration()).getParameterLists(),
                        getTypeForFunctionalParameter(fp),
                        true).build();
            } else {
                expr = transformExpression(spec.getExpression(),
                        CodegenUtil.getBoxingStrategy(param.getParameterModel().getModel()),
                        param.getParameterModel().getType());
            }
            if(classParameter)
                withinDefaultParameterExpression(oldWithinDefaultParameterExpression);
        } else {
            expr = makeErroneous(param, "compiler bug: no default parameter value method");
        }
        //needDollarThis = false;
        return expr;
    }
   
    protected final JCExpression transformArg(SimpleInvocation invocation, int argIndex) {
        final Tree.Term expr = invocation.getArgumentExpression(argIndex);
        if (invocation.hasParameter(argIndex)) {
            ProducedType type = invocation.getParameterType(argIndex);
            if (invocation.isParameterSequenced(argIndex)
                    // Java methods need their underlying type preserved
                    && !invocation.isJavaMethod()) {
                if (!invocation.isArgumentSpread(argIndex)) {
                    // If the parameter is sequenced and the argument is not ...
                    // then the expected type of the *argument* is the type arg to Iterator
                    type = typeFact().getIteratedType(type);
                } else  if (invocation.getArgumentType(argIndex).getSupertype(typeFact().getSequentialDeclaration())
                        == null) {
                    // On the other hand, if the parameter is sequenced and the argument is spread,
                    // but not sequential, then transformArguments() will use getSequence(),
                    // so we only need to expect an Iterable type
                    type = com.redhat.ceylon.compiler.typechecker.model.Util.producedType(
                            typeFact().getIterableDeclaration(),
                            typeFact().getIteratedType(type), typeFact().getIteratedAbsentType(type));
                }
            }
            BoxingStrategy boxingStrategy = invocation.getParameterBoxingStrategy(argIndex);
            int flags = 0;
            if(!invocation.isParameterRaw(argIndex))
                flags |= ExpressionTransformer.EXPR_EXPECTED_TYPE_NOT_RAW;
            if(invocation.isParameterWithConstrainedTypeParameters(argIndex))
                flags |= ExpressionTransformer.EXPR_EXPECTED_TYPE_HAS_CONSTRAINED_TYPE_PARAMETERS;
            if(invocation.isParameterWithDependentCovariantTypeParameters(argIndex))
                flags |= ExpressionTransformer.EXPR_EXPECTED_TYPE_HAS_DEPENDENT_COVARIANT_TYPE_PARAMETERS;
            JCExpression ret = transformExpression(expr,
                    boxingStrategy,
                    type, flags);
            return ret;
        } else {
            // Overloaded methods don't have a reference to a parameter
            // so we have to treat them differently. Also knowing it's
            // overloaded we know we're dealing with Java code so we unbox
            ProducedType type = expr.getTypeModel();
            return expressionGen().transformExpression(expr,
                    BoxingStrategy.UNBOXED,
                    type);
        }
    }
   
    private final List<ExpressionAndType> transformArgumentList(Invocation invocation, TransformedInvocationPrimary transformedPrimary, CallBuilder callBuilder) {
        return transformArguments(invocation, transformedPrimary, callBuilder);  
    }
   
    private final List<ExpressionAndType> transformArguments(Invocation invocation,
            TransformedInvocationPrimary transformedPrimary, CallBuilder callBuilder) {
        ListBuffer<ExpressionAndType> result = ListBuffer.<ExpressionAndType>lb();
        withinInvocation(false);
        appendImplicitArguments(invocation, transformedPrimary, result);
        // Explicit arguments
        if (invocation instanceof SuperInvocation) {
            withinSuperInvocation(((SuperInvocation)invocation).getSub());
            result.addAll(transformArgumentsForSimpleInvocation((SimpleInvocation)invocation, callBuilder));
            withinSuperInvocation(null);
        } else if (invocation instanceof NamedArgumentInvocation) {
            result.addAll(transformArgumentsForNamedInvocation((NamedArgumentInvocation)invocation));
        } else if (invocation instanceof CallableSpecifierInvocation) {
            result.addAll(transformArgumentsForCallableSpecifier((CallableSpecifierInvocation)invocation));
        } else if (invocation instanceof SimpleInvocation) {
            if(invocation.isUnknownArguments())
                result.add(transformUnknownArguments((SimpleInvocation) invocation, callBuilder));
            else
                result.addAll(transformArgumentsForSimpleInvocation((SimpleInvocation)invocation, callBuilder));
        } else {
            throw BugException.unhandledCase(invocation);
        }
        withinInvocation(true);
        return result.toList();
    }

    private void appendImplicitArguments(
            Invocation invocation,
            TransformedInvocationPrimary transformedPrimary,
            ListBuffer<ExpressionAndType> result) {
        // Implicit arguments
        // except for Java array constructors
        Declaration primaryDeclaration = invocation.getPrimaryDeclaration();
        Tree.Term primary = invocation.getPrimary();
        if(primaryDeclaration instanceof Class == false
                || !isJavaArray(((Class) primaryDeclaration).getType())){
            invocation.addReifiedArguments(result);
        }
        if (!(primary instanceof Tree.BaseTypeExpression)
                && !(primary instanceof Tree.QualifiedTypeExpression)
                && Invocation.onValueType(this, primary, primaryDeclaration)
                && transformedPrimary != null) {
            result.add(new ExpressionAndType(transformedPrimary.expr,
                    makeJavaType(primary.getTypeModel())));  
        }
    }
   
    private ExpressionAndType transformUnknownArguments(SimpleInvocation invocation, CallBuilder callBuilder){

        // doesn't really matter, assume Object, it's not used
        ProducedType iteratedType = typeFact().getObjectDeclaration().getType();
        // the single spread argument which is allowed
        JCExpression expr = invocation.getTransformedArgumentExpression(0);
        expr = make().TypeCast(makeJavaType(typeFact().getSequentialDeclaration().getType(), JT_RAW), expr);
        JCExpression type = makeJavaType(typeFact().getSequenceType(iteratedType).getType());
        return new ExpressionAndType(expr, type);
    }
   
    private List<ExpressionAndType> transformArgumentsForSimpleInvocation(SimpleInvocation invocation, CallBuilder callBuilder) {
        List<ExpressionAndType> result = List.<ExpressionAndType>nil();
        int numArguments = invocation.getNumArguments();
        if (invocation.getNumParameters() == 0) {
            // skip transforming arguments
            // (Usually, numArguments would already be null, but it's possible to call a
            //  parameterless function with a *[] argument - see #1593.)
            numArguments = 0;
        }
        boolean wrapIntoArray = false;
        ListBuffer<JCExpression> arrayWrap = new ListBuffer<JCExpression>();
        for (int argIndex = 0; argIndex < numArguments; argIndex++) {
            BoxingStrategy boxingStrategy = invocation.getParameterBoxingStrategy(argIndex);
            ProducedType parameterType = invocation.getParameterType(argIndex);
            // for Java methods of variadic primitives, it's better to wrap them ourselves into an array
            // to avoid ambiguity of foo(1,2) for foo(int...) and foo(Object...) methods
            if(!wrapIntoArray
                    && invocation.isParameterSequenced(argIndex)
                    && invocation.isJavaMethod()
                    && boxingStrategy == BoxingStrategy.UNBOXED
                    && willEraseToPrimitive(typeFact().getDefiniteType(parameterType))
                    && !invocation.isSpread())
                wrapIntoArray = true;

            ExpressionAndType exprAndType;
            if (invocation.isArgumentSpread(argIndex)) {
                if (!invocation.isParameterSequenced(argIndex)) {
                    result = transformSpreadTupleArgument(invocation, callBuilder,
                            result, argIndex);
                    break;
                }
                if(invocation.isJavaMethod()){
                    // if it's a java method we need a special wrapping
                    exprAndType = transformSpreadArgument(invocation,
                            numArguments, argIndex, boxingStrategy,
                            parameterType);
                    argIndex = numArguments;
                }else{
                    ProducedType argType = invocation.getArgumentType(argIndex);
                    if (argType.getSupertype(typeFact().getSequentialDeclaration()) != null) {
                        exprAndType = transformArgument(invocation, argIndex,
                                boxingStrategy);
                    } else if (argType.getSupertype(typeFact().getIterableDeclaration()) != null) {
                        exprAndType = transformArgument(invocation, argIndex,
                                boxingStrategy);
                        JCExpression sequential = iterableToSequential(exprAndType.expression);
                        if(invocation.isParameterVariadicPlus(argIndex)){
                            ProducedType iteratedType = typeFact().getIteratedType(argType);
                            sequential = utilInvocation().castSequentialToSequence(sequential, iteratedType);
                        }
                        exprAndType = new ExpressionAndType(sequential, exprAndType.type);
                    } else {
                        exprAndType = new ExpressionAndType(makeErroneous(invocation.getNode(), "compiler bug: unexpected spread argument"), makeErroneous(invocation.getNode(), "compiler bug: unexpected spread argument"));
                    }
                }
            } else if (!invocation.isParameterSequenced(argIndex)
                    // if it's sequenced, Java and there's no spread at all, pass it along
                    || (invocation.isParameterSequenced(argIndex) && invocation.isJavaMethod() && !invocation.isSpread())) {
                exprAndType = transformArgument(invocation, argIndex,
                        boxingStrategy);
                // Callable has a variadic 1-param method that if you invoke it with a Java Object[] will confuse javac and give
                // preference to the variadic method instead of the $call$(Object) one, so we force a cast to Object to resolve it
                // This is not required for primitive arrays since they are not Object[]
                if(numArguments == 1
                        && invocation.isIndirect()){
                    ProducedType argumentType = invocation.getArgumentType(0);
                    if(isJavaObjectArray(argumentType)
                            || isNull(argumentType)){
                        exprAndType = new ExpressionAndType(make().TypeCast(makeJavaType(typeFact().getObjectDeclaration().getType()), exprAndType.expression),
                                exprAndType.type);
                    }
                }else if(invocation.isParameterSequenced(argIndex) && invocation.isJavaMethod() && !invocation.isSpread()){
                    // in fact, the very same problem happens when passing null or object arrays to a java variadic method
                    ProducedType argumentType = invocation.getArgumentType(argIndex);
                    if(isJavaObjectArray(argumentType)
                            || isNull(argumentType)){
                        // remove any ambiguity
                        exprAndType = new ExpressionAndType(make().TypeCast(makeJavaType(parameterType), exprAndType.expression),
                                exprAndType.type);
                    }
                }
            } else {
                // we must have a sequenced param
                if(invocation.isSpread()){
                    exprAndType = transformSpreadArgument(invocation,
                            numArguments, argIndex, boxingStrategy,
                            parameterType);
                    argIndex = numArguments;
                }else{
                    exprAndType = transformVariadicArgument(invocation,
                            numArguments, argIndex, parameterType);
                    argIndex = numArguments;
                }
            }
            if(!wrapIntoArray)
                result = result.append(exprAndType);
            else
                arrayWrap.append(exprAndType.expression);
        }
        if (invocation.isIndirect()
                && invocation.isParameterSequenced(numArguments)
                && !invocation.isArgumentSpread(numArguments-1)
                && ((IndirectInvocation)invocation).getNumParameters() > numArguments) {
            // Calling convention for indirect variadic invocation's requires
            // explicit variadic argument (can't use the overloading trick)
            result = result.append(new ExpressionAndType(makeEmptyAsSequential(true), make().Erroneous()));
        }
        if(wrapIntoArray){
            // must have at least one arg, so take the last one
            ProducedType parameterType = invocation.getParameterType(numArguments-1);
            JCExpression arrayType = makeJavaType(parameterType, JT_RAW);
           
            JCNewArray arrayExpr = make().NewArray(arrayType, List.<JCExpression>nil(), arrayWrap.toList());
            JCExpression arrayTypeExpr = make().TypeArray(makeJavaType(parameterType, JT_RAW));
            result = result.append(new ExpressionAndType(arrayExpr, arrayTypeExpr));
        }
        return result;
    }

    private ExpressionAndType transformVariadicArgument(
            SimpleInvocation invocation, int numArguments, int argIndex,
            ProducedType parameterType) {
        ExpressionAndType exprAndType;
        final ProducedType iteratedType = typeFact().getIteratedType(parameterType);
        final JCExpression expr;
        final JCExpression type;
        // invoking f(a, b, c), where declared f(A a, B* b)
        // collect each remaining argument and box with an ArraySequence<T>
        List<JCExpression> x = List.<JCExpression>nil();
        for (int ii = argIndex ; ii < numArguments; ii++) {
            x = x.append(invocation.getTransformedArgumentExpression(ii));
        }
        expr = makeSequence(x, iteratedType, JT_TYPE_ARGUMENT);
        type = makeJavaType(typeFact().getSequenceType(iteratedType).getType());
        exprAndType = new ExpressionAndType(expr, type);
        return exprAndType;
    }

    private ExpressionAndType transformSpreadArgument(
            SimpleInvocation invocation, int numArguments, int argIndex,
            BoxingStrategy boxingStrategy, ProducedType parameterType) {
        ExpressionAndType exprAndType;
        final ProducedType iteratedType = typeFact().getIteratedType(parameterType);
        final JCExpression expr;
        final JCExpression type;
        // optimise "*javaArray.iterable" into "javaArray" for java variadic parameters, since we can pass them just along
        if(invocation.isJavaMethod()
                && numArguments == argIndex+1
                && !invocation.isArgumentComprehension(argIndex)){
            Expression argumentExpression = invocation.getArgumentExpression(argIndex);
            Term argument = Decl.unwrapExpressionsUntilTerm(argumentExpression);
            if (argument instanceof Tree.QualifiedMemberExpression) {
                Tree.QualifiedMemberExpression qualifiedMemberArgument = (Tree.QualifiedMemberExpression)argument;
                if ("iterable".equals(qualifiedMemberArgument.getIdentifier().getText())
                    && isJavaArray(qualifiedMemberArgument.getPrimary().getTypeModel())) {
                    // just pass the array as-is
                    // we don't care at all about unboxing or casting since we can't be dealing with boxing
                    // and we generate our own cast, at least for non-primitive arrays where it may be ambiguous,
                    // we could avoid the cast for non-type-parameter and non-Object arrays, but that's more expensive
                    // to check for
                    JCExpression primary = transformExpression(qualifiedMemberArgument.getPrimary());
                    type = makeJavaType(typeFact().getSequenceType(iteratedType).getType());
                    if(isJavaObjectArray(qualifiedMemberArgument.getPrimary().getTypeModel())){
                        expr = make().TypeCast(makeJavaType(qualifiedMemberArgument.getPrimary().getTypeModel()), primary);
                    }else{
                        expr = primary;
                    }
                    return new ExpressionAndType(expr, type);
                }
            }
        }
        // invoking f(a, *b), where declared f(A a, B* b)
        // we can have several remaining arguments and the last one is spread
        List<JCExpression> x = List.<JCExpression>nil();
        for (int ii = argIndex ; ii < numArguments; ii++) {
            JCExpression argExpr = invocation.getTransformedArgumentExpression(ii);
            // the last parameter is spread and must be put first
            if(ii < numArguments - 1){
                x = x.append(argExpr);
            }else{
                // convert to a Sequential if required
                ProducedType argType = invocation.getArgumentType(ii);
                if(!typeFact().isSequentialType(argType))
                    argExpr = iterableToSequential(argExpr);
                x = x.prepend(argExpr);
            }
        }
        if(invocation.isJavaMethod()){
            // collect all the initial arguments and wrap into a Java array
            // first arg is the spread part
            JCExpression last = x.head;
            // remove it from x
            x = x.tail;
           
            ProducedType lastType = invocation.getArgumentType(numArguments-1);

            // must translate it into a Util call
            expr = sequenceToJavaArray(last, parameterType, boxingStrategy, lastType, x);
        }else{
            JCExpression typeExpr = makeJavaType(iteratedType, JT_TYPE_ARGUMENT);
            JCExpression sequentialExpr = utilInvocation().sequentialInstance(typeExpr, makeReifiedTypeArgument(iteratedType), x.head, x.tail);
            if (invocation.isParameterVariadicPlus(argIndex)) {
                expr = utilInvocation().castSequentialToSequence(sequentialExpr, iteratedType);
            } else {
                expr = sequentialExpr;
            }
        }
        type = makeJavaType(typeFact().getSequenceType(iteratedType).getType());
        exprAndType = new ExpressionAndType(expr, type);
        return exprAndType;
    }

    private List<ExpressionAndType> transformSpreadTupleArgument(
            SimpleInvocation invocation, CallBuilder callBuilder,
            List<ExpressionAndType> result, final int argIndex) {
        BoxingStrategy boxingStrategy;
        // Spread tuple Argument
        // invoking f(*args), where declared f(A a, B a) (last param not sequenced)
        final Tree.Expression tupleArgument = invocation.getArgumentExpression(argIndex);
        final int minimumTupleArguments = typeFact().getTupleMinimumLength(tupleArgument.getTypeModel());
        final boolean tupleUnbounded = typeFact().isTupleLengthUnbounded(tupleArgument.getTypeModel());
        final ProducedType callableType = invocation.getPrimary().getTypeModel().getFullType();
       
        // Only evaluate the tuple expr once
        SyntheticName tupleAlias = naming.alias("tuple");
        JCExpression tupleType;
        JCExpression tupleExpr = transformExpression(tupleArgument, BoxingStrategy.BOXED, null);
        tupleType = makeJavaType(typeFact().getSequentialDeclaration().getType(), JT_RAW);
        tupleExpr = make().TypeCast(makeJavaType(typeFact().getSequentialDeclaration().getType(), JT_RAW), tupleExpr);
       
        callBuilder.appendStatement(makeVar(tupleAlias, tupleType, tupleExpr));
       
        if (callBuilder.getArgumentHandling() == 0) {
            // XXX Hack: Only do this if we're not already doing
            // something funky with arguments e.g. SpreadOp
            callBuilder.argumentHandling(CallBuilder.CB_LET, naming.alias("spreadarg"));
        }
        callBuilder.voidMethod(invocation.getReturnType() == null
                || Decl.isUnboxedVoid(invocation.getPrimaryDeclaration()));
       
        /* Cases:
            *[] -> () => nothing
            *[] -> (Integer=) => nothing
            *[] -> (Integer*) => nothing
            *[Integer] -> (Integer) => extract
            *[Integer] -> (Integer=) => extract
            *[Integer] -> (Integer*) => pass the tuple as-is
            *[Integer*] -> (Integer*) => pass the tuple as-is
            *[Integer+] -> (Integer*) => pass the tuple as-is
            *[Integer] -> (Integer, Integer*) => extract and drop the tuple
            *[Integer,Integer] -> (Integer, Integer) => extract
            *[Integer,Integer] -> (Integer=, Integer=) => extract
            *[Integer,Integer] -> (Integer, Integer*) => extract and pass the tuple rest
            *[Integer,Integer*] -> (Integer, Integer*) => extract and pass the tuple rest
            *[Integer,Integer+] -> (Integer, Integer*) => extract and pass the tuple rest
        */
       
        int spreadArgIndex = argIndex;
        final int maxParameters = getNumParametersOfCallable(callableType);
        boolean variadic = maxParameters > 0 && invocation.isParameterSequenced(maxParameters-1);
        // we extract from the tuple not more than we have tuple members, but even less than that if we don't
        // have enough parameters to put them in
        final int argumentsToExtract = Math.min(argIndex + minimumTupleArguments, variadic ? maxParameters - 1 : maxParameters);
        for (; spreadArgIndex < argumentsToExtract; spreadArgIndex++) {
            boxingStrategy = invocation.getParameterBoxingStrategy(spreadArgIndex);
            ProducedType paramType = getParameterTypeOfCallable(callableType, spreadArgIndex);
            JCExpression tupleIndex = boxType(make().Literal((long)spreadArgIndex-argIndex),
                    typeFact().getIntegerDeclaration().getType());
            JCExpression tupleElement = make().Apply(null,
                    naming.makeQualIdent(tupleAlias.makeIdent(), "get"),
                    List.<JCExpression>of(tupleIndex));
           
            tupleElement = applyErasureAndBoxing(tupleElement,
                    typeFact().getAnythingDeclaration().getType(),
                    true, boxingStrategy, paramType);
            JCExpression argType = makeJavaType(paramType, boxingStrategy == BoxingStrategy.BOXED ? JT_NO_PRIMITIVES : 0);
            result = result.append(new ExpressionAndType(tupleElement, argType));
        }
        // if we're variadic AND
        // - the tuple is unbounded (which means we must have an unknown number of elements left to pass)
        // - OR the tuple is bounded but we did not pass them all
        if (variadic
                && (tupleUnbounded || argumentsToExtract < (minimumTupleArguments + argIndex))) {
            boxingStrategy = invocation.getParameterBoxingStrategy(spreadArgIndex);
            ProducedType paramType = getParameterTypeOfCallable(callableType, spreadArgIndex);
            JCExpression tupleElement = tupleAlias.makeIdent();
            // argIndex = 1, tuple = [Integer], params = [Integer, Integer*], spreadArgIndex = 1 => no span
            // argIndex = 0, tuple = [Integer+], params = [Integer, Integer*], spreadArgIndex = 1 => spanFrom(1)
            if(spreadArgIndex - argIndex > 0){
                JCExpression tupleIndex = boxType(make().Literal((long)spreadArgIndex-argIndex),
                        typeFact().getIntegerDeclaration().getType());
                tupleElement = make().Apply(null, naming.makeQualIdent(tupleElement, "spanFrom"),
                        List.<JCExpression>of(tupleIndex));
            }
            tupleElement = applyErasureAndBoxing(tupleElement,
                    typeFact().getAnythingDeclaration().getType(),
                    true, boxingStrategy, paramType);
            JCExpression argType = makeJavaType(paramType, boxingStrategy == BoxingStrategy.BOXED ? JT_NO_PRIMITIVES : 0);
           
            JCExpression expr;
            if(invocation.isJavaMethod()){
                // no need to handle leading arguments since that is handled by transformSpreadArgument
                // if ever we have leading arguments we never end up in this method
                expr = sequenceToJavaArray(tupleElement, paramType, boxingStrategy, paramType, List.<JCExpression>nil());               
            }else{
                expr = tupleElement;
            }
            result = result.append(new ExpressionAndType(expr, argType));
        } else if (variadic
                && invocation.isIndirect()
                && argumentsToExtract >= minimumTupleArguments
                && !tupleUnbounded) {
            result = result.append(new ExpressionAndType(makeEmptyAsSequential(true), makeJavaType(typeFact().getSequenceType(typeFact().getAnythingDeclaration().getType()), JT_RAW)));
        }
        return result;
    }

    private ExpressionAndType transformArgument(SimpleInvocation invocation,
            int argIndex, BoxingStrategy boxingStrategy) {
        ExpressionAndType exprAndType;
        final JCExpression expr;
        final JCExpression type;
        expr = invocation.getTransformedArgumentExpression(argIndex);
        type = makeJavaType(invocation.getParameterType(argIndex), boxingStrategy == BoxingStrategy.BOXED ? JT_NO_PRIMITIVES : 0);
        exprAndType = new ExpressionAndType(expr, type);
        return exprAndType;
    }
   
    private List<ExpressionAndType> transformArgumentsForNamedInvocation(NamedArgumentInvocation invocation) {
        List<ExpressionAndType> result = List.<ExpressionAndType>nil();
        for (ExpressionAndType argAndType : invocation.getArgumentsAndTypes()) {
            result = result.append(argAndType);
        }
        return result;
    }
   
    private List<ExpressionAndType> transformArgumentsForCallableSpecifier(CallableSpecifierInvocation invocation) {
        List<ExpressionAndType> result = List.<ExpressionAndType>nil();
        int argIndex = 0;
        for(Parameter parameter : invocation.getMethod().getParameterLists().get(0).getParameters()) {
            ProducedType exprType = expressionGen().getTypeForParameter(parameter, null, this.TP_TO_BOUND);
            Parameter declaredParameter = invocation.getMethod().getParameterLists().get(0).getParameters().get(argIndex);
           
            JCExpression arg = naming.makeName(parameter.getModel(), Naming.NA_MEMBER);
           
            arg = expressionGen().applyErasureAndBoxing(
                    arg,
                    exprType,
                    !parameter.getModel().getUnboxed(),
                    BoxingStrategy.BOXED,// Callables always have boxed params
                    declaredParameter.getType());
            result = result.append(new ExpressionAndType(arg, makeJavaType(declaredParameter.getType())));
            argIndex++;
        }
        return result;
    }
   
    public final JCExpression transformInvocation(final Invocation invocation) {
        boolean prevFnCall = withinInvocation(true);
        try {
            final CallBuilder callBuilder = CallBuilder.instance(this);
            if (invocation.getPrimary() instanceof Tree.StaticMemberOrTypeExpression){
                transformTypeArguments(callBuilder,
                        (Tree.StaticMemberOrTypeExpression)invocation.getPrimary());
            }
            if (invocation instanceof CallableSpecifierInvocation) {
                return transformCallableSpecifierInvocation(callBuilder, (CallableSpecifierInvocation)invocation);
            } else {
                at(invocation.getNode());
                Tree.Term primary = Decl.unwrapExpressionsUntilTerm(invocation.getPrimary());
                JCExpression result = transformTermForInvocation(primary, new InvocationTermTransformer(invocation, callBuilder));
                return result;
               
            }
        } finally {
            withinInvocation(prevFnCall);
        }
    }

    protected JCExpression transformPositionalInvocationOrInstantiation(Invocation invocation, CallBuilder callBuilder, TransformedInvocationPrimary transformedPrimary) {
        JCExpression resultExpr;
        if (invocation.isMemberRefInvocation()) {
            resultExpr = transformInvocation(invocation, callBuilder, transformedPrimary);
        } else if (invocation.getPrimary() instanceof Tree.BaseTypeExpression) {
            resultExpr = transformBaseInstantiation(invocation, callBuilder, transformedPrimary);
        } else if (invocation.getPrimary() instanceof Tree.QualifiedTypeExpression) {
            resultExpr = transformQualifiedInstantiation(invocation, callBuilder, transformedPrimary);
        } else {  
            resultExpr = transformInvocation(invocation, callBuilder, transformedPrimary);
        }
       
        if(invocation.handleBoxing)
            resultExpr = applyErasureAndBoxing(resultExpr, invocation.getReturnType(),
                    invocation.erased, !invocation.unboxed, invocation.boxingStrategy, invocation.getReturnType(), 0);
        return resultExpr;
    }

    private JCExpression transformInvocation(Invocation invocation, CallBuilder callBuilder,
            TransformedInvocationPrimary transformedPrimary) {
        invocation.location(callBuilder);
        if(invocation.getQmePrimary() != null
                && isJavaArray(invocation.getQmePrimary().getTypeModel())
                && transformedPrimary.selector != null
                && (transformedPrimary.selector.equals("get")
                    || transformedPrimary.selector.equals("set"))){
            if(transformedPrimary.selector.equals("get"))
                callBuilder.arrayRead(transformedPrimary.expr);
            else if(transformedPrimary.selector.equals("set")){
                callBuilder.arrayWrite(transformedPrimary.expr);
                ProducedType arrayType = invocation.getQmePrimary().getTypeModel();
                if(isJavaObjectArray(arrayType) && invocation instanceof PositionalInvocation){
                    ProducedType elementType = arrayType.getTypeArgumentList().get(0);
                    ProducedType argumentType = ((PositionalInvocation)invocation).getArgumentType(1);
                    if(!argumentType.isSubtypeOf(typeFact().getOptionalType(elementType)))
                        callBuilder.javaArrayWriteNeedsCast(true);
                }
            }else
                return makeErroneous(invocation.getNode(), "compiler bug: extraneous array selector: "+transformedPrimary.selector);
        } else if (invocation.isUnknownArguments()) {
            // if we have an unknown parameter list, like Callble<Ret,Args>, need to prepend the callable
            // to the argument list, and invoke Util.apply
            // note that ATM the typechecker only allows a single argument to be passed in spread form in this
            // case so we don't need to look at parameter types
            JCExpression callableTypeExpr = makeJavaType(invocation.getPrimary().getTypeModel());
            ExpressionAndType callableArg = new ExpressionAndType(transformedPrimary.expr, callableTypeExpr);
            ProducedType returnType = invocation.getReturnType();
            JCExpression returnTypeExpr = makeJavaType(returnType, JT_NO_PRIMITIVES);
            callBuilder.prependArgumentAndType(callableArg);
            callBuilder.typeArgument(returnTypeExpr);
            callBuilder.invoke(make().Select(make().QualIdent(syms().ceylonUtilType.tsym),
                                             names().fromString("apply")));
        } else if (invocation.isOnValueType()) {
            JCExpression primTypeExpr = makeJavaType(invocation.getQmePrimary().getTypeModel(), JT_NO_PRIMITIVES | JT_VALUE_TYPE);
            callBuilder.invoke(naming.makeQuotedQualIdent(primTypeExpr, transformedPrimary.selector));

        } else {
            callBuilder.invoke(naming.makeQuotedQualIdent(transformedPrimary.expr, transformedPrimary.selector));
        }
        return callBuilder.build();
    }

    private JCExpression transformQualifiedInstantiation(Invocation invocation, CallBuilder callBuilder,
            TransformedInvocationPrimary transformedPrimary) {
       
        Tree.QualifiedTypeExpression qte = (Tree.QualifiedTypeExpression)invocation.getPrimary();
        Declaration declaration = qte.getDeclaration();
        invocation.location(callBuilder);
        if (Decl.isJavaStaticPrimary(invocation.getPrimary())) {
            callBuilder.instantiate(transformedPrimary.expr);
        } else if (!Strategy.generateInstantiator(declaration)) {
            JCExpression qualifier;
            JCExpression qualifierType;
            if (declaration.getContainer() instanceof Interface) {
                // When doing qualified invocation through an interface we need
                // to get the companion.
                Interface qualifyingInterface = (Interface)declaration.getContainer();
                qualifier = transformedPrimary.expr;
                qualifierType = makeJavaType(qualifyingInterface.getType(), JT_COMPANION);
            } else {
                qualifier = transformedPrimary.expr;
                if (declaration.getContainer() instanceof TypeDeclaration) {
                    qualifierType = makeJavaType(((TypeDeclaration)declaration.getContainer()).getType());
                } else {
                    qualifierType = null;
                }
            }
            ProducedType classType = (ProducedType)qte.getTarget();
            JCExpression type;
            // special case for package-qualified things that are not really qualified
            if(qualifier == null){
                type = makeJavaType(classType, AbstractTransformer.JT_CLASS_NEW);
            }else{
                // Note: here we're not fully qualifying the class name because the JLS says that if "new" is qualified the class name
                // is qualified relative to it
                type = makeJavaType(classType, AbstractTransformer.JT_CLASS_NEW | AbstractTransformer.JT_NON_QUALIFIED);
            }
            callBuilder.instantiate(new ExpressionAndType(qualifier, qualifierType), type);
        } else {
            callBuilder.typeArguments(List.<JCExpression>nil());
            for (ProducedType tm : qte.getTypeArguments().getTypeModels()) {
                callBuilder.typeArgument(makeJavaType(tm, AbstractTransformer.JT_TYPE_ARGUMENT));
            }
            callBuilder.invoke(naming.makeInstantiatorMethodName(transformedPrimary.expr, (Class)declaration));
        }
        JCExpression result = callBuilder.build();
        if (Strategy.isInstantiatorUntyped(declaration)) {
            result = make().TypeCast(makeJavaType(invocation.getReturnType()), result);
        }
        return result;
    }

    private JCExpression transformBaseInstantiation(Invocation invocation, CallBuilder callBuilder,
            TransformedInvocationPrimary transformedPrimary) {
        JCExpression resultExpr;
        Tree.BaseTypeExpression type = (Tree.BaseTypeExpression)invocation.getPrimary();
        Declaration declaration = type.getDeclaration();
        invocation.location(callBuilder);
        if (Strategy.generateInstantiator(declaration)) {
            resultExpr = callBuilder
                    .typeArguments(List.<JCExpression>nil())
                    .invoke(naming.makeInstantiatorMethodName(transformedPrimary.expr, (Class)declaration))
                    .build();
            if (Strategy.isInstantiatorUntyped(declaration)) {
                // $new method declared to return Object, so needs typecast
                resultExpr = make().TypeCast(makeJavaType(
                        ((TypeDeclaration)declaration).getType()), resultExpr);
            }
        } else {
            ProducedType classType = (ProducedType)type.getTarget();
            if(isJavaArray(classType)){
                JCExpression typeExpr = makeJavaType(classType, AbstractTransformer.JT_CLASS_NEW | AbstractTransformer.JT_RAW);
                callBuilder.javaArrayInstance(typeExpr);
                if(isJavaObjectArray(classType)){
                    ProducedType elementType = classType.getTypeArgumentList().get(0);
                    MultidimensionalArray multiArray = getMultiDimensionalArrayInfo(elementType);
                    if(multiArray != null)
                        elementType = multiArray.type;
                    // if it is an array of Foo<X> we need a raw instanciation and cast
                    // array of Foo is fine, array of Nothing too
                    if(elementType.getDeclaration() instanceof ClassOrInterface
                            || elementType.getDeclaration() instanceof NothingType){
                        if(!elementType.getTypeArgumentList().isEmpty())
                            callBuilder.javaArrayInstanceNeedsCast(makeJavaType(classType, AbstractTransformer.JT_NO_PRIMITIVES));
                    }else{
                        // if it's an array of union, intersection or type param we need a runtime allocation
                        callBuilder.javaArrayInstanceIsGeneric(makeReifiedTypeArgument(elementType),
                                multiArray != null ? multiArray.dimension + 1 : 1);
                    }
                }
            }else{
                JCExpression typeExpr = makeJavaType(classType, AbstractTransformer.JT_CLASS_NEW);
                callBuilder.instantiate(typeExpr);
            }
            resultExpr = callBuilder.build();
        }
        return resultExpr;
    }
   
    private JCExpression transformCallableSpecifierInvocation(CallBuilder callBuilder, CallableSpecifierInvocation invocation) {
        at(invocation.getNode());
        JCExpression result = callBuilder
            .invoke(naming.makeQuotedQualIdent(invocation.getCallable(), Naming.getCallableMethodName(invocation.getMethod())))
            .argumentsAndTypes(transformArgumentList(invocation, null, callBuilder))
            .build();
        if(invocation.handleBoxing)
            result = applyErasureAndBoxing(result, invocation.getReturnType(),
                    !invocation.unboxed, invocation.boxingStrategy, invocation.getReturnType());
        return result;
    }
   
    private final void transformTypeArguments(
            CallBuilder callBuilder,
            Tree.StaticMemberOrTypeExpression mte) {
        java.util.List<TypeParameter> tps = null;
        Declaration declaration = mte.getDeclaration();
        if (declaration instanceof Generic) {
            tps = ((Generic)declaration).getTypeParameters();
        }
        if (tps != null) {
            for (TypeParameter tp : tps) {
                ProducedType ta = mte.getTarget().getTypeArguments().get(tp);
                java.util.List<ProducedType> bounds = null;
                boolean needsCastForBounds = false;
                if(!tp.getSatisfiedTypes().isEmpty()){
                    bounds = new ArrayList<ProducedType>(tp.getSatisfiedTypes().size());
                    for(ProducedType bound : tp.getSatisfiedTypes()){
                        // substitute the right type arguments
                        bound = substituteTypeArgumentsForTypeParameterBound(mte.getTarget(), bound);
                        bounds.add(bound);
                        needsCastForBounds |= needsCast(ta, bound, false, false, false);
                    }
                }
                boolean hasMultipleBounds;
                ProducedType firstBound;
                if(bounds != null){
                    hasMultipleBounds = bounds.size() > 1;
                    firstBound = bounds.isEmpty() ? null : bounds.get(0);
                }else{
                    hasMultipleBounds = false;
                    firstBound = null;
                }
                if (willEraseToObject(ta) || needsCastForBounds) {
                    boolean boundsSelfDependent = isBoundsSelfDependant(tp);
                    if (hasDependentTypeParameters(tps, tp)
                            // if we must use the bounds and we have more than one, we cannot use one to satisfy them all
                            // and we cannot represent the intersection type in Java so give up
                            || hasMultipleBounds
                            // if we are going to use the first bound and it is self-dependent, we will make it raw
                            || boundsSelfDependent
                            || (firstBound != null && willEraseToObject(firstBound))) {
                        // we just can't satisfy the bounds if there are more than one, just pray,
                        // BUT REMEMBER THERE IS NO SUCH THING AS A RAW METHOD CALL IN JAVA
                        // so at some point we'll have to introduce an intersection type AST node to satisfy multiple bounds
                        if(hasMultipleBounds){
                            callBuilder.typeArguments(List.<JCExpression>nil());
                            return;
                        }
                        // if we have a bound
                        if(firstBound != null){
                            // if it's self-dependent we cannot satisfy it without a raw type
                            if(boundsSelfDependent)
                                callBuilder.typeArgument(makeJavaType(firstBound, JT_TYPE_ARGUMENT|JT_RAW));
                            else
                                callBuilder.typeArgument(makeJavaType(firstBound, JT_TYPE_ARGUMENT));
                        }else{
                            // no bound, let's go with Object then
                            callBuilder.typeArgument(makeJavaType(typeFact().getObjectDeclaration().getType(), JT_TYPE_ARGUMENT));
                        }
                    }else if (firstBound == null) {
                        callBuilder.typeArgument(makeJavaType(ta, JT_TYPE_ARGUMENT));
                    } else {
                        callBuilder.typeArgument(makeJavaType(firstBound, JT_TYPE_ARGUMENT));
                    }
                } else {
                    callBuilder.typeArgument(makeJavaType(ta, JT_TYPE_ARGUMENT));
                }
            }
        }
    }

    boolean erasesTypeArguments(ProducedReference producedReference) {
        java.util.List<TypeParameter> tps = null;
        Declaration declaration = producedReference.getDeclaration();
        if (declaration instanceof Generic) {
            tps = ((Generic)declaration).getTypeParameters();
        }
        if (tps != null) {
            for (TypeParameter tp : tps) {
                ProducedType ta = producedReference.getTypeArguments().get(tp);
                java.util.List<ProducedType> bounds = null;
                boolean needsCastForBounds = false;
                if(!tp.getSatisfiedTypes().isEmpty()){
                    bounds = new ArrayList<ProducedType>(tp.getSatisfiedTypes().size());
                    for(ProducedType bound : tp.getSatisfiedTypes()){
                        // substitute the right type arguments
                        bound = substituteTypeArgumentsForTypeParameterBound(producedReference, bound);
                        bounds.add(bound);
                        needsCastForBounds |= needsCast(ta, bound, false, false, false);
                    }
                }
                if (willEraseToObject(ta) || needsCastForBounds) {
                    return true;
                }
            }
        }
        return false;
    }

    protected JCExpression transformNamedArgumentInvocationOrInstantiation(NamedArgumentInvocation invocation,
            CallBuilder callBuilder,
            TransformedInvocationPrimary transformedPrimary) {
        JCExpression resultExpr = transformPositionalInvocationOrInstantiation(invocation, callBuilder, transformedPrimary);
        // apply the default parameters
        if (invocation.getVars() != null && !invocation.getVars().isEmpty()) {
            if (invocation.getReturnType() == null || Decl.isUnboxedVoid(invocation.getPrimaryDeclaration())) {
                // void methods get wrapped like (let $arg$1=expr, $arg$0=expr in call($arg$0, $arg$1); null)
                resultExpr = make().LetExpr(
                        invocation.getVars().append(make().Exec(resultExpr)).toList(),
                        makeNull());
            } else {
                // all other methods like (let $arg$1=expr, $arg$0=expr in call($arg$0, $arg$1))
                resultExpr = make().LetExpr(
                        invocation.getVars().toList(),
                        resultExpr);
            }
        }
        return resultExpr;
    }
   
    //
    // Invocations
    public void transformSuperInvocation(Tree.ExtendedType extendedType, ClassDefinitionBuilder classBuilder) {
        HasErrorException error = errors().getFirstExpressionErrorAndMarkBrokenness(extendedType);
        if (error != null) {
            classBuilder.superCall(this.makeThrowUnresolvedCompilationError(error));
            return;
        }
        if (extendedType.getInvocationExpression().getPositionalArgumentList() != null) {
            Tree.InvocationExpression invocation = extendedType.getInvocationExpression();
            Declaration primaryDeclaration = ((Tree.MemberOrTypeExpression)invocation.getPrimary()).getDeclaration();
            java.util.List<ParameterList> paramLists = ((Functional)primaryDeclaration).getParameterLists();
            if(paramLists.isEmpty()){
                classBuilder.superCall(at(extendedType).Exec(makeErroneous(extendedType, "compiler bug: super class " + primaryDeclaration.getName() + " is missing parameter list")));
                return;
            }
            SuperInvocation builder = new SuperInvocation(this,
                    classBuilder.getForDefinition(),
                    invocation,
                    paramLists.get(0));
           
            CallBuilder callBuilder = CallBuilder.instance(this);
            boolean prevFnCall = withinInvocation(true);
            try {
                if (invocation.getPrimary() instanceof Tree.StaticMemberOrTypeExpression){
                    transformTypeArguments(callBuilder,
                            (Tree.StaticMemberOrTypeExpression)invocation.getPrimary());
                }
                at(builder.getNode());
                JCExpression expr = null;
                if (Strategy.generateInstantiator(builder.getPrimaryDeclaration())
                        && builder.getPrimaryDeclaration().getContainer() instanceof Interface) {
                    // If the subclass is inner to an interface then it will be
                    // generated inner to the companion and we need to qualify the
                    // super(), *unless* the subclass is nested within the same
                    // interface as it's superclass.
                    Scope outer = builder.getSub().getContainer();
                    while (!(outer instanceof Package)) {
                        if (outer == builder.getPrimaryDeclaration().getContainer()) {
                            expr = naming.makeSuper();
                            break;
                        }
                        outer = outer.getContainer();
                    }
                    if (expr == null) {                   
                        Interface iface = (Interface)builder.getPrimaryDeclaration().getContainer();
                        JCExpression superQual;
                        if (Decl.getClassOrInterfaceContainer(classBuilder.getForDefinition(), false) instanceof Interface) {
                            superQual = naming.makeCompanionAccessorCall(naming.makeQuotedThis(), iface);
                        } else {
                            superQual = naming.makeCompanionFieldName(iface);
                        }
                        expr = naming.makeQualifiedSuper(superQual);
                    }
                } else {
                    expr = naming.makeSuper();
                }
                final List<JCExpression> superArguments = transformSuperInvocationArguments(
                        extendedType, classBuilder, builder, callBuilder);
                JCExpression superExpr = callBuilder.invoke(expr)   
                    .arguments(superArguments)
                    .build();
                classBuilder.superCall(at(extendedType).Exec(superExpr));
            } finally {
                withinInvocation(prevFnCall);
            }
        }
    }

    /**
     * Transforms the arguments for the invocation of a superclass initializer
     * (call to {@code super()}).
     *
     * This is complicated by the need to avoid
     * #929, so when a backward branch is needed in the evaluation of any
     * argument expression we generate methods on the companion class
     * (one for each argument) to evaluate the arguments so that the uninitialized
     * {@code this} is not on the operand stack.
     */
    private List<JCExpression> transformSuperInvocationArguments(
            Tree.ExtendedType extendedType,
            ClassDefinitionBuilder classBuilder, SuperInvocation invocation, CallBuilder callBuilder) {
        // We could create a TransformedPrimary(expr, "super") here if needed
        List<ExpressionAndType> superArgumentsAndTypes = transformArgumentList(invocation, null, callBuilder);
        final List<JCExpression> superArguments = ExpressionAndType.toExpressionList(superArgumentsAndTypes);
        return superArguments;
    }
   
    public JCExpression transform(Tree.InvocationExpression ce) {
        JCExpression ret = checkForInvocationExpressionOptimisation(ce);
        if(ret != null)
            return ret;
       
        Tree.Term primary = Decl.unwrapExpressionsUntilTerm(ce.getPrimary());
        Declaration primaryDeclaration = null;
        ProducedReference producedReference = null;
        if (primary instanceof Tree.MemberOrTypeExpression) {
            producedReference = ((Tree.MemberOrTypeExpression)primary).getTarget();
            primaryDeclaration = ((Tree.MemberOrTypeExpression)primary).getDeclaration();
        }
        Invocation invocation;
        if (ce.getPositionalArgumentList() != null) {
            if ((Util.isIndirectInvocation(ce)
                    || isWithinDefaultParameterExpression(primaryDeclaration.getContainer()))
                    && !Decl.isJavaStaticPrimary(ce.getPrimary())){
                // indirect invocation
                invocation = new IndirectInvocation(this,
                        primary, primaryDeclaration,
                        ce);
            } else {
                // direct invocation
                java.util.List<Parameter> parameters = ((Functional)primaryDeclaration).getParameterLists().get(0).getParameters();
                invocation = new PositionalInvocation(this,
                        primary, primaryDeclaration,producedReference,
                        ce,
                        parameters);
            }
        } else if (ce.getNamedArgumentList() != null) {
            invocation = new NamedArgumentInvocation(this,
                    primary,
                    primaryDeclaration,
                    producedReference,
                    ce);
        } else {
            return makeErroneous(ce, "no arguments");
        }
        return transformInvocation(invocation);
    }

    public JCExpression transformFunctional(Tree.StaticMemberOrTypeExpression expr,
            Functional functional) {
        return CallableBuilder.methodReference(gen(), expr,
                    functional.getParameterLists().get(0));
    }

    //
    // Member expressions

    public static interface TermTransformer {
        JCExpression transform(JCExpression primaryExpr, String selector);
    }

    // Qualified members
   
    public JCExpression transform(Tree.QualifiedMemberExpression expr) {
        // check for an optim
        JCExpression ret = checkForQualifiedMemberExpressionOptimisation(expr);
        if(ret != null)
            return ret;
        if (expr.getPrimary() instanceof Tree.BaseTypeExpression) {
            Tree.BaseTypeExpression primary = (Tree.BaseTypeExpression)expr.getPrimary();
            return transformMemberReference(expr, primary);
        } else if (expr.getPrimary() instanceof Tree.QualifiedTypeExpression) {
            Tree.QualifiedTypeExpression primary = (Tree.QualifiedTypeExpression)expr.getPrimary();
            return transformMemberReference(expr, primary);
        }
        return transform(expr, null);
    }

    JCExpression transformMemberReference(
            Tree.QualifiedMemberOrTypeExpression expr,
            Tree.MemberOrTypeExpression primary) {
        Declaration member = expr.getDeclaration();
        ProducedType qualifyingType = primary.getTypeModel();
        Tree.TypeArguments typeArguments = expr.getTypeArguments();
        boolean prevSyntheticClassBody = withinSyntheticClassBody(true);
        try {
            if (member.isStaticallyImportable()) {
                if (member instanceof Method) {
                    Method method = (Method)member;
                    ProducedReference producedReference = method.getProducedReference(qualifyingType, typeArguments.getTypeModels());
                    return CallableBuilder.javaStaticMethodReference(
                            gen(),
                            expr.getTypeModel(),
                            method,
                            producedReference).build();
                } else if (member instanceof FieldValue) {
                    return naming.makeName(
                            (TypedDeclaration)member, Naming.NA_FQ | Naming.NA_WRAPPER_UNQUOTED);
                } else if (member instanceof Value) {
                    CallBuilder callBuilder = CallBuilder.instance(this);
                    JCExpression qualExpr = naming.makeTypeDeclarationExpression(null, (TypeDeclaration)member.getContainer(), DeclNameFlag.QUALIFIED);
                    callBuilder.invoke(naming.makeQualifiedName(qualExpr, (TypedDeclaration)member, Naming.NA_GETTER | Naming.NA_MEMBER));
                    return callBuilder.build();
                } else if (member instanceof Class) {
                    ProducedReference producedReference = expr.getTarget();
                    return CallableBuilder.javaStaticMethodReference(
                            gen(),
                            expr.getTypeModel(),
                            (Class)member,
                            producedReference).build();
                }
            }
            if (member instanceof Method) {
                Method method = (Method)member;
                if (!method.isParameter()) {
                    ProducedReference producedReference = method.getProducedReference(qualifyingType, typeArguments.getTypeModels());
                    return CallableBuilder.unboundFunctionalMemberReference(
                            gen(),
                            expr,
                            expr.getTypeModel(),
                            method,
                            producedReference).build();
                } else {
                    ProducedReference producedReference = method.getProducedReference(qualifyingType, typeArguments.getTypeModels());
                    return CallableBuilder.unboundFunctionalMemberReference(
                            gen(),
                            expr,
                            expr.getTypeModel(),
                            method,
                            producedReference).build();
                }
            } else if (member instanceof Value) {
                return CallableBuilder.unboundValueMemberReference(
                        gen(),
                        expr,
                        expr.getTypeModel(),
                        ((TypedDeclaration)member)).build();
            } else if (member instanceof Class) {
                ProducedReference producedReference = expr.getTarget();
                return CallableBuilder.unboundFunctionalMemberReference(
                        gen(),
                        expr,
                        expr.getTypeModel(),
                        (Class)member,
                        producedReference).build();
            } else {
                return makeErroneous(expr, "compiler bug: member reference of " + expr + " not supported yet");
            }
        } finally {
            withinSyntheticClassBody(prevSyntheticClassBody);
        }
    }
   
    private JCExpression transform(Tree.QualifiedMemberExpression expr, TermTransformer transformer) {
        JCExpression result;
        if (expr.getMemberOperator() instanceof Tree.SafeMemberOp) {
            Naming.SyntheticName tmpVarName = naming.alias("safe");
            JCExpression typeExpr = makeJavaType(expr.getTarget().getQualifyingType(), JT_NO_PRIMITIVES);
            JCExpression transExpr = transformMemberExpression(expr, tmpVarName.makeIdent(), transformer);
            if (isFunctionalResult(expr.getTypeModel())) {
                return transExpr;
            }
            // the marker we get for boxing on a QME with a SafeMemberOp is always unboxed
            // since it returns an optional type, but that doesn't tell us if the underlying
            // expr is or not boxed
            boolean isBoxed = !CodegenUtil.isUnBoxed((TypedDeclaration)expr.getDeclaration());
            transExpr = boxUnboxIfNecessary(transExpr, isBoxed, expr.getTarget().getType(), BoxingStrategy.BOXED);
            JCExpression testExpr = make().Binary(JCTree.NE, tmpVarName.makeIdent(), makeNull());
            JCExpression condExpr = make().Conditional(testExpr, transExpr, makeNull());
            JCExpression primaryExpr = transformQualifiedMemberPrimary(expr);
            result = makeLetExpr(tmpVarName, null, typeExpr, primaryExpr, condExpr);
        } else if (expr.getMemberOperator() instanceof Tree.SpreadOp) {
            result = transformSpreadOperator(expr, transformer);
        } else {
            JCExpression primaryExpr = transformQualifiedMemberPrimary(expr);
            result = transformMemberExpression(expr, primaryExpr, transformer);
        }
        return result;
    }

   
   
    private JCExpression transformSpreadOperator(final Tree.QualifiedMemberExpression expr, TermTransformer transformer) {
        at(expr);
       
        boolean spreadMethodReferenceOuter = !expr.equals(this.spreading) && !isWithinInvocation() && isCeylonCallableSubtype(expr.getTypeModel());
        boolean spreadMethodReferenceInner = expr.equals(this.spreading) && isWithinInvocation();
        Tree.QualifiedMemberExpression oldSpreading = spreading;
        if (spreadMethodReferenceOuter) {
            spreading = expr;
        }
        try {
            Naming.SyntheticName varBaseName = naming.alias("spread");
            ListBuffer<JCStatement> letStmts = ListBuffer.<JCStatement>lb();
            final Naming.SyntheticName srcIterableName;
            if (spreadMethodReferenceInner) {
                // use the var we initialized in the outer
                srcIterableName = this.memberPrimary;
            } else {
                srcIterableName = varBaseName.suffixedBy(Suffix.$iterable$);
            }
            if (spreadMethodReferenceOuter) {
                // if we're in the outer, note then name of the var for use in the inner.
                this.memberPrimary = srcIterableName;
            }
            Naming.SyntheticName srcIteratorName = varBaseName.suffixedBy(Suffix.$iterator$);
            ProducedType srcElementType = expr.getTarget().getQualifyingType();
            JCExpression srcIterableTypeExpr = makeJavaType(typeFact().getIterableType(srcElementType), JT_NO_PRIMITIVES);
            JCExpression srcIterableExpr;
            if (spreadMethodReferenceInner) {
                srcIterableExpr = srcIterableName.makeIdent();
            } else {
                srcIterableExpr = transformExpression(expr.getPrimary(), BoxingStrategy.BOXED, typeFact().getIterableType(srcElementType));
            }
            if (!spreadMethodReferenceInner) {
                JCVariableDecl srcIterable = null;
                srcIterable = makeVar(Flags.FINAL, srcIterableName, srcIterableTypeExpr, srcIterableExpr);
                letStmts.prepend(srcIterable);
            }
            ProducedType resultElementType = expr.getTarget().getType();
            ProducedType resultAbsentType = typeFact().getIteratedAbsentType(expr.getPrimary().getTypeModel());
           
            // private Iterator<srcElementType> iterator = srcIterableName.iterator();
            JCVariableDecl srcIterator = makeVar(Flags.FINAL, srcIteratorName, makeJavaType(typeFact().getIteratorType(srcElementType)),
                    make().Apply(null,
                            naming.makeQualIdent(srcIterableName.makeIdent(), "iterator"),
                            List.<JCExpression>nil()));
           
            Naming.SyntheticName iteratorResultName = varBaseName.suffixedBy(Suffix.$element$);
            /* public Object next() {
             *     Object result;
             *     if (!((result = iterator.next()) instanceof Finished)) {
             *         result = transformedMember(result);
             *     }
             *     return result;
             */
           
            /* Any arguments in the member of the spread would get re-evaluated on each iteration
             * so we need to shift them to the scope of the Let to ensure they're evaluated once.
             */
            boolean aliasArguments = (transformer instanceof InvocationTermTransformer)
                    && ((InvocationTermTransformer)transformer).invocation.getNode() instanceof Tree.InvocationExpression
                    && ((Tree.InvocationExpression)((InvocationTermTransformer)transformer).invocation.getNode()).getPositionalArgumentList() != null;
            if (aliasArguments) {
                ((InvocationTermTransformer)transformer).callBuilder.argumentHandling(
                        CallBuilder.CB_ALIAS_ARGS, varBaseName);
            }
           
            JCNewClass iterableClass;
            boolean prevSyntheticClassBody = expressionGen().withinSyntheticClassBody(true);
            try {
                JCExpression transformedElement = applyErasureAndBoxing(iteratorResultName.makeIdent(), typeFact().getAnythingDeclaration().getType(), CodegenUtil.hasTypeErased(expr.getPrimary()),
                        true, BoxingStrategy.BOXED,
                        srcElementType, 0);
                transformedElement = transformMemberExpression(expr, transformedElement, transformer);
               
                // This short-circuit is here for spread invocations
                // The code has been called recursively and the part after this if-statement will
                // be handled by the previous recursion
                if (spreadMethodReferenceOuter) {
                    return make().LetExpr(letStmts.toList(), transformedElement);
                }
               
                transformedElement = applyErasureAndBoxing(transformedElement, resultElementType,
                        // don't trust the erased flag of expr, as it reflects the result type of the overall spread expr,
                        // not necessarily of the applied member
                        CodegenUtil.hasTypeErased((TypedDeclaration)expr.getTarget().getDeclaration()),
                        !CodegenUtil.isUnBoxed(expr), BoxingStrategy.BOXED, resultElementType, 0);
               
                MethodDefinitionBuilder nextMdb = MethodDefinitionBuilder.systemMethod(this, "next");
                nextMdb.isOverride(true);
                nextMdb.annotationFlags(Annotations.IGNORE);
                nextMdb.modifiers(Flags.PUBLIC | Flags.FINAL);
                nextMdb.resultType(null, make().Type(syms().objectType));
                nextMdb.body(List.of(
                        makeVar(iteratorResultName,
                            make().Type(syms().objectType), null),
                        make().If(
                                make().Unary(JCTree.NOT,
                                make().TypeTest(make().Assign(
                                        iteratorResultName.makeIdent(),
                                        make().Apply(null,
                                                naming.makeQualIdent(srcIteratorName.makeIdent(), "next"),
                                                List.<JCExpression>nil())),
                                        make().Type(syms().ceylonFinishedType))),
                                make().Block(0, List.<JCStatement>of(make().Exec(make().Assign(iteratorResultName.makeIdent(),
                                        transformedElement)))),
                                null),
                        make().Return(iteratorResultName.makeIdent())));
                JCMethodDecl nextMethod = nextMdb.build();
               
                // new AbstractIterator()
                JCNewClass iteratorClass = make().NewClass(null,
                        null,
                        make().TypeApply(make().QualIdent(syms().ceylonAbstractIteratorType.tsym),
                                List.of(makeJavaType(resultElementType, JT_TYPE_ARGUMENT))),
                        List.of(makeReifiedTypeArgument(resultElementType)),
                        make().AnonymousClassDef(make().Modifiers(0), List.of(srcIterator, nextMethod)));
                       
                MethodDefinitionBuilder iteratorMdb = MethodDefinitionBuilder.systemMethod(this, "iterator");
                iteratorMdb.isOverride(true);
                iteratorMdb.annotationFlags(Annotations.IGNORE);
                iteratorMdb.modifiers(Flags.PUBLIC | Flags.FINAL);
                iteratorMdb.resultType(null, makeJavaType(typeFact().getIteratorType(resultElementType)));
                iteratorMdb.body(make().Return(iteratorClass));
                       
                // new AbstractIterable()
                iterableClass = make().NewClass(null,
                        null,
                        make().TypeApply(make().QualIdent(syms().ceylonAbstractIterableType.tsym),
                                List.of(makeJavaType(resultElementType, JT_TYPE_ARGUMENT), makeJavaType(resultAbsentType, JT_TYPE_ARGUMENT))),
                        List.of(makeReifiedTypeArgument(resultElementType), makeReifiedTypeArgument(resultAbsentType)),
                        make().AnonymousClassDef(make().Modifiers(0), List.<JCTree>of(iteratorMdb.build())));
            } finally {
                expressionGen().withinSyntheticClassBody(prevSyntheticClassBody);
            }
           
            if (aliasArguments) {
                letStmts = letStmts.appendList(((InvocationTermTransformer)transformer).callBuilder.getStatements());
            }
           
            JCMethodInvocation result = make().Apply(null,
                    naming.makeQualIdent(iterableClass, "sequence"),
                    List.<JCExpression>nil());
            JCExpression spread = letStmts.isEmpty() ? result : make().LetExpr(letStmts.toList(), result);
           
            // Do we *statically* know the result must be a Sequence
            final boolean primaryIsSequence = typeFact().isNonemptyIterableType(expr.getPrimary().getTypeModel());
            ProducedType returnElementType = expr.getTarget().getType();
            if(primaryIsSequence){
                int flags = EXPR_DOWN_CAST;
                spread = applyErasureAndBoxing(spread,
                        typeFact().getSequentialType(returnElementType),
                        false,
                        true,
                        BoxingStrategy.BOXED,
                        primaryIsSequence ?
                                typeFact().getSequenceType(returnElementType)
                                : typeFact().getSequentialType(returnElementType),
                                flags);
            }
            return spread;
        } finally {
            spreading = oldSpreading;
        }
       
    }

    JCExpression transformQualifiedMemberPrimary(Tree.QualifiedMemberOrTypeExpression expr) {
        if(expr.getTarget() == null)
            return makeErroneous(expr, "compiler bug: "
                    // make sure we don't die of a missing declaration too
                    + (expr.getDeclaration() != null ? expr.getDeclaration().getName() : expr)
                    + " has a null target");
        // consider package qualifiers as non-prefixed, we always qualify them anyways, this is
        // only useful for the typechecker resolving
        Tree.Primary primary = expr.getPrimary();
        if(primary instanceof Tree.Package)
            return null;
        ProducedType type = expr.getTarget().getQualifyingType();
        if(expr.getMemberOperator() instanceof Tree.SafeMemberOp && !isOptional(type)){
            ProducedType optionalType = typeFact().getOptionalType(type);
            optionalType.setUnderlyingType(type.getUnderlyingType());
            type = optionalType;
        }
        BoxingStrategy boxing = expr.getMemberOperator() instanceof Tree.SafeMemberOp == false
                && Decl.isValueTypeDecl(primary)
                && CodegenUtil.isUnBoxed(primary)
                ? BoxingStrategy.UNBOXED : BoxingStrategy.BOXED;
        JCExpression result;
        if (isSuper(primary)) {
            result = transformSuper(expr);
        } else if (isSuperOf(primary)) {
            result = transformSuperOf(expr);
        } else if (Decl.isJavaStaticPrimary(primary)) {
            // Java static field or method access
            result = transformJavaStaticMember((Tree.QualifiedMemberOrTypeExpression)primary, expr.getTypeModel());
        } else {
            result = transformExpression(primary, boxing, type);
        }
       
        return result;
    }

    private JCExpression transformJavaStaticMember(Tree.QualifiedMemberOrTypeExpression qmte, ProducedType staticType) {
        Declaration decl = qmte.getDeclaration();
        if (decl instanceof FieldValue) {
            Value member = (Value)decl;
            return naming.makeName(member, Naming.NA_FQ | Naming.NA_WRAPPER_UNQUOTED);
        } else if (decl instanceof Value) {
            Value member = (Value)decl;
            CallBuilder callBuilder = CallBuilder.instance(this);
            ProducedType qualifyingType = ((TypeDeclaration)member.getContainer()).getType();
            callBuilder.invoke(naming.makeQualifiedName(
                    makeJavaType(qualifyingType, JT_RAW | JT_NO_PRIMITIVES),
                    member,
                    Naming.NA_GETTER | Naming.NA_MEMBER));
            return utilInvocation().checkNull(callBuilder.build());
        } else if (decl instanceof Method) {
            Method method = (Method)decl;
            final ParameterList parameterList = method.getParameterLists().get(0);
            ProducedType qualifyingType = qmte.getPrimary().getTypeModel();
            Tree.TypeArguments typeArguments = qmte.getTypeArguments();
            ProducedReference producedReference = method.getProducedReference(qualifyingType, typeArguments.getTypeModels());
            return utilInvocation().checkNull(makeJavaStaticInvocation(gen(),
                    method, producedReference, parameterList));
        } else if (decl instanceof Class) {
            Class class_ = (Class)decl;
            final ParameterList parameterList = class_.getParameterLists().get(0);
            ProducedReference producedReference = qmte.getTarget();
            return utilInvocation().checkNull(makeJavaStaticInvocation(gen(),
                    class_, producedReference, parameterList));
        } else {
            return makeErroneous(qmte, "compiler bug: unsupported static");
        }
    }

    JCExpression makeJavaStaticInvocation(CeylonTransformer gen,
            final Functional methodOrClass,
            ProducedReference producedReference,
            final ParameterList parameterList) {
        CallBuilder callBuilder = CallBuilder.instance(gen);
        if (methodOrClass instanceof Method) {
            callBuilder.invoke(gen.naming.makeName(
                    (Method)methodOrClass, Naming.NA_FQ | Naming.NA_WRAPPER_UNQUOTED));
        } else if (methodOrClass instanceof Class) {
            callBuilder.instantiate(
                    gen.makeJavaType(((Class)methodOrClass).getType(), JT_RAW | JT_NO_PRIMITIVES));
        }
        ListBuffer<ExpressionAndType> reified = ListBuffer.lb();
       
        DirectInvocation.addReifiedArguments(gen, producedReference, reified);
        for (ExpressionAndType reifiedArgument : reified) {
            callBuilder.argument(reifiedArgument.expression);
        }
       
        for (Parameter parameter : parameterList.getParameters()) {
            callBuilder.argument(gen.naming.makeQuotedIdent(parameter.getName()));
        }
        JCExpression innerInvocation = callBuilder.build();
        return innerInvocation;
    }
   
    /**
     * Removes the parentheses from the given term
     */
    static Tree.Term eliminateParens(Tree.Term term) {
        while (term instanceof Tree.Expression) {
            term = ((Tree.Expression) term).getTerm();
        }
        return term;
    }
   
    /**
     * Is the given primary a {@code super of Foo}
     * expression (modulo parentheses and multiple {@code of}
     */
    private static boolean isSuperOf(Tree.Primary primary) {
        return primary instanceof Tree.Expression
                && Util.eliminateParensAndWidening(((Tree.Expression)primary).getTerm()) instanceof Tree.Super;
    }
   
    /**
     * Is the given primary a {@code super} expression
     * (modulo parentheses)
     */
    private static boolean isSuper(Tree.Primary primary) {
        return eliminateParens(primary) instanceof Tree.Super;
    }
   
    /**
     * Is the given primary a {@code super} or {@code super of Foo}
     * expression (modulo parentheses and multiple {@code of}
     */
    static boolean isSuperOrSuperOf(Tree.Primary primary) {
        return isSuper(primary) || isSuperOf(primary);
    }
   
    private JCExpression transformSuperOf(Tree.QualifiedMemberOrTypeExpression superOfQualifiedExpr) {
        Tree.Term superOf = eliminateParens(superOfQualifiedExpr.getPrimary());
        if (!(superOf instanceof Tree.OfOp)) {
            throw new BugException();
        }
        Tree.Type superType = ((Tree.OfOp)superOf).getType();
        if (!(eliminateParens(((Tree.OfOp)superOf).getTerm()) instanceof Tree.Super)) {
            throw new BugException();
        }
        Declaration member = superOfQualifiedExpr.getDeclaration();
        TypeDeclaration inheritedFrom = superType.getTypeModel().getDeclaration();
        if (inheritedFrom instanceof Interface) {
            inheritedFrom = (TypeDeclaration)inheritedFrom.getMember(member.getName(), null, false).getContainer();
        }
        return widen(superOfQualifiedExpr, inheritedFrom);
    }

    private JCExpression widen(
            Tree.QualifiedMemberOrTypeExpression superOfQualifiedExpr,
            TypeDeclaration inheritedFrom) {
        JCExpression result;
        if (inheritedFrom instanceof Class) {
            result = naming.makeSuper();
        } else if (inheritedFrom instanceof Interface) {
            Interface iface = (Interface)inheritedFrom;
            JCExpression qualifier = null;
            if (needDollarThis(superOfQualifiedExpr.getScope())) {
                qualifier = naming.makeQuotedThis();
                if (iface.equals(typeFact().getIdentifiableDeclaration())) {
                    result = naming.makeQualifiedSuper(qualifier);
                } else {
                    result = naming.makeCompanionAccessorCall(qualifier, iface);
                }
            } else {
                if (iface.equals(typeFact().getIdentifiableDeclaration())) {
                    result = naming.makeQualifiedSuper(qualifier);
                } else {
                    result = naming.makeCompanionFieldName(iface);
                }
            }
        } else {
            result = makeErroneous(superOfQualifiedExpr, "compiler bug: " + (inheritedFrom == null ? "null" : inheritedFrom.getClass().getName()) + " is an unhandled case in widen()");
        }
        return result;
    }

    public JCExpression transformSuper(Tree.QualifiedMemberOrTypeExpression superQualifiedExpr) {
        Declaration member = superQualifiedExpr.getDeclaration();
        TypeDeclaration inheritedFrom = (TypeDeclaration)member.getContainer();
        return widen(superQualifiedExpr, inheritedFrom);
    }
   
    // Base members
   
    public JCExpression transform(Tree.BaseMemberExpression expr) {
        return transform(expr, null);
    }

    private JCExpression transform(Tree.BaseMemberOrTypeExpression expr, TermTransformer transformer) {
        return transformMemberExpression(expr, null, transformer);
    }

    // Type members
   
    public JCExpression transform(Tree.QualifiedTypeExpression expr) {
        if (expr.getPrimary() instanceof Tree.BaseTypeExpression) {
            Tree.BaseTypeExpression primary = (Tree.BaseTypeExpression)expr.getPrimary();
            return transformMemberReference(expr, primary);           
        } else if (expr.getPrimary() instanceof Tree.QualifiedTypeExpression) {
            Tree.QualifiedTypeExpression primary = (Tree.QualifiedTypeExpression)expr.getPrimary();
            return transformMemberReference(expr, primary);
        }
        return transform(expr, null);
    }
   
    public JCExpression transform(Tree.BaseTypeExpression expr) {
        return transform(expr, null);
    }
   
    private JCExpression transform(Tree.QualifiedTypeExpression expr, TermTransformer transformer) {
        JCExpression primaryExpr = transformQualifiedMemberPrimary(expr);
        return transformMemberExpression(expr, primaryExpr, transformer);
    }
   
    // Generic code for all primaries
   
    public JCExpression transformTermForInvocation(Tree.Term term, TermTransformer transformer) {
        if (term instanceof Tree.QualifiedMemberExpression) {
            return transform((Tree.QualifiedMemberExpression)term, transformer);
        } else if (term instanceof Tree.BaseMemberExpression) {
            return transform((Tree.BaseMemberExpression)term, transformer);
        } else if (term instanceof Tree.BaseTypeExpression) {
            return transform((Tree.BaseTypeExpression)term, transformer);
        } else if (term instanceof Tree.QualifiedTypeExpression) {
            return transform((Tree.QualifiedTypeExpression)term, transformer);
        } else {
            // do not consider our term to be part of an invocation, we want it to be a Callable
            boolean oldWi = withinInvocation(false);
            JCExpression primaryExpr;
            try{
                primaryExpr = transformExpression(term);
                if (transformer != null) {
                    primaryExpr = transformer.transform(primaryExpr, null);
                }
            }finally{
                withinInvocation(oldWi);
            }
            return primaryExpr;
        }
    }
   
    private JCExpression transformMemberExpression(Tree.StaticMemberOrTypeExpression expr, JCExpression primaryExpr, TermTransformer transformer) {
        JCExpression result = null;

        // do not throw, an error will already have been reported
        Declaration decl = expr.getDeclaration();
        if (decl == null) {
            return makeErroneous(expr, "compiler bug: expression with no declaration");
        }
       
        // Try to find the original declaration, in case we have conditionals that refine the type of objects without us
        // creating a tmp variable (in which case we have a substitution for it)
        while(decl instanceof TypedDeclaration){
            TypedDeclaration typedDecl = (TypedDeclaration) decl;
            if(!naming.isSubstituted(decl) && typedDecl.getOriginalDeclaration() != null){
                decl = ((TypedDeclaration) decl).getOriginalDeclaration();
            }else{
                break;
            }
        }
       
        // Explanation: primaryExpr and qualExpr both specify what is to come before the selector
        // but the important difference is that primaryExpr is used for those situations where
        // the result comes from the actual Ceylon code while qualExpr is used for those situations
        // where we need to refer to synthetic objects (like wrapper classes for toplevel methods)
       
        JCExpression qualExpr = null;
        String selector = null;
        // true for Java interop using fields, and for super constructor parameters, which must use
        // parameters rather than getter methods
        boolean mustUseField = false;
        // true for default parameter methods
        boolean mustUseParameter = false;
        if (decl instanceof Functional
                && (!(decl instanceof Method) || !decl.isParameter()
                        || functionalParameterRequiresCallable((Method)decl, expr))
                && isFunctionalResult(expr.getTypeModel())) {
            result = transformFunctional(expr, (Functional)decl);
        } else if (Decl.isGetter(decl)) {
            // invoke the getter
            if (decl.isToplevel()) {
                primaryExpr = null;
                qualExpr = naming.makeName((Value)decl, Naming.NA_FQ | Naming.NA_WRAPPER | Naming.NA_MEMBER);
                selector = null;
            } else if (Decl.withinClassOrInterface(decl) && !Decl.isLocalToInitializer(decl)) {
                selector = naming.selector((Value)decl);
            } else {
                // method local attr
                if (!isRecursiveReference(expr)) {
                    primaryExpr = naming.makeQualifiedName(primaryExpr, (Value)decl, Naming.NA_Q_LOCAL_INSTANCE);
                }
                selector = naming.selector((Value)decl);
            }
        } else if (Decl.isValueOrSharedOrCapturedParam(decl)) {
            if (decl.isToplevel()) {
                // ERASURE
                if ("null".equals(decl.getName())) {
                    // FIXME this is a pretty brain-dead way to go about erase I think
                    result = makeNull();
                } else if (isBooleanTrue(decl)) {
                    result = makeBoolean(true);
                } else if (isBooleanFalse(decl)) {
                    result = makeBoolean(false);
                } else {
                    // it's a toplevel attribute
                    primaryExpr = naming.makeName((TypedDeclaration)decl, Naming.NA_FQ | Naming.NA_WRAPPER);
                    selector = naming.selector((TypedDeclaration)decl);
                }
            } else if (Decl.isClassAttribute(decl) || Decl.isClassParameter(decl)) {
                mustUseField = Decl.isJavaField(decl)
                        || (isWithinSuperInvocation()
                                && primaryExpr == null
                                && withinSuperInvocation == decl.getContainer());
                mustUseParameter = (primaryExpr == null && isWithinDefaultParameterExpression(decl.getContainer()));
                if (mustUseField || mustUseParameter){
                    if(decl instanceof FieldValue) {
                        selector = ((FieldValue)decl).getRealName();
                    } else if (isWithinSuperInvocation()
                            && ((Value)decl).isVariable()
                            && ((Value)decl).isCaptured()) {
                        selector = Naming.getAliasedParameterName(((Value)decl).getInitializerParameter());
                    } else {
                        selector = decl.getName();
                    }
                } else {
                    // invoke the getter, using the Java interop form of Util.getGetterName because this is the only case
                    // (Value inside a Class) where we might refer to JavaBean properties
                    selector = naming.selector((TypedDeclaration)decl);
                }
            } else if (decl.isCaptured() || decl.isShared()) {
                TypedDeclaration typedDecl = ((TypedDeclaration)decl);
                TypeDeclaration typeDecl = typedDecl.getType().getDeclaration();
                mustUseField = Decl.isBoxedVariable((TypedDeclaration)decl);
                if (Decl.isLocalNotInitializer(typeDecl)
                        && typeDecl.isAnonymous()
                        // we need the box if it's a captured object
                        && !typedDecl.isSelfCaptured()) {
                    // accessing a local 'object' declaration, so don't need a getter
                } else if (decl.isCaptured()
                        && !((TypedDeclaration) decl).isVariable()
                        // captured objects are never variable but need the box
                        && !typedDecl.isSelfCaptured()) {
                    // accessing a local that is not getter wrapped
                } else {
                    primaryExpr = naming.makeQualifiedName(primaryExpr, (TypedDeclaration)decl, Naming.NA_Q_LOCAL_INSTANCE);
                    selector = naming.selector((TypedDeclaration)decl);
                }
            }
        } else if (Decl.isMethodOrSharedOrCapturedParam(decl)) {
            mustUseParameter = (primaryExpr == null
                    && decl.isParameter()
                    && isWithinDefaultParameterExpression(decl.getContainer()));
            if (!decl.isParameter()
                    && (Decl.isLocalNotInitializer(decl) || (Decl.isLocalToInitializer(decl) && ((Method)decl).isDeferred()))) {
                primaryExpr = null;
                int flags = Naming.NA_MEMBER;
                if (!isRecursiveReference(expr)) {
                    // Only want to quote the method name
                    // e.g. enum.$enum()
                    flags |= Naming.NA_WRAPPER_UNQUOTED;
                }else if(!isReferenceInSameScope(expr)){
                    // always qualify it with this
                    flags |= Naming.NA_WRAPPER | Naming.NA_WRAPPER_WITH_THIS;
                }
                qualExpr = naming.makeName((Method)decl, flags);
                selector = null;
            } else if (decl.isToplevel()) {
                primaryExpr = null;
                qualExpr = naming.makeName((Method)decl, Naming.NA_FQ | Naming.NA_WRAPPER | Naming.NA_MEMBER);
                selector = null;
            } else if (!isWithinInvocation()) {
                selector = null;
            } else {
                // not toplevel, not within method, must be a class member
                selector = naming.selector((Method)decl);
            }
        }
        if (result == null) {
            boolean useGetter = !(decl instanceof Method) && !mustUseField && !mustUseParameter;
            if (qualExpr == null && selector == null) {
                useGetter = Decl.isClassAttribute(decl) && CodegenUtil.isErasedAttribute(decl.getName());
                if (useGetter) {
                    selector = naming.selector((TypedDeclaration)decl);
                } else {
                    selector = naming.substitute(decl);
                }
            }
           
            if (qualExpr == null) {
                qualExpr = primaryExpr;
            }
           
            // FIXME: Stef has a strong suspicion that the four next methods
            // should be merged since they all add a this qualifier in different
            // cases
            if(!mustUseParameter){
                qualExpr = addQualifierForObjectMembersOfInterface(expr, decl, qualExpr);

                qualExpr = addInterfaceImplAccessorIfRequired(qualExpr, expr, decl);

                qualExpr = addThisQualifierIfRequired(qualExpr, expr, decl);

                if (qualExpr == null && needDollarThis(expr)) {
                    qualExpr = makeQualifiedDollarThis((Tree.BaseMemberExpression)expr);
                }
            }
           
            if (qualExpr == null && decl.isStaticallyImportable()
                    // make sure we only do this for things contained in a type, as otherwise
                    // it breaks for qualified calls to static methods in interfaces in Java 8
                    // it only breaks for interfaces because they are statically importable
                    // and not classes
                    && decl.getContainer() instanceof TypeDeclaration) {
                qualExpr = naming.makeTypeDeclarationExpression(null, (TypeDeclaration)decl.getContainer(), DeclNameFlag.QUALIFIED);
            }
            if (Decl.isPrivateAccessRequiringUpcast(expr)) {
                qualExpr = makePrivateAccessUpcast(expr, qualExpr);
            }
           
            if (transformer != null) {
                result = transformer.transform(qualExpr, selector);
            } else {
                Tree.Primary qmePrimary = null;
                if (expr instanceof Tree.QualifiedMemberOrTypeExpression) {
                    qmePrimary = ((Tree.QualifiedMemberOrTypeExpression)expr).getPrimary();
                }
                if (Decl.isValueTypeDecl(qmePrimary)
                        // Safe operators always work on boxed things, so don't use value types
                        && (expr instanceof Tree.QualifiedMemberOrTypeExpression == false
                            || ((Tree.QualifiedMemberOrTypeExpression)expr).getMemberOperator() instanceof Tree.SafeMemberOp == false)
                        // We never want to use value types on boxed things, unless they are java arrays
                        && (CodegenUtil.isUnBoxed(qmePrimary) || isJavaArray(qmePrimary.getTypeModel()))
                        // Java arrays length property does not go via value types
                        && (!isJavaArray(qmePrimary.getTypeModel())
                                || (!"length".equals(selector) && !"hashCode".equals(selector)))) {
                    JCExpression primTypeExpr = makeJavaType(qmePrimary.getTypeModel(), JT_NO_PRIMITIVES | JT_VALUE_TYPE);
                    result = makeQualIdent(primTypeExpr, selector);
                    result = make().Apply(List.<JCTree.JCExpression>nil(),
                            result,
                            List.<JCTree.JCExpression>of(qualExpr));
                } else if (expr instanceof Tree.QualifiedMemberOrTypeExpression
                        && isThrowableMessage((Tree.QualifiedMemberOrTypeExpression)expr)) {
                    result = utilInvocation().throwableMessage(qualExpr);
                } else if (expr instanceof Tree.QualifiedMemberOrTypeExpression
                        && isThrowableSuppressed((Tree.QualifiedMemberOrTypeExpression)expr)) {
                    result = utilInvocation().suppressedExceptions(qualExpr);
                } else {
                    result = makeQualIdent(qualExpr, selector);
                    if (useGetter) {
                        result = make().Apply(List.<JCTree.JCExpression>nil(),
                                result,
                                List.<JCTree.JCExpression>nil());
                    }
                }
            }
        }
       
        return result;
    }

    /**
     * We may need to force a qualified this prefix (direct or outer) in the following cases:
     *
     * - Required because of mixin inheritance with different type arguments (the same is already
     *   done for qualified references, but not for direct references)
     * - The compiler generates anonymous local classes for things like
     *   Callables and Comprehensions. When referring to a member foo
     *   within one of those things we need a qualified {@code this}
     *   to ensure we're accessing the outer instances member, not
     *   a member of the anonymous local class that happens to have the same name.
     */
    private JCExpression addThisQualifierIfRequired(
            JCExpression qualExpr, Tree.StaticMemberOrTypeExpression expr,
            Declaration decl) {
        if (qualExpr == null
                // statics are not members that can be inherited
                && !decl.isStaticallyImportable()
                && decl.isMember()
                // dodge variable refinements with assert/is (these will be turned to locals
                // and have a name mapping)
                && expr.getTarget().getDeclaration() == decl
                && !Decl.isLocalToInitializer(decl)
                && !isWithinSuperInvocation()) {
            // First check whether the expression is captured from an enclosing scope
            TypeDeclaration outer = null;
            // get the ClassOrInterface container of the declaration
            Scope stop = Decl.getClassOrInterfaceContainer(decl, false);
            if (stop instanceof TypeDeclaration) {// reified scope
                Scope scope = expr.getScope();
                while (!(scope instanceof Package)) {
                    if (scope.equals(stop)) {
                        outer = (TypeDeclaration)stop;
                        break;
                    }
                    scope = scope.getContainer();
                }
            }
            // If not it might be inherited...
            if (outer == null) {
                outer = expr.getScope().getInheritingDeclaration(decl);
            }
            if (outer != null) {
                ProducedType targetType = expr.getTarget().getQualifyingType();
                ProducedType declarationContainerType = ((TypeDeclaration)outer).getType();
                // check if we need a variance cast
                VarianceCastResult varianceCastResult = getVarianceCastResult(targetType, declarationContainerType);
                // if we are within a comprehension body, or if we need a variance cast
                if(isWithinSyntheticClassBody() || varianceCastResult != null){
                    if (decl.isShared() && outer instanceof Interface) {
                        // always prefer qualified
                        qualExpr = makeQualifiedDollarThis(declarationContainerType);
                    } else {
                        // Class or companion class,
                        qualExpr = naming.makeQualifiedThis(makeJavaType(((TypeDeclaration)outer).getType(),
                                JT_RAW | (outer instanceof Interface ? JT_COMPANION : 0)));
                    }
                    // add the variance cast if required
                    if(varianceCastResult != null){
                        qualExpr = applyVarianceCasts(qualExpr, targetType, varianceCastResult, 0);
                    }
                }
            } else if (decl.isMember()) {
                throw new BugException(expr, decl.getQualifiedNameString() + " was unexepctecly a member");
            }
        }
        return qualExpr;
    }

    /**
     * §3.2.2 Every interface is a subtype of c.l.Object, so
     * within an Interface {@code string} means {@code $this.toString()}
     * @param expr
     * @param decl
     * @param qualExpr
     * @return
     */
    // Interface we must use $this's implementation of equals, hash and string
    private JCExpression addQualifierForObjectMembersOfInterface(
            Tree.StaticMemberOrTypeExpression expr, Declaration decl,
            JCExpression qualExpr) {
        if (expr instanceof Tree.BaseMemberExpression
                && qualExpr == null
                && typeFact().getObjectDeclaration().equals(Decl.getClassOrInterfaceContainer(decl))) {
            Scope scope = expr.getScope();
            while (Decl.isLocalNotInitializerScope(scope)) {
                scope = scope.getContainer();
            }
            if (scope instanceof Interface) {
                qualExpr = naming.makeQuotedThis();
            }
        }
        return qualExpr;
    }

    /**
     * Determines whether we need to generate an AbstractCallable when taking
     * a method reference to a method that's declared as a FunctionalParameter
     */
    private boolean functionalParameterRequiresCallable(Method functionalParameter, Tree.StaticMemberOrTypeExpression expr) {
        if (!functionalParameter.isParameter()) {
            throw new BugException();
        }
        boolean hasMethod = Strategy.createMethod(functionalParameter);
        if (!hasMethod) {
            // A functional parameter that's not method wrapped will already be Callable-wrapped
            return false;
        }
        // Optimization: If we're in a scope where the Callable field is visible
        // we don't need to create a method ref       
        Scope scope = expr.getScope();
        while (true) {
            if (scope instanceof Package) {
                break;
            }
            if (scope.equals(functionalParameter.getContainer())) {
                return false;
            }
            scope = scope.getContainer();
        }
        // Otherwise we do require an AbstractCallable.
        return true;
    }

    //
    // Array access

    private JCExpression addInterfaceImplAccessorIfRequired(JCExpression qualExpr, Tree.StaticMemberOrTypeExpression expr, Declaration decl) {
        // Partial fix for https://github.com/ceylon/ceylon-compiler/issues/1023
        // For interfaces we sometimes need to access either the interface instance or its $impl class
        Scope declContainer = Decl.container(decl);
        if(qualExpr != null
                // this is only for interface containers
                && declContainer instanceof Interface
                // we only ever need the $impl if the declaration is not shared
                && !decl.isShared()){
            Interface declaration = (Interface) declContainer;
            // access the interface $impl instance
            qualExpr = naming.makeCompanionAccessorCall(qualExpr, declaration);
            // When the interface is local the accessor returns Object
            // so we need to cast it to the type of the companion
            if (Decl.isAncestorLocal(declaration)) {
                ProducedType type;
                // try to find the best type
                if(expr instanceof Tree.QualifiedMemberOrTypeExpression)
                    type = ((Tree.QualifiedMemberOrTypeExpression) expr).getPrimary().getTypeModel();
                else
                    type = declaration.getType();
                qualExpr = make().TypeCast(makeJavaType(type, JT_COMPANION), qualExpr);
            }
        }
        return qualExpr;
    }

    private JCExpression makeQualifiedDollarThis(Tree.BaseMemberExpression expr) {
        Declaration decl = expr.getDeclaration();
        Interface interf = (Interface) Decl.getClassOrInterfaceContainer(decl);
        // find the target container interface that is or satisfies the given interface
        Scope scope = expr.getScope();
        boolean needsQualified = false;
        while(scope != null){
            if(scope instanceof Interface){
                if(Decl.equalScopeDecl(scope, interf) || ((Interface)scope).inherits(interf)){
                    break;
                }
                // we only need to qualify it if we're aiming for a $this of an outer interface than the interface we are caught in
                needsQualified = true;
            }
            scope = scope.getContainer();
        }
        if(!needsQualified)
            return naming.makeQuotedThis();
        interf = (Interface) scope;
        return makeQualifiedDollarThis(interf.getType());
    }
   
    private JCExpression makeQualifiedDollarThis(ProducedType targetType){
        JCExpression qualifiedCompanionThis = naming.makeQualifiedThis(makeJavaType(targetType, JT_COMPANION | JT_RAW));
        return naming.makeQualifiedDollarThis(qualifiedCompanionThis);
    }

    private boolean needDollarThis(Tree.StaticMemberOrTypeExpression expr) {
        if (expr instanceof Tree.BaseMemberExpression) {
            // We need to add a `$this` prefix to the member expression if:
            // * The member was declared on an interface I and
            // * The member is being used in the companion class of I or
            //   // REMOVED: some subinterface of I, and
            //   some member type of I, and
            // * The member is shared (non-shared means its only on the companion class)
            // FIXME: https://github.com/ceylon/ceylon-compiler/issues/1019
            final Declaration decl = expr.getDeclaration();
            if(!Decl.withinInterface(decl))
                return false;
           
            // Find the method/getter/setter where the expr is being used
            Scope scope = expr.getScope();
            while (scope != null && scope instanceof Interface == false) {
                scope = scope.getContainer();
            }
            // Is it being used in an interface (=> impl)
            if (scope instanceof Interface) {
                return decl.isShared();
            }
        }
        return false;
    }
   
    private boolean needDollarThis(Scope scope) {
        while (Decl.isLocalNotInitializerScope(scope)) {
            scope = scope.getContainer();
        }
        return scope instanceof Interface;
    }

    public JCTree transform(Tree.IndexExpression access) {
        // depends on the operator
        Tree.ElementOrRange elementOrRange = access.getElementOrRange();
        boolean isElement = elementOrRange instanceof Tree.Element;
       
        // let's see what types there are
        ProducedType leftType = access.getPrimary().getTypeModel();
        // find the corresponding supertype
        Interface leftSuperTypeDeclaration;
        if(isElement)
            leftSuperTypeDeclaration = typeFact().getCorrespondenceDeclaration();
        else
            leftSuperTypeDeclaration = typeFact().getRangedDeclaration();
        ProducedType leftCorrespondenceOrRangeType = leftType.getSupertype(leftSuperTypeDeclaration);
        ProducedType rightType = getTypeArgument(leftCorrespondenceOrRangeType, 0);
       
        JCExpression lhs = transformExpression(access.getPrimary(), BoxingStrategy.BOXED, leftCorrespondenceOrRangeType);
       
        // now find the access code
        JCExpression safeAccess;
       
        if(isElement){
            Tree.Element element = (Tree.Element) elementOrRange;
           
            // do the index
            JCExpression index = transformExpression(element.getExpression(), BoxingStrategy.BOXED, rightType);

            // tmpVar.item(index)
            safeAccess = at(access).Apply(List.<JCTree.JCExpression>nil(),
                                          makeSelect(lhs, "get"), List.of(index));
            // Because tuple index access has the type of the indexed element
            // (not the union of types in the sequential) a typecast may be required.
            ProducedType sequentialElementType = getTypeArgument(leftCorrespondenceOrRangeType, 1);
            ProducedType expectedType = access.getTypeModel();
            int flags = 0;
            if(!expectedType.isExactly(sequentialElementType)
                    // could be optional too, for regular Correspondence item access
                    && !expectedType.isExactly(typeFact().getOptionalType(sequentialElementType)))
                flags |= EXPR_DOWN_CAST;
            safeAccess = applyErasureAndBoxing(safeAccess,
                                               sequentialElementType,
                                               CodegenUtil.hasTypeErased(access), true, BoxingStrategy.BOXED,
                                               expectedType, flags);
        }else{
            // do the indices
            Tree.ElementRange range = (Tree.ElementRange) elementOrRange;
            JCExpression start = transformExpression(range.getLowerBound(), BoxingStrategy.BOXED, rightType);

            // is this a span or segment?
            String method;
            final List<JCExpression> args;
            if (range.getLowerBound() != null
                    && range.getLength() != null) {
                method = "measure";
                JCExpression length = transformExpression(range.getLength(), BoxingStrategy.UNBOXED, typeFact().getIntegerDeclaration().getType());
                args = List.of(start, length);
            } else if (range.getLowerBound() == null) {
                method = "spanTo";
                JCExpression end = transformExpression(range.getUpperBound(), BoxingStrategy.BOXED, rightType);
                args = List.of(end);
            } else if (range.getUpperBound() == null) {
                method = "spanFrom";
                args = List.of(start);
            } else if (range.getLowerBound() != null
                    && range.getUpperBound() != null) {
                method = "span";
                JCExpression end = transformExpression(range.getUpperBound(), BoxingStrategy.BOXED, rightType);
                args = List.of(start, end);
            } else {
                method = "unknown";
                args = List.<JCExpression>of(makeErroneous(range, "compiler bug: unhandled range"));
            }

            // Because tuple open span access has the type of the indexed element
            // (not a sequential of the union of types in the ranged) a typecast may be required.
            ProducedType rangedSpanType = getTypeArgument(leftCorrespondenceOrRangeType, 2);
            ProducedType expectedType = access.getTypeModel();
            int flags = 0;
            if(!expectedType.isExactly(rangedSpanType)){
                flags |= EXPR_DOWN_CAST;
                // make sure we barf properly if we missed a heuristics
                if(method.equals("spanFrom")){
                    // make a "Util.<method>(lhs, start, end)" call
                    at(access);
                    safeAccess = utilInvocation().tuple_spanFrom(args.prepend(lhs));
                }else{
                    safeAccess = makeErroneous(access, "compiler bug: only the spanFrom method should be specialised for Tuples");
                }
            }else{
                // make a "lhs.<method>(start, end)" call
                safeAccess = at(access).Apply(List.<JCTree.JCExpression>nil(),
                        makeSelect(lhs, method), args);
            }
            safeAccess = applyErasureAndBoxing(safeAccess,
                                               rangedSpanType,
                                               CodegenUtil.hasTypeErased(access), true, BoxingStrategy.BOXED,
                                               expectedType, flags);
        }

        return safeAccess;
    }

    //
    // Assignment

    public JCExpression transform(Tree.AssignOp op) {
        return transformAssignment(op, op.getLeftTerm(), op.getRightTerm());
    }

    private JCExpression transformAssignment(Node op, Tree.Term leftTerm, Tree.Term rightTerm) {
        // Remember and disable inStatement for RHS
        boolean tmpInStatement = inStatement;
        inStatement = false;
       
        // FIXME: can this be anything else than a Tree.MemberOrTypeExpression or Tree.ParameterizedExpression?
        final JCExpression rhs;
        BoxingStrategy boxing;
        if (leftTerm instanceof Tree.MemberOrTypeExpression) {
            TypedDeclaration decl = (TypedDeclaration) ((Tree.MemberOrTypeExpression)leftTerm).getDeclaration();
            boxing = CodegenUtil.getBoxingStrategy(decl);
            rhs = transformExpression(rightTerm, boxing, leftTerm.getTypeModel(),
                                      decl.hasUncheckedNullType() ? EXPR_TARGET_ACCEPTS_NULL : 0);
        } else {
            // instanceof Tree.ParameterizedExpression
            boxing = CodegenUtil.getBoxingStrategy(leftTerm);
            Tree.ParameterizedExpression paramExpr = (Tree.ParameterizedExpression)leftTerm;
            Method decl = (Method) ((Tree.MemberOrTypeExpression)paramExpr.getPrimary()).getDeclaration();
            CallableBuilder callableBuilder = CallableBuilder.anonymous(
                    gen(),
                    (Tree.Expression)rightTerm,
                    paramExpr.getParameterLists(),
                    paramExpr.getPrimary().getTypeModel(),
                    !decl.isDeferred());
            rhs = callableBuilder.build();
        }

        if (tmpInStatement) {
            return transformAssignment(op, leftTerm, rhs);
        } else {
            ProducedType valueType = leftTerm.getTypeModel();
            return transformAssignAndReturnOperation(op, leftTerm, boxing == BoxingStrategy.BOXED,
                    valueType, valueType, new AssignAndReturnOperationFactory(){
                @Override
                public JCExpression getNewValue(JCExpression previousValue) {
                    return rhs;
                }
            });
        }
    }
   
    private JCExpression transformAssignment(final Node op, Tree.Term leftTerm, JCExpression rhs) {
        // left hand side can be either BaseMemberExpression, QualifiedMemberExpression or array access (M2)
        // TODO: array access (M2)
        JCExpression expr = null;
        if(leftTerm instanceof Tree.BaseMemberExpression) {
            if (needDollarThis((Tree.BaseMemberExpression)leftTerm)) {
                expr = naming.makeQuotedThis();
            }
        } else if(leftTerm instanceof Tree.QualifiedMemberExpression) {
            Tree.QualifiedMemberExpression qualified = ((Tree.QualifiedMemberExpression)leftTerm);
            if (qualified.getPrimary() instanceof Tree.Package) {
                expr = null;
            } else if (isSuper(qualified.getPrimary())) {
                expr = transformSuper(qualified);
            } else if (isSuperOf(qualified.getPrimary())) {
                expr = transformSuperOf(qualified);
            } else if (!qualified.getDeclaration().isStaticallyImportable()) {
                expr = transformExpression(qualified.getPrimary(), BoxingStrategy.BOXED, qualified.getTarget().getQualifyingType());
                if (Decl.isPrivateAccessRequiringUpcast(qualified)) {
                    expr = makePrivateAccessUpcast(qualified, expr);
                }
            }
        } else if(leftTerm instanceof Tree.ParameterizedExpression) {
            // Nothing to do here
            expr = null;
        } else {
            return makeErroneous(op, "compiler bug: "+op.getNodeType() + " is not yet supported");
        }
        return transformAssignment(op, leftTerm, expr, rhs);
    }
   
    private JCExpression transformAssignment(Node op, Tree.Term leftTerm, JCExpression lhs, JCExpression rhs) {
        JCExpression result = null;

        // FIXME: can this be anything else than a Tree.StaticMemberOrTypeExpression or Tree.ParameterizedExpression?
        TypedDeclaration decl;
        if (leftTerm instanceof Tree.StaticMemberOrTypeExpression) {
            decl = (TypedDeclaration) ((Tree.StaticMemberOrTypeExpression)leftTerm).getDeclaration();
            lhs = addInterfaceImplAccessorIfRequired(lhs, (Tree.StaticMemberOrTypeExpression) leftTerm, decl);
        } else {
            // instanceof Tree.ParameterizedExpression
            decl = (TypedDeclaration) ((Tree.MemberOrTypeExpression)((Tree.ParameterizedExpression)leftTerm).getPrimary()).getDeclaration();
        }

        boolean variable = decl.isVariable();
       
        at(op);
        String selector = naming.selector(decl, Naming.NA_SETTER);
        if (decl.isToplevel()) {
            // must use top level setter
            lhs = naming.makeName(decl, Naming.NA_FQ | Naming.NA_WRAPPER);
        } else if (Decl.isGetter(decl)) {
            if (Decl.isTransient(decl) && !decl.isVariable()) {
                JCExpression attr = gen().transformAttributeGetter(decl, rhs);
                result = at(op).Assign(naming.makeQualifiedName(lhs, decl, Naming.NA_WRAPPER), attr);
            } else {
                // must use the setter
                if (Decl.isLocal(decl)) {
                    lhs = naming.makeQualifiedName(lhs, decl, Naming.NA_WRAPPER | Naming.NA_SETTER);
                } else if (decl.isStaticallyImportable()) {
                    lhs = naming.makeTypeDeclarationExpression(null, (TypeDeclaration)decl.getContainer(), DeclNameFlag.QUALIFIED);
                }
            }
        } else if (decl instanceof Method && Decl.isDeferred(decl)) {
            if (Decl.isLocal(decl)) {
                // Deferred method initialization of a local function
                // The Callable field has the same name as the method, so use NA_MEMBER
                result = at(op).Assign(naming.makeQualifiedName(lhs, decl, Naming.NA_WRAPPER_UNQUOTED | Naming.NA_MEMBER), rhs);
            } else {
                // Deferred method initialization of a class function
                result = at(op).Assign(naming.makeQualifiedName(lhs, decl, Naming.NA_MEMBER), rhs);
            }
        } else if ((variable || decl.isLate()) && (Decl.isClassAttribute(decl))) {
            // must use the setter, nothing to do, unless it's a java field
            if(Decl.isJavaField(decl)){
                if (decl.isStaticallyImportable()) {
                    // static field
                    result = at(op).Assign(naming.makeName(decl, Naming.NA_FQ | Naming.NA_WRAPPER_UNQUOTED), rhs);
                }else{
                    // normal field
                    result = at(op).Assign(naming.makeQualifiedName(lhs, decl, Naming.NA_IDENT), rhs);
                }
            }

        } else if (variable && (decl.isCaptured() || decl.isShared())) {
            // must use the qualified setter
            if (Decl.isBoxedVariable(decl)) {
                result = at(op).Assign(naming.makeName(decl, Naming.NA_Q_LOCAL_INSTANCE | Naming.NA_MEMBER | Naming.NA_SETTER), rhs);
            } else if (Decl.isLocalNotInitializer(decl)) {
                lhs = naming.makeQualifiedName(lhs, decl, Naming.NA_WRAPPER);
            } else if (isWithinSuperInvocation()
                    && decl.isCaptured()
                    && decl.isVariable()) {
                lhs = naming.makeUnquotedIdent(Naming.getAliasedParameterName(((Value)decl).getInitializerParameter()));
                result = at(op).Assign(lhs, rhs);
            }
        } else {
            result = at(op).Assign(naming.makeQualifiedName(lhs, decl, Naming.NA_IDENT), rhs);
        }
       
        if (result == null) {
            result = make().Apply(List.<JCTree.JCExpression>nil(),
                    makeQualIdent(lhs, selector),
                    List.<JCTree.JCExpression>of(rhs));
        }
       
        return result;
    }

    /** Creates an anonymous class that extends Iterable and implements the specified comprehension.
     */
    public JCExpression transformComprehension(Tree.Comprehension comp) {
        return transformComprehension(comp, null);
    }

    JCExpression transformComprehension(Tree.Comprehension comp, ProducedType expectedType) {
        ProducedType elementType = comp.getInitialComprehensionClause().getTypeModel();
        // get rid of anonymous types
        elementType = typeFact().denotableType(elementType);
        elementType = wrapInOptionalForInterop(elementType, expectedType, containsUncheckedNulls(comp));
        return new ComprehensionTransformation(comp, elementType).transformComprehension();
    }

    private ProducedType wrapInOptionalForInterop(ProducedType elementType, ProducedType expectedType, boolean containsUncheckedNull) {
        if(expectedType != null && containsUncheckedNull && iteratesOverOptional(expectedType) && !typeFact().isOptionalType(elementType))
            return typeFact().getOptionalType(elementType);
        return elementType;
    }

    private boolean iteratesOverOptional(ProducedType iterableType) {
        ProducedType seqElemType = typeFact().getIteratedType(iterableType);
        return isOptional(seqElemType);
    }
   
    class ComprehensionTransformation {
        private final Tree.Comprehension comp;
        final ProducedType targetIterType;
        final ProducedType absentIterType;
        int idx = 0;
        Tree.ExpressionComprehensionClause excc = null;
        Naming.SyntheticName prevItemVar = null;
        Naming.SyntheticName ctxtName = null;
        Naming.SyntheticName lastIteratorCtxtName = null;
        //Iterator fields
        final ListBuffer<JCTree> fields = new ListBuffer<JCTree>();
        final HashSet<String> fieldNames = new HashSet<String>();
        final ListBuffer<Substitution> fieldSubst = new ListBuffer<Substitution>();
        private JCExpression error;
        private JCStatement initIterator;
        // A list of variable declarations local to the next() method so that
        // the variable captured by whatever gets transformed there holds the value
        // at *that point* on the iteration, and not the (variable) value of
        // the iterator. See #986
        private final ListBuffer<JCStatement> valueCaptures = ListBuffer.<JCStatement>lb();
        public ComprehensionTransformation(final Tree.Comprehension comp, ProducedType elementType) {
            this.comp = comp;
            targetIterType = typeFact().getIterableType(elementType);
            absentIterType = comp.getInitialComprehensionClause().getFirstTypeModel();
        }
   
        public JCExpression transformComprehension() {
            at(comp);
            // make sure "this" will be qualified since we're introducing a new surrounding class
            boolean oldWithinSyntheticClassBody = withinSyntheticClassBody(true);
            try{
                Tree.ComprehensionClause clause = comp.getInitialComprehensionClause();
                while (clause != null) {
                    final Naming.SyntheticName iterVar = naming.synthetic(Prefix.$iterator$, idx);
                    Naming.SyntheticName itemVar = null;
                    if (clause instanceof Tree.ForComprehensionClause) {
                        final Tree.ForComprehensionClause fcl = (Tree.ForComprehensionClause)clause;
                        itemVar = transformForClause(fcl, iterVar, itemVar);
                        if (error != null) {
                            return error;
                        }
                        clause = fcl.getComprehensionClause();
                    } else if (clause instanceof Tree.IfComprehensionClause) {
                        transformIfClause((Tree.IfComprehensionClause)clause);
                        if (error != null) {
                            return error;
                        }
                        clause = ((Tree.IfComprehensionClause)clause).getComprehensionClause();
                        itemVar = prevItemVar;
                    } else if (clause instanceof Tree.ExpressionComprehensionClause) {
                        //Just keep a reference to the expression
                        excc = (Tree.ExpressionComprehensionClause)clause;
                        at(excc);
                        clause = null;
                    } else {
                        return makeErroneous(clause, "compiler bug: comprehension clauses of type " + clause.getClass().getName() + " are not yet supported");
                    }
                    idx++;
                    if (itemVar != null) prevItemVar = itemVar;
                }

                ProducedType iteratedType = typeFact().getIteratedType(targetIterType);

                //Define the next() method for the Iterator
                fields.add(makeNextMethod(iteratedType));
                //Define the inner iterator class

                JCMethodDecl getIterator = makeGetIterator(iteratedType);
                JCExpression iterable = makeAnonymousIterable(iteratedType, getIterator);
                for (Substitution subs : fieldSubst) {
                    subs.close();
                }
                return iterable;
            }finally{
                withinSyntheticClassBody(oldWithinSyntheticClassBody);
            }
        }
       
        /**
         * Builds the {@code next()} method of the {@code AbstractIterator}
         */
        private JCMethodDecl makeNextMethod(ProducedType iteratedType) {
            List<JCStatement> of = valueCaptures.append(make().Return(transformExpression(excc.getExpression(), BoxingStrategy.BOXED, iteratedType))).toList();
            JCStatement stmt = make().If(
                    make().Apply(null,
                        ctxtName.makeIdentWithThis(), List.<JCExpression>nil()),
                    make().Block(0, of),
                    make().Return(makeFinished()));
            return make().MethodDef(make().Modifiers(Flags.PUBLIC | Flags.FINAL), names().fromString("next"),
                makeJavaType(typeFact().getObjectDeclaration().getType()), List.<JCTree.JCTypeParameter>nil(),
                List.<JCTree.JCVariableDecl>nil(), List.<JCExpression>nil(), make().Block(0, List.<JCStatement>of(stmt)), null);
        }
        /**
         * Builds a {@code getIterator()} method which contains a local class
         * extending {@code AbstractIterator} and initialises the iter$0 field
         * to a new instance of that local class.
         *
         * Doesn't use an anonymous class due to #974.
         * @param iteratedType
         * @return
         */
        private JCMethodDecl makeGetIterator(ProducedType iteratedType) {
            ProducedType iteratorType = typeFact().getIteratorType(iteratedType);
            JCExpression iteratorTypeExpr = make().TypeApply(makeIdent(syms().ceylonAbstractIteratorType),
                    List.<JCExpression>of(makeJavaType(iteratedType, JT_TYPE_ARGUMENT)));
            JCExpression iterator = make().NewClass(null, List.<JCExpression>nil(), iteratorTypeExpr,
                    List.<JCExpression>of(makeReifiedTypeArgument(iteratedType)),
                    make().AnonymousClassDef(make().Modifiers(0),
                            fields.toList().prepend(
                                    make().Block(0L,
                                            initIterator == null ? List.<JCStatement>nil() : List.<JCStatement>of(initIterator))
                                    )));
            JCBlock iteratorBlock = make().Block(0, List.<JCStatement>of(
                    make().Return(iterator)));
            return make().MethodDef(make().Modifiers(Flags.PUBLIC | Flags.FINAL), names().fromString("iterator"),
                    makeJavaType(iteratorType, JT_CLASS_NEW|JT_EXTENDS),
                List.<JCTree.JCTypeParameter>nil(), List.<JCTree.JCVariableDecl>nil(), List.<JCExpression>nil(),
                iteratorBlock, null);
        }
        /**
         * Builds an anonymous subclass of AbstractIterable whose
         * {@code getIterator()} uses the given getIteratorBody.
         * @param iteratedType
         * @param iteratorType
         * @param getIteratorBody
         * @return
         */
        private JCExpression makeAnonymousIterable(ProducedType iteratedType,
                JCMethodDecl getIterator) {
            JCExpression iterable = make().NewClass(null, null,
                    make().TypeApply(makeIdent(syms().ceylonAbstractIterableType),
                        List.<JCExpression>of(makeJavaType(iteratedType, JT_TYPE_ARGUMENT),
                                makeJavaType(absentIterType, JT_NO_PRIMITIVES))),
                                List.<JCExpression>of(makeReifiedTypeArgument(iteratedType),
                                        makeReifiedTypeArgument(absentIterType)),
                    make().AnonymousClassDef(make().Modifiers(0),
                            List.<JCTree>of(getIterator)));
            return iterable;
        }

        class IfComprehensionCondList extends CondList {

            private final ListBuffer<JCStatement> varDecls = ListBuffer.lb();
            /**
             * A list of statements that are placed in the main body, before the conditions.
             */
            private final List<JCStatement> preCheck;
            /**
             * A list of statements that are placed in the innermost condition's body.
             */
            private final List<JCStatement> insideCheck;
            /**
             * A list of statements that are placed in the main body, after the conditions.
             */
            private final List<JCStatement> postCheck;
           
            /**
             * An IfComprehensionCondList suitable for "inner" if comprehension clauses.
             * Checks {@code condExpr} before checking the {@code conditions}, and {@code break;}s if the conditions apply.
             * Intended to be placed in a {@code while (true) } loop, to keep checking the conditions until they apply
             * or {@code condExpr} doesn't.
             */
            public IfComprehensionCondList(java.util.List<Tree.Condition> conditions, JCExpression condExpr, Name breakLabel) {
                this(conditions,
                    // check condExpr before the conditions
                    List.<JCStatement>of(make().If(make().Unary(JCTree.NOT, condExpr), make().Break(breakLabel), null)),
                    // break if a condition matches
                    List.<JCStatement>of(make().Break(breakLabel)),
                    null);
            }
           
            /**
             * General-purpose constructor. Places {@code precheck} before the conditions and their variable declarations,
             * {@code insideCheck} in the body of the innermost condition (executed only if all {@code conditions} apply), and
             * {@code postCheck} after the conditions.
             */
            public IfComprehensionCondList(java.util.List<Tree.Condition> conditions,
                    List<JCStatement> preCheck, List<JCStatement> insideCheck, List<JCStatement> postCheck) {
                statementGen().super(conditions, null);
                if(preCheck == null) preCheck = List.<JCStatement>nil();
                if(insideCheck == null) insideCheck = List.<JCStatement>nil();
                if(postCheck == null) postCheck = List.<JCStatement>nil();
                this.preCheck = preCheck;
                this.insideCheck = insideCheck;
                this.postCheck = postCheck;
            }

            @Override
            protected List<JCStatement> transformInnermost(Tree.Condition condition) {
                Cond transformedCond = statementGen().transformCondition(condition, null);
                // The innermost condition's test should be transformed before
                // variable substitution
               
                JCExpression test = transformedCond.makeTest();
                SyntheticName resultVarName = addVarSubs(transformedCond);
                return transformCommon(transformedCond,
                        test,
                        insideCheck,
                        resultVarName);
            }
           
            protected List<JCStatement> transformIntermediate(Tree.Condition condition, java.util.List<Tree.Condition> rest) {
                Cond transformedCond = statementGen().transformCondition(condition, null);
                JCExpression test = transformedCond.makeTest();
                SyntheticName resultVarName = addVarSubs(transformedCond);
                return transformCommon(transformedCond, test, transformList(rest), resultVarName);
            }

            private SyntheticName addVarSubs(Cond transformedCond) {
                if (transformedCond.hasResultDecl()) {
                    Tree.Variable var = transformedCond.getVariable();
                    SyntheticName resultVarName = naming.alias(transformedCond.getVariableName().getName());
                    fieldSubst.add(naming.addVariableSubst(var.getDeclarationModel(), resultVarName.getName()));
                    return resultVarName;
                }
                return null;
            }
           
            protected List<JCStatement> transformCommon(Cond transformedCond,
                    JCExpression test, List<JCStatement> stmts,
                    SyntheticName resultVarName) {
               
                JCStatement decl = transformedCond.makeTestVarDecl(0, true);
                if (decl != null) {
                    varDecls.append(decl);
                }
                if (transformedCond.hasResultDecl()) {
                    fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE),
                            resultVarName.asName(), transformedCond.makeTypeExpr(), null));
                    valueCaptures.add(make().VarDef(make().Modifiers(Flags.FINAL),
                            resultVarName.asName(), transformedCond.makeTypeExpr(), resultVarName.makeIdentWithThis()));
                    stmts = stmts.prepend(make().Exec(make().Assign(resultVarName.makeIdent(), transformedCond.makeResultExpr())));
                }
                stmts = List.<JCStatement>of(make().If(
                        test,
                        make().Block(0, stmts),
                        null));
                return stmts;
            }
           
            public List<JCStatement> getResult() {
                List<JCStatement> stmts = transformList(conditions);
                ListBuffer<JCStatement> result = ListBuffer.lb();
                result.appendList(preCheck);
                result.appendList(varDecls);
                result.appendList(stmts);
                result.appendList(postCheck);
                return result.toList();  
            }

        }
       
        private void transformIfClause(Tree.IfComprehensionClause clause) {
            List<JCStatement> body;
            if (prevItemVar == null) {
              List<JCStatement> initBlock;
              if (clause == comp.getInitialComprehensionClause()) {
                //No previous context
                assert (ctxtName == null);
                ctxtName = naming.synthetic(Prefix.$next$, idx);
                //define a variable that records if the expression was already evaluated
                SyntheticName exhaustedName = ctxtName.suffixedBy(Suffix.$exhausted$);
                    JCVariableDecl exhaustedDef = make().VarDef(make().Modifiers(Flags.PRIVATE),
                            exhaustedName.asName(), makeJavaType(typeFact().getBooleanDeclaration().getType()), null);
                    fields.add(exhaustedDef);
                    JCStatement returnIfExhausted = make().If(exhaustedName.makeIdent(), make().Return(makeBoolean(false)), null);
                    JCStatement setExhaustedTrue = make().Exec(make().Assign(exhaustedName.makeIdent(), makeBoolean(true)));
                    initBlock =  List.<JCStatement>of(
                        //if we already evaluated the expression, return
                        returnIfExhausted,
                            //record that we will have evaluated the expression
                            setExhaustedTrue);
              } else {
                assert (ctxtName != null);
                JCStatement returnIfExhausted = make().If(
                    //if the previous comprehension is false or was already evaluated...
                    make().Unary(JCTree.NOT, make().Apply(null,
                        ctxtName.makeIdentWithThis(), List.<JCExpression>nil())),
                    //return false
                        make().Return(makeBoolean(false)), null);
                ctxtName = naming.synthetic(Prefix.$next$, idx);
                initBlock = List.<JCStatement>of(returnIfExhausted);
              }
               
                JCStatement returnTrue = make().Return(makeBoolean(true));
                JCStatement returnFalse = make().Return(makeBoolean(false));
               
                body = new IfComprehensionCondList(clause.getConditionList().getConditions(),
                    initBlock,
                    List.<JCStatement>of(
                        //if the conditions apply: return true
                        returnTrue),
                    List.<JCStatement>of(
                        //the conditions did not apply: return false
                        returnFalse)).getResult();
            } else {
                //Filter contexts need to check if the previous context applies and then check the condition
                JCExpression condExpr = make().Apply(null,
                    ctxtName.makeIdentWithThis(), List.<JCExpression>nil());
                ctxtName = naming.synthetic(Prefix.$next$, idx);
                Name label = names().fromString("ifcomp_"+idx);
                IfComprehensionCondList ifComprehensionCondList = new IfComprehensionCondList(clause.getConditionList().getConditions(), condExpr, label);
                List<JCStatement> ifs = ifComprehensionCondList.getResult();
                JCStatement loop = make().Labelled(label, make().WhileLoop(makeBoolean(true), make().Block(0, ifs)));
                body = List.<JCStatement>of(loop,
                    make().Return(make().Unary(JCTree.NOT, prevItemVar.suffixedBy(Suffix.$exhausted$).makeIdent())));
          }
            MethodDefinitionBuilder mb = MethodDefinitionBuilder.systemMethod(ExpressionTransformer.this, ctxtName.getName())
                .ignoreModelAnnotations()
                .modifiers(Flags.PRIVATE | Flags.FINAL)
                .resultType(null, makeJavaType(typeFact().getBooleanDeclaration().getType()))
                .body(body);
            fields.add(mb.build());
        }

        private SyntheticName transformForClause(final Tree.ForComprehensionClause clause,
                final Naming.SyntheticName iterVar,
                Naming.SyntheticName itemVar) {
            final Tree.ForComprehensionClause fcl = clause;
            Tree.SpecifierExpression specexpr = fcl.getForIterator().getSpecifierExpression();
            ProducedType iterType = specexpr.getExpression().getTypeModel();
            JCExpression iterTypeExpr = makeJavaType(typeFact().getIteratorType(
                    typeFact().getIteratedType(iterType)));
            ProducedType iterableType = iterType.getSupertype(typeFact().getIterableDeclaration());
            JCExpression iterableExpr = transformExpression(specexpr.getExpression(), BoxingStrategy.BOXED, iterableType);
            if (clause == comp.getInitialComprehensionClause()) {
                //The first iterator can be initialized as a field
                fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE | Flags.FINAL), iterVar.asName(), iterTypeExpr,
                    null));
                fieldNames.add(iterVar.getName());
                initIterator = make().Exec(make().Assign(iterVar.makeIdent(), make().Apply(null, makeSelect(iterableExpr, "iterator"),
                        List.<JCExpression>nil())));
            } else {
                //The subsequent iterators need to be inside a method,
                //in case they depend on the current element of the previous iterator
                fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE), iterVar.asName(), iterTypeExpr, null));
                fieldNames.add(iterVar.getName());
                List<JCStatement> block = List.<JCStatement>nil();
                if (lastIteratorCtxtName != null) {
                    block = block.append(make().If(lastIteratorCtxtName.suffixedBy(Suffix.$exhausted$).makeIdent(),
                            make().Return(makeBoolean(false)),
                            null));
                }
                block = block.appendList(List.<JCStatement>of(
                        make().If(make().Binary(JCTree.NE, iterVar.makeIdent(), makeNull()),
                                make().Return(makeBoolean(true)),
                                null),
                        make().If(make().Unary(JCTree.NOT, make().Apply(null, ctxtName.makeIdentWithThis(), List.<JCExpression>nil())),
                                make().Return(makeBoolean(false)),
                                null),
                        make().Exec(make().Assign(iterVar.makeIdent(),
                                                  make().Apply(null,
                                                               makeSelect(iterableExpr, "iterator"),
                                                               List.<JCExpression>nil()))),
                        make().Return(makeBoolean(true))
                ));
                JCBlock body = make().Block(0l, block);
                fields.add(make().MethodDef(make().Modifiers(Flags.PRIVATE | Flags.FINAL),
                        iterVar.asName(), makeJavaType(typeFact().getBooleanDeclaration().getType()),
                        List.<JCTree.JCTypeParameter>nil(),
                        List.<JCTree.JCVariableDecl>nil(), List.<JCExpression>nil(), body, null));
            }
            if (fcl.getForIterator() instanceof Tree.ValueIterator) {
   
                //Add the item variable as a field in the iterator
                Value item = ((Tree.ValueIterator)fcl.getForIterator()).getVariable().getDeclarationModel();
                itemVar = naming.synthetic(item);
                valueCaptures.append(makeVar(Flags.FINAL, itemVar,
                        makeJavaType(item.getType(),JT_NO_PRIMITIVES), itemVar.makeIdentWithThis()));
                fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE), itemVar.asName(),
                        makeJavaType(item.getType(),JT_NO_PRIMITIVES), null));
                fieldNames.add(itemVar.getName());
   
            } else if (fcl.getForIterator() instanceof Tree.KeyValueIterator) {
                //Add the key and value variables as fields in the iterator
                Tree.KeyValueIterator kviter = (Tree.KeyValueIterator)fcl.getForIterator();
                Value kdec = kviter.getKeyVariable().getDeclarationModel();
                Value vdec = kviter.getValueVariable().getDeclarationModel();
                //But we'll use this as the name for the context function and base for the exhausted field
                itemVar = naming.synthetic(Prefix.$kv$, kdec.getName(), vdec.getName());
                fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE), names().fromString(kdec.getName()),
                        makeJavaType(kdec.getType(), JT_NO_PRIMITIVES), null));
                fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE), names().fromString(vdec.getName()),
                        makeJavaType(vdec.getType(), JT_NO_PRIMITIVES), null));
                fieldNames.add(kdec.getName());
                fieldNames.add(vdec.getName());
            } else {
                error = makeErroneous(fcl, "compiler bug: iterators of type " + fcl.getForIterator().getNodeType() + " not yet supported");
                return null;
            }
            fields.add(make().VarDef(make().Modifiers(Flags.PRIVATE), itemVar.suffixedBy(Suffix.$exhausted$).asName(),
                    makeJavaType(typeFact().getBooleanDeclaration().getType()), null));
           
            //Now the context for this iterator
            ListBuffer<JCStatement> contextBody = new ListBuffer<JCStatement>();
   
            //Assign the next item to an Object variable
            Naming.SyntheticName tmpItem = naming.temp("item");
            contextBody.add(make().VarDef(make().Modifiers(Flags.FINAL), tmpItem.asName(),
                    makeJavaType(typeFact().getObjectDeclaration().getType()),
                    make().Apply(null, makeSelect(iterVar.makeIdent(), "next"),
                            List.<JCExpression>nil())));
            //Then we check if it's exhausted
            contextBody.add(make().Exec(make().Assign(itemVar.suffixedBy(Suffix.$exhausted$).makeIdent(),
                    make().Binary(JCTree.EQ, tmpItem.makeIdent(), makeFinished()))));
            //Variables get assigned in the else block
            ListBuffer<JCStatement> elseBody = new ListBuffer<JCStatement>();
            if (fcl.getForIterator() instanceof Tree.ValueIterator) {
                ProducedType itemType = ((Tree.ValueIterator)fcl.getForIterator()).getVariable().getDeclarationModel().getType();
                elseBody.add(make().Exec(make().Assign(itemVar.makeIdent(),
                        make().TypeCast(makeJavaType(itemType,JT_NO_PRIMITIVES), tmpItem.makeIdent()))));
            } else {
                Tree.KeyValueIterator kviter = (Tree.KeyValueIterator)fcl.getForIterator();
                Value key = kviter.getKeyVariable().getDeclarationModel();
                Value item = kviter.getValueVariable().getDeclarationModel();
                //Assign the key and item to the corresponding fields with the proper type casts
                //equivalent to k=(KeyType)((Entry<KeyType,ItemType>)tmpItem).getKey()
                JCExpression castEntryExprKey = make().TypeCast(
                    makeJavaType(typeFact().getIteratedType(iterType)),
                    tmpItem.makeIdent());
                SyntheticName keyName = naming.synthetic(key);
                SyntheticName itemName = naming.synthetic(item);
                valueCaptures.append(makeVar(Flags.FINAL, keyName,
                        makeJavaType(key.getType(), JT_NO_PRIMITIVES),
                        keyName.makeIdentWithThis()));
                valueCaptures.append(makeVar(Flags.FINAL, itemName,
                        makeJavaType(item.getType(), JT_NO_PRIMITIVES),
                        itemName.makeIdentWithThis()));
                elseBody.add(make().Exec(make().Assign(keyName.makeIdent(),
                    make().TypeCast(makeJavaType(key.getType(), JT_NO_PRIMITIVES),
                        make().Apply(null, makeSelect(castEntryExprKey, "getKey"),
                            List.<JCExpression>nil())
                ))));
                //equivalent to v=(ItemType)((Entry<KeyType,ItemType>)tmpItem).getItem()
                JCExpression castEntryExprItem = make().TypeCast(
                        makeJavaType(typeFact().getIteratedType(iterType)),
                        tmpItem.makeIdent());
                elseBody.add(make().Exec(make().Assign(itemName.makeIdent(),
                    make().TypeCast(makeJavaType(item.getType(), JT_NO_PRIMITIVES),
                        make().Apply(null, makeSelect(castEntryExprItem, "getItem"),
                            List.<JCExpression>nil())
                ))));
            }
            elseBody.add(make().Return(makeBoolean(true)));
           
            ListBuffer<JCStatement> innerBody = new ListBuffer<JCStatement>();
            if (idx>0) {
                //Subsequent contexts run once for every iteration of the previous loop
                //This will reset our previous context by getting a new iterator if the previous loop isn't done
                innerBody.add(make().Exec(make().Assign(iterVar.makeIdent(), makeNull())));
            }else{
                innerBody.add(make().Return(makeBoolean(false)));
            }
            //Assign the next item to the corresponding variables if not exhausted yet
            contextBody.add(make().If(itemVar.suffixedBy(Suffix.$exhausted$).makeIdent(),
                make().Block(0, innerBody.toList()),
                make().Block(0, elseBody.toList())));
           
            List<JCTree.JCStatement> methodBody;
            if (idx>0) {
                //Subsequent iterators may depend on the item from the previous loop so we make sure we have one
                methodBody = List.<JCStatement>of(make().WhileLoop(make().Apply(null, iterVar.makeIdentWithThis(), List.<JCExpression>nil()),
                        make().Block(0, contextBody.toList())));
                if (lastIteratorCtxtName != null) {
                    // It can happen that we never get into the body because the outer iterator is exhausted, if so, mark
                    // this one exhausted too
                    methodBody = methodBody.append(make().If(lastIteratorCtxtName.suffixedBy(Suffix.$exhausted$).makeIdent(),
                            make().Exec(make().Assign(itemVar.suffixedBy(Suffix.$exhausted$).makeIdent(), makeBoolean(true))),
                            null));
                }
                methodBody = methodBody.append(make().Return(makeBoolean(false)));
            }else
                methodBody = contextBody.toList();
            //Create the context method that returns the next item for this iterator
            lastIteratorCtxtName = ctxtName = itemVar;
            fields.add(make().MethodDef(make().Modifiers(Flags.PRIVATE | Flags.FINAL), itemVar.asName(),
                makeJavaType(typeFact().getBooleanDeclaration().getType()),
                List.<JCTree.JCTypeParameter>nil(), List.<JCTree.JCVariableDecl>nil(), List.<JCExpression>nil(),
                make().Block(0, methodBody), null));
            return itemVar;
        }
    }

    //
    // Type helper functions

    private ProducedType getSupertype(Tree.Term term, TypeDeclaration compoundType){
        return term.getTypeModel().getSupertype(compoundType);
    }

    private ProducedType getTypeArgument(ProducedType leftType) {
        if (leftType!=null && leftType.getTypeArguments().size()==1) {
            return leftType.getTypeArgumentList().get(0);
        }
        return null;
    }

    private ProducedType getTypeArgument(ProducedType leftType, int i) {
        if (leftType!=null && leftType.getTypeArguments().size() > i) {
            return leftType.getTypeArgumentList().get(i);
        }
        return null;
    }

    private JCExpression unAutoPromote(JCExpression ret, ProducedType returnType) {
        // +/- auto-promotes to int, so if we're using java types we'll need a cast
        return applyJavaTypeConversions(ret, typeFact().getIntegerDeclaration().getType(),
                returnType, BoxingStrategy.UNBOXED, false, 0);
    }

    private ProducedType getMostPreciseType(Tree.Term term, ProducedType defaultType) {
        // special case for interop when we're dealing with java types
        ProducedType termType = term.getTypeModel();
        if(termType.getUnderlyingType() != null)
            return termType;
        return defaultType;
    }

    //
    // Helper functions
   
    private boolean isRecursiveReference(Tree.StaticMemberOrTypeExpression expr) {
        Declaration decl = expr.getDeclaration();
        Scope s = expr.getScope();
        // do we have decl as our container anywhere in the scope?
        while (s != null && !Decl.equalScopeDecl(s, decl)) {
            s = s.getContainer();
        }
        return Decl.equalScopeDecl(s, decl);
    }

    private boolean isReferenceInSameScope(Tree.StaticMemberOrTypeExpression expr) {
        Declaration decl = expr.getDeclaration();
        Scope s = expr.getScope();
        // are we in the same Declaration container?
        while (s != null && s instanceof Declaration == false) {
            s = s.getContainer();
        }
        return Decl.equalScopeDecl(s, decl);
    }

    boolean isWithinInvocation() {
        return withinInvocation;
    }
   
    boolean isFunctionalResult(ProducedType type) {
        return !isWithinInvocation()
            && isCeylonCallableSubtype(type);  
    }

    boolean withinInvocation(boolean withinInvocation) {
        boolean result = this.withinInvocation;
        this.withinInvocation = withinInvocation;
        return result;
    }

    boolean isWithinSyntheticClassBody() {
        return withinSyntheticClassBody;
    }

    boolean withinSyntheticClassBody(boolean withinSyntheticClassBody) {
        boolean result = this.withinSyntheticClassBody;
        this.withinSyntheticClassBody = withinSyntheticClassBody;
        return result;
    }

    boolean isWithinSuperInvocation() {
        return withinSuperInvocation != null;
    }

    void withinSuperInvocation(ClassOrInterface forDefinition) {
        this.withinSuperInvocation = forDefinition;
    }

    boolean isWithinDefaultParameterExpression(Scope container) {
        return Decl.equalScopeDecl(container, withinDefaultParameterExpression);
    }

    void withinDefaultParameterExpression(ClassOrInterface forDefinition) {
        this.withinDefaultParameterExpression = forDefinition;
    }

    //
    // Optimisations

    private JCExpression checkForQualifiedMemberExpressionOptimisation(Tree.QualifiedMemberExpression expr) {
        JCExpression ret = checkForBitwiseOperators(expr, expr, null);
        if(ret != null)
            return ret;
        ret = checkForCharacterAsInteger(expr);
        if(ret != null)
            return ret;
        ret = checkForByteLiterals(expr);
        if(ret != null)
            return ret;
        return null;
    }

    /*private JCExpression checkForArrayOnJavaArray(Tree.QualifiedMemberExpression expr) {
        if ("array".equals(expr.getIdentifier().getText())) {
            if (expr.getPrimary() instanceof Tree.BaseMemberExpression) {
                if (Decl.isJavaArray(expr.getPrimary().getTypeModel().getDeclaration())) {
                    return transform((Tree.BaseMemberExpression)expr.getPrimary());
                }
            }
        }
        return null;
    }*/

    boolean isThrowableSuppressed(Tree.QualifiedMemberOrTypeExpression expr) {
        return typeFact().getThrowableDeclaration().getDirectMember("suppressed", null, false).equals(
                expr.getDeclaration().getRefinedDeclaration());
    }

    boolean isThrowableMessage(Tree.QualifiedMemberOrTypeExpression expr) {
        return typeFact().getThrowableDeclaration().getDirectMember("message", null, false).equals(
                expr.getDeclaration().getRefinedDeclaration());
    }

    private JCExpression checkForInvocationExpressionOptimisation(Tree.InvocationExpression ce) {
        // FIXME: temporary hack for byte literals
        JCExpression ret = checkForByteLiterals(ce);
        if(ret != null)
            return ret;
        // FIXME: temporary hack for bitwise operators literals
        ret = checkForBitwiseOperators(ce);
        if(ret != null)
            return ret;
        return null;
    }

    private JCExpression checkForByteLiterals(Tree.QualifiedMemberExpression expr) {
        // must be a call on Integer
        Tree.Term left = expr.getPrimary();
        if(left == null || !isCeylonInteger(left.getTypeModel()))
            return null;
        // must be on "byte"
        if(!expr.getIdentifier().getText().equals("byte"))
            return null;
        // must be a normal member op "."
        if(expr.getMemberOperator() instanceof Tree.MemberOp == false)
            return null;
        // must be unboxed
        if(!expr.getUnboxed() || !left.getUnboxed())
            return null;
        // This can't be Tree.NegativeOp as the normal precedence of -1.byte is -(1.byte), not (-1).byte
        // and must be a number literal
        if(left instanceof Tree.NaturalLiteral == false)
            return null;
        // all good
        at(expr);
        try{
            long value = literalValue((Tree.NaturalLiteral) left);
            // in the case of -128 to 127 we don't need to cast to byte by using an int literal, but only for
            // assignment, not for method calls, so it's simpler to always cast
            return make().TypeCast(syms().byteType, make().Literal(value));
        } catch (ErroneousException e) {
            // We should never get here since the error should have been
            // reported by the UnsupportedVisitor and the containing statement
            // replaced with a throw.
            return e.makeErroneous(this);
        }
    }
   
    private JCExpression checkForByteLiterals(Tree.InvocationExpression ce) {
        // same test as in BoxingVisitor.isByteLiteral()
        if(ce.getPrimary() instanceof Tree.BaseTypeExpression
                && ce.getPositionalArgumentList() != null){
            java.util.List<Tree.PositionalArgument> positionalArguments = ce.getPositionalArgumentList().getPositionalArguments();
            if(positionalArguments.size() == 1){
                PositionalArgument argument = positionalArguments.get(0);
                if(argument instanceof Tree.ListedArgument
                        && ((Tree.ListedArgument) argument).getExpression() != null){
                    Term term = ((Tree.ListedArgument)argument).getExpression().getTerm();
                    boolean negative = false;
                    if(term instanceof Tree.NegativeOp){
                        negative = true;
                        term = ((Tree.NegativeOp) term).getTerm();
                    }
                    if(term instanceof Tree.NaturalLiteral){
                        Declaration decl = ((Tree.BaseTypeExpression)ce.getPrimary()).getDeclaration();
                        if(decl instanceof Class){
                            String name = decl.getQualifiedNameString();
                            if(name.equals("ceylon.language::Byte")){
                                at(ce);
                                try{
                                    long value = literalValue((Tree.NaturalLiteral) term);
                                    if(negative)
                                        value = -value;
                                    // in the case of -128 to 127 we don't need to cast to byte by using an int literal, but only for
                                    // assignment, not for method calls, so it's simpler to always cast
                                    return make().TypeCast(syms().byteType, make().Literal(value));
                                } catch (ErroneousException e) {
                                    // We should never get here since the error should have been
                                    // reported by the UnsupportedVisitor and the containing statement
                                    // replaced with a throw.
                                    return e.makeErroneous(this);
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private JCExpression checkForCharacterAsInteger(Tree.QualifiedMemberExpression expr) {
        // must be a call on Character
        Tree.Term left = expr.getPrimary();
        if(left == null || !isCeylonCharacter(left.getTypeModel()))
            return null;
        // must be on "integer"
        if(!expr.getIdentifier().getText().equals("integer"))
            return null;
        // must be a normal member op "."
        if(expr.getMemberOperator() instanceof Tree.MemberOp == false)
            return null;
        // must be unboxed
        if(!expr.getUnboxed() || !left.getUnboxed())
            return null;
        // and must be a character literal
        if(left instanceof Tree.CharLiteral == false)
            return null;
        // all good
        return transform((Tree.CharLiteral)left);
    }

    private JCExpression checkForBitwiseOperators(Tree.InvocationExpression ce) {
        if(!(ce.getPrimary() instanceof Tree.QualifiedMemberExpression))
            return null;
        Tree.QualifiedMemberExpression qme = (Tree.QualifiedMemberExpression) ce.getPrimary();
        // must be a positional arg (FIXME: why?)
        if(ce.getPositionalArgumentList() == null
                || ce.getPositionalArgumentList().getPositionalArguments() == null
                || ce.getPositionalArgumentList().getPositionalArguments().size() != 1)
            return null;
        Tree.PositionalArgument arg = ce.getPositionalArgumentList().getPositionalArguments().get(0);
        if(arg instanceof Tree.ListedArgument == false)
            return null;
        Tree.Expression right = ((Tree.ListedArgument)arg).getExpression();
        return checkForBitwiseOperators(ce, qme, right);
    }
   
    private JCExpression checkForBitwiseOperators(Tree.Term node, Tree.QualifiedMemberExpression qme, Tree.Term right) {
        // must be a call on Integer
        Tree.Term left = qme.getPrimary();
        if(left == null) {
            return null;
        }
        String signature;
        ProducedType binaryType;
        if (isCeylonInteger(left.getTypeModel())) {
            // must be a supported method/attribute
            binaryType = typeFact().getIntegerDeclaration().getType();
            String name = qme.getIdentifier().getText();
            signature = "ceylon.language.Integer."+name;
        } else if (isCeylonByte(left.getTypeModel())) {
            binaryType = typeFact().getByteDeclaration().getType();
            String name = qme.getIdentifier().getText();
            signature = "ceylon.language.Byte."+name;
        } else {
            return null;
        }
        // see if we have an operator for it
        OperatorTranslation operator = Operators.getOperator(signature);
        if(operator != null){
            if(operator.getArity() == 2){
                if(right == null)
                    return null;
                OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(node, left, right, this);
                // check that we can optimise it
                if(!optimisationStrategy.useJavaOperator())
                    return null;
               
                JCExpression leftExpr = transformExpression(left, optimisationStrategy.getBoxingStrategy(), binaryType);
                JCExpression rightExpr = transformExpression(right, optimisationStrategy.getBoxingStrategy(), binaryType);

                return make().Binary(operator.javacOperator, leftExpr, rightExpr);
            }else{
                // must be unary
                if(right != null)
                    return null;
                OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(node, left, this);
                // check that we can optimise it
                if(!optimisationStrategy.useJavaOperator())
                    return null;
               
                JCExpression leftExpr = transformExpression(left, optimisationStrategy.getBoxingStrategy(), binaryType);

                return make().Unary(operator.javacOperator, leftExpr);
            }
        }
        return null;
    }
   
    public List<JCAnnotation> transform(Tree.AnnotationList annotationList) {
        if (annotationList == null) {
            return List.nil();
        }
        if ((gen().disableAnnotations & CeylonTransformer.DISABLE_USER_ANNOS) != 0) {
            return List.nil();
        }
        LinkedHashMap<Class, ListBuffer<JCAnnotation>> annotationSet = new LinkedHashMap<>();
        if (annotationList != null) {
            if (annotationList.getAnonymousAnnotation() != null) {
                transformAnonymousAnnotation(annotationList.getAnonymousAnnotation(), annotationSet);
            }
            if (annotationList.getAnnotations() != null) {
                for (Tree.Annotation annotation : annotationList.getAnnotations()) {
                    transformAnnotation(annotation, annotationSet);
                }
            }
        }
        ListBuffer<JCAnnotation> result = ListBuffer.lb();
        for (Class annotationClass : annotationSet.keySet()) {
            ListBuffer<JCAnnotation> annotations = annotationSet.get(annotationClass);
            if (isSequencedAnnotation(annotationClass)) {
                JCAnnotation wrapperAnnotation = make().Annotation(
                        makeJavaType(annotationClass.getType(), JT_ANNOTATIONS),
                        List.<JCExpression>of(make().NewArray(null,  null, (List)annotations.toList())));
                result.append(wrapperAnnotation);
            } else {
                if (annotations.size() > 1) {
                    makeErroneous(annotationList, "compiler bug: multiple occurances of non-sequenced annotation class " + annotationClass.getQualifiedNameString());
                }
                result.appendList(annotations);
            }
        }
       
        // Special case: Generate a @java.lang.Deprecated() if Ceylon deprecated
        if (annotationList != null) {
            for (Tree.Annotation annotation : annotationList.getAnnotations()) {
                if (isDeprecatedAnnotation(annotation.getPrimary())) {
                    result.append(make().Annotation(make().Type(syms().deprecatedType), List.<JCExpression>nil()));
                }
            }
        }
       
        return result.toList();
    }
   
    void transformAnnotation(Tree.Annotation invocation,
            Map<Class, ListBuffer<JCAnnotation>> annotationSet) {
        at(invocation);
        try {
            JCAnnotation annotation = AnnotationInvocationVisitor.transformConstructor(this, invocation);
            if (annotation != null) {
                Class annotationClass = AnnotationInvocationVisitor.annoClass(invocation);
                putAnnotation(annotationSet, annotation, annotationClass);
            }
        } catch (BugException e) {
            e.addError(invocation);
        }
    }

    /**
     * Returns true if the given primary is {@code ceylon.language.deprecated()}
     */
    private boolean isDeprecatedAnnotation(Tree.Primary primary) {
        return primary instanceof Tree.BaseMemberExpression
                && typeFact().getLanguageModuleDeclaration("deprecated").equals(((Tree.BaseMemberExpression)primary).getDeclaration());
    }

    private void putAnnotation(
            Map<Class, ListBuffer<JCAnnotation>> annotationSet,
            JCAnnotation annotation, Class annotationClass) {
        ListBuffer<JCAnnotation> list = annotationSet.get(annotationClass);
        if (list == null) {
            list = ListBuffer.lb();
        }
        annotationSet.put(annotationClass, list.append(annotation));
    }


    public void transformAnonymousAnnotation(Tree.AnonymousAnnotation annotation, Map<Class, ListBuffer<JCAnnotation>> annos) {
        ProducedType docType = ((TypeDeclaration)typeFact().getLanguageModuleDeclaration("DocAnnotation")).getType();
        JCAnnotation docAnnotation = at(annotation).Annotation(
                makeJavaType(docType,  JT_ANNOTATION),
                List.<JCExpression>of(make().Assign(naming.makeUnquotedIdent("description"),
                        transform(annotation.getStringLiteral()))));
        putAnnotation(annos, docAnnotation, (Class)docType.getDeclaration());
    }
   
    public JCExpression makeMetaLiteralStringLiteralForAnnotation(Tree.MetaLiteral literal) {
        String ref = getSerializedMetaLiteral(literal);
        if (ref != null) {
            return make().Literal(ref);
        }
        return makeErroneous(literal, "compiler bug: " + literal.getNodeType() + " is not a supported meta literal");
    }

    public static Referenceable getMetaLiteralReferenceable(Tree.MetaLiteral ml) {
        if (ml instanceof Tree.TypeLiteral) {
            return ml.getDeclaration();
        } else if (ml instanceof Tree.MemberLiteral) {
            return ml.getDeclaration();
        } else if (ml instanceof Tree.PackageLiteral) {
            return ((Tree.PackageLiteral)ml).getImportPath().getModel();
        } else if (ml instanceof Tree.ModuleLiteral) {
            return ((Tree.ModuleLiteral)ml).getImportPath().getModel();
        }
        return null;
    }
   
    public static String getSerializedMetaLiteral(Tree.MetaLiteral ml) {
        return serializeReferenceable(getMetaLiteralReferenceable(ml));
    }
   
    public static String serializeReferenceable(Referenceable ref) {
        StringBuilder sb = new StringBuilder();
        if (ref instanceof Declaration) {
            appendDeclarationLiteralForAnnotation((Declaration)ref, sb);
        } else if (ref instanceof Package) {
            appendDeclarationLiteralForAnnotation((Package)ref, sb);
        } else if (ref instanceof Module) {
            appendDeclarationLiteralForAnnotation((Module)ref, sb);
        }
        return sb.toString();
    }
   
    public JCExpression makeDeclarationLiteralForAnnotation(Package decl) {
        StringBuilder sb = new StringBuilder();
        appendDeclarationLiteralForAnnotation(decl, sb);
        return make().Literal(sb.toString());
    }
   
    public JCExpression makeDeclarationLiteralForAnnotation(Module decl) {
        StringBuilder sb = new StringBuilder();
        appendDeclarationLiteralForAnnotation(decl, sb);
        return make().Literal(sb.toString());
    }
   
    /*
* ref              ::= version? module ;
*                      // note: version is optional to support looking up the
*                      // runtime version of a package, once we support this
* version          ::= ':' SENTINEL ANYCHAR* SENTINEL ;
* module           ::= dottedIdent package? ;
* dottedIdent      ::= ident ('.' ident)* ;
* package          ::= ':' ( relativePackage | absolutePackage ) ? ( ':' declaration ) ? ;
*                      // note: if no absolute or relative package given, it's the
*                      // root package of the module
* relativePackage  ::= dottedIdent ;
* absolutePackage  ::= '.' dottedIdent ;
*                      // note: to suport package names which don't start
*                      // with the module name
* declaration      ::= type | function | value ;
* type             ::= class | interface ;
* class            ::= 'C' ident ( '.' member )?
* interface        ::= 'I' ident ( '.' member )?
* member           ::= declaration ;
* function         ::= 'F' ident ;
* value            ::= 'V' ident ;
     */
    /**
     * Appends into the given builder a String representation of the given
     * module, suitable for parsing my the DeclarationParser.
     */
    private static void appendDeclarationLiteralForAnnotation(Module module,
            StringBuilder sb) {
        char sentinel = findSentinel(module);
        sb.append(":").append(sentinel).append(module.getVersion()).append(sentinel).append(module.getNameAsString());
    }

    /**
     * Computes a sentinel for the verion number
     */
    private static char findSentinel(Module module) {
        for (char ch : ":\"\'/#!$%\\@~+=*".toCharArray()) {
            if (module.getVersion().indexOf(ch) == -1) {
                return ch;
            }
        }
        // most unlikely end end up here
        char ch = 1;
        while (true) {
            if (module.getVersion().indexOf(ch) == -1) {
                return ch;
            }
            ch++;
        }
    }
   
    /**
     * Appends into the given builder a String representation of the given
     * package, suitable for parsing my the DeclarationParser.
     */
    private static void appendDeclarationLiteralForAnnotation(Package pkg,
            StringBuilder sb) {
        appendDeclarationLiteralForAnnotation(pkg.getModule(), sb);
        sb.append(':');
        String moduleName = pkg.getModule().getNameAsString();
        String packageName = pkg.getNameAsString();
        if (packageName.equals(moduleName)) {
        } else if (packageName.startsWith(moduleName)) {
            sb.append(packageName.substring(moduleName.length()+1));
        } else {
            sb.append('.').append(packageName);
        }
    }
   
    /**
     * Appends into the given builder a String representation of the given
     * declaration, suitable for parsing my the DeclarationParser.
     */
    private static void appendDeclarationLiteralForAnnotation(Declaration decl, StringBuilder sb) {
        Scope container = decl.getContainer();
        while (true) {
            if (container instanceof Declaration) {
                appendDeclarationLiteralForAnnotation((Declaration)container, sb);
                sb.append(".");
                break;
            } else if (container instanceof Package) {
                appendDeclarationLiteralForAnnotation((Package)container, sb);
                sb.append(":");
                break;
            }
            container = container.getContainer();
        }
        if (decl instanceof Class) {
            sb.append("C").append(decl.getName());
        } else if (decl instanceof Interface) {
            sb.append("I").append(decl.getName());
        } else if (decl instanceof TypeAlias) {
            sb.append("A").append(decl.getName());
        } else if (decl instanceof Value) {
            sb.append("V").append(decl.getName());
        } else if (decl instanceof Method) {
            sb.append("F").append(decl.getName());
        } else if (decl instanceof TypeParameter) {
            sb.append("P").append(decl.getName());
        } else {
            throw BugException.unhandledDeclarationCase(decl);
        }
    }
   
    JCExpression makePrivateAccessUpcast(Tree.StaticMemberOrTypeExpression qmte, JCExpression qual) {
        ProducedType pt = Decl.getPrivateAccessType(qmte);
        // By definition the member has private access, so if it's an interface
        // member we want the companion.
        return make().TypeCast(makeJavaType(pt, JT_COMPANION | JT_RAW), qual);
    }

}
TOP

Related Classes of com.redhat.ceylon.compiler.java.codegen.ExpressionTransformer

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