Package com.redhat.ceylon.compiler.typechecker.analyzer

Source Code of com.redhat.ceylon.compiler.typechecker.analyzer.ExpressionVisitor

package com.redhat.ceylon.compiler.typechecker.analyzer;

import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.checkAssignable;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.checkAssignableWithWarning;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.checkCallable;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.checkIsExactlyForInterop;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.checkSupertype;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.declaredInPackage;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.eliminateParensAndWidening;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTupleType;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypeArguments;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypeDeclaration;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypeMember;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypedDeclaration;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.getTypedMember;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.hasError;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.inLanguageModule;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.isEffectivelyBaseMemberExpression;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.isIndirectInvocation;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.isInstantiationExpression;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.spreadType;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.typeDescription;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.typeNamesAsIntersection;
import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.unwrapExpressionUntilTerm;
import static com.redhat.ceylon.compiler.typechecker.model.SiteVariance.IN;
import static com.redhat.ceylon.compiler.typechecker.model.SiteVariance.OUT;
import static com.redhat.ceylon.compiler.typechecker.model.Util.addToIntersection;
import static com.redhat.ceylon.compiler.typechecker.model.Util.addToUnion;
import static com.redhat.ceylon.compiler.typechecker.model.Util.findMatchingOverloadedClass;
import static com.redhat.ceylon.compiler.typechecker.model.Util.getContainingClassOrInterface;
import static com.redhat.ceylon.compiler.typechecker.model.Util.getInterveningRefinements;
import static com.redhat.ceylon.compiler.typechecker.model.Util.getOuterClassOrInterface;
import static com.redhat.ceylon.compiler.typechecker.model.Util.getSignature;
import static com.redhat.ceylon.compiler.typechecker.model.Util.intersectionOfSupertypes;
import static com.redhat.ceylon.compiler.typechecker.model.Util.intersectionType;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isAbstraction;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isOverloadedVersion;
import static com.redhat.ceylon.compiler.typechecker.model.Util.isTypeUnknown;
import static com.redhat.ceylon.compiler.typechecker.model.Util.producedType;
import static com.redhat.ceylon.compiler.typechecker.model.Util.unionType;
import static com.redhat.ceylon.compiler.typechecker.tree.Util.hasUncheckedNulls;
import static com.redhat.ceylon.compiler.typechecker.tree.Util.name;
import static java.util.Collections.emptyList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
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.Scope;
import com.redhat.ceylon.compiler.typechecker.model.Setter;
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.Unit;
import com.redhat.ceylon.compiler.typechecker.model.UnknownType;
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.ImportPath;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.PositionalArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.TypeArguments;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.TypeVariance;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;

/**
* Third and final phase of type analysis.
* Finally visit all expressions and determine their types.
* Use type inference to assign types to declarations with
* the local modifier. Finally, assigns types to the
* associated model objects of declarations declared using
* the local modifier.
*
* @author Gavin King
*
*/
public class ExpressionVisitor extends Visitor {
   
    private Tree.Type returnType;
    private Tree.Expression switchExpression;
    private Declaration returnDeclaration;
    private boolean isCondition;
    private boolean dynamic;
    private boolean inExtendsClause = false;

    private Unit unit;
   
    @Override public void visit(Tree.CompilationUnit that) {
        unit = that.getUnit();
        super.visit(that);
    }
       
    private Declaration beginReturnDeclaration(Declaration d) {
        Declaration od = returnDeclaration;
        returnDeclaration = d;
        return od;
    }
   
    private void endReturnDeclaration(Declaration od) {
        returnDeclaration = od;
    }
   
    private Tree.Type beginReturnScope(Tree.Type t) {
        Tree.Type ort = returnType;
        returnType = t;
        if (returnType instanceof Tree.FunctionModifier ||
                returnType instanceof Tree.ValueModifier) {
            returnType.setTypeModel( unit.getNothingDeclaration().getType() );
        }
        return ort;
    }
   
    private void endReturnScope(Tree.Type t, TypedDeclaration td) {
        if (returnType instanceof Tree.FunctionModifier ||
                returnType instanceof Tree.ValueModifier) {
            td.setType( returnType.getTypeModel() );
        }
        returnType = t;
    }
   
    @Override public void visit(Tree.FunctionArgument that) {
        Tree.Expression e = that.getExpression();
        if (e==null) {
            Tree.Type rt = beginReturnScope(that.getType());          
            Declaration od = beginReturnDeclaration(that.getDeclarationModel());
            super.visit(that);
            endReturnDeclaration(od);
            endReturnScope(rt, that.getDeclarationModel());
        }
        else {
            super.visit(that);
            ProducedType t = unit.denotableType(e.getTypeModel());
            that.getDeclarationModel().setType(t);
            //if (that.getType() instanceof Tree.FunctionModifier) {
                that.getType().setTypeModel(t);
            /*}
            else {
                checkAssignable(t, that.getType().getTypeModel(), e,
                        "expression type must be assignable to specified return type");
            }*/
            if (that.getType() instanceof Tree.VoidModifier &&
                    !isSatementExpression(e)) {
                e.addError("anonymous function is declared void so specified expression must be a statement");
            }
        }
        if (that.getType() instanceof Tree.VoidModifier) {
            ProducedType vt = unit.getType(unit.getAnythingDeclaration());
            that.getDeclarationModel().setType(vt);           
        }
        that.setTypeModel(that.getDeclarationModel()
                .getTypedReference()
                .getFullType());
    }
   
    @Override public void visit(Tree.ExpressionComprehensionClause that) {
        super.visit(that);
        that.setTypeModel(that.getExpression().getTypeModel());
        that.setFirstTypeModel(unit.getNothingDeclaration().getType());
    }
   
    @Override public void visit(Tree.ForComprehensionClause that) {
        super.visit(that);
        that.setPossiblyEmpty(true);
        Tree.ComprehensionClause cc = that.getComprehensionClause();
        if (cc!=null) {
            that.setTypeModel(cc.getTypeModel());
            Tree.ForIterator fi = that.getForIterator();
            if (fi!=null) {
                Tree.SpecifierExpression se = fi.getSpecifierExpression();
                if (se!=null) {
                    Tree.Expression e = se.getExpression();
                    if (e!=null) {
                        ProducedType it = e.getTypeModel();
                        if (it!=null) {
                            ProducedType et = unit.getIteratedType(it);
                            boolean nonemptyIterable = et!=null &&
                                    it.isSubtypeOf(unit.getNonemptyIterableType(et));
                            that.setPossiblyEmpty(!nonemptyIterable ||
                                    cc.getPossiblyEmpty());
                            ProducedType firstType = unionType(unit.getFirstType(it),
                                    cc.getFirstTypeModel(), unit);
                            that.setFirstTypeModel(firstType);
                        }
                    }
                }
            }
        }
    }
   
    @Override public void visit(Tree.IfComprehensionClause that) {
        super.visit(that);
        that.setPossiblyEmpty(true);
        that.setFirstTypeModel(unit.getType(unit.getNullDeclaration()));
        Tree.ComprehensionClause cc = that.getComprehensionClause();
        if (cc!=null) {
            that.setTypeModel(cc.getTypeModel());
        }
    }
   
    @Override public void visit(Tree.Variable that) {
        super.visit(that);
        if (that.getSpecifierExpression()!=null) {
            inferType(that, that.getSpecifierExpression());
            if (that.getType()!=null) {
                ProducedType t = that.getType().getTypeModel();
                if (!isTypeUnknown(t)) {
                    checkType(t, that.getSpecifierExpression());
                }
            }
        }
    }
   
    @Override public void visit(Tree.ConditionList that) {
        if (that.getConditions().isEmpty()) {
            that.addError("empty condition list");
        }
        super.visit(that);
    }

    @Override public void visit(Tree.ResourceList that) {
        if (that.getResources().isEmpty()) {
            that.addError("empty resource list");
        }
        super.visit(that);
    }

    private void initOriginalDeclaration(Tree.Variable that) {
        if (that.getType() instanceof Tree.SyntheticVariable) {
            Tree.BaseMemberExpression bme = (Tree.BaseMemberExpression) that
                    .getSpecifierExpression().getExpression().getTerm();
            ((TypedDeclaration) that.getDeclarationModel())
                .setOriginalDeclaration((TypedDeclaration) bme.getDeclaration());
        }
    }
   
    @Override public void visit(Tree.IsCondition that) {
        //don't recurse to the Variable, since we don't
        //want to check that the specifier expression is
        //assignable to the declared variable type
        //(nor is it possible to infer the variable type)
        isCondition=true;
        Tree.Type t = that.getType();
        if (t!=null) {
            t.visit(this);
        }
        isCondition=false;
        Tree.Variable v = that.getVariable();
        ProducedType type = t==null ? null : t.getTypeModel();
        if (v!=null) {
//            if (type!=null && !that.getNot()) {
//                v.getType().setTypeModel(type);
//                v.getDeclarationModel().setType(type);
//            }
            //v.getType().visit(this);
            Tree.SpecifierExpression se = v.getSpecifierExpression();
            ProducedType knownType;
            if (se==null) {
                knownType = null;
            }
            else {
                se.visit(this);
                checkReferenceIsNonVariable(v, se);
                /*checkAssignable( se.getExpression().getTypeModel(),
                        getOptionalType(getObjectDeclaration().getType()),
                        se.getExpression(),
                        "expression may not be of void type");*/
                initOriginalDeclaration(v);
                //this is a bit ugly (the parser sends us a SyntheticVariable
                //instead of the real StaticType which it very well knows!)
                Tree.Expression e = se.getExpression();
                knownType = e==null ? null : e.getTypeModel();
                //TODO: what to do here in case of !is
                if (knownType!=null) {
                    if (hasUncheckedNulls(e)) {
                        knownType = unit.getOptionalType(knownType);
                    }
                    String help = " (expression is already of the specified type)";
                    if (that.getNot()) {
                        if (intersectionType(type, knownType, unit).isNothing()) {
                            that.addError("does not narrow type: intersection of '" +
                                    type.getProducedTypeName(unit) +
                                    "' and '" +
                                    knownType.getProducedTypeName(unit) + "' is empty" +
                                    help);
                        }
                        else if (knownType.isSubtypeOf(type)) {
                            that.addError("tests assignability to bottom type 'Nothing': '" +
                                    knownType.getProducedTypeName(unit) +
                                    "' is a subtype of '" +
                                    type.getProducedTypeName(unit) + "'");
                        }
                    }
                    else {
                        if (knownType.isSubtypeOf(type)) {
                            that.addError("does not narrow type: '" +
                                    knownType.getProducedTypeName(unit) +
                                    "' is a subtype of '" +
                                    type.getProducedTypeName(unit) + "'" +
                                    help);
                        }
                    }
                }
            }
            defaultTypeToAnything(v);
            if (knownType==null) {
                knownType = unit.getType(unit.getAnythingDeclaration()); //or should we use unknown?
            }
           
            ProducedType it = narrow(that, type, knownType);
            //check for disjointness
            if (it.getDeclaration() instanceof NothingType) {
                if (that.getNot()) {
                    /*that.addError("tests assignability to Nothing type: " +
                            knownType.getProducedTypeName(unit) + " is a subtype of " +
                            type.getProducedTypeName(unit));*/
                }
                else {
                    that.addError("tests assignability to bottom type 'Nothing': intersection of '" +
                            knownType.getProducedTypeName(unit) + "' and '" +
                            type.getProducedTypeName(unit) + "' is empty");
                }
            }
            //do this *after* checking for disjointness!
            knownType=unit.denotableType(knownType);
            //now recompute the narrowed type!
            it = narrow(that, type, knownType);
           
            v.getType().setTypeModel(it);
            v.getDeclarationModel().setType(it);
        }
    }
   
  private ProducedType narrow(Tree.IsCondition that, ProducedType type,
            ProducedType knownType) {
      ProducedType it;
      if (that.getNot()) {
          //a !is condition, narrow to complement
          it = unit.denotableType(knownType.minus(type));
      }
      else {
          //narrow to the intersection of the outer type
          //and the type specified in the condition
          it = intersectionType(type, knownType, that.getUnit());
      }
      return it;
    }
   
    @Override public void visit(Tree.SatisfiesCondition that) {
        super.visit(that);
        that.addUnsupportedError("satisfies conditions not yet supported");
    }
   
    @Override public void visit(Tree.ExistsOrNonemptyCondition that) {
        //don't recurse to the Variable, since we don't
        //want to check that the specifier expression is
        //assignable to the declared variable type
        //(nor is it possible to infer the variable type)
        ProducedType t = null;
        Node n = that;
        Tree.Term term = null;
        Tree.Variable v = that.getVariable();
        if (v!=null) {
            //v.getType().visit(this);
            defaultTypeToAnything(v);
            Tree.SpecifierExpression se = v.getSpecifierExpression();
            if (se==null) {
                v.addError("missing specifier");
            }
            else if (se.getExpression()!=null) {
                se.visit(this);
                if (that instanceof Tree.ExistsCondition) {
                    inferDefiniteType(v, se);
                    checkOptionalType(v, se);
                }
                else if (that instanceof Tree.NonemptyCondition) {
                    inferNonemptyType(v, se);
                    checkEmptyOptionalType(v, se);
                }
                t = se.getExpression().getTypeModel();
                n = v;
                checkReferenceIsNonVariable(v, se);
                initOriginalDeclaration(v);
                term = se.getExpression().getTerm();
            }
        }
        if (that instanceof Tree.ExistsCondition) {
            checkOptional(t, term, n);
        }
        else if (that instanceof Tree.NonemptyCondition) {
            checkEmpty(t, term, n);
        }
    }

    private void defaultTypeToAnything(Tree.Variable v) {
        /*if (v.getType().getTypeModel()==null) {
            v.getType().setTypeModel( getAnythingDeclaration().getType() );
        }*/
        v.getType().visit(this);
        if (v.getDeclarationModel().getType()==null) {
            v.getDeclarationModel().setType( defaultType() );
        }
    }

    private void checkReferenceIsNonVariable(Tree.Variable v,
            Tree.SpecifierExpression se) {
        if (v.getType() instanceof Tree.SyntheticVariable) {
            Tree.BaseMemberExpression term = (Tree.BaseMemberExpression) se.getExpression().getTerm();
      checkReferenceIsNonVariable(term, false);
        }
    }

    private void checkReferenceIsNonVariable(Tree.BaseMemberExpression ref,
        boolean isSwitch) {
        Declaration d = ref.getDeclaration();
        if (d!=null) {
          int code = isSwitch ? 3101:3100;
            String help=" (assign to a new local value to narrow type)";
            if (!(d instanceof Value)) {
                ref.addError("referenced declaration is not a value: '" +
                        d.getName(unit) + "'", code);
            }
            else if (isNonConstant(d)) {
                ref.addError("referenced value is non-constant: '" +
                        d.getName(unit) + "'" + help, code);
            }
            else if (d.isDefault() || d.isFormal()) {
                ref.addError("referenced value may be refined by a non-constant value: '" +
                        d.getName(unit) + "'" + help, code);
            }
        }
    }

    private boolean isNonConstant(Declaration d) {
        return d instanceof Value &&
                (((Value) d).isVariable() || ((Value) d).isTransient());
    }
   
    private void checkEmpty(ProducedType t, Tree.Term term, Node n) {
        /*if (t==null) {
            n.addError("expression must be a type with fixed size: type not known");
        }
        else*/
        if (!isTypeUnknown(t)) {
            if (!unit.isSequentialType(unit.getDefiniteType(t))) {
                term.addError("expression must be a possibly-empty sequential type: '" +
                        t.getProducedTypeName(unit) + "' is not a subtype of 'Anything[]?'");
            }
            else if (!unit.isPossiblyEmptyType(t)) {
                term.addError("expression must be a possibly-empty sequential type: '" +
                        t.getProducedTypeName(unit) + "' is not possibly-empty");
            }
        }
    }
   
    private void checkOptional(ProducedType t, Tree.Term term, Node n) {
        /*if (t==null) {
            n.addError("expression must be of optional type: type not known");
        }
        else*/
        if (!isTypeUnknown(t) && !unit.isOptionalType(t) &&
                !hasUncheckedNulls(term)) {
            term.addError("expression must be of optional type: '" +
                    t.getProducedTypeName(unit) + "' is not optional");
        }
    }

    @Override public void visit(Tree.BooleanCondition that) {
        super.visit(that);
        if (that.getExpression()!=null) {
            ProducedType t = that.getExpression().getTypeModel();
            if (!isTypeUnknown(t)) {
                checkAssignable(t, unit.getType(unit.getBooleanDeclaration()), that,
                        "expression must be of boolean type");
            }
        }
    }

    @Override public void visit(Tree.Resource that) {
        super.visit(that);
        ProducedType t = null;
        Node typedNode = null;
        Tree.Expression e = that.getExpression();
        Tree.Variable v = that.getVariable();
        if (e!=null) {
            t = e.getTypeModel();
            typedNode = e;
           
        }
        else if (v!=null) {
            t = v.getType().getTypeModel();
            typedNode = v.getType();
            Tree.SpecifierExpression se = v.getSpecifierExpression();
            if (se==null) {
                v.addError("missing resource specifier");
            }
            else {
                e = se.getExpression();
                if (typedNode instanceof Tree.ValueModifier) {
                    typedNode = se.getExpression();
                }
            }
        }
        else {
            that.addError("missing resource expression");
        }
        if (typedNode!=null) {
            if (!isTypeUnknown(t)) {
                if (e != null) {
                    ProducedType ot = unit.getType(unit.getObtainableDeclaration());
                    ProducedType dt = unit.getType(unit.getDestroyableDeclaration());
                    if (isInstantiationExpression(e)) {
                        if (!t.isSubtypeOf(dt) && !t.isSubtypeOf(ot)) {
                            typedNode.addError("resource must be either obtainable or destroyable: '" +
                                    t.getProducedTypeName(unit) + "' is neither 'Obtainable' nor 'Destroyable'");
                        }
                    }
                    else {
                        checkAssignable(t, ot, typedNode, "resource must be obtainable");
                    }
                }
            }
        }
    }
   
    @Override public void visit(Tree.ForIterator that) {
        super.visit(that);
        Tree.SpecifierExpression se = that.getSpecifierExpression();
        if (se!=null) {
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                ProducedType et = e.getTypeModel();
                if (!isTypeUnknown(et)) {
                    if (!unit.isIterableType(et)) {
                        se.addError("expression is not iterable: '" +
                                et.getProducedTypeName(unit) +
                                "' is not a subtype of 'Iterable'");
                    }
                    else if (et!=null && unit.isEmptyType(et)) {
                        se.addError("iterated expression is definitely empty: '" +
                                et.getProducedTypeName(unit) +
                                "' is a subtype of 'Empty'");
                    }
                }
            }
        }
    }

    @Override public void visit(Tree.ValueIterator that) {
        super.visit(that);
        Tree.Variable v = that.getVariable();
        if (v!=null) {
            inferContainedType(v, that.getSpecifierExpression());
            checkContainedType(v, that.getSpecifierExpression());
        }
    }

    @Override public void visit(Tree.KeyValueIterator that) {
        super.visit(that);
        Tree.SpecifierExpression se = that.getSpecifierExpression();
        if (se!=null) {
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                ProducedType et = e.getTypeModel();
                if (!isTypeUnknown(et)) {
                    ProducedType it = unit.getIteratedType(et);
                    if (it!=null && !isTypeUnknown(it)) {
                        if (!unit.isEntryType(it)) {
                            se.addError("iterated element type is not an entry type: '" +
                                    it.getProducedTypeName(unit) +
                                    "' is not a subtype of 'Entry'");
                        }
                    }
                }
            }
        }
        Tree.Variable kv = that.getKeyVariable();
        Tree.Variable vv = that.getValueVariable();
        if (kv!=null && vv!=null) {
            inferKeyType(kv, that.getSpecifierExpression());
            inferValueType(vv, that.getSpecifierExpression());
            checkKeyValueType(kv, vv, that.getSpecifierExpression());
        }
    }
   
    @Override public void visit(Tree.AttributeDeclaration that) {
        super.visit(that);
        Value dec = that.getDeclarationModel();
        Tree.SpecifierOrInitializerExpression sie =
                that.getSpecifierOrInitializerExpression();
        inferType(that, sie);
        Tree.Type type = that.getType();
        if (type!=null) {
          ProducedType t = type.getTypeModel();
          if (type instanceof Tree.LocalModifier) {
            if (dec.isParameter()) {
              type.addError("parameter may not have inferred type: '" +
                  dec.getName() + "' must declare an explicit type");
            }
            else if (isTypeUnknown(t)) {
                if (sie==null) {
                    type.addError("value must specify an explicit type or definition", 200);
                }
                else if (!hasError(sie)) {
                    type.addError("value type could not be inferred");
                }
            }
          }
          if (!isTypeUnknown(t)) {
            checkType(t, dec.getName(), sie, 2100);
          }
        }
      Setter setter = dec.getSetter();
      if (setter!=null) {
        setter.getParameter().getModel().setType(dec.getType());
      }
    }
   
    @Override public void visit(Tree.ParameterizedExpression that) {
        super.visit(that);
        Tree.Term p = that.getPrimary();
        if (!hasError(that)) {
            if (p instanceof Tree.QualifiedMemberExpression ||
                    p instanceof Tree.BaseMemberExpression) {
                Tree.MemberOrTypeExpression mte =
                        (Tree.MemberOrTypeExpression) p;
                if (p.getTypeModel()!=null &&
                        mte.getDeclaration()!=null) {
                    ProducedType pt = p.getTypeModel();
                    if (pt!=null) {
                        for (int j=0; j<that.getParameterLists().size(); j++) {
                            Tree.ParameterList pl =
                                    that.getParameterLists().get(j);
                            ProducedType ct =
                                    pt.getSupertype(unit.getCallableDeclaration());
                            String refName =
                                    mte.getDeclaration().getName();
                            if (ct==null) {                       
                                pl.addError("no matching parameter list in referenced declaration: '" +
                                        refName + "'");
                            }
                            else if (ct.getTypeArgumentList().size()>=2) {
                                ProducedType tupleType =
                                        ct.getTypeArgumentList().get(1);
                                List<ProducedType> argTypes =
                                        unit.getTupleElementTypes(tupleType);
                                boolean variadic =
                                        unit.isTupleLengthUnbounded(tupleType);
                                boolean atLeastOne =
                                        unit.isTupleVariantAtLeastOne(tupleType);
                                List<Tree.Parameter> params = pl.getParameters();
                                if (argTypes.size()!=params.size()) {
                                    pl.addError("wrong number of declared parameters: '" +
                                            refName  + "' has " + argTypes.size() +
                                            " parameters");
                                }
                                for (int i=0; i<argTypes.size()&&i<params.size(); i++) {
                                    ProducedType at = argTypes.get(i);
                                    Tree.Parameter param = params.get(i);
                                    ProducedType t = param.getParameterModel().getModel()
                                            .getTypedReference()
                                            .getFullType();
                                    String paramName = param.getParameterModel().getName();
                                    checkAssignable(at, t, param,
                                            "type of parameter '" + paramName +
                                            "' must be a supertype of parameter type in declaration of '" +
                                            refName + "'");
                                }
                                if (!params.isEmpty()) {
                                    Tree.Parameter lastParam =
                                            params.get(params.size()-1);
                                    boolean refSequenced =
                                            lastParam.getParameterModel().isSequenced();
                                    boolean refAtLeastOne =
                                            lastParam.getParameterModel().isAtLeastOne();
                                    if (refSequenced && !variadic) {
                                        lastParam.addError("parameter list in declaration of '" +
                                                refName + "' does not have a variadic parameter");
                                    }
                                    else if (!refSequenced && variadic) {
                                        lastParam.addError("parameter list in declaration of '" +
                                                refName + "' has a variadic parameter");
                                    }
                                    else if (refAtLeastOne && !atLeastOne) {
                                        lastParam.addError("variadic parameter in declaration of '" +
                                                refName + "' is optional");
                                    }
                                    else if (!refAtLeastOne && atLeastOne) {
                                        lastParam.addError("variadic parameter in declaration of '" +
                                                refName + "' is not optional");
                                    }
                                }
                                pt = ct.getTypeArgumentList().get(0);
                                that.setTypeModel(pt);
                            }
                        }
                    }
                }
            }
        }
    }
   
    @Override public void visit(Tree.SpecifierStatement that) {
        super.visit(that);
        boolean hasParams = false;
        Tree.Term me = that.getBaseMemberExpression();
        while (me instanceof Tree.ParameterizedExpression) {
            hasParams = true;
            me = ((Tree.ParameterizedExpression) me).getPrimary();
        }
        assign(me);
        Tree.SpecifierExpression sie = that.getSpecifierExpression();
        if (me instanceof Tree.BaseMemberExpression) {
            Declaration d = that.getDeclaration();
            if (d instanceof TypedDeclaration) {
                if (that.getRefinement()) {
                    // interpret this specification as a
                    // refinement of an inherited member
                    if (d instanceof Value) {
                        refineValue(that);
                    }
                    else if (d instanceof Method) {
                        refineMethod(that);
                    }
                    Tree.BaseMemberExpression bme =
                            (Tree.BaseMemberExpression) me;
                    bme.setDeclaration(that.getDeclaration());
                }
                else if (d instanceof MethodOrValue) {
                    MethodOrValue mv = (MethodOrValue) d;
                    if (mv.isShortcutRefinement()) {
                        String desc;
                        if (d instanceof Value) {
                            desc = "value";
                        }
                        else {
                            desc = "function";
                        }
                        me.addError(desc + " already specified: '" +
                                    d.getName(unit) + "'");
                    }
                    else if (!mv.isVariable() && !mv.isLate()) {
                        String desc;
                        if (d instanceof Value) {
                            desc = "value is neither variable nor late and";
                        }
                        else {
                            desc = "function";
                        }
                        if (mv.isToplevel()) {
                            me.addError("toplevel " + desc +
                                    " may not be specified: '" +
                                    d.getName(unit) + "'", 803);
                        }
                        else if (!mv.isDefinedInScope(that.getScope())) {
                            me.addError(desc + " may not be specified here: '" +
                                    d.getName(unit) + "'", 803);
                        }
                    }
                }
                if (hasParams && d instanceof Method &&
                        ((Method) d).isDeclaredVoid() &&
                        !isSatementExpression(sie.getExpression())) {
                    sie.addError("function is declared void so specified expression must be a statement: '" +
                            d.getName(unit) + "' is declared 'void'");
                }
                if (d instanceof Value &&
                        sie instanceof Tree.LazySpecifierExpression) {
                    ((Value) d).setTransient(true);
                }
               
                ProducedType t = that.getBaseMemberExpression().getTypeModel();
                if (that.getBaseMemberExpression()==me && d instanceof Method) {
                    //if the declaration of the method has
                    //defaulted parameters, we should ignore
                    //that when determining if the RHS is
                    //an acceptable implementation of the
                    //method
                    //TODO: this is a pretty nasty way to
                    //      handle the problem
                    t = eraseDefaultedParameters(t);
                }
                if (!isTypeUnknown(t)) {
                    checkType(t, d.getName(unit), sie, 2100);
                }
            }
            if (that.getBaseMemberExpression() instanceof Tree.ParameterizedExpression) {
                if (!(sie instanceof Tree.LazySpecifierExpression)) {
                    sie.addError("functions with parameters must be specified using =>");
                }
            }
            else {
                if (sie instanceof Tree.LazySpecifierExpression && d instanceof Method) {
                    sie.addError("functions without parameters must be specified using =");
                }
            }
        }
        else {
            me.addError("illegal specification statement: only a function or value may be specified");
        }
    }
   
    boolean isSatementExpression(Tree.Expression e) {
        if (e==null) {
            return false;
        }
        else {
            Tree.Term t = e.getTerm();
            return t instanceof Tree.InvocationExpression ||
                    t instanceof Tree.PostfixOperatorExpression ||
                    t instanceof Tree.AssignmentOp ||
                    t instanceof Tree.PrefixOperatorExpression;
        }
    }
   
    private ProducedType eraseDefaultedParameters(ProducedType t) {
        ProducedType ct =
                t.getSupertype(unit.getCallableDeclaration());
        if (ct!=null) {
            List<ProducedType> typeArgs =
                    ct.getTypeArgumentList();
            if (typeArgs.size()>=2) {
                ProducedType rt = typeArgs.get(0);
                ProducedType pts = typeArgs.get(1);
                List<ProducedType> argTypes =
                        unit.getTupleElementTypes(pts);
                boolean variadic =
                        unit.isTupleLengthUnbounded(pts);
                boolean atLeastOne =
                        unit.isTupleVariantAtLeastOne(pts);
                if (variadic) {
                    ProducedType spt =
                            argTypes.get(argTypes.size()-1);
                    argTypes.set(argTypes.size()-1,
                            unit.getIteratedType(spt));
                }
                return producedType(unit.getCallableDeclaration(), rt,
                        unit.getTupleType(argTypes, variadic, atLeastOne, -1));
            }
        }
        return t;
    }
   
    static ProducedReference getRefinedMember(MethodOrValue d,
            ClassOrInterface classOrInterface) {
        ProducedType supertype = classOrInterface.getType()
                .getSupertype((TypeDeclaration) d.getContainer());
        return d.getProducedReference(supertype,
                Collections.<ProducedType>emptyList());
    }
   
    private void refineValue(Tree.SpecifierStatement that) {
        Value refinedValue = (Value) that.getRefined();
        Value value = (Value) that.getDeclaration();
        ClassOrInterface ci = (ClassOrInterface) value.getContainer();
        ProducedReference refinedProducedReference =
                getRefinedMember(refinedValue, ci);
        value.setType(refinedProducedReference.getType());
    }

    private void refineMethod(Tree.SpecifierStatement that) {
        Method refinedMethod = (Method) that.getRefined();
        Method method = (Method) that.getDeclaration();
        ClassOrInterface ci = (ClassOrInterface) method.getContainer();
        Declaration root = refinedMethod.getRefinedDeclaration();
        method.setRefinedDeclaration(root);
        if (getInterveningRefinements(method.getName(),
                getSignature(method), root,
                ci, (TypeDeclaration) root.getContainer())
                .isEmpty()) {
            that.getBaseMemberExpression()
                .addError("shortcut refinement does not exactly refine any overloaded inherited member");
        }
        else {
            ProducedReference refinedProducedReference =
                    getRefinedMember(refinedMethod, ci);
            method.setType(refinedProducedReference.getType());
            List<Tree.ParameterList> parameterLists;
            Tree.Term me = that.getBaseMemberExpression();
            if (me instanceof Tree.ParameterizedExpression) {
                parameterLists =
                        ((Tree.ParameterizedExpression) me).getParameterLists();
            }
            else {
                parameterLists = emptyList();
            }
            for (int i=0; i<refinedMethod.getParameterLists().size(); i++) {
                ParameterList refinedParameters =
                        refinedMethod.getParameterLists().get(i);
                ParameterList parameters =
                        method.getParameterLists().get(i);
                Tree.ParameterList parameterList =
                        parameterLists.size()<=i ?
                                null : parameterLists.get(i);
                for (int j=0; j<refinedParameters.getParameters().size(); j++) {
                    Parameter refinedParameter =
                            refinedParameters.getParameters().get(j);
                    ProducedType refinedParameterType =
                            refinedProducedReference
                            .getTypedParameter(refinedParameter)
                            .getFullType();
                    if (parameterList==null ||
                            parameterList.getParameters().size()<=j) {
                        Parameter p = parameters.getParameters().get(j);
                        p.getModel().setType(refinedParameterType);
                    }
                    else {
                        Tree.Parameter parameter =
                                parameterList.getParameters().get(j);
                        Parameter p = parameter.getParameterModel();
                        ProducedType parameterType =
                                p.getModel().getTypedReference().getFullType();
                        Node typeNode = parameter;
                        if (parameter instanceof Tree.ParameterDeclaration) {
                            Tree.Type type =
                                    ((Tree.ParameterDeclaration) parameter)
                                    .getTypedDeclaration().getType();
                            if (type!=null) {
                                typeNode = type;
                            }
                        }
                        checkIsExactlyForInterop(that.getUnit(),
                                refinedParameters.isNamedParametersSupported(),
                                parameterType, refinedParameterType, typeNode,
                                "type of parameter '" + p.getName() +
                                "' of '" + method.getName() +
                                "' declared by '" + ci.getName() +
                                "' is different to type of corresponding parameter '" +
                                refinedParameter.getName() + "' of refined method '" +
                                refinedMethod.getName() + "' of '" +
                                ((Declaration) refinedMethod.getContainer()).getName() +
                                "'");
                    }
                }
            }
        }
    }
   
    @Override public void visit(Tree.TypeParameterDeclaration that) {
        super.visit(that);
        TypeParameter tpd = that.getDeclarationModel();
        ProducedType dta = tpd.getDefaultTypeArgument();
        if (dta!=null) {
            for (ProducedType st: tpd.getSatisfiedTypes()) {
                checkAssignable(dta, st,
                        that.getTypeSpecifier().getType(),
                        "default type argument does not satisfy type constraint");
            }
        }
    }
   
    @Override public void visit(Tree.InitializerParameter that) {
        super.visit(that);
        Parameter p = that.getParameterModel();
        MethodOrValue model = p.getModel();
        if (model!=null) {
          ProducedType type =
                  model.getTypedReference().getFullType();
          if (type!=null && !isTypeUnknown(type)) {
            checkType(type, that.getSpecifierExpression());
          }
        }
        else {
            Declaration a = that.getScope()
                    .getDirectMember(p.getName(),
                            null, false);
            if (a==null) {
                that.addError("parameter declaration does not exist: '" +
                        p.getName() + "'");
            }
        }
    }
   
    private void checkType(ProducedType declaredType,
            Tree.SpecifierOrInitializerExpression sie) {
        if (sie!=null && sie.getExpression()!=null) {
            ProducedType t = sie.getExpression().getTypeModel();
            if (!isTypeUnknown(t)) {
                checkAssignable(t, declaredType, sie,
                        "specified expression must be assignable to declared type");
            }
        }
    }

    private void checkType(ProducedType declaredType, String name,
            Tree.SpecifierOrInitializerExpression sie, int code) {
        if (sie!=null && sie.getExpression()!=null) {
            ProducedType t = sie.getExpression().getTypeModel();
            if (!isTypeUnknown(t)) {
                checkAssignable(t, declaredType, sie,
                        "specified expression must be assignable to declared type of '" +
                                name + "'",
                        code);
            }
        }
    }

    private void checkFunctionType(ProducedType et, Tree.Type that,
            Tree.SpecifierExpression se) {
        if (!isTypeUnknown(et)) {
            checkAssignable(et, that.getTypeModel(), se,
                    "specified expression type must be assignable to declared return type",
                    2100);
        }
    }

    private void checkOptionalType(Tree.Variable var,
            Tree.SpecifierExpression se) {
        if (var.getType()!=null &&
                !(var.getType() instanceof Tree.LocalModifier)) {
            ProducedType vt = var.getType().getTypeModel();
            if (!isTypeUnknown(vt)) {
                checkAssignable(vt,
                        unit.getType(unit.getObjectDeclaration()),
                        var.getType(),
                        "specified type may not be optional");
            }
            Tree.Expression e = se.getExpression();
            if (se!=null && e!=null) {
                ProducedType set = e.getTypeModel();
                if (set!=null) {
                    if (!isTypeUnknown(vt) && !isTypeUnknown(set)) {
                        checkAssignable(unit.getDefiniteType(set), vt, se,
                                "specified expression must be assignable to declared type");
                    }
                }
            }
        }
    }

    private void checkEmptyOptionalType(Tree.Variable var,
            Tree.SpecifierExpression se) {
        if (var.getType()!=null &&
                !(var.getType() instanceof Tree.LocalModifier)) {
            ProducedType vt = var.getType().getTypeModel();
            if (!isTypeUnknown(vt)) {
                checkAssignable(vt,
                        unit.getSequenceType(unit.getType(unit.getAnythingDeclaration())),
                        var.getType(),
                        "specified type must be a nonempty sequence type");
            }
            Tree.Expression e = se.getExpression();
            if (se!=null && e!=null) {
                ProducedType set = e.getTypeModel();
                if (!isTypeUnknown(vt) && !isTypeUnknown(set)) {
                    checkType(unit.getOptionalType(unit.getPossiblyEmptyType(vt)), se);
                }
            }
        }
    }

    private void checkContainedType(Tree.Variable var,
            Tree.SpecifierExpression se) {
        if (var.getType()!=null) {
            ProducedType vt = var.getType().getTypeModel();
            if (!isTypeUnknown(vt)) {
                checkType(unit.getIterableType(vt), se);
            }
        }
    }

    private void checkKeyValueType(Tree.Variable key, Tree.Variable value,
            Tree.SpecifierExpression se) {
        if (key.getType()!=null && value.getType()!=null) {
            ProducedType kt = key.getType().getTypeModel();
            ProducedType vt = value.getType().getTypeModel();
            if (!isTypeUnknown(kt) && !isTypeUnknown(vt)) {
                checkType(unit.getIterableType(unit.getEntryType(kt, vt)), se);
            }
        }
    }
   
    @Override public void visit(Tree.AttributeGetterDefinition that) {
        Tree.Type type = that.getType();
        Tree.Type rt = beginReturnScope(type);
        Value dec = that.getDeclarationModel();
        Declaration od = beginReturnDeclaration(dec);
        super.visit(that);
        endReturnScope(rt, dec);
        endReturnDeclaration(od);
        Setter setter = dec.getSetter();
        if (setter!=null) {
            setter.getParameter().getModel().setType(dec.getType());
        }
        if (type instanceof Tree.LocalModifier) {
            if (isTypeUnknown(type.getTypeModel())) {
                type.addError("getter type could not be inferred");
            }
        }
    }

    @Override public void visit(Tree.AttributeArgument that) {
        Tree.SpecifierExpression se =
                that.getSpecifierExpression();
        Tree.Type type = that.getType();
        if (se==null) {
            Tree.Type rt = beginReturnScope(type);
            Declaration od =
                    beginReturnDeclaration(that.getDeclarationModel());
            super.visit(that);
            endReturnDeclaration(od);
            endReturnScope(rt, that.getDeclarationModel());
        }
        else {
            super.visit(that);
            inferType(that, se);
            if (type!=null) {
                ProducedType t = type.getTypeModel();
                if (!isTypeUnknown(t)) {
                    checkType(t,
                            that.getDeclarationModel().getName(),
                            se, 2100);
                }
            }
        }
        if (type instanceof Tree.LocalModifier) {
            if (isTypeUnknown(type.getTypeModel())) {
                if (se==null || !hasError(se)) {
                    Node node = type.getToken()==null ? that : type;
                    node.addError("argument type could not be inferred");
                }
            }
        }
    }

    @Override public void visit(Tree.AttributeSetterDefinition that) {
        Tree.Type rt = beginReturnScope(that.getType());
        Setter sd = that.getDeclarationModel();
        Declaration od = beginReturnDeclaration(sd);
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, sd);
        Tree.SpecifierExpression se = that.getSpecifierExpression();
        if (se!=null) {
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                if (!isSatementExpression(e)) {
                    se.addError("specified expression must be a statement: '" +
                            sd.getName() + "'");
                }
            }
        }
    }

    @Override public void visit(Tree.MethodDeclaration that) {
        super.visit(that);
        Tree.Type type = that.getType();
        Tree.SpecifierExpression se =
                that.getSpecifierExpression();
        if (se!=null) {
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                ProducedType returnType = e.getTypeModel();
                inferFunctionType(that, returnType);
                if (type!=null &&
                        !(type instanceof Tree.DynamicModifier)) {
                    checkFunctionType(returnType, type, se);
                }
                if (type instanceof Tree.VoidModifier &&
                        !isSatementExpression(e)) {
                    se.addError("function is declared void so specified expression must be a statement: '" +
                            that.getDeclarationModel().getName() + "' is declared 'void'");
                }
            }
        }
        if (type instanceof Tree.LocalModifier) {
            Method dec = that.getDeclarationModel();
            if (dec.isParameter()) {
                type.addError("parameter may not have inferred type: '" +
                        dec.getName() + "' must declare an explicit type");
            }
            else if (isTypeUnknown(type.getTypeModel())) {
                if (se==null) {
                    type.addError("function must specify an explicit return type or definition", 200);
                }
                else if (!hasError(se)) {
                    type.addError("function type could not be inferred");
                }
            }
        }
    }

    @Override public void visit(Tree.MethodDefinition that) {
        Tree.Type type = that.getType();
        Tree.Type rt = beginReturnScope(type);
        Declaration od =
                beginReturnDeclaration(that.getDeclarationModel());
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, that.getDeclarationModel());
        if (type instanceof Tree.LocalModifier) {
            if (isTypeUnknown(type.getTypeModel())) {
                type.addError("function type could not be inferred");
            }
        }
    }

    @Override public void visit(Tree.MethodArgument that) {
        Tree.SpecifierExpression se = that.getSpecifierExpression();
        Method d = that.getDeclarationModel();
        Tree.Type type = that.getType();
        if (se==null) {
            Tree.Type rt = beginReturnScope(type);          
            Declaration od = beginReturnDeclaration(d);
            super.visit(that);
            endReturnDeclaration(od);
            endReturnScope(rt, d);
        }
        else {
            super.visit(that);
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                ProducedType returnType = e.getTypeModel();
                inferFunctionType(that, returnType);
                if (type!=null &&
                        !(type instanceof Tree.DynamicModifier)) {
                    checkFunctionType(returnType, type, se);
                }
                if (d.isDeclaredVoid() && !isSatementExpression(e)) {
                    se.addError("functional argument is declared void so specified expression must be a statement: '" +
                            d.getName() + "' is declared 'void'");
                }
            }
        }
        if (type instanceof Tree.LocalModifier) {
            if (isTypeUnknown(type.getTypeModel())) {
                if (se==null || hasError(type)) {
                    Node node = type.getToken()==null ? that : type;
                    node.addError("argument type could not be inferred");
                }
            }
        }
    }

    @Override public void visit(Tree.ClassDefinition that) {
        Tree.Type rt =
                beginReturnScope(new Tree.VoidModifier(that.getToken()));
        Declaration od =
                beginReturnDeclaration(that.getDeclarationModel());
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, null);
        validateEnumeratedSupertypes(that,
                that.getDeclarationModel());
    }
   
    @Override public void visit(Tree.ClassOrInterface that) {
        super.visit(that);
        validateEnumeratedSupertypeArguments(that,
                that.getDeclarationModel());
    }

    @Override public void visit(Tree.InterfaceDefinition that) {
        Tree.Type rt = beginReturnScope(null);
        Declaration od =
                beginReturnDeclaration(that.getDeclarationModel());
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, null);
        validateEnumeratedSupertypes(that,
                that.getDeclarationModel());
    }

    @Override public void visit(Tree.ObjectDefinition that) {
        Tree.Type rt =
                beginReturnScope(new Tree.VoidModifier(that.getToken()));
        Declaration od =
                beginReturnDeclaration(that.getDeclarationModel());
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, null);
        validateEnumeratedSupertypes(that,
                that.getAnonymousClass());
    }

    @Override public void visit(Tree.ObjectArgument that) {
        Tree.Type rt =
                beginReturnScope(new Tree.VoidModifier(that.getToken()));
        Declaration od =
                beginReturnDeclaration(that.getDeclarationModel());
        super.visit(that);
        endReturnDeclaration(od);
        endReturnScope(rt, null);
        validateEnumeratedSupertypes(that,
                that.getAnonymousClass());
    }
   
    @Override public void visit(Tree.ClassDeclaration that) {
        super.visit(that);
        Class alias = that.getDeclarationModel();
        Class c = alias.getExtendedTypeDeclaration();
        if (c!=null) {
            if (c.isAbstract()) {
                if (!alias.isFormal() && !alias.isAbstract()) {
                    that.addError("alias of abstract class must be annotated abstract", 310);
                }
            }
            if (c.isAbstraction()) {
                that.addError("class alias may not alias overloaded class");
            }
            else {
                //TODO: all this can be removed once the backend
                //      implements full support for the new class
                //      alias stuff
                ProducedType at = alias.getExtendedType();
                ParameterList cpl = c.getParameterList();
                ParameterList apl = alias.getParameterList();
                if (cpl!=null && apl!=null) {
                    int cps = cpl.getParameters().size();
                    int aps = apl.getParameters().size();
                    if (cps!=aps) {
                        that.getParameterList()
                                .addUnsupportedError("wrong number of initializer parameters declared by class alias: " +
                                        alias.getName());
                    }
                   
                    for (int i=0; i<cps && i<aps; i++) {
                        Parameter ap = apl.getParameters().get(i);
                        Parameter cp = cpl.getParameters().get(i);
                        ProducedType pt = at.getTypedParameter(cp).getType();
                        //TODO: properly check type of functional parameters!!
                        checkAssignableWithWarning(ap.getType(), pt, that, "alias parameter " +
                                ap.getName() + " must be assignable to corresponding class parameter " +
                                cp.getName());
                    }
                   
                    //temporary restrictions
                    if (that.getClassSpecifier()!=null) {
                        Tree.InvocationExpression ie =
                                that.getClassSpecifier()
                                    .getInvocationExpression();
                        if (ie!=null) {
                            Tree.PositionalArgumentList pal =
                                    ie.getPositionalArgumentList();
                            if (pal!=null) {
                                List<PositionalArgument> pas =
                                        pal.getPositionalArguments();
                                if (cps!=pas.size()) {
                                    pal.addUnsupportedError("wrong number of arguments for aliased class: " +
                                            alias.getName() + " has " + cps + " parameters");
                                }
                                for (int i=0; i<pas.size() && i<cps && i<aps; i++) {
                                    Tree.PositionalArgument pa = pas.get(i);
                                    Parameter aparam = apl.getParameters().get(i);
                                    Parameter cparam = cpl.getParameters().get(i);
                                    if (pa instanceof Tree.ListedArgument) {
                                        if (cparam.isSequenced()) {
                                            pa.addUnsupportedError("argument to variadic parameter of aliased class must be spread");
                                        }
                                        Tree.Expression e =
                                                ((Tree.ListedArgument) pa).getExpression();
                                        checkAliasArg(aparam, e);
                                    }
                                    else if (pa instanceof Tree.SpreadArgument) {
                                        if (!cparam.isSequenced()) {
                                            pa.addUnsupportedError("argument to non-variadic parameter of aliased class may not be spread");
                                        }
                                        Tree.Expression e =
                                                ((Tree.SpreadArgument) pa).getExpression();
                                        checkAliasArg(aparam, e);
                                    }
                                    else if (pa!=null) {
                                        pa.addUnsupportedError("argument to parameter or aliased class must be listed or spread");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void checkAliasArg(Parameter param, Tree.Expression e) {
        if (e!=null && param!=null) {
            MethodOrValue p = param.getModel();
            if (p!=null) {
                Tree.Term term = e.getTerm();
                if (term instanceof Tree.BaseMemberExpression) {
                    Declaration d =
                            ((Tree.BaseMemberExpression) term).getDeclaration();
                    if (d!=null && !d.equals(p)) {
                        e.addUnsupportedError("argument must be a parameter reference to " +
                                p.getName());
                    }
                }
                else {
                    e.addUnsupportedError("argument must be a parameter reference to " +
                            p.getName());
                }
            }
        }
    }
   
    private void inferType(Tree.TypedDeclaration that,
            Tree.SpecifierOrInitializerExpression spec) {
        if (that.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) that.getType();
            if (spec!=null) {
                setType(local, spec, that);
            }
        }
    }

    private void inferType(Tree.AttributeArgument that,
            Tree.SpecifierOrInitializerExpression spec) {
        if (that.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) that.getType();
            if (spec!=null) {
                setType(local, spec, that);
            }
        }
    }

    private void inferFunctionType(Tree.TypedDeclaration that, ProducedType et) {
        if (that.getType() instanceof Tree.FunctionModifier) {
            Tree.FunctionModifier local =
                    (Tree.FunctionModifier) that.getType();
            if (et!=null) {
                setFunctionType(local, et, that);
            }
        }
    }
   
    private void inferFunctionType(Tree.MethodArgument that, ProducedType et) {
        if (that.getType() instanceof Tree.FunctionModifier) {
            Tree.FunctionModifier local =
                    (Tree.FunctionModifier) that.getType();
            if (et!=null) {
                setFunctionType(local, et, that);
            }
        }
    }
   
    private void inferDefiniteType(Tree.Variable that,
            Tree.SpecifierExpression se) {
        if (that.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) that.getType();
            if (se!=null) {
                setTypeFromOptionalType(local, se, that);
            }
        }
    }

    private void inferNonemptyType(Tree.Variable that,
            Tree.SpecifierExpression se) {
        if (that.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) that.getType();
            if (se!=null) {
                setTypeFromEmptyType(local, se, that);
            }
        }
    }

    private void inferContainedType(Tree.Variable that,
            Tree.SpecifierExpression se) {
        if (that.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) that.getType();
            if (se!=null) {
                setTypeFromIterableType(local, se, that);
            }
        }
    }

    private void inferKeyType(Tree.Variable key,
            Tree.SpecifierExpression se) {
        if (key.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) key.getType();
            if (se!=null) {
                setTypeFromKeyType(local, se, key);
            }
        }
    }

    private void inferValueType(Tree.Variable value,
            Tree.SpecifierExpression se) {
        if (value.getType() instanceof Tree.LocalModifier) {
            Tree.LocalModifier local =
                    (Tree.LocalModifier) value.getType();
            if (se!=null) {
                setTypeFromValueType(local, se, value);
            }
        }
    }
   
    private void setTypeFromOptionalType(Tree.LocalModifier local,
            Tree.SpecifierExpression se, Tree.Variable that) {
        Tree.Expression e = se.getExpression();
        if (e!=null) {
            ProducedType expressionType = e.getTypeModel();
            if (!isTypeUnknown(expressionType)) {
                ProducedType t;
                if (unit.isOptionalType(expressionType)) {
                    t = unit.getDefiniteType(expressionType);
                }
                else {
                    t=expressionType;
                }
                local.setTypeModel(t);
                that.getDeclarationModel().setType(t);
            }
        }
    }
   
    private void setTypeFromEmptyType(Tree.LocalModifier local,
            Tree.SpecifierExpression se, Tree.Variable that) {
        Tree.Expression e = se.getExpression();
        if (e!=null) {
            ProducedType expressionType = e.getTypeModel();
            if (!isTypeUnknown(expressionType)) {
//                if (expressionType.getDeclaration() instanceof Interface &&
//                        expressionType.getDeclaration().equals(unit.getSequentialDeclaration())) {
//                    expressionType = unit.getEmptyType(unit.getSequenceType(expressionType.getTypeArgumentList().get(0)));
//                }
                ProducedType t;
                if (unit.isPossiblyEmptyType(expressionType)) {
                    t = unit.getNonemptyDefiniteType(expressionType);
                }
                else {
                    t = expressionType;
                }
                local.setTypeModel(t);
                that.getDeclarationModel().setType(t);
            }
        }
    }
   
    private void setTypeFromIterableType(Tree.LocalModifier local,
            Tree.SpecifierExpression se, Tree.Variable that) {
        if (se.getExpression()!=null) {
            ProducedType expressionType =
                    se.getExpression().getTypeModel();
            if (expressionType!=null) {
                ProducedType t =
                        unit.getIteratedType(expressionType);
                if (t!=null) {
                    local.setTypeModel(t);
                    that.getDeclarationModel().setType(t);
                }
            }
        }
    }
   
    private void setTypeFromKeyType(Tree.LocalModifier local,
            Tree.SpecifierExpression se, Tree.Variable that) {
        Tree.Expression e = se.getExpression();
        if (e!=null) {
            ProducedType expressionType = e.getTypeModel();
            if (expressionType!=null) {
                ProducedType entryType =
                        unit.getIteratedType(expressionType);
                if (entryType!=null) {
                    ProducedType kt =
                            unit.getKeyType(entryType);
                    if (kt!=null) {
                        local.setTypeModel(kt);
                        that.getDeclarationModel().setType(kt);
                    }
                }
            }
        }
    }
   
    private void setTypeFromValueType(Tree.LocalModifier local,
            Tree.SpecifierExpression se, Tree.Variable that) {
        Tree.Expression e = se.getExpression();
        if (e!=null) {
            ProducedType expressionType =
                    e.getTypeModel();
            if (expressionType!=null) {
                ProducedType entryType =
                        unit.getIteratedType(expressionType);
                if (entryType!=null) {
                    ProducedType vt =
                            unit.getValueType(entryType);
                    if (vt!=null) {
                        local.setTypeModel(vt);
                        that.getDeclarationModel().setType(vt);
                    }
                }
            }
        }
    }
   
    private void setType(Tree.LocalModifier local,
            Tree.SpecifierOrInitializerExpression s,
            Tree.TypedDeclaration that) {
        Tree.Expression e = s.getExpression();
        if (e!=null) {
            ProducedType type = e.getTypeModel();
            if (type!=null) {
                ProducedType t =
                        unit.denotableType(type)
                            .withoutUnderlyingType();
                local.setTypeModel(t);
                that.getDeclarationModel().setType(t);
            }
        }
    }
       
    private void setType(Tree.LocalModifier local,
            Tree.SpecifierOrInitializerExpression s,
            Tree.AttributeArgument that) {
        Tree.Expression e = s.getExpression();
        if (e!=null) {
            ProducedType type = e.getTypeModel();
            if (type!=null) {
                ProducedType t =
                        unit.denotableType(type)
                            .withoutUnderlyingType();
                local.setTypeModel(t);
                that.getDeclarationModel().setType(t);
            }
        }
    }
       
    private void setFunctionType(Tree.FunctionModifier local,
            ProducedType et, Tree.TypedDeclaration that) {
        ProducedType t =
                unit.denotableType(et)
                    .withoutUnderlyingType();
        local.setTypeModel(t);
        that.getDeclarationModel().setType(t);
    }
       
    private void setFunctionType(Tree.FunctionModifier local,
            ProducedType et, Tree.MethodArgument that) {
        ProducedType t =
                unit.denotableType(et)
                    .withoutUnderlyingType();
        local.setTypeModel(t);
        that.getDeclarationModel().setType(t);
    }
       
    @Override public void visit(Tree.Throw that) {
        super.visit(that);
        Tree.Expression e = that.getExpression();
        if (e!=null) {
            ProducedType et = e.getTypeModel();
            if (!isTypeUnknown(et)) {
                checkAssignable(et,
                        unit.getType(unit.getThrowableDeclaration()),
                        e, "thrown expression must be a throwable");
//                if (et.getDeclaration().isParameterized()) {
//                    e.addUnsupportedError("parameterized types in throw not yet supported");
//                }
            }
        }
    }
   
    @Override public void visit(Tree.Return that) {
        super.visit(that);
        if (returnType==null) {
            //misplaced return statements are already handled by ControlFlowVisitor
            //missing return types declarations already handled by TypeVisitor
            //that.addError("could not determine expected return type");
        }
        else {
            that.setDeclaration(returnDeclaration);
            Tree.Expression e = that.getExpression();
            String name = returnDeclaration.getName();
            if (name==null || returnDeclaration.isAnonymous()) {
                name = "anonymous function";
            }
            else {
                name = "'" + name + "'";
            }
            if (e==null) {
                if (!(returnType instanceof Tree.VoidModifier)) {
                    that.addError("non-void function or getter must return a value: " +
                            name + " is not a void function");
                }
            }
            else {
                ProducedType et = returnType.getTypeModel();
                ProducedType at = e.getTypeModel();
                if (returnType instanceof Tree.VoidModifier) {
                    that.addError("void function, setter, or class initializer may not return a value: " +
                            name + " is declared 'void'");
                }
                else if (returnType instanceof Tree.LocalModifier) {
                    inferReturnType(et, at);
                }
                else {
                    if (!isTypeUnknown(et) && !isTypeUnknown(at)) {
                        checkAssignable(at, et, e,
                                "returned expression must be assignable to return type of " +
                                        name, 2100);
                    }
                }
            }
        }
    }

    private void inferReturnType(ProducedType et, ProducedType at) {
        if (at!=null) {
            at = unit.denotableType(at);
            if (et==null || et.isSubtypeOf(at)) {
                returnType.setTypeModel(at);
            }
            else {
                if (!at.isSubtypeOf(et)) {
                    UnionType ut = new UnionType(unit);
                    List<ProducedType> list =
                            new ArrayList<ProducedType>(2);
                    addToUnion(list, et);
                    addToUnion(list, at);
                    ut.setCaseTypes(list);
                    returnType.setTypeModel( ut.getType() );
                }
            }
        }
    }
   
    ProducedType unwrap(ProducedType pt,
            Tree.QualifiedMemberOrTypeExpression mte) {
        ProducedType result;
        Tree.MemberOperator op =
                mte.getMemberOperator();
        Tree.Primary p = mte.getPrimary();
        if (op instanceof Tree.SafeMemberOp)  {
            checkOptional(pt, p, p);
            result = unit.getDefiniteType(pt);
        }
        else if (op instanceof Tree.SpreadOp) {
            if (unit.isIterableType(pt)) {
                result = unit.getIteratedType(pt);
            }
            else {
                p.addError("expression must be of iterable type: '" +
                        pt.getProducedTypeName(unit) +
                        "' is not a subtype of 'Iterable'");
                result = pt;
            }
        }
        else {
            result = pt;
        }
        if (result==null) {
            result = new UnknownType(mte.getUnit()).getType();
        }
        return result;
    }
   
    ProducedType wrap(ProducedType pt, ProducedType receivingType,
            Tree.QualifiedMemberOrTypeExpression mte) {
        Tree.MemberOperator op = mte.getMemberOperator();
        if (op instanceof Tree.SafeMemberOp)  {
            return unit.getOptionalType(pt);
        }
        else if (op instanceof Tree.SpreadOp) {
            //note: the following is nice, even though
            //      it is not actually blessed by the
            //      language spec!
            return unit.isNonemptyIterableType(receivingType) ?
                    unit.getSequenceType(pt) :
                    unit.getSequentialType(pt);
        }
        else {
            return pt;
        }
    }
   
    @Override public void visit(Tree.InvocationExpression that) {
       
        Tree.Primary p = that.getPrimary();
        p.visit(this);
       
        Tree.PositionalArgumentList pal =
                that.getPositionalArgumentList();
        if (pal!=null) {
            inferParameterTypes(p, pal);
            pal.visit(this);
        }
       
        Tree.NamedArgumentList nal =
                that.getNamedArgumentList();
        if (nal!=null) {
            inferParameterTypes(p, nal);
            nal.visit(this);
        }
       
        if (p!=null) {
            visitInvocationPositionalArgs(that);
            visitInvocationPrimary(that);
            if (isIndirectInvocation(that)) {
                visitIndirectInvocation(that);
            }
            else {
                visitDirectInvocation(that);
            }
        }
       
    }

    private void inferParameterTypes(Tree.Primary p,
            Tree.PositionalArgumentList pal) {
        Tree.Term term = unwrapExpressionUntilTerm(p);
        if (term instanceof Tree.MemberOrTypeExpression) {
            Tree.MemberOrTypeExpression mte =
                    (Tree.MemberOrTypeExpression) term;
            Declaration dec = mte.getDeclaration();
            if (dec instanceof Functional) {
                inferParameterTypesDirectly(dec, pal, mte);
            }
            else if (dec instanceof Value) {
                ProducedType pt = ((Value) dec).getType();
                inferParameterTypesIndirectly(pal, pt);
            }
        }
        else {
            inferParameterTypesIndirectly(pal, p.getTypeModel());
        }
    }

    private void inferParameterTypes(Tree.Primary p,
            Tree.NamedArgumentList nal) {
        Tree.Term term = unwrapExpressionUntilTerm(p);
        if (term instanceof Tree.MemberOrTypeExpression) {
            Tree.MemberOrTypeExpression mte =
                    (Tree.MemberOrTypeExpression) term;
            Declaration dec = mte.getDeclaration();
            if (dec instanceof Functional) {
                inferParameterTypesDirectly(dec, nal, mte);
            }
        }
    }

    private void inferParameterTypesIndirectly(Tree.PositionalArgumentList pal,
            ProducedType pt) {
        if (unit.isCallableType(pt)) {
            List<ProducedType> paramTypes =
                    unit.getCallableArgumentTypes(pt);
            List<Tree.PositionalArgument> args =
                    pal.getPositionalArguments();
            for (int i=0; i<paramTypes.size() && i<args.size(); i++) {
                ProducedType paramType = paramTypes.get(i);
                Tree.PositionalArgument arg = args.get(i);
                if (arg instanceof Tree.ListedArgument &&
                        unit.isCallableType(paramType)) {
                    Tree.ListedArgument la = (Tree.ListedArgument) arg;
                    Tree.Expression e = la.getExpression();
                    if (e!=null) {
                        Tree.Term term = unwrapExpressionUntilTerm(e.getTerm());
                        if (term instanceof Tree.FunctionArgument) {
                            inferParameterTypesFromCallableType(paramType,
                                    (Tree.FunctionArgument) term);
                        }
                        else if (term instanceof Tree.StaticMemberOrTypeExpression) {
                            Tree.StaticMemberOrTypeExpression smte =
                                    (Tree.StaticMemberOrTypeExpression) term;
                            smte.setParameterType(paramType);
                        }
                    }
                }
            }
        }
    }

    private void inferParameterTypesDirectly(Declaration dec,
            Tree.PositionalArgumentList pal,
            Tree.MemberOrTypeExpression mte) {
        ProducedReference pr =
                getInvokedProducedReference(dec, mte);
        List<ParameterList> pls =
                ((Functional) dec).getParameterLists();
        if (!pls.isEmpty()) {
            ParameterList pl = pls.get(0);
            List<Parameter> params = pl.getParameters();
            List<Tree.PositionalArgument> args =
                    pal.getPositionalArguments();
            int j=0;
            for (int i=0; i<args.size() && j<params.size(); i++) {
                Parameter param = params.get(j);
                Tree.PositionalArgument arg = args.get(i);
                if (arg instanceof Tree.ListedArgument) {
                    Tree.ListedArgument la =
                            (Tree.ListedArgument) arg;
                    la.setParameter(param);
                    inferParameterTypes(pr, param,
                            la.getExpression(),
                            param.isSequenced());
                }
                if (!param.isSequenced()) {
                    j++;
                }
            }
        }
    }

    private void inferParameterTypesDirectly(Declaration dec,
            Tree.NamedArgumentList nal,
            Tree.MemberOrTypeExpression mte) {
        ProducedReference pr =
                getInvokedProducedReference(dec, mte);
        List<ParameterList> pls =
                ((Functional) dec).getParameterLists();
        if (!pls.isEmpty()) {
            Set<Parameter> foundParameters = new HashSet<Parameter>();
            ParameterList pl = pls.get(0);
            List<Tree.NamedArgument> args =
                    nal.getNamedArguments();
            for (int i=0; i<args.size(); i++) {
                Tree.NamedArgument arg = args.get(i);
                if (arg instanceof Tree.SpecifiedArgument) {
                    Tree.SpecifiedArgument sa =
                            (Tree.SpecifiedArgument) arg;
                    Parameter param =
                            getMatchingParameter(pl, arg,
                                    foundParameters);
                    if (param!=null) {
                        foundParameters.add(param);
                        sa.setParameter(param);
                        Tree.SpecifierExpression se =
                                sa.getSpecifierExpression();
                        if (se!=null) {
                            inferParameterTypes(pr, param,
                                    se.getExpression(), false);
                        }
                    }
                }
            }
            Tree.SequencedArgument sa = nal.getSequencedArgument();
            if (sa!=null) {
                Parameter param =
                        getUnspecifiedParameter(pr, pl,
                                foundParameters);
                if (param!=null) {
                    for (Tree.PositionalArgument pa: sa.getPositionalArguments()) {
                        if (pa instanceof Tree.ListedArgument) {
                            Tree.ListedArgument la =
                                    (Tree.ListedArgument) pa;
                            la.setParameter(param);
                            inferParameterTypes(pr, param,
                                    la.getExpression(), true);
                        }
                    }
                }
            }
        }
    }

    private ProducedReference getInvokedProducedReference(Declaration dec,
            Tree.MemberOrTypeExpression mte) {
        Tree.TypeArguments tas =
                mte instanceof Tree.StaticMemberOrTypeExpression ?
                        ((Tree.StaticMemberOrTypeExpression) mte).getTypeArguments() : null;
        List<TypeParameter> tps = ((Functional) dec).getTypeParameters();
        ProducedReference pr;
        if (mte instanceof Tree.QualifiedMemberOrTypeExpression &&
                !(((Tree.QualifiedMemberOrTypeExpression) mte).getPrimary() instanceof Tree.Package)) {
            Tree.QualifiedMemberOrTypeExpression qmte =
                    (Tree.QualifiedMemberOrTypeExpression) mte;
            ProducedType pt = qmte.getPrimary().getTypeModel().resolveAliases();
            ProducedType qt = unwrap(pt, qmte);
            pr = qt.getTypedReference(dec,
                    getTypeArguments(tas, tps, qt));
        }
        else {
            ProducedType qt = mte.getScope().getDeclaringType(dec);
            pr = dec.getProducedReference(qt,
                    getTypeArguments(tas, tps, qt));
        }
        return pr;
    }

    private void inferParameterTypes(ProducedReference pr,
            Parameter param, Tree.Expression e, boolean variadic) {
        if (e!=null) {
            Tree.Term term = unwrapExpressionUntilTerm(e.getTerm());
            ProducedTypedReference tpr = pr.getTypedParameter(param);
            if (term instanceof Tree.FunctionArgument) {
                if (param.getModel() instanceof Functional) {
                    //NOTE: this branch is basically redundant
                    //      and could be removed
                    inferParameterTypesFromCallableParameter(pr,
                            param, (Tree.FunctionArgument) term);
                }
                else {
                    ProducedType paramType = tpr.getFullType();
                    if (variadic) {
                        paramType = unit.getIteratedType(paramType);
                    }
                    if (unit.isCallableType(paramType)) {
                        inferParameterTypesFromCallableType(paramType,
                                (Tree.FunctionArgument) term);
                    }
                }
            }
            else if (term instanceof Tree.StaticMemberOrTypeExpression) {
                Tree.StaticMemberOrTypeExpression stme =
                        (Tree.StaticMemberOrTypeExpression) term;
                if (stme instanceof Tree.QualifiedMemberOrTypeExpression &&
                        stme.getStaticMethodReference()) {
                    Tree.QualifiedMemberOrTypeExpression qmte =
                            (Tree.QualifiedMemberOrTypeExpression) stme;
                    Tree.StaticMemberOrTypeExpression ote =
                            (Tree.StaticMemberOrTypeExpression) qmte.getPrimary();
                    ote.setTargetParameter(tpr);
                }
                else {
                    stme.setTargetParameter(tpr);
                }
            }
        }
    }
   
    private List<ProducedType> inferFunctionRefTypeArgs(
            Tree.StaticMemberOrTypeExpression smte) {
        TypeArguments typeArguments = smte.getTypeArguments();
        Declaration dec = smte.getDeclaration();
        if (typeArguments instanceof Tree.InferredTypeArguments &&
                dec instanceof Generic &&
                !((Generic) dec).getTypeParameters().isEmpty()) {
            ProducedTypedReference param = smte.getTargetParameter();
            ProducedType paramType = smte.getParameterType();
            if (paramType==null && param!=null) {
                paramType = param.getFullType();
            }
            ProducedReference arg = getProducedReference(smte);
            if (!smte.getStaticMethodReferencePrimary() &&
                    dec instanceof Functional &&
                    param!=null) {
                Functional fun = (Functional) dec;
                List<ParameterList> apls = fun.getParameterLists();
                Declaration pdec = param.getDeclaration();
                if (pdec instanceof Functional) {
                    Functional pfun = (Functional) pdec;
                    List<ParameterList> ppls = pfun.getParameterLists();
                    if (apls.isEmpty() || ppls.isEmpty()) {
                        return null; //TODO: to give a nicer error
                    }
                    else {
                        List<ProducedType> inferredTypes =
                                new ArrayList<ProducedType>();
                        List<Parameter> apl = apls.get(0).getParameters();
                        List<Parameter> ppl = ppls.get(0).getParameters();
                        for (TypeParameter tp: fun.getTypeParameters()) {
                            List<ProducedType> list =
                                    new ArrayList<ProducedType>();
                            for (int i=0; i<apl.size() && i<ppl.size(); i++) {
                                Parameter ap = apl.get(i);
                                Parameter pp = ppl.get(i);
                                ProducedType type =
                                        param.getTypedParameter(pp).getFullType();
                                ProducedType template =
                                        arg.getTypedParameter(ap).getFullType();
                                ProducedType it =
                                        inferTypeArg(tp, template, type,
                                                true, false,
                                                new ArrayList<TypeParameter>());
                                if (it!=null &&
                                        !it.containsTypeParameters()) {
                                    addToUnionOrIntersection(tp, list, it);
                                }
                            }
                            inferredTypes.add(formUnionOrIntersection(tp, list));
                        }
                        return inferredTypes;
                    }
                }
            }
            if (paramType!=null) {
                if (unit.isSequentialType(paramType)) {
                    paramType = unit.getSequentialElementType(paramType);
                }
                if (unit.isCallableType(paramType)) {
                    ProducedType template;
                    if (smte.getStaticMethodReferencePrimary()) {
                        template = producedType(unit.getTupleDeclaration(),
                                arg.getType(), arg.getType(),
                                unit.getEmptyDeclaration().getType());
                    }
                    else {
                        template = unit.getCallableTuple(arg.getFullType());
                    }
                    ProducedType type =
                            unit.getCallableTuple(paramType);
                    List<ProducedType> inferredTypes =
                            new ArrayList<ProducedType>();
                    for (TypeParameter tp: ((Generic) dec).getTypeParameters()) {
                        ProducedType it =
                                inferTypeArg(tp, template, type,
                                        true, false,
                                        new ArrayList<TypeParameter>());
                        if (it!=null &&
                                !it.containsTypeParameters()) {
                            inferredTypes.add(it);
                        }
                        else {
                            inferredTypes.add(unit.getNothingDeclaration().getType());
                        }
                    }
                    return inferredTypes;
                }
                else {
                    return null;
                }
            }
            else {
                return null;
            }
        }
        else {
            return null;
        }
    }

    private ProducedReference getProducedReference(
            Tree.StaticMemberOrTypeExpression smte) {
        //TODO: this might not be right for static refs
        ProducedType qt;
        if (smte instanceof Tree.QualifiedMemberOrTypeExpression) {
            Tree.QualifiedMemberOrTypeExpression qte =
                    (Tree.QualifiedMemberOrTypeExpression) smte;
            qt = qte.getPrimary().getTypeModel();
        }
        else {
            qt = null;
        }
        Declaration dec = smte.getDeclaration();
        if (smte.getStaticMethodReferencePrimary()) {
            //TODO: why this special case, exactly?
            return ((TypeDeclaration) dec).getType();
        }
        else {
            return dec.getProducedReference(qt,
                    Collections.<ProducedType>emptyList());
        }
    }

    private void inferParameterTypesFromCallableType(ProducedType paramType,
            Tree.FunctionArgument anon) {
        List<Tree.ParameterList> apls = anon.getParameterLists();
        if (!apls.isEmpty()) {
            List<ProducedType> types =
                    unit.getCallableArgumentTypes(paramType);
            List<Tree.Parameter> aps = apls.get(0).getParameters();
            for (int j=0; j<types.size() && j<aps.size(); j++) {
                ProducedType type = types.get(j);
                Tree.Parameter ap = aps.get(j);
                if (ap instanceof Tree.InitializerParameter) {
                    Parameter parameter = ap.getParameterModel();
                    if (parameter.getModel()==null) {
                        Value model = new Value();
                        model.setUnit(unit);
                        model.setType(type);
                        model.setName(parameter.getName());
                        parameter.setModel(model);
                        model.setInitializerParameter(parameter);
                        Method fun = anon.getDeclarationModel();
                        model.setContainer(fun);
                        fun.addMember(model);
                    }
                }
            }
        }
    }

    private void inferParameterTypesFromCallableParameter(ProducedReference pr,
            Parameter param, Tree.FunctionArgument anon) {
        Functional f = (Functional) param.getModel();
        List<ParameterList> fpls = f.getParameterLists();
        List<Tree.ParameterList> apls = anon.getParameterLists();
        if (!fpls.isEmpty() && !apls.isEmpty()) {
            List<Parameter> fps = fpls.get(0).getParameters();
            List<Tree.Parameter> aps = apls.get(0).getParameters();
            for (int j=0; j<fps.size() && j<aps.size(); j++) {
                Parameter fp = fps.get(j);
                Tree.Parameter ap = aps.get(j);
                if (ap instanceof Tree.InitializerParameter) {
                    Parameter parameter = ap.getParameterModel();
                    if (parameter.getModel()==null) {
                        ProducedType t =
                                pr.getTypedParameter(fp).getType();
                        Value model = new Value();
                        model.setUnit(unit);
                        model.setType(t);
                        model.setName(parameter.getName());
                        parameter.setModel(model);
                        model.setInitializerParameter(parameter);
                        Method fun = anon.getDeclarationModel();
                        model.setContainer(fun);
                        fun.addMember(model);
                    }
                }
            }
        }
    }
   
    private void visitInvocationPositionalArgs(Tree.InvocationExpression that) {
        Tree.Primary p = that.getPrimary();
        Tree.PositionalArgumentList pal = that.getPositionalArgumentList();
        if (pal!=null && p instanceof Tree.MemberOrTypeExpression) {
            Tree.MemberOrTypeExpression mte = (Tree.MemberOrTypeExpression) p;
            //set up the "signature" on the primary
            //so that we can resolve the correct
            //overloaded declaration
            List<Tree.PositionalArgument> args = pal.getPositionalArguments();
            List<ProducedType> sig = new ArrayList<ProducedType>(args.size());
            for (Tree.PositionalArgument pa: args) {
                sig.add(pa.getTypeModel());
            }
            mte.setSignature(sig);
            mte.setEllipsis(hasSpreadArgument(args));
        }
    }
   
    private void checkSuperInvocation(Tree.MemberOrTypeExpression qmte) {
        Declaration member = qmte.getDeclaration();
        if (member!=null) {
            if (member.isFormal() && !inExtendsClause) {
                qmte.addError("supertype member is declared formal: '" + member.getName() +
                        "' of '" + ((TypeDeclaration) member.getContainer()).getName() + "'");
            }
            else {
                ClassOrInterface ci = getContainingClassOrInterface(qmte.getScope());
                if (ci!=null) {
                    Declaration etm = ci.getExtendedTypeDeclaration()
                            .getMember(member.getName(), null, false);
                    if (etm!=null && !etm.equals(member) && etm.refines(member)) {
                        qmte.addError("inherited member is refined by intervening superclass: '" +
                                ((TypeDeclaration) etm.getContainer()).getName() +
                                "' refines '" + member.getName() + "' declared by '" +
                                ((TypeDeclaration) member.getContainer()).getName() + "'");
                    }
                    for (TypeDeclaration td: ci.getSatisfiedTypeDeclarations()) {
                        Declaration stm = td.getMember(member.getName(), null, false);
                        if (stm!=null && !stm.equals(member) && stm.refines(member)) {
                            qmte.addError("inherited member is refined by intervening superinterface: '" +
                                    ((TypeDeclaration) stm.getContainer()).getName() +
                                    "' refines '" + member.getName() + "' declared by '" +
                                    ((TypeDeclaration) member.getContainer()).getName() + "'");
                        }
                    }
                }
            }
        }
    }
   
    private void visitInvocationPrimary(Tree.InvocationExpression that) {
        Tree.Primary primary = that.getPrimary();
        Tree.Term term = unwrapExpressionUntilTerm(primary);
        if (term instanceof Tree.StaticMemberOrTypeExpression) {
            Tree.StaticMemberOrTypeExpression mte =
                    (Tree.StaticMemberOrTypeExpression) term;
            Tree.TypeArguments tas = mte.getTypeArguments();
           
            if (term instanceof Tree.BaseTypeExpression) {
                Tree.BaseTypeExpression bte =
                        (Tree.BaseTypeExpression) term;
                TypeDeclaration type =
                        resolveBaseTypeExpression(bte, true);
                if (type!=null) {
                    List<ProducedType> typeArgs =
                            inferTypeArguments(that, type, tas, null);
                    visitBaseTypeExpression(bte, type, typeArgs, tas);
                }
            }
           
            else if (term instanceof Tree.QualifiedTypeExpression) {
                Tree.QualifiedTypeExpression qte =
                        (Tree.QualifiedTypeExpression) term;
                TypeDeclaration type =
                        resolveQualifiedTypeExpression(qte, true);
                if (type!=null) {
                    ProducedType qt =
                            qte.getPrimary().getTypeModel().resolveAliases();
                    List<ProducedType> typeArgs =
                            inferTypeArguments(that, type, tas, qt);
                    if (qte.getPrimary() instanceof Tree.Package) {
                        visitBaseTypeExpression(qte, type, typeArgs, tas);
                    }
                    else {
                        visitQualifiedTypeExpression(qte, qt, type, typeArgs, tas);
                    }
                }
            }
           
            else if (term instanceof Tree.BaseMemberExpression) {
                Tree.BaseMemberExpression bme =
                        (Tree.BaseMemberExpression) term;
                TypedDeclaration base =
                        resolveBaseMemberExpression(bme, true);
                if (base!=null) {
                    List<ProducedType> typeArgs =
                            inferTypeArguments(that, base, tas, null);
                    visitBaseMemberExpression(bme, base, typeArgs, tas);
                }
            }
           
            else if (term instanceof Tree.QualifiedMemberExpression) {
                Tree.QualifiedMemberExpression qme =
                        (Tree.QualifiedMemberExpression) term;
                TypedDeclaration member =
                        resolveQualifiedMemberExpression(qme, true);
                if (member!=null) {
                    ProducedType qt =
                            qme.getPrimary().getTypeModel().resolveAliases();
                    List<ProducedType> typeArgs =
                            inferTypeArguments(that, member, tas, qt);
                    if (qme.getPrimary() instanceof Tree.Package) {
                        visitBaseMemberExpression(qme, member, typeArgs, tas);
                    }
                    else {
                        visitQualifiedMemberExpression(qme, qt, member, typeArgs, tas);
                    }
                }
            }
           
        }
        if (primary instanceof Tree.ExtendedTypeExpression) {
            visitExtendedTypePrimary((Tree.ExtendedTypeExpression) primary);
        }
    }

    private List<ProducedType> inferTypeArguments(
            Tree.InvocationExpression that, Declaration dec,
            Tree.TypeArguments tas, ProducedType qt) {
        List<ProducedType> typeArgs =
                tas instanceof Tree.InferredTypeArguments ?
                        getInferedTypeArguments(that, dec) :
                        getTypeArguments(tas, getTypeParameters(dec), qt);
        tas.setTypeModels(typeArgs);
        return typeArgs;
    }
   
    private List<ProducedType> getInferedTypeArguments(Tree.InvocationExpression that,
            Declaration dec) {
        if (dec instanceof Functional) {
            Functional fun = (Functional) dec;
            List<ProducedType> typeArgs = new ArrayList<ProducedType>();
            if (!fun.getParameterLists().isEmpty()) {
                ParameterList parameters = fun.getParameterLists().get(0);
                for (TypeParameter tp: fun.getTypeParameters()) {
                    ProducedType it = inferTypeArgument(that,
                            that.getPrimary().getTypeModel(),
                            tp, parameters);
                    if (it.containsUnknowns()) {
                        that.addError("could not infer type argument from given arguments: type parameter '" +
                                tp.getName() + "' could not be inferred");
                    }
                    else {
                        it = constrainInferredType(fun, tp, it);
                    }
                    typeArgs.add(it);
                }
            }
            return typeArgs;
        }
        else {
            return emptyList();
        }
    }

    private ProducedType constrainInferredType(Functional dec,
            TypeParameter tp, ProducedType ta) {
        //Note: according to the language spec we should only
        //      do this for contravariant  parameters, but in
        //      fact it also helps for special cases like
        //      emptyOrSingleton(null)
        List<ProducedType> list = new ArrayList<ProducedType>();
        addToIntersection(list, ta, unit);
        //Intersect the inferred type with any
        //upper bound constraints on the type.
        for (ProducedType st: tp.getSatisfiedTypes()) {
            //TODO: substitute in the other inferred type args
            //      of the invocation
            //TODO: st.getProducedType(receiver, dec, typeArgs);
            if (!st.containsTypeParameters()) {
                addToIntersection(list, st, unit);
            }
        }
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(list);
        ProducedType type = it.canonicalize().getType();
        return type;
    }

    private ProducedType inferTypeArgument(Tree.InvocationExpression that,
            ProducedReference pr, TypeParameter tp, ParameterList parameters) {
        List<ProducedType> inferredTypes = new ArrayList<ProducedType>();
        Tree.PositionalArgumentList pal = that.getPositionalArgumentList();
        Tree.NamedArgumentList nal = that.getNamedArgumentList();
        if (pal!=null) {
            inferTypeArgumentFromPositionalArgs(tp, parameters, pr,
                    pal, inferredTypes);
        }
        else if (nal!=null) {
            inferTypeArgumentFromNamedArgs(tp, parameters, pr,
                    nal, inferredTypes);
        }
        return formUnionOrIntersection(tp, inferredTypes);
    }

    private void inferTypeArgumentFromNamedArgs(TypeParameter tp,
            ParameterList parameters, ProducedReference pr,
            Tree.NamedArgumentList args,
            List<ProducedType> inferredTypes) {
        Set<Parameter> foundParameters = new HashSet<Parameter>();
        for (Tree.NamedArgument arg: args.getNamedArguments()) {
            inferTypeArgFromNamedArg(arg, tp, pr, parameters,
                    inferredTypes, foundParameters);
        }
        Parameter sp = getUnspecifiedParameter(null, parameters,
                foundParameters);
        if (sp!=null) {
          Tree.SequencedArgument sa = args.getSequencedArgument();
          inferTypeArgFromSequencedArg(sa, tp, sp, inferredTypes);
        }   
    }

    private void inferTypeArgFromSequencedArg(Tree.SequencedArgument sa,
            TypeParameter tp, Parameter sp,
            List<ProducedType> inferredTypes) {
      ProducedType att;
      if (sa==null) {
        att = unit.getEmptyDeclaration().getType();
      }
      else {
        List<Tree.PositionalArgument> args =
                sa.getPositionalArguments();
        att = getTupleType(args, unit, false);
      }
        ProducedType spt = sp.getType();
        addToUnionOrIntersection(tp, inferredTypes,
                inferTypeArg(tp, spt, att, true, false,
                        new ArrayList<TypeParameter>()));
    }

    private void inferTypeArgFromNamedArg(Tree.NamedArgument arg,
            TypeParameter tp, ProducedReference pr,
            ParameterList parameters,
            List<ProducedType> inferredTypes,
            Set<Parameter> foundParameters) {
        ProducedType type = null;
        if (arg instanceof Tree.SpecifiedArgument) {
            Tree.SpecifierExpression se =
                    ((Tree.SpecifiedArgument) arg).getSpecifierExpression();
            Tree.Expression e = se.getExpression();
            if (e!=null) {
                type = e.getTypeModel();
            }
        }
        else if (arg instanceof Tree.TypedArgument) {
            //copy/pasted from checkNamedArgument()
            Tree.TypedArgument ta = (Tree.TypedArgument) arg;
            type = ta.getDeclarationModel()
                .getTypedReference() //argument can't have type parameters
                .getFullType();
        }
        if (type!=null) {
            Parameter parameter =
                    getMatchingParameter(parameters, arg,
                            foundParameters);
            if (parameter!=null) {
                foundParameters.add(parameter);
                ProducedType pt = pr.getTypedParameter(parameter)
                        .getFullType();
//              if (parameter.isSequenced()) pt = unit.getIteratedType(pt);
                addToUnionOrIntersection(tp,inferredTypes,
                        inferTypeArg(tp, pt, type, true, false,
                                new ArrayList<TypeParameter>()));
            }
        }
    }

    private void inferTypeArgumentFromPositionalArgs(TypeParameter tp,
            ParameterList parameters, ProducedReference pr,
            Tree.PositionalArgumentList pal,
            List<ProducedType> inferredTypes) {
        List<Parameter> params = parameters.getParameters();
        for (int i=0; i<params.size(); i++) {
            Parameter parameter = params.get(i);
            List<Tree.PositionalArgument> args =
                    pal.getPositionalArguments();
            if (args.size()>i) {
                Tree.PositionalArgument a = args.get(i);
                ProducedType at = a.getTypeModel();
                if (a instanceof Tree.SpreadArgument) {
                    at = spreadType(at, unit, true);
                    List<Parameter> subList =
                            params.subList(i, params.size());
                    ProducedType ptt =
                            unit.getParameterTypesAsTupleType(subList, pr);
                    addToUnionOrIntersection(tp, inferredTypes,
                            inferTypeArg(tp, ptt, at, true, false,
                                    new ArrayList<TypeParameter>()));
                }
                else if (a instanceof Tree.Comprehension) {
                    if (parameter.isSequenced()) {
                        inferTypeArgFromComprehension(tp, parameter,
                                ((Tree.Comprehension) a),
                                inferredTypes);
                    }
                }
                else {
                    if (parameter.isSequenced()) {
                        inferTypeArgFromPositionalArgs(tp, parameter,
                                args.subList(i, args.size()),
                                inferredTypes);
                        break;
                    }
                    else {
                        ProducedType pt =
                                pr.getTypedParameter(parameter)
                                  .getFullType();
                        addToUnionOrIntersection(tp, inferredTypes,
                                inferTypeArg(tp, pt, at, true, false,
                                        new ArrayList<TypeParameter>()));
                    }
                }
            }
        }
    }

    private void inferTypeArgFromPositionalArgs(TypeParameter tp,
            Parameter parameter, List<Tree.PositionalArgument> args,
            List<ProducedType> inferredTypes) {
        for (int k=0; k<args.size(); k++) {
            Tree.PositionalArgument sa = args.get(k);
            ProducedType sat = sa.getTypeModel();
            if (sat!=null) {
                ProducedType pt = parameter.getType();
                if (sa instanceof Tree.SpreadArgument) {
                    sat = spreadType(sat, unit, true);
                    addToUnionOrIntersection(tp, inferredTypes,
                            inferTypeArg(tp, pt, sat, true, false,
                                    new ArrayList<TypeParameter>()));
                }
                else {
                    ProducedType spt = unit.getIteratedType(pt);
                    addToUnionOrIntersection(tp, inferredTypes,
                            inferTypeArg(tp, spt, sat, true, false,
                                    new ArrayList<TypeParameter>()));
                }
            }
        }
    }
   
    private void inferTypeArgFromComprehension(TypeParameter tp,
            Parameter parameter, Tree.Comprehension c,
            List<ProducedType> inferredTypes) {
            ProducedType sat = c.getTypeModel();
            if (sat!=null) {
                ProducedType pt = parameter.getType();
                ProducedType spt = unit.getIteratedType(pt);
                addToUnionOrIntersection(tp, inferredTypes,
                        inferTypeArg(tp, spt, sat, true, false,
                                new ArrayList<TypeParameter>()));
            }
    }
   
    private ProducedType formUnionOrIntersection(TypeParameter tp,
            List<ProducedType> inferredTypes) {
        if (tp.isContravariant()) {
            return formIntersection(inferredTypes);
        }
        else {
            return formUnion(inferredTypes);
        }
    }
   
    private ProducedType unionOrIntersection(TypeParameter tp,
            List<ProducedType> inferredTypes) {
        if (inferredTypes.isEmpty()) {
            return null;
        }
        else {
            return formUnionOrIntersection(tp, inferredTypes);
        }
    }
   
    private void addToUnionOrIntersection(TypeParameter tp,
            List<ProducedType> list, ProducedType pt) {
        if (tp.isContravariant()) {
            addToIntersection(list, pt, unit);
        }
        else {
            addToUnion(list, pt);
        }
    }

    private ProducedType union(List<ProducedType> types) {
        if (types.isEmpty()) {
            return null;
        }
        return formUnion(types);
    }

    private ProducedType intersection(List<ProducedType> types) {
        if (types.isEmpty()) {
            return null;
        }
        return formIntersection(types);
    }

    private ProducedType formUnion(List<ProducedType> types) {
        UnionType ut = new UnionType(unit);
        ut.setCaseTypes(types);
        return ut.getType();
    }
   
    private ProducedType formIntersection(List<ProducedType> types) {
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(types);
        return it.canonicalize().getType();
    }
   
    private ProducedType inferTypeArg(TypeParameter tp,
            ProducedType paramType,ProducedType argType,
            boolean covariant, boolean contravariant,
            List<TypeParameter> visited) {
        return inferTypeArg(tp, tp, paramType, argType,
                covariant, contravariant, visited);
    }
   
    private ProducedType inferTypeArg(TypeParameter tp,
            TypeParameter tp0,
            ProducedType paramType,ProducedType argType,
            boolean covariant, boolean contravariant,
            List<TypeParameter> visited) {
        if (paramType!=null && argType!=null) {
            paramType = paramType.resolveAliases();
            argType = argType.resolveAliases();
            TypeDeclaration paramTypeDec = paramType.getDeclaration();
            if (paramTypeDec instanceof TypeParameter &&
                    paramTypeDec.equals(tp)) {
                if (tp0.isContravariant() && covariant ||
                    tp0.isCovariant() && contravariant) {
                    return null;
                }
                else {
                    return unit.denotableType(argType);
                }
            }
            else if (paramTypeDec instanceof TypeParameter) {
                TypeParameter tp2 =
                        (TypeParameter) paramTypeDec;
                if (!visited.contains(tp2)) {
                    visited.add(tp2);
                    List<ProducedType> list = new ArrayList<ProducedType>();
                    for (ProducedType upperBound: tp2.getSatisfiedTypes()) {
                        addToUnionOrIntersection(tp, list,
                                inferTypeArg(tp, tp2, upperBound, argType,
                                        covariant, contravariant,
                                        visited));
                        ProducedType supertype =
                                argType.getSupertype(upperBound.getDeclaration());
                        if (supertype!=null) {
                            inferTypeArg(tp, tp2, paramType, supertype,
                                    covariant, contravariant,
                                    list, visited);
                        }
                    }
                    return unionOrIntersection(tp, list);
                }
                else {
                    return null;
                }
            }
            else if (paramTypeDec instanceof UnionType) {
                List<ProducedType> list = new ArrayList<ProducedType>();
                //If there is more than one type parameter in
                //the union, ignore this union when inferring
                //types.
                //TODO: This is all a bit adhoc. The problem is that
                //      when a parameter type involves a union of type
                //      parameters, it in theory imposes a compound
                //      constraint upon the type parameters, but our
                //      algorithm doesn't know how to deal with compound
                //      constraints
                /*ProducedType typeParamType = null;
                boolean found = false;
                for (ProducedType ct: paramType.getDeclaration().getCaseTypes()) {
                    TypeDeclaration ctd = ct.getDeclaration();
                    if (ctd instanceof TypeParameter) {
                        typeParamType = ct;
                    }
                    if (ct.containsTypeParameters()) { //TODO: check that they are "free" type params                       
                        if (found) {
                            //the parameter type involves two type
                            //parameters which are being inferred
                            return null;
                        }
                        else {
                            found = true;
                        }
                    }
                }*/
                ProducedType pt = paramType;
                ProducedType apt = argType;
                if (argType.getDeclaration() instanceof UnionType) {
                    for (ProducedType act: argType.getDeclaration().getCaseTypes()) {
                        //some element of the argument union is already a subtype
                        //of the parameter union, so throw it away from both unions
                        if (act.substitute(argType.getTypeArguments()).isSubtypeOf(paramType)) {
                            pt = pt.shallowMinus(act);
                            apt = apt.shallowMinus(act);
                        }
                    }
                }
                if (pt.getDeclaration() instanceof UnionType)  {
                    boolean found = false;
                  for (TypeDeclaration td: pt.getDeclaration().getCaseTypeDeclarations()) {
                    if (td instanceof TypeParameter) {
                      if (found) return null;
                      found = true;
                    }
                  }
                  //just one type parameter left in the union
                    for (ProducedType ct: pt.getDeclaration().getCaseTypes()) {
                      addToUnionOrIntersection(tp, list,
                              inferTypeArg(tp,
                                      ct.substitute(pt.getTypeArguments()), apt,
                                      covariant, contravariant,
                                      visited));
                    }
                }
                else {
                  addToUnionOrIntersection(tp, list,
                          inferTypeArg(tp, pt, apt,
                                  covariant, contravariant,
                                  visited));
                }
                return unionOrIntersection(tp, list);
                /*else {
                    //if the param type is of form T|A1 and the arg type is
                    //of form A2|B then constrain T by B and A1 by A2
                    ProducedType pt = paramType.minus(typeParamType);
                    addToUnionOrIntersection(tp, list, inferTypeArg(tp,
                            paramType.minus(pt), argType.minus(pt), visited));
                    addToUnionOrIntersection(tp, list, inferTypeArg(tp,
                            paramType.minus(typeParamType), pt, visited));
                    //return null;
                }*/
            }
            else if (paramTypeDec instanceof IntersectionType) {
                List<ProducedType> list = new ArrayList<ProducedType>();
                for (ProducedType ct: paramTypeDec.getSatisfiedTypes()) {
                    addToUnionOrIntersection(tp, list,
                            inferTypeArg(tp,
                                    ct.substitute(paramType.getTypeArguments()),
                                    argType,
                                    covariant, contravariant,
                                    visited));
                }
                return unionOrIntersection(tp,list);
            }
            else if (argType.getDeclaration() instanceof UnionType) {
                List<ProducedType> list = new ArrayList<ProducedType>();
                for (ProducedType ct: argType.getDeclaration().getCaseTypes()) {
                    addToUnion(list,
                            inferTypeArg(tp,
                                    paramType,
                                    ct.substitute(paramType.getTypeArguments()),
                                    covariant, contravariant,
                                    visited));
                }
                return union(list);
            }
            else if (argType.getDeclaration() instanceof IntersectionType) {
                List<ProducedType> list = new ArrayList<ProducedType>();
                for (ProducedType ct: argType.getDeclaration().getSatisfiedTypes()) {
                    addToIntersection(list,
                            inferTypeArg(tp,
                                    paramType,
                                    ct.substitute(paramType.getTypeArguments()),
                                    covariant, contravariant,
                                    visited), unit);
                }
                return intersection(list);
            }
            else {
                ProducedType supertype =
                        argType.getSupertype(paramTypeDec);
                if (supertype!=null) {
                    List<ProducedType> list = new ArrayList<ProducedType>();
                    if (paramType.getQualifyingType()!=null &&
                            supertype.getQualifyingType()!=null) {
                        addToUnionOrIntersection(tp, list,
                                inferTypeArg(tp,
                                        paramType.getQualifyingType(),
                                        supertype.getQualifyingType(),
                                        covariant, contravariant,
                                        visited));
                    }
                    inferTypeArg(tp, paramType, supertype,
                            covariant, contravariant,
                            list, visited);
                    return unionOrIntersection(tp, list);
                }
                else {
                    return null;
                }
            }
        }
        else {
            return null;
        }
    }

    private void inferTypeArg(TypeParameter tp,
            ProducedType paramType, ProducedType supertype,
            boolean covariant, boolean contravariant,
            List<ProducedType> list, List<TypeParameter> visited) {
        inferTypeArg(tp, tp, paramType, supertype,
                covariant, contravariant, list, visited);
    }
   
    private void inferTypeArg(TypeParameter tp, TypeParameter tp0,
            ProducedType paramType, ProducedType supertype,
            boolean covariant, boolean contravariant,
            List<ProducedType> list, List<TypeParameter> visited) {
        List<TypeParameter> typeParameters =
                paramType.getDeclaration().getTypeParameters();
        List<ProducedType> paramTypeArgs =
                paramType.getTypeArgumentList();
        List<ProducedType> superTypeArgs =
                supertype.getTypeArgumentList();
        for (int j=0;
                j<paramTypeArgs.size() &&
                j<superTypeArgs.size() &&
                j<typeParameters.size();
                j++) {
            ProducedType paramTypeArg = paramTypeArgs.get(j);
            ProducedType argTypeArg = superTypeArgs.get(j);
            TypeParameter typeParameter = typeParameters.get(j);
            boolean co;
            boolean contra;
            if (paramType.isCovariant(typeParameter)) {
                //leave them alone
                co = covariant;
                contra = contravariant;
            }
            else if (paramType.isContravariant(typeParameter)) {
                if (covariant|contravariant) {
                    //flip them
                    co = !covariant;
                    contra = !contravariant;
                }
                else {
                    //leave them invariant
                    co = false;
                    contra = false;
                }
            }
            else { //invariant
                co = false;
                contra = false;
            }
            addToUnionOrIntersection(tp, list,
                    inferTypeArg(tp, tp0,
                            paramTypeArg, argTypeArg,
                            co, contra,
                            visited));
        }
    }
   
    private void visitDirectInvocation(Tree.InvocationExpression that) {
        Tree.Term p = unwrapExpressionUntilTerm(that.getPrimary());
        Tree.MemberOrTypeExpression mte = (Tree.MemberOrTypeExpression) p;
        ProducedReference prf = mte.getTarget();
        Functional dec = (Functional) mte.getDeclaration();
        if (dec!=null) {
            if (!(p instanceof Tree.ExtendedTypeExpression)) {
                if (dec instanceof Class && ((Class) dec).isAbstract()) {
                    that.addError("abstract class may not be instantiated: '" +
                            dec.getName(unit) + "'");
                }
            }
            if (that.getNamedArgumentList()!=null &&
                    dec.isAbstraction()) {
                //TODO: this is not really right - it's the fact
                //      that we're calling Java and don't have
                //      meaningful parameter names that is the
                //      real problem, not the overload
                that.addError("overloaded declarations may not be called using named arguments: '" +
                        dec.getName(unit) + "'");
            }
            //that.setTypeModel(prf.getType());
            ProducedType ct = p.getTypeModel();
            if (ct!=null && !ct.getTypeArgumentList().isEmpty()) {
                //pull the return type out of the Callable
                that.setTypeModel(ct.getTypeArgumentList().get(0));
            }
            if (that.getNamedArgumentList() != null) {
                List<ParameterList> parameterLists = dec.getParameterLists();
                if (!parameterLists.isEmpty()
                        && !parameterLists.get(0).isNamedParametersSupported()) {
                    that.addError("named invocations of Java methods not supported");
                }
            }
            if (dec.isAbstraction()) {
                //nothing to check the argument types against
                //that.addError("no matching overloaded declaration");
            }
            else {
                //typecheck arguments using the parameter list
                //of the target declaration
                checkInvocationArguments(that, prf, dec);
            }
        }
    }

    private void visitIndirectInvocation(Tree.InvocationExpression that) {
       
        if (that.getNamedArgumentList()!=null) {
            that.addError("named arguments not supported for indirect invocations");
        }
        Tree.PositionalArgumentList pal = that.getPositionalArgumentList();
        if (pal==null) {
            return;
        }
       
        Tree.Primary p = that.getPrimary();
        ProducedType pt = p.getTypeModel();
        if (!isTypeUnknown(pt)) {
            if (checkCallable(pt, p, "invoked expression must be callable")) {
                List<ProducedType> typeArgs = pt.getSupertype(unit.getCallableDeclaration())
                        .getTypeArgumentList();
                if (!typeArgs.isEmpty()) {
                    that.setTypeModel(typeArgs.get(0));
                }
                //typecheck arguments using the type args of Callable
                if (typeArgs.size()>=2) {
                    ProducedType paramTypesAsTuple = typeArgs.get(1);
                    if (paramTypesAsTuple!=null) {
                        TypeDeclaration pttd = paramTypesAsTuple.getDeclaration();
                        if (pttd instanceof ClassOrInterface &&
                                (pttd.equals(unit.getTupleDeclaration()) ||
                                pttd.equals(unit.getSequenceDeclaration()) ||
                                pttd.equals(unit.getSequentialDeclaration()) ||
                                pttd.equals(unit.getEmptyDeclaration()))) {
                            //we have a plain tuple type so we can check the
                            //arguments individually
                            checkIndirectInvocationArguments(that, paramTypesAsTuple,
                                    unit.getTupleElementTypes(paramTypesAsTuple),
                                    unit.isTupleLengthUnbounded(paramTypesAsTuple),
                                    unit.isTupleVariantAtLeastOne(paramTypesAsTuple),
                                    unit.getTupleMinimumLength(paramTypesAsTuple));
                        }
                        else {
                            //we have something exotic, a union of tuple types
                            //or whatever, so just check the whole argument tuple
                            checkAssignable(getTupleType(pal.getPositionalArguments(), unit, false),
                                    paramTypesAsTuple, pal,
                                    "argument list type must be assignable to parameter list type");
                        }
                    }               
                }
            }
        }
    }
   
    private void checkInvocationArguments(Tree.InvocationExpression that,
            ProducedReference prf, Functional dec) {
        List<ParameterList> pls = dec.getParameterLists();
        if (pls.isEmpty()) {
            if (dec instanceof TypeDeclaration) {
                that.addError("type has no parameter list: '" +
                        dec.getName(unit) + "'");
            }
            else {
                that.addError("function has no parameter list: '" +
                        dec.getName(unit) + "'");
            }
        }
        else /*if (!dec.isOverloaded())*/ {
            ParameterList pl = pls.get(0);           
            Tree.PositionalArgumentList args = that.getPositionalArgumentList();
            if (args!=null) {
                checkPositionalArguments(pl, prf, args, that);
            }
            Tree.NamedArgumentList namedArgs = that.getNamedArgumentList();
            if (namedArgs!=null) {
                if (pl.isNamedParametersSupported()) {
                    namedArgs.getNamedArgumentList().setParameterList(pl);
                    checkNamedArguments(pl, prf, namedArgs);
                }
            }
        }
    }
   
    /*private void checkSpreadArgumentSequential(Tree.SpreadArgument arg,
            ProducedType argTuple) {
        if (!unit.isSequentialType(argTuple)) {
            arg.addError("spread argument expression is not sequential: " +
                    argTuple.getProducedTypeName(unit) + " is not a sequence type");
        }
    }*/
   
    private void checkNamedArguments(ParameterList pl, ProducedReference pr,
            Tree.NamedArgumentList nal) {
        List<Tree.NamedArgument> na = nal.getNamedArguments();       
        Set<Parameter> foundParameters = new HashSet<Parameter>();
       
        for (Tree.NamedArgument a: na) {
            checkNamedArg(a, pl, pr, foundParameters);
        }
       
        Tree.SequencedArgument sa = nal.getSequencedArgument();
        if (sa!=null) {
            checkSequencedArg(sa, pl, pr, foundParameters);       
        }
        else {
            Parameter sp = getUnspecifiedParameter(pr, pl, foundParameters);
            if (sp!=null && !unit.isNonemptyIterableType(sp.getType())) {
                foundParameters.add(sp);
            }
        }
           
        for (Parameter p: pl.getParameters()) {
            if (!foundParameters.contains(p) &&
                    !p.isDefaulted() && (!p.isSequenced() || p.isAtLeastOne())) {
                nal.addError("missing named argument to parameter '" +
                        p.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'");
            }
        }
    }
   
    private void checkSequencedArg(Tree.SequencedArgument sa, ParameterList pl,
            ProducedReference pr, Set<Parameter> foundParameters) {
        Parameter sp = getUnspecifiedParameter(pr, pl, foundParameters);
        if (sp==null) {
            sa.addError("all iterable parameters specified by named argument list: '" +
                    pr.getDeclaration().getName(unit) +
                    "' does not declare any additional parameters of type 'Iterable'");
        }
        else {
            if (!foundParameters.add(sp)) {
                sa.addError("duplicate argument for parameter: '" +
                        sp.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'");
            }
            else if (!dynamic && isTypeUnknown(sp.getType())) {
                sa.addError("parameter type could not be determined: '" +
                        sp.getName() + "' of '" + sp.getDeclaration().getName(unit) + "'");
            }
            checkSequencedArgument(sa, pr, sp);
        }
    }

    private void checkNamedArg(Tree.NamedArgument a, ParameterList pl,
            ProducedReference pr, Set<Parameter> foundParameters) {
        Parameter p = getMatchingParameter(pl, a, foundParameters);
        if (p==null) {
            if (a.getIdentifier()==null) {
                a.addError("all parameters specified by named argument list: '" +
                        pr.getDeclaration().getName(unit) +
                        "' does not declare any additional parameters");
            }
            else {
                a.addError("no matching parameter for named argument '" +
                        name(a.getIdentifier()) + "' declared by '" +
                        pr.getDeclaration().getName(unit) + "'", 101);
            }
        }
        else {
            if (!foundParameters.add(p)) {
                a.addError("duplicate argument for parameter: '" +
                        p.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'");
            }
            else if (!dynamic && isTypeUnknown(p.getType())) {
                a.addError("parameter type could not be determined: '" +
                        p.getName() + "' of '" + p.getDeclaration().getName(unit) + "'");
            }
            checkNamedArgument(a, pr, p);
            //hack in an identifier node just for the backend:
            //TODO: get rid of this nasty thing
            if (a.getIdentifier()==null) {
                Tree.Identifier node = new Tree.Identifier(null);
                node.setScope(a.getScope());
                node.setUnit(a.getUnit());
                node.setText(p.getName());
                a.setIdentifier(node);
            }
        }
    }

    private void checkNamedArgument(Tree.NamedArgument a, ProducedReference pr,
            Parameter p) {
        a.setParameter(p);
        ProducedType argType = null;
        if (a instanceof Tree.SpecifiedArgument) {
            Tree.SpecifiedArgument sa = (Tree.SpecifiedArgument) a;
            Tree.Expression e = sa.getSpecifierExpression().getExpression();
            if (e!=null) {
                argType = e.getTypeModel();
            }
        }
        else if (a instanceof Tree.TypedArgument) {
            Tree.TypedArgument ta = (Tree.TypedArgument) a;
            argType = ta.getDeclarationModel()
                .getTypedReference() //argument can't have type parameters
                .getFullType();
            checkArgumentToVoidParameter(p, ta);
            if (!dynamic && isTypeUnknown(argType)) {
                a.addError("could not determine type of named argument: '" + p.getName() + "'");
            }
        }
        ProducedType pt = pr.getTypedParameter(p).getFullType();
//      if (p.isSequenced()) pt = unit.getIteratedType(pt);
        if (!isTypeUnknown(argType) && !isTypeUnknown(pt)) {
            Node node;
            if (a instanceof Tree.SpecifiedArgument) {
                node = ((Tree.SpecifiedArgument) a).getSpecifierExpression();
            }
            else {
                node = a;
            }
            checkAssignable(argType, pt, node,
                    "named argument must be assignable to parameter '" +
                            p.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'" +
                            (pr.getQualifyingType()==null ? "" :
                                " in '" + pr.getQualifyingType().getProducedTypeName(unit) + "'"),
                            2100);
        }
    }

    private void checkArgumentToVoidParameter(Parameter p, Tree.TypedArgument ta) {
        if (ta instanceof Tree.MethodArgument) {
            Tree.MethodArgument ma = (Tree.MethodArgument) ta;
            Tree.SpecifierExpression se = ma.getSpecifierExpression();
            if (se!=null && se.getExpression()!=null) {
                Tree.Type t = ta.getType();
                // if the argument is explicitly declared
                // using the function modifier, it should
                // be allowed, even if the parameter is
                // declared void
                //TODO: what is a better way to check that
                //      this is really the "shortcut" form
                if (t instanceof Tree.FunctionModifier &&
                        t.getToken()==null &&
                    p.isDeclaredVoid() &&
                    !isSatementExpression(se.getExpression())) {
                    ta.addError("functional parameter is declared void so argument may not evaluate to a value: '" +
                            p.getName() + "' is declared 'void'");
                }
            }
        }
    }
   
    private void checkSequencedArgument(Tree.SequencedArgument sa, ProducedReference pr,
            Parameter p) {
        sa.setParameter(p);
        List<Tree.PositionalArgument> args = sa.getPositionalArguments();
        ProducedType paramType = pr.getTypedParameter(p).getFullType();
        ProducedType att = getTupleType(args, unit, false)
                .getSupertype(unit.getIterableDeclaration());
        if (!isTypeUnknown(att) && !isTypeUnknown(paramType)) {
            checkAssignable(att, paramType, sa,
                    "iterable arguments must be assignable to iterable parameter " +
                            p.getName() + " of " + pr.getDeclaration().getName(unit) +
                            (pr.getQualifyingType()==null ? "" :
                                " in '" + pr.getQualifyingType().getProducedTypeName(unit)) + "'");
        }
    }
   
    private Parameter getMatchingParameter(ParameterList pl, Tree.NamedArgument na,
            Set<Parameter> foundParameters) {
        Tree.Identifier id = na.getIdentifier();
        if (id==null) {
            for (Parameter p: pl.getParameters()) {
                if (!foundParameters.contains(p)) {
                    return p;
                }
            }
        }
        else {
            String name = name(id);
            for (Parameter p: pl.getParameters()) {
                if (p.getName()!=null &&
                        p.getName().equals(name)) {
                    return p;
                }
            }
        }
        return null;
    }

    private Parameter getUnspecifiedParameter(ProducedReference pr,
            ParameterList pl, Set<Parameter> foundParameters) {
        for (Parameter p: pl.getParameters()) {
            ProducedType t = pr==null ?
                    p.getType() :
                    pr.getTypedParameter(p).getFullType();
            if (t!=null) {
                t = t.resolveAliases();
                if (!foundParameters.contains(p) &&
                    unit.isIterableParameterType(t)) {
                    return p;
                }
            }
        }
        return null;
    }
   
    private void checkPositionalArguments(ParameterList pl, ProducedReference pr,
            Tree.PositionalArgumentList pal, Tree.InvocationExpression that) {
        List<Tree.PositionalArgument> args = pal.getPositionalArguments();
        List<Parameter> params = pl.getParameters();
        for (int i=0; i<params.size(); i++) {
            Parameter p = params.get(i);
            if (i>=args.size()) {
                if (!p.isDefaulted() && (!p.isSequenced() || p.isAtLeastOne())) {
                    Node n = that instanceof Tree.Annotation && args.isEmpty() ? that : pal;
                    n.addError("missing argument to required parameter '" +
                            p.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'");
                }
            }
            else {
                Tree.PositionalArgument a = args.get(i);
                if (!dynamic && isTypeUnknown(p.getType())) {
                    a.addError("parameter type could not be determined: '" +
                            p.getName() + "' of '" + p.getDeclaration().getName(unit) + "'");
                }
                if (a instanceof Tree.SpreadArgument) {
                    checkSpreadArgument(pr, p, a,
                            (Tree.SpreadArgument) a,
                            params.subList(i, params.size()));
                    break;
                }
                else if (a instanceof Tree.Comprehension) {
                    if (p.isSequenced()) {
                        checkComprehensionPositionalArgument(p, pr,
                                (Tree.Comprehension) a, p.isAtLeastOne());
                    }
                    else {
                        a.addError("not a variadic parameter: parameter '" +
                                p.getName() + "' of '" + pr.getDeclaration().getName() + "'");
                    }
                    break;
                }
                else {
                    if (p.isSequenced()) {
                        checkSequencedPositionalArgument(p, pr,
                                args.subList(i, args.size()));
                        return; //Note: early return!
                    }
                    else {
                        checkPositionalArgument(p, pr, (Tree.ListedArgument) a);
                    }
                }
            }
        }
       
        for (int i=params.size(); i<args.size(); i++) {
            Tree.PositionalArgument arg = args.get(i);
            if (arg instanceof Tree.SpreadArgument) {
                if (unit.isEmptyType(arg.getTypeModel())) {
                    continue;
                }
            }
            arg.addError("no matching parameter declared by '" +
                    pr.getDeclaration().getName(unit) + "': '" +
                    pr.getDeclaration().getName(unit) + "' has " +
                    params.size() + " parameters", 2000);
        }
   
    }

    private void checkSpreadArgument(ProducedReference pr, Parameter p,
            Tree.PositionalArgument a, Tree.SpreadArgument arg,
            List<Parameter> psl) {
        a.setParameter(p);
        ProducedType rat = arg.getTypeModel();
        if (!isTypeUnknown(rat)) {
            if (!unit.isIterableType(rat)) {
                //note: check already done by visit(SpreadArgument)
                /*arg.addError("spread argument is not iterable: " +
                        rat.getProducedTypeName(unit) +
                        " is not a subtype of Iterable");*/
            }
            else {
                ProducedType at = spreadType(rat, unit, true);
                //checkSpreadArgumentSequential(arg, at);
                ProducedType ptt = unit.getParameterTypesAsTupleType(psl, pr);
                if (!isTypeUnknown(at) && !isTypeUnknown(ptt)) {
                    checkAssignable(at, ptt, arg,
                            "spread argument not assignable to parameter types");
                }
            }
        }
    }
   
    private void checkIndirectInvocationArguments(Tree.InvocationExpression that,
            ProducedType paramTypesAsTuple, List<ProducedType> paramTypes,
            boolean sequenced, boolean atLeastOne, int firstDefaulted) {
       
        Tree.PositionalArgumentList pal = that.getPositionalArgumentList();
        List<Tree.PositionalArgument> args = pal.getPositionalArguments();
       
        for (int i=0; i<paramTypes.size(); i++) {
            if (isTypeUnknown(paramTypes.get(i))) {
                that.addError("parameter types cannot be determined from function reference");
                return;
            }
        }
       
        for (int i=0; i<paramTypes.size(); i++) {
            if (i>=args.size()) {
                if (i<firstDefaulted && (!sequenced || atLeastOne || i!=paramTypes.size()-1)) {
                    pal.addError("missing argument for required parameter " + i);
                }
            }
            else {
                Tree.PositionalArgument arg = args.get(i);
                ProducedType at = arg.getTypeModel();
                if (arg instanceof Tree.SpreadArgument) {
                    int fd = firstDefaulted<0?-1:firstDefaulted<i?0:firstDefaulted-i;
                    checkSpreadIndirectArgument((Tree.SpreadArgument) arg,
                            paramTypes.subList(i, paramTypes.size()),
                            sequenced, atLeastOne, fd, at);
                    break;
                }
                else if (arg instanceof Tree.Comprehension) {
                    ProducedType paramType = paramTypes.get(i);
                    if (sequenced && i==paramTypes.size()-1) {
                        checkComprehensionIndirectArgument((Tree.Comprehension) arg,
                            paramType, atLeastOne);
                    }
                    else {
                        arg.addError("not a variadic parameter: parameter " + i);
                    }
                    break;
                }
                else {
                    ProducedType paramType = paramTypes.get(i);
                    if (sequenced && i==paramTypes.size()-1) {
                        checkSequencedIndirectArgument(args.subList(i, args.size()),
                                paramType);
                        return; //Note: early return!
                    }
                    else if (at!=null && paramType!=null &&
                            !isTypeUnknown(at) && !isTypeUnknown(paramType)) {
                        checkAssignable(at, paramType, arg,
                                "argument must be assignable to parameter type");
                    }
                }
            }
        }
       
        for (int i=paramTypes.size(); i<args.size(); i++) {
            Tree.PositionalArgument arg = args.get(i);
            if (arg instanceof Tree.SpreadArgument) {
                if (unit.isEmptyType(arg.getTypeModel())) {
                    continue;
                }
            }
            arg.addError("no matching parameter: function reference has " +
                    paramTypes.size() + " parameters", 2000);
        }
       
    }

    private void checkSpreadIndirectArgument(Tree.SpreadArgument sa,
            List<ProducedType> psl, boolean sequenced,
            boolean atLeastOne, int firstDefaulted, ProducedType at) {
        //checkSpreadArgumentSequential(sa, at);
        if (!isTypeUnknown(at)) {
            if (!unit.isIterableType(at)) {
              //note: check already done by visit(SpreadArgument)
                /*sa.addError("spread argument is not iterable: " +
                        at.getProducedTypeName(unit) +
                        " is not a subtype of Iterable");*/
            }
            else {
                ProducedType sat = spreadType(at, unit, true);
                //TODO: this ultimately repackages the parameter
                //      information as a tuple - it would be
                //      better to just truncate the original
                //      tuple type we started with
                List<ProducedType> pts = new ArrayList<ProducedType>(psl);
                if (sequenced) {
                    pts.set(pts.size()-1,
                            unit.getIteratedType(pts.get(pts.size()-1)));
                }
                ProducedType ptt = unit.getTupleType(pts, sequenced, atLeastOne,
                        firstDefaulted);
                if (!isTypeUnknown(sat) && !isTypeUnknown(ptt)) {
                    checkAssignable(sat, ptt, sa,
                            "spread argument not assignable to parameter types");
                }
            }
        }
    }

    private void checkSequencedIndirectArgument(List<Tree.PositionalArgument> args,
            ProducedType paramType) {
        ProducedType set = paramType==null ? null : unit.getIteratedType(paramType);
        for (int j=0; j<args.size(); j++) {
            Tree.PositionalArgument a = args.get(j);
            ProducedType at = a.getTypeModel();
            if (!isTypeUnknown(at) && !isTypeUnknown(paramType)) {
                if (a instanceof Tree.SpreadArgument) {
                    at = spreadType(at, unit, true);
                    checkAssignable(at, paramType, a,
                            "spread argument must be assignable to variadic parameter",
                                    2101);
                }
                else {
                    checkAssignable(at, set, a,
                            "argument must be assignable to variadic parameter",
                                    2101);
                    //if we already have an arg to a nonempty variadic parameter,
                    //we can treat it like a possibly-empty variadic now
                    paramType = paramType.getSupertype(unit.getSequentialDeclaration());
                }
            }
        }
    }
   
    private void checkComprehensionIndirectArgument(Tree.Comprehension c,
            ProducedType paramType, boolean atLeastOne) {
        Tree.InitialComprehensionClause icc = ((Tree.Comprehension) c).getInitialComprehensionClause();
        if (icc.getPossiblyEmpty() && atLeastOne) {
            c.addError("variadic parameter is required but comprehension is possibly empty");
        }
        ProducedType at = c.getTypeModel();
        ProducedType set = paramType==null ? null : unit.getIteratedType(paramType);
        if (!isTypeUnknown(at) && !isTypeUnknown(set)) {
            checkAssignable(at, set, c,
                    "argument must be assignable to variadic parameter");
        }
    }
   
    private void checkSequencedPositionalArgument(Parameter p, ProducedReference pr,
            List<Tree.PositionalArgument> args) {
        ProducedType paramType = pr.getTypedParameter(p).getFullType();
        ProducedType set = paramType==null ? null : unit.getIteratedType(paramType);
        for (int j=0; j<args.size(); j++) {
            Tree.PositionalArgument a = args.get(j);
            a.setParameter(p);
            ProducedType at = a.getTypeModel();
            if (!isTypeUnknown(at) && !isTypeUnknown(paramType)) {
                if (a instanceof Tree.SpreadArgument) {
                    at = spreadType(at, unit, true);
                    checkAssignable(at, paramType, a,
                            "spread argument must be assignable to variadic parameter " +
                                    p.getName()+ " of " + pr.getDeclaration().getName(unit) +
                                    (pr.getQualifyingType()==null ? "" :
                                        " in '" + pr.getQualifyingType().getProducedTypeName(unit)) + "'",
                                    2101);
                }
                else {
                    checkAssignable(at, set, a,
                            "argument must be assignable to variadic parameter " +
                                    p.getName()+ " of " + pr.getDeclaration().getName(unit) +
                                    (pr.getQualifyingType()==null ? "" :
                                        " in '" + pr.getQualifyingType().getProducedTypeName(unit)) + "'",
                                    2101);
                    //if we already have an arg to a nonempty variadic parameter,
                    //we can treat it like a possibly-empty variadic now
                    paramType = paramType.getSupertype(unit.getSequentialDeclaration());
                }
            }
        }
    }
   
    private void checkComprehensionPositionalArgument(Parameter p, ProducedReference pr,
            Tree.Comprehension c, boolean atLeastOne) {
        Tree.InitialComprehensionClause icc = ((Tree.Comprehension) c).getInitialComprehensionClause();
        if (icc.getPossiblyEmpty() && atLeastOne) {
            c.addError("variadic parameter is required but comprehension is possibly empty");
        }
        ProducedType paramType = pr.getTypedParameter(p).getFullType();
        c.setParameter(p);
        ProducedType at = c.getTypeModel();
        if (!isTypeUnknown(at) && !isTypeUnknown(paramType)) {
            ProducedType set = paramType==null ? null : unit.getIteratedType(paramType);
            checkAssignable(at, set, c,
                    "argument must be assignable to variadic parameter '" +
                            p.getName() + "' of '" + pr.getDeclaration().getName(unit) +
                            (pr.getQualifyingType()==null ? "'" :
                                "' in '" + pr.getQualifyingType().getProducedTypeName(unit)) + "'",
                            2101);
        }
    }
   
    private boolean hasSpreadArgument(List<Tree.PositionalArgument> args) {
        int size = args.size();
        if (size>0) {
            return args.get(size-1) instanceof Tree.SpreadArgument;
        }
        else {
            return false;
        }
    }

    private void checkPositionalArgument(Parameter p, ProducedReference pr,
            Tree.ListedArgument a) {
        ProducedType paramType = pr.getTypedParameter(p).getFullType();
        a.setParameter(p);
        ProducedType at = a.getTypeModel();
        if (!isTypeUnknown(at) && !isTypeUnknown(paramType)) {
            checkAssignable(at, paramType, a,
                    "argument must be assignable to parameter '" +
                            p.getName() + "' of '" + pr.getDeclaration().getName(unit) + "'" +
                            (pr.getQualifyingType()==null ? "" :
                                " in '" + pr.getQualifyingType().getProducedTypeName(unit) + "'"),
                            2100);
        }
    }
   
    @Override public void visit(Tree.Comprehension that) {
        super.visit(that);
        that.setTypeModel(that.getInitialComprehensionClause().getTypeModel());
    }
   
    @Override public void visit(Tree.SpreadType that) {
        super.visit(that);
        Tree.Type t = that.getType();
        if (t!=null) {
            checkAssignable(that.getTypeModel(),
                    unit.getSequentialType(unit.getType(unit.getAnythingDeclaration())),
                    t, "spread type must be a sequence type");
        }
    }

    @Override public void visit(Tree.SpreadArgument that) {
        super.visit(that);
        Tree.Expression e = that.getExpression();
        if (e!=null) {
            ProducedType t = e.getTypeModel();
            if (t!=null) {
                if (!isTypeUnknown(t)) {
                    if (!unit.isIterableType(t)) {
                        e.addError("spread argument is not iterable: '" +
                                t.getProducedTypeName(unit) +
                                "' is not a subtype of 'Iterable'");
                    }
                }
                that.setTypeModel(t);
            }
        }
    }
   
    @Override public void visit(Tree.ListedArgument that) {
        super.visit(that);
        if (that.getExpression()!=null) {
            that.setTypeModel(that.getExpression().getTypeModel());
        }
    }
   
    private boolean involvesUnknownTypes(Tree.ElementOrRange eor) {
        if (eor instanceof Tree.Element) {
            return isTypeUnknown(((Tree.Element) eor).getExpression().getTypeModel());
        }
        else {
            Tree.ElementRange er = (Tree.ElementRange) eor;
            return er.getLowerBound()!=null && isTypeUnknown(er.getLowerBound().getTypeModel()) ||
                    er.getUpperBound()!=null && isTypeUnknown(er.getUpperBound().getTypeModel());
        }
    }
   
    @Override public void visit(Tree.IndexExpression that) {
        super.visit(that);
        ProducedType pt = type(that);
        if (pt==null) {
            that.addError("could not determine type of receiver");
        }
        else {
            /*if (that.getIndexOperator() instanceof Tree.SafeIndexOp) {
                if (unit.isOptionalType(pt)) {
                    pt = unit.getDefiniteType(pt);
                }
                else {
                    that.getPrimary().addError("receiving type not of optional type: " +
                            pt.getDeclaration().getName(unit) + " is not a subtype of Optional");
                }
            }*/
            if (that.getElementOrRange()==null) {
                that.addError("malformed index expression");
            }
            else if (!isTypeUnknown(pt) &&
                     !involvesUnknownTypes(that.getElementOrRange())) {
                if (that.getElementOrRange() instanceof Tree.Element) {
                    ProducedType cst = pt.getSupertype(unit.getCorrespondenceDeclaration());
                    if (cst==null) {
                        that.getPrimary().addError("illegal receiving type for index expression: '" +
                                pt.getDeclaration().getName(unit) + "' is not a subtype of 'Correspondence'");
                    }
                    else {
                        List<ProducedType> args = cst.getTypeArgumentList();
                        ProducedType kt = args.get(0);
                        ProducedType vt = args.get(1);
                        Tree.Element e = (Tree.Element) that.getElementOrRange();
                        checkAssignable(e.getExpression().getTypeModel(), kt,
                                e.getExpression(),
                                "index must be assignable to key type");
                        ProducedType rt = unit.getOptionalType(vt);
                        that.setTypeModel(rt);
                        Tree.Term t = e.getExpression().getTerm();
                        //TODO: in theory we could do a whole lot
                        //      more static-execution of the
                        //      expression, but this seems
                        //      perfectly sufficient
                        refineTypeForTupleElement(that, pt, t);
                    }
                }
                else {
                    ProducedType rst = pt.getSupertype(unit.getRangedDeclaration());
                    if (rst==null) {
                        that.getPrimary().addError("illegal receiving type for index range expression: '" +
                                pt.getDeclaration().getName(unit) + "' is not a subtype of 'Ranged'");
                    }
                    else {
                        List<ProducedType> args = rst.getTypeArgumentList();
                        ProducedType kt = args.get(0);
                        ProducedType rt = args.get(2);
                        Tree.ElementRange er = (Tree.ElementRange) that.getElementOrRange();
                        if (er.getLowerBound()!=null) {
                            checkAssignable(er.getLowerBound().getTypeModel(), kt,
                                    er.getLowerBound(),
                                    "lower bound must be assignable to index type");
                        }
                        if (er.getUpperBound()!=null) {
                            checkAssignable(er.getUpperBound().getTypeModel(), kt,
                                    er.getUpperBound(),
                                    "upper bound must be assignable to index type");
                        }
                        if (er.getLength()!=null) {
                            checkAssignable(er.getLength().getTypeModel(),
                                    unit.getType(unit.getIntegerDeclaration()),
                                    er.getLength(),
                                    "length must be an integer");
                        }
                        that.setTypeModel(rt);
//                        if (er.getLowerBound()!=null && er.getUpperBound()!=null) {
//                            refineTypeForTupleRange(that, pt,
//                                    er.getLowerBound().getTerm(),
//                                    er.getUpperBound().getTerm());
//                        }
//                        else if (er.getLowerBound()!=null) {
                        if (er.getLowerBound()!=null &&
                                er.getUpperBound()==null &&
                                er.getLength()==null) {
                            refineTypeForTupleOpenRange(that, pt,
                                    er.getLowerBound().getTerm());
                        }
                        /*if (that.getIndexOperator() instanceof Tree.SafeIndexOp) {
                            that.setTypeModel(unit.getOptionalType(that.getTypeModel()));
                        }*/
                    }
                }
            }
        }
    }

    private void refineTypeForTupleElement(Tree.IndexExpression that,
            ProducedType pt, Tree.Term t) {
        boolean negated = false;
        if (t instanceof Tree.NegativeOp) {
            t = ((Tree.NegativeOp) t).getTerm();
            negated = true;
        }
        else if (t instanceof Tree.PositiveOp) {
            t = ((Tree.PositiveOp) t).getTerm();
        }
        ProducedType tt = pt;
        if (unit.isSequentialType(tt)) {
            if (t instanceof Tree.NaturalLiteral) {
                int index = Integer.parseInt(t.getText());
                if (negated) index = -index;
                List<ProducedType> elementTypes = unit.getTupleElementTypes(tt);
                boolean variadic = unit.isTupleLengthUnbounded(tt);
                int minimumLength = unit.getTupleMinimumLength(tt);
                boolean atLeastOne = unit.isTupleVariantAtLeastOne(tt);
                if (elementTypes!=null) {
                    if (elementTypes.isEmpty()) {
                        that.setTypeModel(unit.getType(unit.getNullDeclaration()));
                    }
                    else if (index<0) {
                        that.setTypeModel(unit.getType(unit.getNullDeclaration()));
                    }
                    else if (index<elementTypes.size()-(variadic?1:0)) {
                        ProducedType iet = elementTypes.get(index);
                        if (iet==null) return;
                        if (index>=minimumLength) {
                            iet = unionType(iet,
                                    unit.getType(unit.getNullDeclaration()),
                                    unit);
                        }
                        that.setTypeModel(iet);
                    }
                    else if (variadic) {
                        ProducedType iet = elementTypes.get(elementTypes.size()-1);
                        if (iet==null) return;
                        ProducedType it = unit.getIteratedType(iet);
                        if (it==null) return;
                        if (!atLeastOne || index>=elementTypes.size()) {
                            it = unionType(it,
                                    unit.getType(unit.getNullDeclaration()),
                                    unit);
                        }
                        that.setTypeModel(it);
                    }
                    else {
                        that.setTypeModel(unit.getType(unit.getNullDeclaration()));
                    }
                }
            }
        }
    }
   
    private void refineTypeForTupleOpenRange(Tree.IndexExpression that,
            ProducedType pt, Tree.Term l) {
        boolean lnegated = false;
        if (l instanceof Tree.NegativeOp) {
            l = ((Tree.NegativeOp) l).getTerm();
            lnegated = true;
        }
        else if (l instanceof Tree.PositiveOp) {
            l = ((Tree.PositiveOp) l).getTerm();
        }
        ProducedType tt = pt;
        if (unit.isSequentialType(tt)) {
            if (l instanceof Tree.NaturalLiteral) {
                int lindex = Integer.parseInt(l.getText());
                if (lnegated) lindex = -lindex;
                List<ProducedType> elementTypes = unit.getTupleElementTypes(tt);
                boolean variadic = unit.isTupleLengthUnbounded(tt);
                boolean atLeastOne = unit.isTupleVariantAtLeastOne(tt);
                int minimumLength = unit.getTupleMinimumLength(tt);
                List<ProducedType> list = new ArrayList<ProducedType>();
                if (elementTypes!=null) {
                    if (lindex<0) {
                        lindex=0;
                    }
                    for (int index=lindex;
                            index<elementTypes.size()-(variadic?1:0);
                            index++) {
                        ProducedType et = elementTypes.get(index);
                        if (et==null) return;
                        list.add(et);
                    }
                    if (variadic) {
                        ProducedType it = elementTypes.get(elementTypes.size()-1);
                        if (it==null) return;
                        ProducedType rt = unit.getIteratedType(it);
                        if (rt==null) return;
                        list.add(rt);
                    }
                    ProducedType rt = unit.getTupleType(list, variadic,
                            atLeastOne && lindex<elementTypes.size(),
                            minimumLength-lindex);
                    //intersect with the type determined using
                    //Ranged, which may be narrower, for example,
                    //for String
                    that.setTypeModel(intersectionType(rt,
                            that.getTypeModel(), unit));
                }
            }
        }
    }

    private ProducedType type(Tree.PostfixExpression that) {
        Tree.Primary p = that.getPrimary();
        return p==null ? null : p.getTypeModel();
    }
   
    private void assign(Tree.Term term) {
        if (term instanceof Tree.MemberOrTypeExpression) {
            Tree.MemberOrTypeExpression m =
                    (Tree.MemberOrTypeExpression) term;
            m.setAssigned(true);
        }
    }
   
    @Override public void visit(Tree.PostfixOperatorExpression that) {
        assign(that.getTerm());
        super.visit(that);
        ProducedType type = type(that);
        visitIncrementDecrement(that, type, that.getTerm());
        checkAssignability(that.getTerm(), that);
    }

    @Override public void visit(Tree.PrefixOperatorExpression that) {
        assign(that.getTerm());
        super.visit(that);
        ProducedType type = type(that);
        if (that.getTerm()!=null) {
            visitIncrementDecrement(that, type, that.getTerm());
            checkAssignability(that.getTerm(), that);
        }
    }
   
    private void visitIncrementDecrement(Tree.Term that,
            ProducedType pt, Tree.Term term) {
        if (!isTypeUnknown(pt)) {
            ProducedType ot = checkSupertype(pt, unit.getOrdinalDeclaration(),
                    term, "operand expression must be of enumerable type");
            if (ot!=null) {
                ProducedType ta = ot.getTypeArgumentList().get(0);
                checkAssignable(ta, pt, that,
                        "result type must be assignable to declared type");
            }
            that.setTypeModel(pt);
        }
    }
   
    /*@Override public void visit(Tree.SumOp that) {
        super.visit( (Tree.BinaryOperatorExpression) that );
        ProducedType lhst = leftType(that);
        if (lhst!=null) {
            //take into account overloading of + operator
            if (lhst.isSubtypeOf(getStringDeclaration().getType())) {
                visitBinaryOperator(that, getStringDeclaration());
            }
            else {
                visitBinaryOperator(that, getNumericDeclaration());
            }
        }
    }*/

    private void visitScaleOperator(Tree.ScaleOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            TypeDeclaration sd = unit.getScalableDeclaration();
            ProducedType st = checkSupertype(rhst, sd, that,
                    "right operand must be of scalable type");
            if (st!=null) {
                ProducedType ta = st.getTypeArgumentList().get(0);
                ProducedType rt = st.getTypeArgumentList().get(1);
                //hardcoded implicit type conversion Integer->Float
                TypeDeclaration fd = unit.getFloatDeclaration();
                TypeDeclaration id = unit.getIntegerDeclaration();
                if (lhst.getDeclaration().inherits(id) &&
                        ta.getDeclaration().inherits(fd)) {
                    lhst = fd.getType();
                }
                checkAssignable(lhst, ta, that,
                        "scale factor must be assignable to scale type");
                that.setTypeModel(rt);
            }
        }
    }
   
    private void checkComparable(Tree.BinaryOperatorExpression that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            checkOperandTypes(lhst, rhst, unit.getComparableDeclaration(), that,
                    "operand expressions must be comparable");
        }
    }
   
    private void visitComparisonOperator(Tree.BinaryOperatorExpression that) {
        checkComparable(that);
        that.setTypeModel( unit.getType(unit.getBooleanDeclaration()) );           
    }

    private void visitCompareOperator(Tree.CompareOp that) {
        checkComparable(that);
        that.setTypeModel( unit.getType(unit.getComparisonDeclaration()) );           
    }
   
    private void visitWithinOperator(Tree.WithinOp that) {
        Tree.Term lbt = that.getLowerBound().getTerm();
        Tree.Term ubt = that.getUpperBound().getTerm();
        ProducedType lhst = lbt==null ? null : lbt.getTypeModel();
        ProducedType rhst = ubt==null ? null : ubt.getTypeModel();
        ProducedType t = that.getTerm().getTypeModel();
        if (!isTypeUnknown(t) &&
            !isTypeUnknown(lhst) && !isTypeUnknown(rhst)) {
            checkOperandTypes(t, lhst, rhst, unit.getComparableDeclaration(),
                    that, "operand expressions must be comparable");
        }
        that.setTypeModel( unit.getType(unit.getBooleanDeclaration()) );           
    }

    private void visitSpanOperator(Tree.RangeOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        ProducedType ot = checkOperandTypes(lhst, rhst,
                unit.getEnumerableDeclaration(), that,
                "operand expressions must be of compatible enumerable type");
        if (ot!=null) {
            that.setTypeModel(unit.getSpanType(ot));
        }
    }
   
    private void visitMeasureOperator(Tree.SegmentOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        ProducedType ot = checkSupertype(lhst, unit.getEnumerableDeclaration(),
                that.getLeftTerm(), "left operand must be of enumerable type");
        if (!isTypeUnknown(rhst)) {
            checkAssignable(rhst, unit.getType(unit.getIntegerDeclaration()),
                    that.getRightTerm(), "right operand must be an integer");
        }
        if (ot!=null) {
            ProducedType ta = ot.getTypeArgumentList().get(0);
            that.setTypeModel(unit.getMeasureType(ta));
        }
    }
   
    private void visitEntryOperator(Tree.EntryOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        ProducedType ot = unit.getType(unit.getObjectDeclaration());
        checkAssignable(lhst, ot, that.getLeftTerm(),
                "operand expression must not be an optional type");
//        checkAssignable(rhst, ot, that.getRightTerm(),
//                "operand expression must not be an optional type");
        that.setTypeModel( unit.getEntryType(unit.denotableType(lhst),
                unit.denotableType(rhst)) );
    }
   
    private void visitIdentityOperator(Tree.BinaryOperatorExpression that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            TypeDeclaration id = unit.getIdentifiableDeclaration();
            checkAssignable(lhst, id.getType(), that.getLeftTerm(),
                    "operand expression must be of type 'Identifiable'");
            checkAssignable(rhst, id.getType(), that.getRightTerm(),
                    "operand expression must be of type 'Identifiable'");
            if (intersectionType(lhst, rhst, unit).isNothing()) {
                that.addError("values of disjoint types are never identical: '" +
                        lhst.getProducedTypeName(unit) +
                        "' has empty intersection with '" +
                        rhst.getProducedTypeName(unit) + "'");
            }
        }
        that.setTypeModel(unit.getType(unit.getBooleanDeclaration()));
    }
   
    private void visitEqualityOperator(Tree.BinaryOperatorExpression that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            TypeDeclaration od = unit.getObjectDeclaration();
            checkAssignable(lhst, od.getType(), that.getLeftTerm(),
                    "operand expression must be of type Object");
            checkAssignable(rhst, od.getType(), that.getRightTerm(),
                    "operand expression must be of type Object");
        }
        that.setTypeModel(unit.getType(unit.getBooleanDeclaration()));
    }
   
    private void visitAssignOperator(Tree.AssignOp that) {
        ProducedType rhst = rightType(that);
        ProducedType lhst = leftType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            ProducedType leftHandType = lhst;
            // allow assigning null to java properties that could after all be null
            if (hasUncheckedNulls(that.getLeftTerm()))
                leftHandType = unit.getOptionalType(leftHandType);
            checkAssignable(rhst, leftHandType, that.getRightTerm(),
                    "assigned expression must be assignable to declared type",
                    2100);
        }
        that.setTypeModel(rhst);
//      that.setTypeModel(lhst); //this version is easier on backend
    }

    private ProducedType checkOperandTypes(ProducedType lhst, ProducedType rhst,
            TypeDeclaration td, Node node, String message) {
        ProducedType lhsst = checkSupertype(lhst, td, node, message);
        if (lhsst!=null) {
            ProducedType at = lhsst.getTypeArgumentList().get(0);
            checkAssignable(rhst, at, node, message);
            return at;
        }
        else {
            return null;
        }
    }
   
    private ProducedType checkOperandTypes(ProducedType t,
            ProducedType lhst, ProducedType rhst,
            TypeDeclaration td, Node node, String message) {
        ProducedType st = checkSupertype(t, td, node, message);
        if (st!=null) {
            ProducedType at = st.getTypeArgumentList().get(0);
            checkAssignable(lhst, at, node, message);
            checkAssignable(rhst, at, node, message);
            return at;
        }
        else {
            return null;
        }
    }
   
    private void visitArithmeticOperator(Tree.BinaryOperatorExpression that,
            TypeDeclaration type) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            //hardcoded implicit type conversion Integer->Float
            TypeDeclaration fd = unit.getFloatDeclaration();
            TypeDeclaration id = unit.getIntegerDeclaration();
            if (rhst.getDeclaration().inherits(fd) &&
                lhst.getDeclaration().inherits(id)) {
                lhst = fd.getType();
            }
            else if (rhst.getDeclaration().inherits(id) &&
                     lhst.getDeclaration().inherits(fd)) {
                rhst = fd.getType();
            }
            ProducedType nt = checkSupertype(lhst, type, that.getLeftTerm(),
                    that instanceof Tree.SumOp ?
                            "left operand must be of summable type" :
                            "left operand must be of numeric type");
            if (nt!=null) {
                List<ProducedType> tal = nt.getTypeArgumentList();
                if (tal.isEmpty()) return;
                ProducedType tt = tal.get(0);
                that.setTypeModel(tt);
                ProducedType ot;
                if (that instanceof Tree.PowerOp) {
                    if (tal.size()<2) return;
                    ot = tal.get(1);
                }
                else {
                    ot = tt;
                }
                checkAssignable(rhst, ot, that,
                        that instanceof Tree.SumOp ?
                        "right operand must be of compatible summable type" :
                        "right operand must be of compatible numeric type");
            }
        }
    }
   
    private void visitArithmeticAssignOperator(Tree.BinaryOperatorExpression that,
            TypeDeclaration type) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            //hardcoded implicit type conversion Integer->Float
            TypeDeclaration fd = unit.getFloatDeclaration();
            TypeDeclaration id = unit.getIntegerDeclaration();
            if (rhst.getDeclaration().inherits(id) &&
                lhst.getDeclaration().inherits(fd)) {
                rhst = fd.getType();
            }
            ProducedType nt = checkSupertype(lhst, type, that.getLeftTerm(),
                    that instanceof Tree.AddAssignOp ?
                            "operand expression must be of summable type" :
                            "operand expression must be of numeric type");
            that.setTypeModel(lhst);
            if (nt!=null) {
                ProducedType t = nt.getTypeArgumentList().get(0);
                //that.setTypeModel(t); //stef requests lhst to make it easier on backend
                checkAssignable(rhst, t, that,
                        that instanceof Tree.AddAssignOp ?
                                "right operand must be of compatible summable type" :
                                "right operand must be of compatible numeric type");
                checkAssignable(t, lhst, that,
                        "result type must be assignable to declared type");
            }
        }
    }
   
    private void visitSetOperator(Tree.BitwiseOp that) {
        //TypeDeclaration sd = unit.getSetDeclaration();
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            checkAssignable(lhst, unit.getSetType(unit.getType(unit.getObjectDeclaration())),
                    that.getLeftTerm(), "set operand expression must be a set");
            checkAssignable(lhst, unit.getSetType(unit.getType(unit.getObjectDeclaration())),
                    that.getRightTerm(), "set operand expression must be a set");
            ProducedType lhset = unit.getSetElementType(lhst);
            ProducedType rhset = unit.getSetElementType(rhst);
            ProducedType et;
            if (that instanceof Tree.IntersectionOp) {
                et = intersectionType(rhset, lhset, unit);
            }
            else if (that instanceof Tree.ComplementOp) {
                et = lhset;
            }
            else {
                et = unionType(rhset, lhset, unit);
            }           
            that.setTypeModel(unit.getSetType(et));
        }
    }

    private void visitSetAssignmentOperator(Tree.BitwiseAssignmentOp that) {
        //TypeDeclaration sd = unit.getSetDeclaration();
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            checkAssignable(lhst, unit.getSetType(unit.getType(unit.getObjectDeclaration())),
                    that.getLeftTerm(), "set operand expression must be a set");
            checkAssignable(lhst, unit.getSetType(unit.getType(unit.getObjectDeclaration())),
                    that.getRightTerm(), "set operand expression must be a set");
            ProducedType lhset = unit.getSetElementType(lhst);
            ProducedType rhset = unit.getSetElementType(rhst);
            if (that instanceof Tree.UnionAssignOp) {
                checkAssignable(rhset, lhset, that.getRightTerm(),
                        "resulting set element type must be assignable to to declared set element type");
            }           
            that.setTypeModel(unit.getSetType(lhset)); //in theory, we could make this narrower
        }
    }

    private void visitLogicalOperator(Tree.BinaryOperatorExpression that) {
        ProducedType bt = unit.getType(unit.getBooleanDeclaration());
        ProducedType lt = leftType(that);
        ProducedType rt = rightType(that);
        if (!isTypeUnknown(rt) && !isTypeUnknown(lt)) {
            checkAssignable(lt, bt, that,
                    "logical operand expression must be a boolean value");
            checkAssignable(rt, bt, that,
                    "logical operand expression must be a boolean value");
        }
        that.setTypeModel(bt);
    }

    private void visitDefaultOperator(Tree.DefaultOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            checkOptional(lhst, that.getLeftTerm(), that.getLeftTerm());
            List<ProducedType> list = new ArrayList<ProducedType>(2);
            addToUnion(list, unit.denotableType(rhst));
            addToUnion(list, unit.getDefiniteType(unit.denotableType(lhst)));
            UnionType ut = new UnionType(unit);
            ut.setCaseTypes(list);
            ProducedType rt = ut.getType();
            that.setTypeModel(rt);
            /*that.setTypeModel(rhst);
            ProducedType ot;
            if (isOptionalType(rhst)) {
                ot = rhst;
            }
            else {
                ot = getOptionalType(rhst);
            }
            if (!lhst.isSubtypeOf(ot)) {
                that.getLeftTerm().addError("must be of type: " +
                        ot.getProducedTypeName(unit));
            }*/
        }
    }

    private void visitThenOperator(Tree.ThenOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(lhst)) {
            checkAssignable(lhst, unit.getType(unit.getBooleanDeclaration()), that.getLeftTerm(),
                    "operand expression must be a boolean value");
        }
        if ( rhst!=null && !isTypeUnknown(rhst)) {
            checkAssignable(rhst, unit.getType(unit.getObjectDeclaration()), that.getRightTerm(),
                    "operand expression may not be an optional type");
            that.setTypeModel(unit.getOptionalType(unit.denotableType(rhst)));
        }
    }

    private void visitInOperator(Tree.InOp that) {
        ProducedType lhst = leftType(that);
        ProducedType rhst = rightType(that);
        if (!isTypeUnknown(rhst) && !isTypeUnknown(lhst)) {
            ProducedType ct = checkSupertype(rhst,unit.getCategoryDeclaration(),
                that.getRightTerm(), "operand expression must be a category");
            if (ct!=null) {
                ProducedType at = ct.getTypeArguments().isEmpty() ?
                        null : ct.getTypeArgumentList().get(0);
              checkAssignable(lhst, at, that.getLeftTerm(),
                  "operand expression must be assignable to category type");
            }
        }
        that.setTypeModel( unit.getType(unit.getBooleanDeclaration()) );
    }
   
    private void visitUnaryOperator(Tree.UnaryOperatorExpression that,
            TypeDeclaration type) {
        ProducedType t = type(that);
        if (!isTypeUnknown(t)) {
            ProducedType nt = checkSupertype(t, type, that.getTerm(),
                    "operand expression must be of correct type");
            if (nt!=null) {
                ProducedType at = nt.getTypeArguments().isEmpty() ?
                        nt : nt.getTypeArgumentList().get(0);
                that.setTypeModel(at);
            }
        }
    }

    private void visitExistsOperator(Tree.Exists that) {
        checkOptional(type(that), that.getTerm(), that);
        that.setTypeModel(unit.getType(unit.getBooleanDeclaration()));
    }
   
    private void visitNonemptyOperator(Tree.Nonempty that) {
        checkEmpty(type(that), that.getTerm(), that);
        that.setTypeModel(unit.getType(unit.getBooleanDeclaration()));
    }
   
    private void visitOfOperator(Tree.OfOp that) {
        Tree.Type rt = that.getType();
        if (rt!=null) {
            ProducedType t = rt.getTypeModel();
            if (!isTypeUnknown(t)) {
                that.setTypeModel(t);
                Tree.Term tt = that.getTerm();
                if (tt!=null) {
                    if (tt!=null) {
                        ProducedType pt = tt.getTypeModel();
                        if (!isTypeUnknown(pt)) {
                            if (!t.covers(pt)) {
                                that.addError("specified type does not cover the cases of the operand expression: '" +
                                        t.getProducedTypeName(unit) + "' does not cover '" +
                                        pt.getProducedTypeName(unit) + "'");
                            }
                        }
                    }
                }
            }
            /*else if (dynamic) {
                that.addError("static type not known");
            }*/
        }
    }
   
    private void visitIsOperator(Tree.IsOp that) {
        Tree.Type rt = that.getType();
        if (rt!=null) {
            ProducedType t = rt.getTypeModel();
            if (t!=null) {
                if (that.getTerm()!=null) {
                    ProducedType pt = that.getTerm().getTypeModel();
                    if (pt!=null && pt.isSubtypeOf(t)) {
                        that.addError("expression type is a subtype of the type: '" +
                                pt.getProducedTypeName(unit) + "' is assignable to '" +
                                t.getProducedTypeName(unit) + "'");
                    }
                    else {
                        if (intersectionType(t, pt, unit).isNothing()) {
                            that.addError("tests assignability to bottom type 'Nothing': intersection of '" +
                                    pt.getProducedTypeName(unit) + "' and '" +
                                    t.getProducedTypeName(unit) + "' is empty");
                        }
                    }
                }
            }
        }
        that.setTypeModel(unit.getType(unit.getBooleanDeclaration()));
    }

    private void checkAssignability(Tree.Term that, Node node) {
        if (that instanceof Tree.QualifiedMemberOrTypeExpression ||
            that instanceof Tree.BaseMemberOrTypeExpression) {
            Tree.StaticMemberOrTypeExpression smte =
                    (Tree.StaticMemberOrTypeExpression) that;
            Declaration dec = smte.getDeclaration();
            if (dec!=null &&
                    (!isEffectivelyBaseMemberExpression(smte) ||
                     !unit.equals(dec.getUnit()))) { //Note: other cases handled in SpecificationVisitor
                if (dec instanceof Value) {
                    Value value = (Value) dec;
                    if (!value.isVariable() && !value.isLate()) {
                        that.addError("value is not a variable: '" +
                                dec.getName(unit) + "'", 800);
                    }
                }
                else {
                    that.addError("not a variable value: '" +
                            dec.getName(unit) + "'");
                }
            }
            if (that instanceof Tree.QualifiedMemberOrTypeExpression) {
                Tree.QualifiedMemberOrTypeExpression qmte =
                        (Tree.QualifiedMemberOrTypeExpression) that;
                if (!(qmte.getMemberOperator() instanceof Tree.MemberOp)) {
                    that.addUnsupportedError("assignment to expression involving ?. or *. not supported");
                }
            }
        }
        else {
            that.addError("expression cannot be assigned");
        }
    }
   
    private ProducedType rightType(Tree.BinaryOperatorExpression that) {
        Tree.Term rt = that.getRightTerm();
        return rt==null? null : rt.getTypeModel();
    }

    private ProducedType leftType(Tree.BinaryOperatorExpression that) {
        Tree.Term lt = that.getLeftTerm();
        return lt==null ? null : lt.getTypeModel();
    }
   
    private ProducedType type(Tree.UnaryOperatorExpression that) {
        Tree.Term t = that.getTerm();
        return t==null ? null : t.getTypeModel();
    }
   
    private Interface getArithmeticDeclaration(Tree.ArithmeticOp that) {
        if (that instanceof Tree.PowerOp) {
            return unit.getExponentiableDeclaration();
        }
        else if (that instanceof Tree.SumOp) {
            return unit.getSummableDeclaration();
        }
        else if (that instanceof Tree.DifferenceOp) {
            return unit.getInvertableDeclaration();
        }
        else if (that instanceof Tree.RemainderOp) {
            return unit.getIntegralDeclaration();
        }
        else {
            return unit.getNumericDeclaration();
        }
    }

    private Interface getArithmeticDeclaration(Tree.ArithmeticAssignmentOp that) {
        if (that instanceof Tree.AddAssignOp) {
            return unit.getSummableDeclaration();
        }
        else if (that instanceof Tree.SubtractAssignOp) {
            return unit.getInvertableDeclaration();
        }
        else if (that instanceof Tree.RemainderAssignOp) {
            return unit.getIntegralDeclaration();
        }
        else {
            return unit.getNumericDeclaration();
        }
    }

    @Override public void visit(Tree.ArithmeticOp that) {
        super.visit(that);
        visitArithmeticOperator(that, getArithmeticDeclaration(that));
    }

    @Override public void visit(Tree.BitwiseOp that) {
        super.visit(that);
        visitSetOperator(that);
    }

    @Override public void visit(Tree.ScaleOp that) {
        super.visit(that);
        visitScaleOperator(that);
    }

    @Override public void visit(Tree.LogicalOp that) {
        super.visit(that);
        visitLogicalOperator(that);
    }

    @Override public void visit(Tree.EqualityOp that) {
        super.visit(that);
        visitEqualityOperator(that);
    }

    @Override public void visit(Tree.ComparisonOp that) {
        super.visit(that);
        visitComparisonOperator(that);
    }

    @Override public void visit(Tree.WithinOp that) {
        super.visit(that);
        visitWithinOperator(that);
    }

    @Override public void visit(Tree.IdenticalOp that) {
        super.visit(that);
        visitIdentityOperator(that);
    }

    @Override public void visit(Tree.CompareOp that) {
        super.visit(that);
        visitCompareOperator(that);
    }

    @Override public void visit(Tree.DefaultOp that) {
        super.visit(that);
        visitDefaultOperator(that);
    }
       
    @Override public void visit(Tree.ThenOp that) {
        super.visit(that);
        visitThenOperator(that);
    }
       
    @Override public void visit(Tree.NegativeOp that) {
        super.visit(that);
        visitUnaryOperator(that, unit.getInvertableDeclaration());
    }
       
    @Override public void visit(Tree.PositiveOp that) {
        super.visit(that);
        visitUnaryOperator(that, unit.getInvertableDeclaration());
    }
   
    @Override public void visit(Tree.NotOp that) {
        super.visit(that);
        visitUnaryOperator(that, unit.getBooleanDeclaration());
    }
   
    @Override public void visit(Tree.AssignOp that) {
        assign(that.getLeftTerm());
        super.visit(that);
        visitAssignOperator(that);
        checkAssignability(that.getLeftTerm(), that);
    }
   
    @Override public void visit(Tree.ArithmeticAssignmentOp that) {
        assign(that.getLeftTerm());
        super.visit(that);
        visitArithmeticAssignOperator(that, getArithmeticDeclaration(that));
        checkAssignability(that.getLeftTerm(), that);
    }
   
    @Override public void visit(Tree.LogicalAssignmentOp that) {
        assign(that.getLeftTerm());
        super.visit(that);
        visitLogicalOperator(that);
        checkAssignability(that.getLeftTerm(), that);
    }
   
    @Override public void visit(Tree.BitwiseAssignmentOp that) {
        assign(that.getLeftTerm());
        super.visit(that);
        visitSetAssignmentOperator(that);
        checkAssignability(that.getLeftTerm(), that);
    }
   
    @Override public void visit(Tree.RangeOp that) {
        super.visit(that);
        visitSpanOperator(that);
    }
   
    @Override public void visit(Tree.SegmentOp that) {
        super.visit(that);
        visitMeasureOperator(that);
    }
       
    @Override public void visit(Tree.EntryOp that) {
        super.visit(that);
        visitEntryOperator(that);
    }
   
    @Override public void visit(Tree.Exists that) {
        super.visit(that);
        visitExistsOperator(that);
    }
   
    @Override public void visit(Tree.Nonempty that) {
        super.visit(that);
        visitNonemptyOperator(that);
    }
   
    @Override public void visit(Tree.IsOp that) {
        super.visit(that);
        visitIsOperator(that);
    }
   
    @Override public void visit(Tree.OfOp that) {
        super.visit(that);
        visitOfOperator(that);
    }
   
    @Override public void visit(Tree.Extends that) {
        super.visit(that);
        that.addUnsupportedError("extends operator not yet supported");
    }
   
    @Override public void visit(Tree.Satisfies that) {
        super.visit(that);
        that.addUnsupportedError("satisfies operator not yet supported");
    }
   
    @Override public void visit(Tree.InOp that) {
        super.visit(that);
        visitInOperator(that);
    }
   
    @Override
    public void visit(Tree.BaseType that) {
        super.visit(that);
        TypeDeclaration type = that.getDeclarationModel();
        if (type!=null) {
            if (!type.isVisible(that.getScope())) {
                that.addError("type is not visible: " +
                        baseDescription(that), 400);
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private type is not visible: " +
                    baseDescription(that));
            }
            //don't need to consider "protected" because
            //toplevel types can't be declared protected
            //and inherited protected member types are
            //visible to subclasses
        }
    }

    @Override
    public void visit(Tree.QualifiedType that) {
        super.visit(that);
        TypeDeclaration type = that.getDeclarationModel();
        if (type!=null) {
            if (!type.isVisible(that.getScope())) {
                that.addError("member type is not visible: " +
                        qualifiedDescription(that), 400);
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private member type is not visible: " +
                        qualifiedDescription(that));
            }
            //this is actually slightly too restrictive
            //since a qualified type may in fact be an
            //inherited member type, but in that case
            //you can just get rid of the qualifier, so
            //in fact this restriction is OK
            else if (type.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected member type is not visible: " +
                        qualifiedDescription(that));
            }
            //Note: we should remove this check if we ever
            //      make qualified member types like T.Member
            //      into a sort of virtual type
            Tree.StaticType outerType = that.getOuterType();
      if (outerType instanceof Tree.SimpleType) {
        if (((Tree.SimpleType) outerType).getDeclarationModel() instanceof TypeParameter) {
          outerType.addError("type parameter should not occur as qualifying type: " +
              qualifiedDescription(that));
        }
      }
        }
    }

    private void checkBaseVisibility(Node that, TypedDeclaration member,
            String name) {
        if (!member.isVisible(that.getScope())) {
            that.addError("function or value is not visible: '" +
                    name + "'", 400);
        }
        else if (member.isPackageVisibility() &&
                !declaredInPackage(member, unit)) {
            that.addError("package private function or value is not visible: '" +
                    name + "'");
        }
        //don't need to consider "protected" because
        //there are no toplevel members in Java and
        //inherited protected members are visible to
        //subclasses
    }
   
    private void checkQualifiedVisibility(Node that, TypedDeclaration member,
            String name, String container, boolean selfReference) {
        if (!member.isVisible(that.getScope())) {
            that.addError("method or attribute is not visible: '" +
                    name + "' of " + container, 400);
        }
        else if (member.isPackageVisibility() &&
                !declaredInPackage(member, unit)) {
            that.addError("package private method or attribute is not visible: '" +
                    name + "' of " + container);
        }
        //this is actually too restrictive since
        //it doesn't take into account "other
        //instance" access (access from a different
        //instance of the same type)
        else if (member.isProtectedVisibility() &&
                !selfReference &&
                !declaredInPackage(member, unit)) {
            that.addError("protected method or attribute is not visible: '" +
                    name + "' of " + container);
        }
    }

    private void checkBaseTypeAndConstructorVisibility(
            Tree.BaseTypeExpression that, String name, TypeDeclaration type) {
        //Note: the handling of "protected" here looks
        //      wrong because Java has a crazy rule
        //      that you can't instantiate protected
        //      member classes from a subclass
        if (isOverloadedVersion(type)) { 
            //it is a Java constructor
            //get the actual type that
            //owns the constructor
            //Declaration at = type.getContainer().getDirectMember(type.getName(), null, false);
            Declaration at = type.getExtendedTypeDeclaration();
            if (!at.isVisible(that.getScope())) {
                that.addError("type is not visible: '" + name + "'");
            }
            else if (at.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private type is not visible: '" + name + "'");
            }
            else if (at.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected type is not visible: '" + name + "'");
            }
            else if (!type.isVisible(that.getScope())) {
                that.addError("type constructor is not visible: '" + name + "'");
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private constructor is not visible: '" + name + "'");
            }
            else if (type.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected constructor is not visible: '" + name + "'");
            }
        }
        else {
            if (!type.isVisible(that.getScope())) {
                that.addError("type is not visible: '" + name + "'", 400);
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private type is not visible: '" + name + "'");
            }
            else if (type.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected type is not visible: '" + name + "'");
            }
        }
    }
   
    private void checkQualifiedTypeAndConstructorVisibility(
            Tree.QualifiedTypeExpression that, TypeDeclaration type,
            String name, String container) {
        //Note: the handling of "protected" here looks
        //      wrong because Java has a crazy rule
        //      that you can't instantiate protected
        //      member classes from a subclass
        if (isOverloadedVersion(type)) {
            //it is a Java constructor
            //get the actual type that
            //owns the constructor
            //Declaration at = type.getContainer().getDirectMember(type.getName(), null, false);
            Declaration at = type.getExtendedTypeDeclaration();
            if (!at.isVisible(that.getScope())) {
                that.addError("member type is not visible: '" +
                        name + "' of '" + container);
            }
            else if (at.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private member type is not visible: '" +
                        name + "' of type " + container);
            }
            else if (at.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected member type is not visible: '" +
                        name + "' of type " + container);
            }
            else if (!type.isVisible(that.getScope())) {
                that.addError("member type constructor is not visible: '" +
                        name + "' of " + container);
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private member type constructor is not visible: '" +
                        name + "' of " + container);
            }
            else if (type.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected member type constructor is not visible: '" +
                        name + "' of " + container);
            }
        }
        else {
            if (!type.isVisible(that.getScope())) {
                that.addError("member type is not visible: '" +
                        name + "' of " + container, 400);
            }
            else if (type.isPackageVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("package private member type is not visible: '" +
                        name + "' of " + container);
            }
            else if (type.isProtectedVisibility() &&
                    !declaredInPackage(type, unit)) {
                that.addError("protected member type is not visible: '" +
                        name + "' of " + container);
            }
        }
    }

    private static String baseDescription(Tree.BaseType that) {
        return "'" + name(that.getIdentifier()) +"'";
    }
   
    private static String qualifiedDescription(Tree.QualifiedType that) {
        String name = name(that.getIdentifier());
        Declaration d = that.getOuterType().getTypeModel().getDeclaration();
        return "'" + name + "' of type '" + d.getName() + "'";
    }
   
    @Override public void visit(Tree.BaseMemberExpression that) {
        super.visit(that);
        boolean notDirectlyInvoked = !that.getDirectlyInvoked();
        TypedDeclaration member =
                resolveBaseMemberExpression(that, notDirectlyInvoked);
        if (member!=null && notDirectlyInvoked) {
            Tree.TypeArguments tal = that.getTypeArguments();
            List<ProducedType> typeArgs;
            if (explicitTypeArguments(member, tal)) {
                typeArgs = getTypeArguments(tal,
                        getTypeParameters(member), null);
            }
            else {
                typeArgs = inferFunctionRefTypeArgs(that);
            }
            if (typeArgs!=null) {
                tal.setTypeModels(typeArgs);
                visitBaseMemberExpression(that, member, typeArgs, tal);
                //otherwise infer type arguments later
            }
            else {
                typeArgumentsImplicit(that);
            }
        }
    }

    private TypedDeclaration resolveBaseMemberExpression(
            Tree.BaseMemberExpression that,
            boolean error) {
        String name = name(that.getIdentifier());
        TypedDeclaration member = getTypedDeclaration(that.getScope(),
                name, that.getSignature(), that.getEllipsis(),
                that.getUnit());
        if (member==null) {
            if (!dynamic && error) {
                that.addError("function or value does not exist: '" +
                        name + "'", 100);
                unit.getUnresolvedReferences().add(that.getIdentifier());
            }
        }
        else {
            member = (TypedDeclaration) handleAbstraction(member, that);
            that.setDeclaration(member);
            if (error) {
                checkBaseVisibility(that, member, name);
            }
        }
        return member;
    }

    private List<TypeParameter> getTypeParameters(Declaration member) {
        return member instanceof Generic ?
                ((Generic) member).getTypeParameters() :
                Collections.<TypeParameter>emptyList();
    }
   
    @Override public void visit(Tree.QualifiedMemberExpression that) {
        super.visit(that);
        boolean notDirectlyInvoked = !that.getDirectlyInvoked();
        TypedDeclaration member =
                resolveQualifiedMemberExpression(that, notDirectlyInvoked);
        if (member!=null && notDirectlyInvoked) {
            Tree.TypeArguments tal = that.getTypeArguments();
            ProducedType pt =
                    that.getPrimary().getTypeModel()
                    .resolveAliases(); //TODO: probably not necessary
            List<ProducedType> typeArgs;
            if (explicitTypeArguments(member, tal)) {
                typeArgs = getTypeArguments(tal,
                        getTypeParameters(member), pt);
            }
            else {
                typeArgs = inferFunctionRefTypeArgs(that);
            }
            if (typeArgs!=null) {
                tal.setTypeModels(typeArgs);
                if (that.getPrimary() instanceof Tree.Package) {
                    visitBaseMemberExpression(that, member, typeArgs, tal);
                }
                else {
                    visitQualifiedMemberExpression(that, pt, member, typeArgs, tal);
                }
                //otherwise infer type arguments later
            }
            else {
                typeArgumentsImplicit(that);
            }
        }
    }

    private TypedDeclaration resolveQualifiedMemberExpression(
            Tree.QualifiedMemberExpression that,
            boolean error) {
        Tree.Primary p = that.getPrimary();
        ProducedType pt = p.getTypeModel();
        boolean packageQualified = p instanceof Tree.Package;
        boolean check = packageQualified ||
                //that.getStaticMethodReference() ||
                pt!=null &&
                //account for dynamic blocks
                (!pt.getType().isUnknown() ||
                        that.getMemberOperator() instanceof Tree.SpreadOp);
        boolean nameNonempty = that.getIdentifier()!=null &&
                        !that.getIdentifier().getText().equals("");
        if (nameNonempty && check) {
            TypedDeclaration member;
            String name = name(that.getIdentifier());
            String container;
            boolean ambiguous;
            List<ProducedType> signature = that.getSignature();
            boolean ellipsis = that.getEllipsis();
            if (packageQualified) {
                container = "package '" + unit.getPackage().getNameAsString() + "'";
                Declaration pm = unit.getPackage()
                        .getMember(name, signature, ellipsis);
                if (pm instanceof TypedDeclaration) {
                    member = (TypedDeclaration) pm;
                }
                else {
                    member = null;
                }
                ambiguous = false;
            }
            else {
                pt = pt.resolveAliases(); //needed for aliases like "alias Id<T> => T"
                TypeDeclaration d = getDeclaration(that, pt);
                container = "type '" + d.getName(unit) + "'";
                ClassOrInterface ci =
                        getContainingClassOrInterface(that.getScope());
                if (ci!=null && d.inherits(ci) && !(d instanceof NothingType)) {
                    Declaration direct =
                            ci.getDirectMember(name, signature, ellipsis);
                    if (direct instanceof TypedDeclaration) {
                        member = (TypedDeclaration) direct;
                    }
                    else {
                        member = getTypedMember(d, name, signature, ellipsis, unit);
                    }
                }
                else {
                    member = getTypedMember(d, name, signature, ellipsis, unit);
                }
                ambiguous = member==null &&
                        d.isMemberAmbiguous(name, unit, signature, ellipsis);
            }
            if (member==null) {
                if (error) {
                    if (ambiguous) {
                        that.addError("method or attribute is ambiguous: '" +
                                name + "' for " + container);
                    }
                    else {
                        that.addError("method or attribute does not exist: '" +
                                name + "' in " + container, 100);
                        unit.getUnresolvedReferences().add(that.getIdentifier());
                    }
                }
            }
            else {
                member = (TypedDeclaration) handleAbstraction(member, that);
                that.setDeclaration(member);
                boolean selfReference = isSelfReference(p);
                if (!selfReference && !member.isShared()) {
                    member.setOtherInstanceAccess(true);
                }
                if (error) {
                    checkQualifiedVisibility(that, member, name, container,
                            selfReference);
                    checkSuperMember(that);
                }
            }
            return member;
        }
        else {
            return null;
        }
    }

    private boolean isSelfReference(Tree.Primary p) {
        return
                p instanceof Tree.This ||
                p instanceof Tree.Outer ||
                p instanceof Tree.Super;
    }

    private void checkSuperMember(Tree.QualifiedMemberOrTypeExpression that) {
        Tree.Term t = eliminateParensAndWidening(that.getPrimary());
        if (t instanceof Tree.Super) {
            checkSuperInvocation(that);
        }
    }

    private void typeArgumentsImplicit(Tree.StaticMemberOrTypeExpression that) {
        Generic dec = (Generic) that.getDeclaration();
        StringBuilder params = new StringBuilder();
        for (TypeParameter tp: dec.getTypeParameters()) {
            if (params.length()>0) params.append(", ");
            params.append("'").append(tp.getName()).append("'");
        }
        that.addError("missing type arguments to generic declaration: '" +
                that.getDeclaration().getName(unit) +
                "' declares type parameters " + params);
    }
   
    private void visitQualifiedMemberExpression(Tree.QualifiedMemberExpression that,
            ProducedType receivingType, TypedDeclaration member,
            List<ProducedType> typeArgs, Tree.TypeArguments tal) {
        ProducedType receiverType =
                accountForStaticReferenceReceiverType(that,
                        unwrap(receivingType, that));
        if (acceptsTypeArguments(receiverType, member, typeArgs, tal, that, false)) {
            ProducedTypedReference ptr =
                    receiverType.getTypedMember(member, typeArgs,
                            that.getAssigned());
            /*if (ptr==null) {
                that.addError("member method or attribute does not exist: " +
                        member.getName(unit) + " of type " +
                        receiverType.getDeclaration().getName(unit));
            }
            else {*/
            that.setTarget(ptr);
            ProducedType fullType =
                    ptr.getFullType(wrap(ptr.getType(), receivingType, that));
            if (!dynamic && !isAbstraction(member) &&
                    isTypeUnknown(fullType)) {
                //this occurs with an ambiguous reference
                //to a member of an intersection type
                that.addError("could not determine type of method or attribute reference: '" +
                        member.getName(unit) + "' of '" +
                        receiverType.getDeclaration().getName(unit) + "'");
            }
            that.setTypeModel(accountForStaticReferenceType(that, member, fullType));
            //}
        }
    }

    private ProducedType accountForStaticReferenceReceiverType(Tree.QualifiedMemberOrTypeExpression that,
            ProducedType receivingType) {
        if (that.getStaticMethodReference()) {
            Tree.MemberOrTypeExpression primary =
                    (Tree.MemberOrTypeExpression) that.getPrimary();
            ProducedReference target = primary.getTarget();
            return target==null ?
                    new UnknownType(unit).getType() :
                    target.getType();
        }
        else {
            return receivingType;
        }
    }
   
    private ProducedType accountForStaticReferenceType(Tree.QualifiedMemberOrTypeExpression that,
            Declaration member, ProducedType type) {
        if (that.getStaticMethodReference()) {
            Tree.MemberOrTypeExpression qmte =
                    (Tree.MemberOrTypeExpression) that.getPrimary();
            if (member.isStaticallyImportable()) {
                return type;
            }
            else {
                ProducedReference target = qmte.getTarget();
                if (target==null) {
                    return new UnknownType(unit).getType();
                }
                else {
                    return getStaticReferenceType(type,
                            target.getType());
                }
            }
        }
        else {
            return type;
        }
    }
   
    private ProducedType getStaticReferenceType(ProducedType type, ProducedType rt) {
        return producedType(unit.getCallableDeclaration(), type,
                producedType(unit.getTupleDeclaration(),
                        rt, rt, unit.getType(unit.getEmptyDeclaration())));
    }
   
    private void visitBaseMemberExpression(Tree.StaticMemberOrTypeExpression that,
            TypedDeclaration member, List<ProducedType> typeArgs,
            Tree.TypeArguments tal) {
        if (acceptsTypeArguments(member, typeArgs, tal, that, false)) {
            ProducedType outerType =
                    that.getScope().getDeclaringType(member);
            ProducedTypedReference pr =
                    member.getProducedTypedReference(outerType, typeArgs,
                            that.getAssigned());
            that.setTarget(pr);
            ProducedType fullType = pr.getFullType();
            if (!dynamic && !isAbstraction(member) &&
                    isTypeUnknown(fullType)) {
                that.addError("could not determine type of function or value reference: '" +
                        member.getName(unit) + "'");
            }
            if (dynamic && isTypeUnknown(fullType)) {
                //deliberately throw away the partial
                //type information we have
                return;
            }
            that.setTypeModel(fullType);
        }
    }

    @Override public void visit(Tree.BaseTypeExpression that) {
        super.visit(that);
        boolean notDirectlyInvoked = !that.getDirectlyInvoked();
        TypeDeclaration type =
                resolveBaseTypeExpression(that, notDirectlyInvoked);
        if (type!=null && notDirectlyInvoked) {
            Tree.TypeArguments tal = that.getTypeArguments();
            List<ProducedType> typeArgs;
            if (explicitTypeArguments(type, tal)) {
                typeArgs = getTypeArguments(tal,
                        type.getTypeParameters(), null);
            }
            else {
                typeArgs = inferFunctionRefTypeArgs(that);
            }
            if (typeArgs!=null) {
                tal.setTypeModels(typeArgs);
                visitBaseTypeExpression(that, type, typeArgs, tal);
                //otherwise infer type arguments later
            }
            else {
                typeArgumentsImplicit(that);
            }
        }
    }

    private TypeDeclaration resolveBaseTypeExpression(
            Tree.BaseTypeExpression that,
            boolean error) {
        String name = name(that.getIdentifier());
        TypeDeclaration type = getTypeDeclaration(that.getScope(),
                name, that.getSignature(), that.getEllipsis(),
                that.getUnit());
        if (type==null) {
            if (!dynamic && error) {
                that.addError("type does not exist: '" + name + "'", 102);
                unit.getUnresolvedReferences().add(that.getIdentifier());
            }
        }
        else {
            type = (TypeDeclaration) handleAbstraction(type, that);
            that.setDeclaration(type);
            if (error) {
                if (checkConcreteClass(type, that)) {
                    if (checkSealedReference(type, that)) {
                        checkBaseTypeAndConstructorVisibility(that, name, type);
                    }
                }
            }
        }
        return type;
    }

    private boolean checkConcreteClass(TypeDeclaration type,
            Tree.MemberOrTypeExpression that) {
        if (that.getStaticMethodReferencePrimary()) {
//            if (!(type instanceof ClassOrInterface)) {
//                that.addError("type cannot be instantiated: '" +
//                        type.getName(unit) + "' is not a class or interface");
//                return false;
//            }
//            else {
                return true;
//            }
        }
        else {
            if (type instanceof Class) {
                if (((Class) type).isAbstract()) {
                    that.addError("class cannot be instantiated: '" +
                            type.getName(unit) + "' is abstract");
                    return false;
                }
                else {
                    return true;
                }
            }
            else if (type instanceof TypeParameter) {
                if (((TypeParameter) type).getParameterList()==null) {
                    that.addError("type parameter cannot be instantiated: '" +
                            type.getName(unit) + "'");
                    return false;
                }
                else {
                    return true;
                }
            }
            else {
                that.addError("type cannot be instantiated: '" +
                        type.getName(unit) + "' is not a class");
                return false;
            }
        }
    }

  private boolean checkSealedReference(TypeDeclaration type,
            Tree.MemberOrTypeExpression that) {
      if (type.isSealed() && !inSameModule(type) &&
          (!that.getStaticMethodReferencePrimary())) {
        that.addError("invokes or references a sealed class in a different module: '" +
            type.getName(unit) + "' in '" +
            type.getUnit().getPackage().getModule().getNameAsString() + "'");
        return false;
      }
      else {
          return true;
      }
    }
   
   void visitExtendedTypePrimary(Tree.ExtendedTypeExpression that) {
        Declaration dec = that.getDeclaration();
        if (dec instanceof Class) {
            Class c = (Class) dec;
            if (c.isAbstraction()) {
                //if the constructor is overloaded
                //resolve the right overloaded version
                Declaration result =
                        findMatchingOverloadedClass(c,
                                that.getSignature(),
                                that.getEllipsis());
                if (result!=null && result!=dec) {
                    //patch the reference, which was already
                    //initialized to the abstraction
                    that.setDeclaration((TypeDeclaration) result);
                    if (isOverloadedVersion(result)) { 
                        //it is a Java constructor
                        if (result.isPackageVisibility() &&
                                !declaredInPackage(result, unit)) {
                            that.addError("package private constructor is not visible: '" +
                                    result.getName() + "'");
                        }
                    }
                }
                //else report to user that we could not
                //find a matching overloaded constructor
            }
        }
    }
   
    @Override public void visit(Tree.QualifiedMemberOrTypeExpression that) {
        super.visit(that);
//        Declaration d = that.getDeclaration();
//        if (!d.isStaticallyImportable()) {
            Tree.Term p = that.getPrimary();
            while (p instanceof Tree.Expression &&
                    p.getMainToken()==null) { //this hack allows actual parenthesized expressions through
                p = ((Tree.Expression) p).getTerm();
            }
            if (p instanceof Tree.MemberOrTypeExpression) {
                Declaration pd = ((Tree.MemberOrTypeExpression) p).getDeclaration();
                if (!(that.getStaticMethodReference()) &&
                        pd instanceof Functional) {
                    //this is a direct function ref
                    //its not a type, it can't have members
                    that.addError("direct function references do not have members");
                }
            }
//        }
    }
   
    @Override public void visit(Tree.QualifiedTypeExpression that) {
        super.visit(that);
        boolean notDirectlyInvoked = !that.getDirectlyInvoked();
        TypeDeclaration type =
                resolveQualifiedTypeExpression(that, notDirectlyInvoked);
        if (type!=null && notDirectlyInvoked) {
            Tree.TypeArguments tal = that.getTypeArguments();
            ProducedType pt =
                    that.getPrimary().getTypeModel()
                    .resolveAliases(); //TODO: probably not necessary
            List<ProducedType> typeArgs;
            if (explicitTypeArguments(type, tal)) {
                typeArgs = getTypeArguments(tal,
                        type.getTypeParameters(), pt);
            }
            else {
                typeArgs = inferFunctionRefTypeArgs(that);
            }
            if (typeArgs!=null) {
                tal.setTypeModels(typeArgs);
                if (that.getPrimary() instanceof Tree.Package) {
                    visitBaseTypeExpression(that, type, typeArgs, tal);
                }
                else {
                    visitQualifiedTypeExpression(that, pt, type, typeArgs, tal);
                }
                //otherwise infer type arguments later
            }
            else {
                typeArgumentsImplicit(that);
            }
        }
    }

    private TypeDeclaration resolveQualifiedTypeExpression(
            Tree.QualifiedTypeExpression that,
            boolean error) {
        Tree.Primary p = that.getPrimary();
        ProducedType pt = p.getTypeModel();
        boolean packageQualified = p instanceof Tree.Package;
        boolean check = packageQualified ||
                that.getStaticMethodReference() ||
                pt!=null &&
                //account for dynamic blocks
                (!pt.isUnknown() ||
                        that.getMemberOperator() instanceof Tree.SpreadOp);
        if (check) {
            TypeDeclaration type;
            String name = name(that.getIdentifier());
            String container;
            boolean ambiguous;
            List<ProducedType> signature = that.getSignature();
            boolean ellipsis = that.getEllipsis();
            if (packageQualified) {
                container = "package '" + unit.getPackage().getNameAsString() + "'";
                Declaration pm = unit.getPackage()
                        .getMember(name, signature, ellipsis);
                if (pm instanceof TypeDeclaration) {
                    type = (TypeDeclaration) pm;
                }
                else {
                    type = null;
                }
                ambiguous = false;
            }
            else {
                pt = pt.resolveAliases(); //needed for aliases like "alias Id<T> => T"
                TypeDeclaration d = getDeclaration(that, pt);
                container = "type '" + d.getName(unit) + "'";
                ClassOrInterface ci =
                        getContainingClassOrInterface(that.getScope());
                if (ci!=null && d.inherits(ci) && !(d instanceof NothingType)) {
                    Declaration direct =
                            ci.getDirectMember(name, signature, ellipsis);
                    if (direct instanceof TypeDeclaration) {
                        type = (TypeDeclaration) direct;
                    }
                    else {
                        type = getTypeMember(d, name, signature, ellipsis, unit);
                    }
                }
                else {
                    type = getTypeMember(d, name, signature, ellipsis, unit);
                }
                ambiguous = type==null && d.isMemberAmbiguous(name, unit,
                        signature, ellipsis);
            }
            if (type==null) {
                if (error) {
                    if (ambiguous) {
                        that.addError("member type is ambiguous: '" +
                                name + "' for " + container);
                    }
                    else {
                        that.addError("member type does not exist: '" +
                                name + "' in " + container, 100);
                        unit.getUnresolvedReferences().add(that.getIdentifier());
                    }
                }
            }
            else {
                type = (TypeDeclaration) handleAbstraction(type, that);
                that.setDeclaration(type);
                if (!isSelfReference(p) && !type.isShared()) {
                    type.setOtherInstanceAccess(true);
                }
                if (error) {
                    if (checkConcreteClass(type, that)) {
                        if (checkSealedReference(type, that)) {
                            checkQualifiedTypeAndConstructorVisibility(that, type, name, container);
                        }
                    }
                    if (!inExtendsClause) {
                        checkSuperMember(that);
                    }
                }
            }
            return type;
        }
        else {
            return null;
        }
    }
   
    private TypeDeclaration getDeclaration(Tree.QualifiedMemberOrTypeExpression that,
            ProducedType pt) {
        if (that.getStaticMethodReference()) {
            TypeDeclaration td = (TypeDeclaration)
                    ((Tree.MemberOrTypeExpression) that.getPrimary()).getDeclaration();
            return td==null ? new UnknownType(unit) : td;
        }
        else {
            return unwrap(pt, that).getDeclaration();
        }
    }

    private boolean explicitTypeArguments(Declaration dec, Tree.TypeArguments tal) {
        return !dec.isParameterized() ||
                tal instanceof Tree.TypeArgumentList;
    }
   
    @Override public void visit(Tree.SimpleType that) {
        //this one is a declaration, not an expression!
        //we are only validating type arguments here
        super.visit(that);
        ProducedType pt = that.getTypeModel();
        if (pt!=null) {
            TypeDeclaration type = that.getDeclarationModel();//pt.getDeclaration()
            Tree.TypeArgumentList tal = that.getTypeArgumentList();
            //No type inference for declarations
            if (type!=null) {
                List<TypeParameter> params = type.getTypeParameters();
                List<ProducedType> ta = getTypeArguments(tal,
                        params, pt.getQualifyingType());
                acceptsTypeArguments(type, ta, tal, that, that.getMetamodel());
                //the type has already been set by TypeVisitor
                if (tal!=null) {
                    List<Tree.Type> args = tal.getTypes();
                    for (int i = 0; i<args.size(); i++) {
                        Tree.Type t = args.get(i);
                        if (t instanceof Tree.StaticType) {
                            TypeVariance variance =
                                    ((Tree.StaticType) t).getTypeVariance();
                            if (variance!=null) {
                                TypeParameter p = params.get(i);
                                if (p.isInvariant()) {
                                    if (variance.getText().equals("out")) {
                                        pt.setVariance(p, OUT);
                                    }
                                    else if (variance.getText().equals("in")) {
                                        pt.setVariance(p, IN);
                                    }
                                }
                                else {
                                    variance.addError("type parameter is not declared invariant: '" +
                                            p.getName() + "' of '" + type.getName(unit) + "'");
                                }
                            }
                        }
                    }
                }
            }
        }
    }
   
    @Override public void visit(Tree.EntryType that) {
        super.visit(that);
        checkAssignable(that.getKeyType().getTypeModel(), unit.getType(unit.getObjectDeclaration()),
                that.getKeyType(), "entry key type must not be an optional type");
//        checkAssignable(that.getValueType().getTypeModel(), unit.getType(unit.getObjectDeclaration()),
//                that.getValueType(), "entry item type must not be an optional type");
    }

    private void visitQualifiedTypeExpression(Tree.QualifiedTypeExpression that,
            ProducedType receivingType, TypeDeclaration memberType,
            List<ProducedType> typeArgs, Tree.TypeArguments tal) {
        ProducedType receiverType =
                accountForStaticReferenceReceiverType(that,
                        unwrap(receivingType, that));
        if (acceptsTypeArguments(receiverType, memberType, typeArgs, tal, that, false)) {
            ProducedType type = receiverType.getTypeMember(memberType, typeArgs);
            that.setTarget(type);
            ProducedType fullType =
                    type.getFullType(wrap(type, receivingType, that));
            if (!dynamic && !that.getStaticMethodReference() &&
                    memberType instanceof Class &&
                    !isAbstraction(memberType) &&
                    isTypeUnknown(fullType)) {
                //this occurs with an ambiguous reference
                //to a member of an intersection type
                that.addError("could not determine type of member class reference: '" +
                        memberType.getName(unit+ "' of '" +
                        receiverType.getDeclaration().getName(unit) + "'");
            }
            that.setTypeModel(accountForStaticReferenceType(that, memberType, fullType));
        }
    }

    private void visitBaseTypeExpression(Tree.StaticMemberOrTypeExpression that,
            TypeDeclaration baseType, List<ProducedType> typeArgs,
            Tree.TypeArguments tal) {
        if (acceptsTypeArguments(baseType, typeArgs, tal, that, false)) {
            ProducedType outerType =
                    that.getScope().getDeclaringType(baseType);
            ProducedType type =
                    baseType.getProducedType(outerType, typeArgs);
            ProducedType fullType = type.getFullType();
            that.setTypeModel(fullType);
            that.setTarget(type);
        }
    }

    @Override public void visit(Tree.Expression that) {
        //i.e. this is a parenthesized expression
        super.visit(that);
        Tree.Term term = that.getTerm();
        if (term==null) {
            that.addError("expression not well formed");
        }
        else {
            ProducedType t = term.getTypeModel();
            if (t==null) {
                //that.addError("could not determine type of expression");
            }
            else {
                that.setTypeModel(t);
            }
        }
    }
   
    @Override public void visit(Tree.Outer that) {
        ProducedType ci = getOuterClassOrInterface(that.getScope());
        if (ci==null) {
            that.addError("outer appears outside a nested class or interface definition");
        }
        else {
            that.setTypeModel(ci);
        }
        /*if (defaultArgument) {
            that.addError("reference to outer from default argument expression");
        }*/
    }

    @Override public void visit(Tree.Super that) {
        ClassOrInterface ci = getContainingClassOrInterface(that.getScope());
        if (inExtendsClause) {
            if (ci!=null) {
                if (ci.isClassOrInterfaceMember()) {
                    ClassOrInterface oci = (ClassOrInterface) ci.getContainer();
                    that.setTypeModel(intersectionOfSupertypes(oci));
                }
            }
        }
        else {
            //TODO: for consistency, move these errors to SelfReferenceVisitor
            if (ci==null) {
                that.addError("super occurs outside any type definition");
            }
            else {
                that.setTypeModel(intersectionOfSupertypes(ci));
            }
        }
    }

    @Override public void visit(Tree.This that) {
        ClassOrInterface ci = getContainingClassOrInterface(that.getScope());
        if (inExtendsClause) {
            if (ci!=null) {
                if (ci.isClassOrInterfaceMember()) {
                    ClassOrInterface s = (ClassOrInterface) ci.getContainer();
                    that.setTypeModel(s.getType());
                }
            }
        }
        else {
            if (ci==null) {
                that.addError("this appears outside a class or interface definition");
            }
            else {
                that.setTypeModel(ci.getType());
            }
        }
    }
   
    @Override public void visit(Tree.Package that) {
        if (!that.getQualifier()) {
            that.addError("package must qualify a reference to a toplevel declaration");
        }
        super.visit(that);
    }
   
   
    @Override public void visit(Tree.Dynamic that) {
        super.visit(that);
        if (dynamic) {
            Tree.NamedArgumentList nal = that.getNamedArgumentList();
            if (nal!=null) {
                for (Tree.NamedArgument na: nal.getNamedArguments()) {
                    if (na instanceof Tree.SpecifiedArgument) {
                        if (na.getIdentifier()==null) {
                            na.addError("missing argument name in dynamic instantiation expression");
                        }
                    }
                }
            }
        }
        else {
            that.addError("dynamic instantiation expression occurs outside dynamic block");
        }
    }
   
    @Override public void visit(Tree.Tuple that) {
        super.visit(that);
        ProducedType tt = null;
        Tree.SequencedArgument sa = that.getSequencedArgument();
        if (sa!=null) {
            tt = getTupleType(sa.getPositionalArguments(), unit, true);
        }
        else {
            tt = unit.getType(unit.getEmptyDeclaration());
        }
        if (tt!=null) {
            that.setTypeModel(tt);
            if (tt.containsUnknowns()) {
                that.addError("tuple element type could not be inferred");
            }
        }
    }

    @Override public void visit(Tree.SequenceEnumeration that) {
        super.visit(that);
        ProducedType st = null;
        Tree.SequencedArgument sa = that.getSequencedArgument();
        if (sa!=null) {
            ProducedType tt = getTupleType(sa.getPositionalArguments(), unit, false);
            if (tt!=null) {
                st = tt.getSupertype(unit.getIterableDeclaration());
                if (st==null) {
                    st = unit.getIterableType(new UnknownType(unit).getType());
                }
            }
        }
        else {
            st = unit.getIterableType(unit.getNothingDeclaration().getType());
        }
        if (st!=null) {
            that.setTypeModel(st);
            if (st.containsUnknowns()) {
                that.addError("iterable element type could not be inferred");
            }
        }
    }

    /*private ProducedType getGenericElementType(List<Tree.Expression> es,
            Tree.Ellipsis ell) {
        List<ProducedType> list = new ArrayList<ProducedType>();
        for (int i=0; i<es.size(); i++) {
            Tree.Expression e = es.get(i);
            if (e.getTypeModel()!=null) {
                ProducedType et = e.getTypeModel();
                if (i==es.size()-1 && ell!=null) {
                    ProducedType it = unit.getIteratedType(et);
                    if (it==null) {
                        ell.addError("last element expression is not iterable: " +
                                et.getProducedTypeName(unit) + " is not an iterable type");
                    }
                    else {
                        addToUnion(list, it);
                    }
                }
                else {
                    addToUnion(list, unit.denotableType(e.getTypeModel()));
                }
            }
        }
        if (list.isEmpty()) {
            return unit.getType(unit.getBooleanDeclaration());
        }
        else if (list.size()==1) {
            return list.get(0);
        }
        else {
            UnionType ut = new UnionType(unit);
            ut.setExtendedType( unit.getType(unit.getObjectDeclaration()) );
            ut.setCaseTypes(list);
            return ut.getType();
        }
    }*/

    @Override public void visit(Tree.CatchVariable that) {
        super.visit(that);
        Tree.Variable var = that.getVariable();
        if (var!=null) {
            Tree.Type vt = var.getType();
            if (vt instanceof Tree.LocalModifier) {
                ProducedType et = unit.getType(unit.getExceptionDeclaration());
                vt.setTypeModel(et);
                var.getDeclarationModel().setType(et);
            }
            else {
                ProducedType tt = unit.getType(unit.getThrowableDeclaration());
                checkAssignable(vt.getTypeModel(), tt, vt,
                        "catch type must be a throwable type");
//                TypeDeclaration d = vt.getTypeModel().getDeclaration();
//                if (d instanceof IntersectionType) {
//                    vt.addUnsupportedError("intersection types in catch not yet supported");
//                }
//                else if (d.isParameterized()) {
//                    vt.addUnsupportedError("parameterized types in catch not yet supported");
//                }
            }
        }
    }
   
    @Override public void visit(Tree.StringTemplate that) {
        super.visit(that);
        for (Tree.Expression e: that.getExpressions()) {
            ProducedType et = e.getTypeModel();
            if (!isTypeUnknown(et)) {
                checkAssignable(et, unit.getType(unit.getObjectDeclaration()), e,
                        "interpolated expression must not be an optional type");
            }
        }
        setLiteralType(that, unit.getStringDeclaration());
    }
   
    @Override public void visit(Tree.StringLiteral that) {
        setLiteralType(that, unit.getStringDeclaration());
    }
   
    @Override public void visit(Tree.NaturalLiteral that) {
        setLiteralType(that, unit.getIntegerDeclaration());
    }
   
    @Override public void visit(Tree.FloatLiteral that) {
        setLiteralType(that, unit.getFloatDeclaration());
    }
   
    @Override public void visit(Tree.CharLiteral that) {
        String result = that.getText();
        if (result.codePointCount(1, result.length()-1)!=1) {
            that.addError("character literal must contain exactly one character");
        }
        setLiteralType(that, unit.getCharacterDeclaration());
    }
   
    @Override public void visit(Tree.QuotedLiteral that) {
        setLiteralType(that, unit.getStringDeclaration());
    }
   
    private void setLiteralType(Tree.Atom that, TypeDeclaration languageType) {
        that.setTypeModel(unit.getType(languageType));
    }
   
    @Override
    public void visit(Tree.CompilerAnnotation that) {
        //don't visit the argument      
    }
   
    @Override
    public void visit(Tree.MatchCase that) {
        super.visit(that);
        for (Tree.Expression e: that.getExpressionList().getExpressions()) {
            if (e!=null) {
                ProducedType t = e.getTypeModel();
                if (!isTypeUnknown(t)) {
                    if (switchExpression!=null) {
                        ProducedType st = switchExpression.getTypeModel();
                        if (!isTypeUnknown(st)) {
                            if (!hasUncheckedNulls(switchExpression.getTerm()) || !isNullCase(t)) {
                                checkAssignable(t, st, e,
                                        "case must be assignable to switch expression type");
                            }
                        }
                    }
                    Tree.Term term = e.getTerm();
                    if (term instanceof Tree.NegativeOp) {
                        term = ((Tree.NegativeOp) term).getTerm();
                    }
                    if (term instanceof Tree.Literal) {
                        if (term instanceof Tree.FloatLiteral) {
                            e.addError("literal case may not be a 'Float' literal");
                        }
                    }
                    else if (term instanceof Tree.MemberOrTypeExpression) {
                        ProducedType ut = unionType(unit.getType(unit.getNullDeclaration()),
                                unit.getType(unit.getIdentifiableDeclaration()), unit);
                        TypeDeclaration dec = t.getDeclaration();
                        if ((!dec.isToplevel() && !dec.isStaticallyImportable()) || !dec.isAnonymous()) {
                            e.addError("case must refer to a toplevel object declaration or literal value");
                        }
                        else {
                            checkAssignable(t, ut, e, "case must be identifiable or null");
                        }
                    }
                    else if (term!=null) {
                        e.addError("case must be a literal value or refer to a toplevel object declaration");
                    }
                }
            }
        }
    }
   
    @Override
    public void visit(Tree.SatisfiesCase that) {
        super.visit(that);
        that.addUnsupportedError("satisfies cases are not yet supported");
    }
   
    @Override
    public void visit(Tree.IsCase that) {
        Tree.Type t = that.getType();
        if (t!=null) {
            t.visit(this);
        }
        Tree.Variable v = that.getVariable();
        if (switchExpression!=null) {
            ProducedType st = switchExpression.getTypeModel();
            if (v!=null) {
                if (dynamic || !isTypeUnknown(st)) { //eliminate dupe errors
                    v.visit(this);
                }
                initOriginalDeclaration(v);
            }
            if (t!=null) {
                ProducedType pt = t.getTypeModel();
                ProducedType it = intersectionType(pt, st, unit);
                if (!hasUncheckedNulls(switchExpression.getTerm()) || !isNullCase(pt)) {
                    if (it.isExactly(unit.getNothingDeclaration().getType())) {
                        that.addError("narrows to bottom type 'Nothing': '" +
                                pt.getProducedTypeName(unit) + "' has empty intersection with '" +
                                st.getProducedTypeName(unit) + "'");
                    }
                    /*checkAssignable(ct, switchType, cc.getCaseItem(),
                        "case type must be a case of the switch type");*/
                }
                if (v!=null) {
                    v.getType().setTypeModel(it);
                    v.getDeclarationModel().setType(it);
                }
            }
        }
    }
   
    @Override
    public void visit(Tree.SwitchStatement that) {
        Tree.Expression ose = switchExpression;
        switchExpression = that.getSwitchClause().getExpression();
        super.visit(that);
        Tree.SwitchCaseList switchCaseList = that.getSwitchCaseList();
        if (switchCaseList!=null && switchExpression!=null) {
            checkCases(switchCaseList);
            if (switchCaseList.getElseClause()==null) {
                checkCasesExhaustive(switchCaseList, that.getSwitchClause());
            }
        }
        switchExpression = ose;
    }
   
    private void checkCases(Tree.SwitchCaseList switchCaseList) {
        List<Tree.CaseClause> cases = switchCaseList.getCaseClauses();
        boolean hasIsCase = false;
        for (Tree.CaseClause cc: cases) {
            if (cc.getCaseItem() instanceof Tree.IsCase) {
                hasIsCase = true;
            }
            for (Tree.CaseClause occ: cases) {
                if (occ==cc) break;
                checkCasesDisjoint(cc, occ);
            }
        }
        if (hasIsCase) {
            Tree.Term st = switchExpression.getTerm();
            if (st instanceof Tree.BaseMemberExpression) {
                checkReferenceIsNonVariable((Tree.BaseMemberExpression) st, true);
            }
            else if (st!=null) {
                st.addError("switch expression must be a value reference in switch with type cases", 3102);
            }
        }  
    }
   
    private void checkCasesExhaustive(Tree.SwitchCaseList switchCaseList,
            Tree.SwitchClause switchClause) {
        ProducedType st = switchExpression.getTypeModel();
        if (!isTypeUnknown(st)) {
            //form the union of all the case types
            List<Tree.CaseClause> caseClauses = switchCaseList.getCaseClauses();
            List<ProducedType> list = new ArrayList<ProducedType>(caseClauses.size());
            for (Tree.CaseClause cc: caseClauses) {
                ProducedType ct = getTypeIgnoringLiterals(cc);
                if (isTypeUnknown(ct)) {
                    return; //Note: early exit!
                }
                else {
                    addToUnion(list, ct);
                }
            }
            UnionType ut = new UnionType(unit);
            ut.setCaseTypes(list);
            //if the union of the case types covers
            //the switch expression type then the
            //switch is exhaustive
            if (!ut.getType().covers(st)) {
                switchClause.addError("case types must cover all cases of the switch type or an else clause must appear: '" +
                                ut.getType().getProducedTypeName(unit) + "' does not cover '" +
                                st.getProducedTypeName(unit) + "'");
            }
        }
        /*else if (dynamic) {
            that.addError("else clause must appear: static type not known");
        }*/
    }
   
    private void checkCasesDisjoint(Tree.CaseClause cc, Tree.CaseClause occ) {
        Tree.CaseItem cci = cc.getCaseItem();
        Tree.CaseItem occi = occ.getCaseItem();
        if (cci instanceof Tree.IsCase || occi instanceof Tree.IsCase) {
            checkCasesDisjoint(getType(cc), getType(occ), cci);
        }
        else {
            checkCasesDisjoint(getTypeIgnoringLiterals(cc),
                    getTypeIgnoringLiterals(occ), cci);
        }
        if (cci instanceof Tree.MatchCase && occi instanceof Tree.MatchCase) {
            checkLiteralsDisjoint((Tree.MatchCase) cci, (Tree.MatchCase) occi);
        }
    }
   
    private void checkLiteralsDisjoint(Tree.MatchCase cci, Tree.MatchCase occi) {
        for (Tree.Expression e: cci.getExpressionList().getExpressions()) {
            for (Tree.Expression f: occi.getExpressionList().getExpressions()) {
                Tree.Term et = e.getTerm();
                Tree.Term ft = f.getTerm();
                boolean eneg = et instanceof Tree.NegativeOp;
                boolean fneg = ft instanceof Tree.NegativeOp;
                if (eneg) {
                    et = ((Tree.NegativeOp) et).getTerm();
                }
                if (fneg) {
                    ft = ((Tree.NegativeOp) ft).getTerm();
                }
                if (et instanceof Tree.Literal && ft instanceof Tree.Literal) {
                    String ftv = getLiteralText(ft);
                    String etv = getLiteralText(et);
                    if (et instanceof Tree.NaturalLiteral &&
                        ft instanceof Tree.NaturalLiteral &&
                        ((ftv.startsWith("#") && !etv.startsWith("#")) ||
                        (!ftv.startsWith("#") && etv.startsWith("#")) ||
                        (ftv.startsWith("$") && !etv.startsWith("$")) ||
                        (!ftv.startsWith("$") && etv.startsWith("$")))) {
                        cci.addUnsupportedError("literal cases with mixed bases not yet supported");
                    }
                    else if (etv.equals(ftv) && eneg==fneg) {
                        cci.addError("literal cases must be disjoint: " +
                                (eneg?"-":"") +
                                etv.replaceAll("\\p{Cntrl}","?") +
                                " occurs in multiple cases");
                    }
                }
            }
        }
    }

    private static String getLiteralText(Tree.Term et) {
        String etv = et.getText();
        if (et instanceof Tree.CharLiteral) {
            return "'" + etv + "'";
        }
        else if (et instanceof Tree.StringLiteral) {
            return "\"" + etv + "\"";
        }
        else {
            return etv;
        }
    }

    private boolean isNullCase(ProducedType ct) {
        TypeDeclaration d = ct.getDeclaration();
        return d!=null && d instanceof Class &&
                d.equals(unit.getNullDeclaration());
    }

    private ProducedType getType(Tree.CaseItem ci) {
        Tree.Type t = ((Tree.IsCase) ci).getType();
        if (t!=null) {
            return t.getTypeModel().getUnionOfCases();
        }
        else {
            return null;
        }
    }
   
    private ProducedType getType(Tree.CaseClause cc) {
        Tree.CaseItem ci = cc.getCaseItem();
        if (ci instanceof Tree.IsCase) {
            return getType(ci);
        }
        else if (ci instanceof Tree.MatchCase) {
            List<Tree.Expression> es = ((Tree.MatchCase) ci).getExpressionList().getExpressions();
            List<ProducedType> list = new ArrayList<ProducedType>(es.size());
            for (Tree.Expression e: es) {
                if (e.getTypeModel()!=null) {
                    addToUnion(list, e.getTypeModel());
                }
            }
            return formUnion(list);
        }
        else {
            return null;
        }
    }

    private ProducedType getTypeIgnoringLiterals(Tree.CaseClause cc) {
        Tree.CaseItem ci = cc.getCaseItem();
        if (ci instanceof Tree.IsCase) {
            return getType(ci);
        }
        else if (ci instanceof Tree.MatchCase) {
            List<Tree.Expression> es = ((Tree.MatchCase) ci).getExpressionList().getExpressions();
            List<ProducedType> list = new ArrayList<ProducedType>(es.size());
            for (Tree.Expression e: es) {
                if (e.getTypeModel()!=null &&
                        !(e.getTerm() instanceof Tree.Literal) &&
                        !(e.getTerm() instanceof Tree.NegativeOp)) {
                    addToUnion(list, e.getTypeModel());
                }
            }
            return formUnion(list);
        }
        else {
            return null;
        }
    }
   
    @Override
    public void visit(Tree.TryCatchStatement that) {
        super.visit(that);
        for (Tree.CatchClause cc: that.getCatchClauses()) {
            if (cc.getCatchVariable()!=null &&
                    cc.getCatchVariable().getVariable()!=null) {
                ProducedType ct = cc.getCatchVariable()
                        .getVariable().getType().getTypeModel();
                if (ct!=null) {
                    for (Tree.CatchClause ecc: that.getCatchClauses()) {
                        if (ecc.getCatchVariable()!=null &&
                                ecc.getCatchVariable().getVariable()!=null) {
                            if (cc==ecc) break;
                            ProducedType ect = ecc.getCatchVariable()
                                    .getVariable().getType().getTypeModel();
                            if (ect!=null) {
                                if (ct.isSubtypeOf(ect)) {
                                    cc.getCatchVariable().getVariable().getType()
                                            .addError("exception type is already handled by earlier catch clause: '"
                                                    + ct.getProducedTypeName(unit) + "'");
                                }
                                if (ct.getDeclaration() instanceof UnionType) {
                                    for (ProducedType ut: ct.getDeclaration().getCaseTypes()) {
                                        if ( ut.substitute(ct.getTypeArguments()).isSubtypeOf(ect) ) {
                                            cc.getCatchVariable().getVariable().getType()
                                                    .addError("exception type is already handled by earlier catch clause: '"
                                                            + ut.getProducedTypeName(unit) + "'");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
   
    @Override
    public void visit(Tree.DynamicStatement that) {
        boolean od = dynamic;
        dynamic = true;
        super.visit(that);
        dynamic = od;
    }
   
    private boolean acceptsTypeArguments(Declaration member, List<ProducedType> typeArguments,
            Tree.TypeArguments tal, Node parent, boolean metamodel) {
        return acceptsTypeArguments(null, member, typeArguments, tal, parent, metamodel);
    }
   
    private static boolean isGeneric(Declaration member) {
        return member instanceof Generic &&
            !((Generic) member).getTypeParameters().isEmpty();
    }
   
    private boolean acceptsTypeArguments(ProducedType receiver, Declaration dec,
            List<ProducedType> typeArguments, Tree.TypeArguments tal, Node parent,
            boolean metamodel) {
        if (dec==null) return false;
        if (isGeneric(dec)) {
            List<TypeParameter> params = ((Generic) dec).getTypeParameters();
            int min = 0;
            for (TypeParameter tp: params) {
                if (!tp.isDefaulted()) min++;
            }
            int max = params.size();
            int args = typeArguments.size();
            if (args<=max && args>=min) {
                for (int i=0; i<args; i++) {
                    TypeParameter param = params.get(i);
                    ProducedType argType = typeArguments.get(i);
                    //Map<TypeParameter, ProducedType> self = Collections.singletonMap(param, arg);
                    boolean argTypeMeaningful = argType!=null &&
                            !(argType.getDeclaration() instanceof UnknownType);
                    for (ProducedType st: param.getSatisfiedTypes()) {
                        //sts = sts.substitute(self);
                        ProducedType sts = st.getProducedType(receiver, dec, typeArguments);
                        if (argType!=null) {
                            if (!isCondition && !argType.isSubtypeOf(sts)) {
                                if (argTypeMeaningful) {
                                    if (tal instanceof Tree.InferredTypeArguments) {
                                        parent.addError("inferred type argument '" + argType.getProducedTypeName(unit)
                                                + "' to type parameter '" + param.getName()
                                                + "' of declaration '" + dec.getName(unit)
                                                + "' is not assignable to upper bound '" + sts.getProducedTypeName(unit)
                                                + "' of '" + param.getName() + "'");
                                    }
                                    else {
                                        ((Tree.TypeArgumentList) tal).getTypes()
                                                .get(i).addError("type parameter '" + param.getName()
                                                        + "' of declaration '" + dec.getName(unit)
                                                        + "' has argument '" + argType.getProducedTypeName(unit)
                                                        + "' is not assignable to upper bound '" + sts.getProducedTypeName(unit)
                                                        + "' of '" + param.getName() + "'", 2102);
                                    }
                                }
                                return false;
                            }
                        }
                    }
                    if (!isCondition &&
                            !argumentSatisfiesEnumeratedConstraint(receiver, dec,
                                    typeArguments, argType, param)) {
                        if (argTypeMeaningful) {
                            if (tal instanceof Tree.InferredTypeArguments) {
                                parent.addError("inferred type argument '" + argType.getProducedTypeName(unit)
                                        + "' to type parameter '" + param.getName()
                                        + "' of declaration '" + dec.getName(unit)
                                        + "' is not one of the enumerated cases of '" + param.getName() + "'");
                            }
                            else {
                                ((Tree.TypeArgumentList) tal).getTypes()
                                        .get(i).addError("type parameter '" + param.getName()
                                                + "' of declaration '" + dec.getName(unit)
                                                + "' has argument '" + argType.getProducedTypeName(unit)
                                                + "' is not one of the enumerated cases of '" + param.getName() + "'");
                            }
                        }
                        return false;
                    }
                }
                return true;
            }
            else {
                if (tal==null || tal instanceof Tree.InferredTypeArguments) {
                    if (!metamodel) {
                        parent.addError("missing type arguments to generic type: '" +
                                dec.getName(unit) + "' declares type parameters");
                    }
                }
                else {
                    String help="";
                    if (args<min) {
                        help = " requires at least " + min + " type arguments";
                    }
                    else if (args>max) {
                        help = " allows at most " + max + " type arguments";
                    }
                    tal.addError("wrong number of type arguments: '" +
                            dec.getName(unit) + "'" + help);
                }
                return false;
            }
        }
        else {
            boolean empty = typeArguments.isEmpty();
            if (!empty) {
                tal.addError("does not accept type arguments: '" +
                        dec.getName(unit) + "'");
            }
            return empty;
        }
    }

    public static boolean argumentSatisfiesEnumeratedConstraint(ProducedType receiver,
            Declaration member, List<ProducedType> typeArguments, ProducedType argType,
            TypeParameter param) {
       
        List<ProducedType> caseTypes = param.getCaseTypes();
        if (caseTypes==null || caseTypes.isEmpty()) {
            //no enumerated constraint
            return true;
        }
       
        //if the type argument is a subtype of one of the cases
        //of the type parameter then the constraint is satisfied
        for (ProducedType ct: caseTypes) {
            ProducedType cts = ct.getProducedType(receiver, member, typeArguments);
            if (argType.isSubtypeOf(cts)) {
                return true;
            }
        }

        //if the type argument is itself a type parameter with
        //an enumerated constraint, and every enumerated case
        //is a subtype of one of the cases of the type parameter,
        //then the constraint is satisfied
        if (argType.getDeclaration() instanceof TypeParameter) {
            List<ProducedType> argCaseTypes = argType.getDeclaration().getCaseTypes();
            if (argCaseTypes!=null && !argCaseTypes.isEmpty()) {
                for (ProducedType act: argCaseTypes) {
                    boolean foundCase = false;
                    for (ProducedType ct: caseTypes) {
                        ProducedType cts = ct.getProducedType(receiver, member, typeArguments);
                        if (act.isSubtypeOf(cts)) {
                            foundCase = true;
                            break;
                        }
                    }
                    if (!foundCase) {
                        return false;
                    }
                }
                return true;
            }
        }

        return false;
    }

    private void visitExtendedOrAliasedType(Tree.SimpleType et,
            Tree.InvocationExpression ie) {
        if (et!=null && ie!=null) {
            ProducedType type = et.getTypeModel();
            if (type!=null) {
                Tree.Primary pr = ie.getPrimary();
                if (pr instanceof Tree.InvocationExpression) {
                    Tree.InvocationExpression iie = (Tree.InvocationExpression) pr;
                    pr = iie.getPrimary();
                }
                if (pr instanceof Tree.ExtendedTypeExpression) {
                    Tree.ExtendedTypeExpression ete = (Tree.ExtendedTypeExpression) pr;
                    ete.setDeclaration(et.getDeclarationModel());
                    ete.setTarget(type);
                    ProducedType qt = type.getQualifyingType();
                    ProducedType ft = type.getFullType();
                    if (ete.getStaticMethodReference()) {
                        ft = producedType(unit.getCallableDeclaration(), ft,
                                producedType(unit.getTupleDeclaration(), qt, qt,
                                        unit.getType(unit.getEmptyDeclaration())));
                    }
                    pr.setTypeModel(ft);
                }
            }
        }
    }
   
    @Override
    public void visit(Tree.ClassSpecifier that) {
        Tree.InvocationExpression ie = that.getInvocationExpression();
        visitExtendedOrAliasedType(that.getType(), ie);
       
        inExtendsClause = true;
        super.visit(that);
        inExtendsClause = false;
       
        //Dupe check:
        /*if (ie!=null &&
                ie.getPrimary() instanceof Tree.MemberOrTypeExpression) {
            checkOverloadedReference((Tree.MemberOrTypeExpression) ie.getPrimary());
        }*/
    }

    @Override
    public void visit(Tree.ExtendedType that) {
        visitExtendedOrAliasedType(that.getType(),
                that.getInvocationExpression());
       
        inExtendsClause = true;
        super.visit(that);
        inExtendsClause = false;
               
        TypeDeclaration td = (TypeDeclaration) that.getScope();
        if (td.isAlias()) {
            return;
        }
        Tree.SimpleType et = that.getType();
        if (et!=null) {
            ProducedType type = et.getTypeModel();
            if (type!=null) {
                checkSelfTypes(et, td, type);
                checkExtensionOfMemberType(et, td, type);
                //checkCaseOfSupertype(et, td, type);
                if (!td.isAlias()) {
                    TypeDeclaration etd = td.getExtendedTypeDeclaration();
                    while (etd!=null && etd.isAlias()) {
                        etd = etd.getExtendedTypeDeclaration();
                    }
                    if (etd!=null) {
                        if (etd.isFinal()) {
                            et.addError("extends a final class: '" +
                                    etd.getName(unit) + "'");
                        }
                      if (etd.isSealed() && !inSameModule(etd)) {
                        et.addError("extends a sealed class in a different module: '" +
                            etd.getName(unit) + "' in '" +
                            etd.getUnit().getPackage().getModule().getNameAsString() + "'");
                      }
                    }
                }
//                if (td.isParameterized() &&
//                        type.getDeclaration().inherits(unit.getExceptionDeclaration())) {
//                    et.addUnsupportedError("generic exception types not yet supported");
//                }
            }
            checkSupertypeVarianceAnnotations(et);
        }
    }

    private void checkSupertypeVarianceAnnotations(Tree.SimpleType et) {
        Tree.TypeArgumentList tal = et.getTypeArgumentList();
        if (tal!=null) {
            for (Tree.Type t: tal.getTypes()) {
                if (t instanceof Tree.StaticType) {
                    TypeVariance variance = ((Tree.StaticType) t).getTypeVariance();
                    if (variance!=null) {
                        variance.addError("supertype expression may not specify variance");
                    }
                }
            }
        }
    }

  private boolean inSameModule(TypeDeclaration etd) {
      return etd.getUnit().getPackage().getModule()
          .equals(unit.getPackage().getModule());
    }

    @Override
    public void visit(Tree.SatisfiedTypes that) {
        super.visit(that);
        TypeDeclaration td = (TypeDeclaration) that.getScope();
        if (td.isAlias()) {
            return;
        }
        Set<TypeDeclaration> set = new HashSet<TypeDeclaration>();
        if (td.getSatisfiedTypes().isEmpty()) return; //handle undecidability case
        for (Tree.StaticType t: that.getTypes()) {
            ProducedType type = t.getTypeModel();
            if (type!=null && type.getDeclaration()!=null) {
                type = type.resolveAliases();
                TypeDeclaration std = type.getDeclaration();
                if (td instanceof ClassOrInterface &&
                        !inLanguageModule(that.getUnit())) {
                    if (unit.isCallableType(type)) {
                        t.addError("satisfies 'Callable'");
                    }
                    if (type.getDeclaration().equals(unit.getConstrainedAnnotationDeclaration())) {
                        t.addError("directly satisfies 'ConstrainedAnnotation'");
                    }
                }
                if (!set.add(type.getDeclaration())) {
                    //this error is not really truly necessary
                    //but the spec says it is an error, and
                    //the backend doesn't like it
                    t.addError("duplicate satisfied type: '" +
                            type.getDeclaration().getName(unit) +
                            "' of '" + td.getName() + "'");
                }
              if (td instanceof ClassOrInterface &&
                  std.isSealed() && !inSameModule(std)) {
              t.addError("satisfies a sealed interface in a different module: '" +
                  std.getName(unit) + "' in '" +
                  std.getUnit().getPackage().getModule().getNameAsString() + "'");
              }
                checkSelfTypes(t, td, type);
                checkExtensionOfMemberType(t, td, type);
                /*if (!(td instanceof TypeParameter)) {
                    checkCaseOfSupertype(t, td, type);
                }*/
            }
            if (t instanceof Tree.SimpleType) {
                checkSupertypeVarianceAnnotations((Tree.SimpleType) t);
            }
        }
        //Moved to RefinementVisitor, which
        //handles this kind of stuff:
        /*if (td instanceof TypeParameter) {
            List<ProducedType> list = new ArrayList<ProducedType>();
            for (ProducedType st: td.getSatisfiedTypes()) {
                addToIntersection(list, st, unit);
            }
            IntersectionType it = new IntersectionType(unit);
            it.setSatisfiedTypes(list);
            if (it.getType().getDeclaration() instanceof NothingType) {
                that.addError("upper bound constraints cannot be satisfied by any type except Nothing");
            }
        }*/
    }

    /*void checkCaseOfSupertype(Tree.StaticType t, TypeDeclaration td,
            ProducedType type) {
        //TODO: I think this check is a bit too restrictive, since
        //      it doesn't allow intermediate types between the
        //      enumerated type and the case type, but since the
        //      similar check below doesn't work, we need it
        if (type.getDeclaration().getCaseTypes()!=null) {
            for (ProducedType ct: type.getDeclaration().getCaseTypes()) {
                if (ct.substitute(type.getTypeArguments())
                        .isExactly(td.getType())) {
                    return;
                }
            }
            t.addError("not a case of supertype: " +
                    type.getDeclaration().getName(unit));
        }
    }*/

    @Override
    public void visit(Tree.CaseTypes that) {
        super.visit(that);
        //this forces every case to be a subtype of the
        //enumerated type, so that we can make use of the
        //enumerated type is equivalent to its cases
        TypeDeclaration td = (TypeDeclaration) that.getScope();
       
        //TODO: get rid of this awful hack:
        List<ProducedType> cases = td.getCaseTypes();
        td.setCaseTypes(null);
       
        if (td instanceof TypeParameter) {
            for (Tree.StaticType t: that.getTypes()) {
                for (Tree.StaticType ot: that.getTypes()) {
                    if (t==ot) break;
                    checkCasesDisjoint(t.getTypeModel(), ot.getTypeModel(), ot);
                }
            }
        }
        else {
            Set<TypeDeclaration> typeSet = new HashSet<TypeDeclaration>();
            for (Tree.StaticType st: that.getTypes()) {
                ProducedType type = st.getTypeModel();
                TypeDeclaration ctd = type.getDeclaration();
                if (type!=null && ctd!=null) {
                    type = type.resolveAliases();
                    ctd = type.getDeclaration();
                    if (!typeSet.add(ctd)) {
                        //this error is not really truly necessary
                        st.addError("duplicate case type: '" +
                                ctd.getName(unit) +
                                "' of '" + td.getName() + "'");
                    }
                    if (!(ctd instanceof TypeParameter)) {
                        //it's not a self type
                        if (checkDirectSubtype(td, st, type)) {
                            checkAssignable(type, td.getType(), st,
                                    getCaseTypeExplanation(td, type));
                        }
                        //note: this is a better, faster way to call
                        //      validateEnumeratedSupertypeArguments()
                        //      but unfortunately it winds up displaying
                        //      the error on the wrong node, confusing
                        //      the user
                        /*ProducedType supertype = type.getDeclaration().getType().getSupertype(td);
                        validateEnumeratedSupertypeArguments(t, type.getDeclaration(), supertype);*/
                    }
                    if (ctd instanceof ClassOrInterface && st instanceof Tree.SimpleType) {
                        Tree.TypeArgumentList tal = ((Tree.SimpleType) st).getTypeArgumentList();
                        if (tal!=null) {
                            List<Tree.Type> args = tal.getTypes();
                            List<TypeParameter> typeParameters = ctd.getTypeParameters();
                            for (int i=0; i<args.size() && i<typeParameters.size(); i++) {
                                Tree.Type arg = args.get(i);
                                TypeParameter typeParameter = ctd.getTypeParameters().get(i);
                                ProducedType argType = arg.getTypeModel();
                                if (argType!=null) {
                                    TypeDeclaration argTypeDec = argType.getDeclaration();
                                    if (argTypeDec instanceof TypeParameter) {
                                        if (!((TypeParameter) argTypeDec).getDeclaration().equals(td)) {
                                            arg.addError("type argument is not a type parameter of the enumerated type: '" +
                                                    argTypeDec.getName() + "' is not a type parameter of '" + td.getName());
                                        }
                                    }
                                    else if (typeParameter.isCovariant()) {
                                        checkAssignable(typeParameter.getType(), argType, arg,
                                                "type argument not an upper bound of the type parameter");
                                    }
                                    else if (typeParameter.isContravariant()) {
                                        checkAssignable(argType, typeParameter.getType(), arg,
                                                "type argument not a lower bound of the type parameter");
                                    }
                                    else {
                                        arg.addError("type argument is not a type parameter of the enumerated type: '" +
                                                argTypeDec.getName() + "'");
                                    }
                                }
                            }
                        }
                    }
                }
            }
            Set<Declaration> valueSet = new HashSet<Declaration>();
            for (Tree.BaseMemberExpression bme: that.getBaseMemberExpressions()) {
                ProducedType type = bme.getTypeModel();
                Declaration d = bme.getDeclaration();
                if (d!=null && !valueSet.add(d)) {
                    //this error is not really truly necessary
                    bme.addError("duplicate case: '" +
                            d.getName(unit) +
                            "' of '" + td.getName() + "'");
                }
                if (d!=null && type!=null &&
                        !type.getDeclaration().isAnonymous()) {
                    bme.addError("case must be a toplevel anonymous class: '" +
                            d.getName(unit) + "' is not an anonymous class");
                }
                else if (d!=null && !d.isToplevel()) {
                    bme.addError("case must be a toplevel anonymous class: '" +
                            d.getName(unit) + "' is not toplevel");
                }
                if (type!=null) {
                    if (checkDirectSubtype(td, bme, type)) {
                        checkAssignable(type, td.getType(), bme,
                                getCaseTypeExplanation(td, type));
                    }
                }
            }
        }
       
        //TODO: get rid of this awful hack:
        td.setCaseTypes(cases);
    }

    private static boolean checkDirectSubtype(TypeDeclaration td, Node node,
            ProducedType type) {
        boolean found = false;
        TypeDeclaration ctd = type.getDeclaration();
        if (td instanceof Interface) {
            for (ProducedType st: ctd.getSatisfiedTypes()) {
                if (st!=null &&
                        st.resolveAliases().getDeclaration().equals(td)) {
                    found = true;
                }
            }
        }
        else if (td instanceof Class) {
            ProducedType et = ctd.getExtendedType();
            if (et!=null &&
                    et.resolveAliases().getDeclaration().equals(td)) {
                found = true;
            }
        }
        if (!found) {
            node.addError("case type is not a direct subtype of enumerated type: " +
                    ctd.getName(node.getUnit()));
        }
        return found;
    }

    private String getCaseTypeExplanation(TypeDeclaration td,
            ProducedType type) {
        String message = "case type must be a subtype of enumerated type";
        if (!td.getTypeParameters().isEmpty() &&
                type.getDeclaration().inherits(td)) {
            message += " for every type argument of the generic enumerated type";
        }
        return message;
    }

    private void checkCasesDisjoint(ProducedType type, ProducedType other,
            Node ot) {
        if (!isTypeUnknown(type) && !isTypeUnknown(other)) {
            if (!intersectionType(type.resolveAliases(), other.resolveAliases(), unit).isNothing()) {
                ot.addError("cases are not disjoint: '" +
                        type.getProducedTypeName(unit) + "' and '" +
                        other.getProducedTypeName(unit) + "'");
            }
        }
    }

    private void checkExtensionOfMemberType(Node that, TypeDeclaration td,
            ProducedType type) {
        ProducedType qt = type.getQualifyingType();
        if (qt!=null && td instanceof ClassOrInterface &&
            !type.getDeclaration().isStaticallyImportable()) {
            Scope s = td;
            while (s!=null) {
                s = s.getContainer();
                if (s instanceof TypeDeclaration) {
                  TypeDeclaration otd = (TypeDeclaration) s;
                    if (otd.getType().isSubtypeOf(qt)) {
                        return;
                    }
                }
            }
            that.addError("qualifying type '" + qt.getProducedTypeName(unit) +
                    "' of supertype '" + type.getProducedTypeName(unit) +
                    "' is not an outer type or supertype of any outer type of '" +
                    td.getName(unit) + "'");
        }
    }
   
    private void checkSelfTypes(Tree.StaticType that, TypeDeclaration td, ProducedType type) {
        if (!(td instanceof TypeParameter)) { //TODO: is this really ok?!
            List<TypeParameter> params = type.getDeclaration().getTypeParameters();
            List<ProducedType> args = type.getTypeArgumentList();
            for (int i=0; i<params.size(); i++) {
                TypeParameter param = params.get(i);
                if ( param.isSelfType() && args.size()>i ) {
                    ProducedType arg = args.get(i);
                    if (arg==null) arg = new UnknownType(unit).getType();
                    TypeDeclaration std = param.getSelfTypedDeclaration();
                    ProducedType at;
                    TypeDeclaration mtd;
                    if (param.getContainer().equals(std)) {
                        at = td.getType();
                        mtd = td;
                    }
                    else {
                        //TODO: lots wrong here?
                        mtd = (TypeDeclaration) td.getMember(std.getName(), null, false);
                        at = mtd==null ? null : mtd.getType();
                    }
                    if (at!=null && !at.isSubtypeOf(arg) &&
                            !(mtd.getSelfType()!=null &&
                                mtd.getSelfType().isExactly(arg))) {
                        String help;
                        TypeDeclaration ad = arg.getDeclaration();
                        if (ad instanceof TypeParameter &&
                                ((TypeParameter) ad).getDeclaration().equals(td)) {
                            help = " (try making " + ad.getName() + " a self type of " + td.getName() + ")";
                        }
                        else if (ad instanceof Interface) {
                            help = " (try making " + td.getName() + " satisfy " + ad.getName() + ")";
                        }
                        else if (ad instanceof Class && td instanceof Class) {
                            help = " (try making " + td.getName() + " extend " + ad.getName() + ")";
                        }
                        else {
                            help = "";
                        }
                        that.addError("type argument does not satisfy self type constraint on type parameter '" +
                                param.getName() + "' of '" + type.getDeclaration().getName(unit) + "': '" +
                                arg.getProducedTypeName(unit) + "' is not a supertype or self type of '" +
                                td.getName(unit) + "'" + help);
                    }
                }
            }
        }
    }

    private void validateEnumeratedSupertypes(Node that, ClassOrInterface d) {
        ProducedType type = d.getType();
        for (ProducedType supertype: type.getSupertypes()) {
            if (!type.isExactly(supertype)) {
                TypeDeclaration std = supertype.getDeclaration();
                if (std.getCaseTypes()!=null && !std.getCaseTypes().isEmpty()) {
                    if (std.getCaseTypes().size()==1 &&
                            std.getCaseTypeDeclarations().get(0).isSelfType()) {
                        continue;
                    }
                    List<ProducedType> types =
                            new ArrayList<ProducedType>(std.getCaseTypes().size());
                    for (ProducedType ct: std.getCaseTypes()) {
                        ProducedType cst = type.getSupertype(ct.resolveAliases().getDeclaration());
                        if (cst!=null) {
                            types.add(cst);
                        }
                    }
                    if (types.isEmpty()) {
                        that.addError("not a subtype of any case of enumerated supertype: '" +
                                d.getName(unit) + "' is a subtype of '" + std.getName(unit) + "'");
                    }
                    else if (types.size()>1) {
                        StringBuilder sb = new StringBuilder();
                        for (ProducedType pt: types) {
                            sb.append("'").append(pt.getProducedTypeName(unit)).append("' and ");
                        }
                        sb.setLength(sb.length()-5);
                        that.addError("concrete type is a subtype of multiple cases of enumerated supertype '" +
                                std.getName(unit) + "': '" + d.getName(unit) + "' is a subtype of " + sb);
                    }
                }
            }
        }
    }

    private void validateEnumeratedSupertypeArguments(Node that, ClassOrInterface d) {
        //note: I hate doing the whole traversal here, but it is the
        //      only way to get the error in the right place (see
        //      the note in visit(CaseTypes) for more)
        ProducedType type = d.getType();
        for (ProducedType supertype: type.getSupertypes()) { //traverse the entire supertype hierarchy of the declaration
            if (!type.isExactly(supertype)) {
                List<TypeDeclaration> ctds = supertype.getDeclaration().getCaseTypeDeclarations();
                if (ctds!=null) {
                    for (TypeDeclaration ct: ctds) {
                        if (ct.equals(d)) { //the declaration is a case of the current enumerated supertype
                            validateEnumeratedSupertypeArguments(that, d, supertype);
                            break;
                        }
                    }
                }
            }
        }
    }

    private void validateEnumeratedSupertypeArguments(Node that, TypeDeclaration d,
            ProducedType supertype) {
        for (TypeParameter p: supertype.getDeclaration().getTypeParameters()) {
            ProducedType arg = supertype.getTypeArguments().get(p); //the type argument that the declaration (indirectly) passes to the enumerated supertype
            if (arg!=null) {
                validateEnumeratedSupertypeArgument(that, d, supertype, p, arg);
            }
        }
    }

    private void validateEnumeratedSupertypeArgument(Node that, TypeDeclaration d,
            ProducedType supertype, TypeParameter p, ProducedType arg) {
        TypeDeclaration td = arg.getDeclaration();
        if (td instanceof TypeParameter) {
            TypeParameter tp = (TypeParameter) td;
            if (tp.getDeclaration().equals(d)) { //the argument is a type parameter of the declaration
                //check that the variance of the argument type parameter is
                //the same as the type parameter of the enumerated supertype
                if (p.isCovariant() && !tp.isCovariant()) {
                    that.addError("argument to covariant type parameter of enumerated supertype must be covariant: " +
                            typeDescription(p, unit));
                }
                if (p.isContravariant() && !tp.isContravariant()) {
                    that.addError("argument to contravariant type parameter of enumerated supertype must be contravariant: " +
                            typeDescription(p, unit));
                }
            }
            else {
                that.addError("argument to type parameter of enumerated supertype must be a type parameter of '" +
                        d.getName() + "': " + typeDescription(p, unit));
            }
        }
        else if (p.isCovariant()) {
            if (!(td instanceof NothingType)) {
                //TODO: let it be the union of the lower bounds on p
                that.addError("argument to covariant type parameter of enumerated supertype must be a type parameter or 'Nothing': " +
                        typeDescription(p, unit));
            }
        }
        else if (p.isContravariant()) {
            List<ProducedType> sts = p.getSatisfiedTypes();
            //TODO: do I need to do type arg substitution here??
            ProducedType ub = formIntersection(sts);
            if (!(arg.isExactly(ub))) {
                that.addError("argument to contravariant type parameter of enumerated supertype must be a type parameter or '" +
                        typeNamesAsIntersection(sts, unit) + "': " +
                        typeDescription(p, unit));
            }
        }
        else {
            that.addError("argument to type parameter of enumerated supertype must be a type parameter: " +
                    typeDescription(p, unit));
        }
    }
   
    @Override public void visit(Tree.Term that) {
        super.visit(that);
        if (that.getTypeModel()==null) {
            that.setTypeModel( defaultType() );
        }
    }

    @Override public void visit(Tree.Type that) {
        super.visit(that);
        if (that.getTypeModel()==null) {
            that.setTypeModel( defaultType() );
        }
    }

    private ProducedType defaultType() {
        TypeDeclaration ut = new UnknownType(unit);
        Class ad = unit.getAnythingDeclaration();
        if (ad!=null) {
            ut.setExtendedType(ad.getType());
        }
        return ut.getType();
    }
   
    @Override
    public void visit(Tree.PackageLiteral that) {
        super.visit(that);
        Package p;
        if (that.getImportPath()==null) {
            that.setImportPath(new ImportPath(null));
            p = unit.getPackage();
        }
        else {
            p = TypeVisitor.getPackage(that.getImportPath());
        }
        that.getImportPath().setModel(p);
        that.setTypeModel(unit.getPackageDeclarationType());
    }
   
    @Override
    public void visit(Tree.ModuleLiteral that) {
        super.visit(that);
        Module m;
        if (that.getImportPath()==null) {
            that.setImportPath(new ImportPath(null));
            m = unit.getPackage().getModule();
        }
        else {
            m = TypeVisitor.getModule(that.getImportPath());
        }
        that.getImportPath().setModel(m);
        that.setTypeModel(unit.getModuleDeclarationType());
    }
   
    private boolean declarationLiteral = false;
   
    @Override
    public void visit(Tree.TypeArgumentList that) {
        if (declarationLiteral) {
            that.addError("declaration reference may not specify type arguments");
        }
        super.visit(that);
    }
   
    @Override
    public void visit(Tree.TypeLiteral that) {
        if (that instanceof Tree.InterfaceLiteral||
            that instanceof Tree.ClassLiteral||
            that instanceof Tree.AliasLiteral||
            that instanceof Tree.TypeParameterLiteral) {
            declarationLiteral = true;
        }
        try {
            super.visit(that);
        }
        finally {
            declarationLiteral = false;
        }
        ProducedType t;
        TypeDeclaration d;
        Tree.StaticType type = that.getType();
        Tree.BaseMemberExpression oe = that.getObjectExpression();
        Node errorNode;
    if (type != null) {
          t = type.getTypeModel();
          d = t.getDeclaration();
          errorNode = type;
        }
    else if (oe != null ) {
          t = oe.getTypeModel();
          d = t.getDeclaration();
          if (!d.isAnonymous()) {
            oe.addError("must be a reference to an anonymous class");
          }
          errorNode = oe;
        }
    else {
        errorNode = that;
            ClassOrInterface classOrInterface =
                    getContainingClassOrInterface(that.getScope());
        if (that instanceof Tree.ClassLiteral ||
            that instanceof Tree.InterfaceLiteral) {
            d = classOrInterface;
            if (d==null) {
                errorNode.addError("no containing type");
                return; //EARLY EXIT!!
            }
            else {
                t = classOrInterface.getType();
            }
        }
        else {
            errorNode.addError("missing type reference");
            return; //EARLY EXIT!!
        }
    }
        // FIXME: should we disallow type parameters in there?
        if (t!=null) {
            that.setDeclaration(d);
            that.setWantsDeclaration(true);
            if (that instanceof Tree.ClassLiteral) {
                if (!(d instanceof Class)) {
                    if (d != null) {
                        errorNode.addError("referenced declaration is not a class" +
                                getDeclarationReferenceSuggestion(d));
                    }
                }
                that.setTypeModel(unit.getClassDeclarationType());
            }
            else if (that instanceof Tree.InterfaceLiteral) {
                if (!(d instanceof Interface)) {
                    if (d!=null) {
                        errorNode.addError("referenced declaration is not an interface" +
                                getDeclarationReferenceSuggestion(d));
                    }
                }
                that.setTypeModel(unit.getInterfaceDeclarationType());
            }
            else if (that instanceof Tree.AliasLiteral) {
                if (!(d instanceof TypeAlias)) {
                    errorNode.addError("referenced declaration is not a type alias" +
                            getDeclarationReferenceSuggestion(d));
                }
                that.setTypeModel(unit.getAliasDeclarationType());
            }
            else if (that instanceof Tree.TypeParameterLiteral) {
                if (!(d instanceof TypeParameter)) {
                    errorNode.addError("referenced declaration is not a type parameter" +
                            getDeclarationReferenceSuggestion(d));
                }
                that.setTypeModel(unit.getTypeParameterDeclarationType());
            }
            else if (d != null) {
                that.setWantsDeclaration(false);
                t = t.resolveAliases();
                //checkNonlocalType(that.getType(), t.getDeclaration());
                if (d instanceof Class) {
//                    checkNonlocal(that, t.getDeclaration());
                    if (((Class) d).isAbstraction()) {
                        errorNode.addError("class constructor is overloaded");
                    }
                    else {
                        that.setTypeModel(unit.getClassMetatype(t));
                    }
                }
                else if (d instanceof Interface) {
                    that.setTypeModel(unit.getInterfaceMetatype(t));
                }
                else {
                    that.setTypeModel(unit.getTypeMetaType(t));
                }
            }
        }
    }
   
    @Override
    public void visit(Tree.MemberLiteral that) {
        if (that instanceof Tree.FunctionLiteral ||
            that instanceof Tree.ValueLiteral) {
            declarationLiteral = true;
        }
        try {
            super.visit(that);
        }
        finally {
            declarationLiteral = false;
        }
        Tree.Identifier id = that.getIdentifier();
        if (id!=null) {
            String name = name(id);
            ProducedType qt = null;
            TypeDeclaration qtd = null;
            Tree.StaticType type = that.getType();
            Tree.BaseMemberExpression oe = that.getObjectExpression();
      if (type != null) {
              qt = type.getTypeModel();
              qtd = qt.getDeclaration();
            }
      else if (oe != null) {
              qt = oe.getTypeModel();
              qtd = qt.getDeclaration();
              if (!qtd.isAnonymous()) {
                oe.addError("must be a reference to an anonymous class");
              }
            }
            if (qt != null) {
              qt = qt.resolveAliases();
              if (qtd instanceof UnknownType) {
                // let it go, we already logged an error for the missing type
                return;
              }
              //checkNonlocalType(that.getType(), qtd);
              String container = "type '" + qtd.getName(unit) + "'";
              TypedDeclaration member =
                      getTypedMember(qtd, name, null, false, unit);
              if (member==null) {
                if (qtd.isMemberAmbiguous(name, unit, null, false)) {
                  that.addError("method or attribute is ambiguous: '" +
                      name + "' for " + container);
                }
                else {
                  that.addError("method or attribute does not exist: '" +
                      name + "' in " + container);
                }
              }
              else {
                checkQualifiedVisibility(that, member, name, container, false);
                setMemberMetatype(that, member);
              }
            }
            else {
                TypedDeclaration result =
                        getTypedDeclaration(that.getScope(),
                                name, null, false, unit);
                if (result!=null) {
                    checkBaseVisibility(that, result, name);
                    setMemberMetatype(that, result);
                }
                else {
                    that.addError("function or value does not exist: '" +
                            name(id) + "'", 100);
                    unit.getUnresolvedReferences().add(id);
                }
            }
        }
    }

    private void setMemberMetatype(Tree.MemberLiteral that, TypedDeclaration result) {
        that.setDeclaration(result);
        if (that instanceof Tree.ValueLiteral) {
            if (result instanceof Value) {
                checkNonlocal(that, result);
            }
            else {
                that.getIdentifier().addError("referenced declaration is not a value" +
                        getDeclarationReferenceSuggestion(result));
            }
            if (that.getBroken()) {
                that.addError("keyword object may not appear here: " +
                              "use the value keyword to refer to anonymous class declarations");
            }
            that.setWantsDeclaration(true);
            that.setTypeModel(unit.getValueDeclarationType(result));
        }
        else if (that instanceof Tree.FunctionLiteral) {
            if (result instanceof Method) {
                checkNonlocal(that, result);
            }
            else {
                that.getIdentifier().addError("referenced declaration is not a function" +
                        getDeclarationReferenceSuggestion(result));
            }
            that.setWantsDeclaration(true);
            that.setTypeModel(unit.getFunctionDeclarationType());
        }
        else {
            checkNonlocal(that, result);
            setMetamodelType(that, result);
        }
    }

    private String getDeclarationReferenceSuggestion(Declaration result) {
        String name = ": " + result.getName(unit);
        if (result instanceof Method) {
            return name + " is a function";
        }
        else if (result instanceof Value) {
            return name + " is a value";
        }
        else if (result instanceof Class) {
            return name + " is a class";
        }
        else if (result instanceof Interface) {
            return name + " is an interface";
        }
        else if (result instanceof TypeAlias) {
            return name + " is a type alias";
        }
        else if (result instanceof TypeParameter) {
            return name + " is a type parameter";
        }
        return "";
    }

    private void setMetamodelType(Tree.MemberLiteral that, Declaration result) {
        ProducedType outerType;
        if (result.isClassOrInterfaceMember()) {
            outerType = that.getType()==null ?
                    that.getScope().getDeclaringType(result) :
                    that.getType().getTypeModel();           
        }
        else {
            outerType = null;
        }
        if (result instanceof Method) {
            Method method = (Method) result;
            if (method.isAbstraction()) {
                that.addError("method is overloaded");
            }
            else {
                Tree.TypeArgumentList tal = that.getTypeArgumentList();
                if (explicitTypeArguments(method, tal)) {
                    List<ProducedType> ta = getTypeArguments(tal,
                        getTypeParameters(method), outerType);
                    if (tal != null) {
                        tal.setTypeModels(ta);
                    }
                    if (acceptsTypeArguments(outerType, method, ta, tal, that, false)) {
                        ProducedTypedReference pr = outerType==null ?
                                method.getProducedTypedReference(null, ta) :
                                    outerType.getTypedMember(method, ta);
                                that.setTarget(pr);
                                that.setTypeModel(unit.getFunctionMetatype(pr));
                    }
                }
                else {
                    that.addError("missing type arguments to: '" + method.getName(unit) + "'");
                }
            }
        }
        else if (result instanceof Value) {
            Value value = (Value) result;
            if (that.getTypeArgumentList() != null) {
                that.addError("does not accept type arguments: '" + result.getName(unit) + "'");
            }
            else {
                ProducedTypedReference pr = value.getProducedTypedReference(outerType,
                        Collections.<ProducedType>emptyList());
                that.setTarget(pr);
                that.setTypeModel(unit.getValueMetatype(pr));
            }
        }
    }

    private void checkNonlocal(Node that, Declaration declaration) {
        if ((!declaration.isClassOrInterfaceMember() || !declaration.isShared())
                    && !declaration.isToplevel()) {
            that.addError("metamodel reference to local declaration");
        }
    }
   
    /*private void checkNonlocalType(Node that, TypeDeclaration declaration) {
        if (declaration instanceof UnionType) {
            for (TypeDeclaration ctd: declaration.getCaseTypeDeclarations()) {
                checkNonlocalType(that, ctd);
            }
        }
        if (declaration instanceof IntersectionType) {
            for (TypeDeclaration std: declaration.getSatisfiedTypeDeclarations()) {
                checkNonlocalType(that, std);
            }
        }
        else if (declaration instanceof ClassOrInterface &&
                (!declaration.isClassOrInterfaceMember()||!declaration.isShared())
                        && !declaration.isToplevel()) {
            that.addWarning("metamodel reference to local type not yet supported");
        }
        else if (declaration.getContainer() instanceof TypeDeclaration) {
            checkNonlocalType(that, (TypeDeclaration) declaration.getContainer());
        }
    }*/
   
    private Declaration handleAbstraction(Declaration dec, Tree.MemberOrTypeExpression that) {
        //NOTE: if this is the qualifying type of a static method
        //      reference, don't do anything special here, since
        //      we're not actually calling the constructor
        if (!that.getStaticMethodReferencePrimary() &&
                isAbstraction(dec)) {
            //handle the case where it's not _really_ overloaded,
            //it's just a constructor with a different visibility
            //to the class itself
          List<Declaration> overloads = ((Functional) dec).getOverloads();
          if (overloads.size()==1) {
            return overloads.get(0);
          }
        }
        return dec;
    }
   
}
TOP

Related Classes of com.redhat.ceylon.compiler.typechecker.analyzer.ExpressionVisitor

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.