public class JvmBackendUtil {
    public static boolean isInitialLowerCase(String name) {
        return !name.isEmpty() && isLowerCase(name.codePointAt(0));
    }

    public static boolean isLowerCase(int codepoint) {
        return Character.isLowerCase(codepoint) || codepoint == '_';
    }

    public static String getName(List<String> parts){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.size(); i++) {
            sb.append(parts.get(i));
            if (i < parts.size() - 1) {
                sb.append('.');
            }
        }
        return sb.toString();
    }

    public static String getMirrorName(AnnotatedMirror mirror) {
        String name;
        AnnotationMirror annot = mirror.getAnnotation(AbstractModelLoader.CEYLON_NAME_ANNOTATION);
        if (annot != null) {
            name = (String)annot.getValue();
        } else {
            name = mirror.getName();
            name = name.isEmpty() ? name : NamingBase.stripLeadingDollar(name);
            if (mirror instanceof ClassMirror
                    && JvmBackendUtil.isInitialLowerCase(name)
                    && name.endsWith("_")
                    && mirror.getAnnotation(AbstractModelLoader.CEYLON_CEYLON_ANNOTATION) != null) {
                name = name.substring(0, name.length()-1);
            }
        }
        return name;
    }

    public static boolean isSubPackage(String moduleName, String pkgName) {
        return pkgName.equals(moduleName)
                || pkgName.startsWith(moduleName+".");
    }

    /**
     * Removes the given character from the given string. More efficient than using String.replace
     * which uses regexes.
     */
    public static String removeChar(char c, String string) {
        int nextChar = string.indexOf(c);
        if(nextChar == -1)
            return string;
        int start = 0;
        StringBuilder ret = new StringBuilder(string.length()-1);// we remove at least one
        while(nextChar != -1){
            ret.append(string, start, nextChar);
            start = nextChar+1;
            nextChar = string.indexOf(c, start);
        }
        // don't forget the end part
        ret.append(string, start, string.length());
        return ret.toString();
    }

    public static String strip(String name, boolean isCeylon, boolean isShared) {
        String stripped = NamingBase.stripLeadingDollar(name);
        String privSuffix = NamingBase.Suffix.$priv$.name();
        if(isCeylon && !isShared && name.endsWith(privSuffix))
            return stripped.substring(0, stripped.length() - privSuffix.length());
        return stripped;
    }

    /**
     * Determines whether the declaration is a non-transient non-parameter value 
     * (not a getter)
     * @param decl The declaration
     * @return true if the declaration is a value
     */
    public static boolean isValue(Declaration decl) {
        return (decl instanceof Value)
                && !((Value)decl).isParameter()
                && !((Value)decl).isTransient();
    }

    /**
     * Determines whether the declaration's is a method
     * @param decl The declaration
     * @return true if the declaration is a method
     */
    public static boolean isMethod(Declaration decl) {
        return (decl instanceof Function)
                && !((Function)decl).isParameter();
    }

    public static boolean isCeylon(TypeDeclaration declaration) {
        return ModelUtil.isCeylonDeclaration(declaration);
    }

    public static Declaration getTopmostRefinedDeclaration(Declaration decl){
        return getTopmostRefinedDeclaration(decl, null);
    }

    public static Declaration getTopmostRefinedDeclaration(Declaration decl, Map<Function, Function> methodOverrides){
        if (decl instanceof FunctionOrValue
                && ((FunctionOrValue)decl).isParameter()
                && decl.getContainer() instanceof Class) {
            // Parameters in a refined class are not considered refinements themselves
            // We have in find the refined attribute
            Class c = (Class)decl.getContainer();
            boolean isAlias = c.isAlias();
            boolean isActual = c.isActual();
            // aliases and actual classes actually do refine their extended type parameters so the same rules apply wrt
            // boxing and stuff
            if (isAlias || isActual) {
                Functional ctor = null;
                int index = c.getParameterList().getParameters().indexOf(findParamForDecl(((TypedDeclaration)decl)));
                // ATM we only consider aliases if we're looking at aliases, and same for actual, not mixing the two.
                // Note(Stef): not entirely sure about that one, what about aliases of actual classes?
                while ((isAlias && c.isAlias())
                        || (isActual && c.isActual())) {
                    ctor = (isAlias && c.isAlias()) ? (Functional)((ClassAlias)c).getConstructor() : c;
                    Type et = c.getExtendedType();
                    c = et!=null && et.isClass() ? (Class)et.getDeclaration() : null;
                    // handle compile errors
                    if(c == null)
                        return null;
                }
                if (isActual) {
                    ctor = c;
                }
                // be safe
                if(ctor == null 
                        || ctor.getParameterLists() == null
                        || ctor.getParameterLists().isEmpty()
                        || ctor.getFirstParameterList() == null
                        || ctor.getFirstParameterList().getParameters() == null
                        || ctor.getFirstParameterList().getParameters().size() <= index)
                    return null;
                decl = ctor.getFirstParameterList().getParameters().get(index).getModel();
            }
            if (decl.isShared()) {
                Declaration refinedDecl = c.getRefinedMember(decl.getName(), getSignature(decl), false);//?? ellipsis=false??
                if(refinedDecl != null && !ModelUtil.equal(refinedDecl, decl)) {
                    return getTopmostRefinedDeclaration(refinedDecl, methodOverrides);
                }
            }
            return decl;
        } else if(decl instanceof FunctionOrValue
                && ((FunctionOrValue)decl).isParameter() // a parameter
                && ((decl.getContainer() instanceof Function && !(((Function)decl.getContainer()).isParameter())) // that's not parameter of a functional parameter 
                        || decl.getContainer() instanceof Specification // or is a parameter in a specification
                        || (decl.getContainer() instanceof Function  
                            && ((Function)decl.getContainer()).isParameter() 
                            && createMethod((Function)decl.getContainer())))) {// or is a class functional parameter
            // Parameters in a refined method are not considered refinements themselves
            // so we have to look up the corresponding parameter in the container's refined declaration
            Functional func = (Functional)getParameterized((FunctionOrValue)decl);
            if(func == null)
                return decl;
            Declaration kk = getTopmostRefinedDeclaration((Declaration)func, methodOverrides);
            // error recovery
            if(kk instanceof Functional == false)
                return decl;
            Functional refinedFunc = (Functional) kk;
            // shortcut if the functional doesn't override anything
            if (ModelUtil.equal((Declaration)refinedFunc, (Declaration)func)) {
                return decl;
            }
            if (func.getParameterLists().size() != refinedFunc.getParameterLists().size()) {
                // invalid input
                return decl;
            }
            for (int ii = 0; ii < func.getParameterLists().size(); ii++) {
                if (func.getParameterLists().get(ii).getParameters().size() != refinedFunc.getParameterLists().get(ii).getParameters().size()) {
                    // invalid input
                    return decl;
                }
                // find the index of the parameter in the declaration
                int index = 0;
                for (Parameter px : func.getParameterLists().get(ii).getParameters()) {
                    if (px.getModel() == null || px.getModel().equals(decl)) {
                        // And return the corresponding parameter from the refined declaration
                        return refinedFunc.getParameterLists().get(ii).getParameters().get(index).getModel();
                    }
                    index++;
                }
                continue;
            }
        }else if(methodOverrides != null
                && decl instanceof Function
                && ModelUtil.equal(decl.getRefinedDeclaration(), decl)
                && decl.getContainer() instanceof Specification
                && ((Specification)decl.getContainer()).getDeclaration() instanceof Function
                && ((Function) ((Specification)decl.getContainer()).getDeclaration()).isShortcutRefinement()
                // we do all the previous ones first because they are likely to filter out false positives cheaper than the
                // hash lookup we do next to make sure it is really one of those cases
                && methodOverrides.containsKey(decl)){
            // special case for class X() extends T(){ m = function() => e; } which we inline
            decl = methodOverrides.get(decl);
        }
        Declaration refinedDecl = decl.getRefinedDeclaration();
        if(refinedDecl != null && !ModelUtil.equal(refinedDecl, decl))
            return getTopmostRefinedDeclaration(refinedDecl);
        return decl;
    }

    public static Parameter findParamForDecl(TypedDeclaration decl) {
        String attrName = decl.getName();
        return findParamForDecl(attrName, decl);
    }
    
    public static Parameter findParamForDecl(String attrName, TypedDeclaration decl) {
        Parameter result = null;
        if (decl.getContainer() instanceof Functional) {
            Functional f = (Functional)decl.getContainer();
            result = f.getParameter(attrName);
        }
        return result;
    }

    public static Declaration getParameterized(FunctionOrValue methodOrValue) {
        if (!methodOrValue.isParameter()) {
            return null;
        }
        Scope scope = methodOrValue.getContainer();
        if (scope instanceof Specification) {
            return ((Specification)scope).getDeclaration();
        } else if (scope instanceof Declaration) {
            return (Declaration)scope;
        } 
        return null;
    }

    public static boolean createMethod(FunctionOrValue model) {
        return model instanceof Function
                && model.isParameter()
                && model.isClassMember()
                && (model.isShared() || model.isCaptured());
    }

    public static boolean supportsReified(Declaration declaration){
        if(declaration instanceof ClassOrInterface){
            // Java constructors don't support reified type arguments
            return isCeylon((TypeDeclaration) declaration);
        }else if(declaration instanceof Function){
            if (((Function)declaration).isParameter()) {
                // those can never be parameterised
                return false;
            }
            if(declaration.isToplevel())
                return true;
            // Java methods don't support reified type arguments
            Function m = (Function) getTopmostRefinedDeclaration(declaration);
            // See what its container is
            ClassOrInterface container = ModelUtil.getClassOrInterfaceContainer(m);
            // a method which is not a toplevel and is not a class method, must be a method within method and
            // that must be Ceylon so it supports it
            if(container == null)
                return true;
            return supportsReified(container);
        }else if(declaration instanceof Constructor){
            // Java constructors don't support reified type arguments
            return isCeylon((Constructor) declaration);
        }else{
            return false;
        }
    }
    
    public static boolean isCompanionClassNeeded(TypeDeclaration decl) {
        return decl instanceof Interface 
                && BooleanUtil.isNotFalse(((Interface)decl).isCompanionClassNeeded());
    }

    /**
     * Determines whether the given attribute should be accessed and assigned 
     * via a {@code VariableBox}
     */
    public static boolean isBoxedVariable(TypedDeclaration attr) {
        return ModelUtil.isNonTransientValue(attr)
                && ModelUtil.isLocalNotInitializer(attr)
                && ((attr.isVariable() && attr.isCaptured())
                        // self-captured objects must also be boxed like variables
                        || attr.isSelfCaptured());
    }

    public static boolean isJavaArray(TypeDeclaration decl) {
        if(decl instanceof Class == false)
            return false;
        Class c = (Class) decl;
        String name = c.getQualifiedNameString();
        return name.equals("java.lang::ObjectArray")
                || name.equals("java.lang::ByteArray")
                || name.equals("java.lang::ShortArray")
                || name.equals("java.lang::IntArray")
                || name.equals("java.lang::LongArray")
                || name.equals("java.lang::FloatArray")
                || name.equals("java.lang::DoubleArray")
                || name.equals("java.lang::BooleanArray")
                || name.equals("java.lang::CharArray");
    }
} public interface ModelCompleter {

    /**
     * Completes loading of a class
     */
    void complete(LazyClass lazyClass);

    /**
     * Completes loading of a class's type parameters only
     */
    void completeTypeParameters(LazyClass lazyClass);

    /**
     * Completes loading of an interface
     */
    void complete(LazyInterface lazyInterface);

    /**
     * Completes loading of an interface's type parameters only
     */
    void completeTypeParameters(LazyInterface lazyInterface);

    /**
     * Completes loading of a toplevel attribute
     */
    void complete(LazyValue lazyValue);
    
    /**
     * Completes loading of a toplevel method
     */
    void complete(LazyFunction lazyMethod);

    /**
     * Completes loading of a lazy class alias
     */
    void complete(LazyClassAlias lazyClassAlias);

    /**
     * Completes loading of a lazy class alias's type parameters only
     */
    void completeTypeParameters(LazyClassAlias lazyClassAlias);

    /**
     * Completes loading of a lazy interface alias
     */
    void complete(LazyInterfaceAlias lazyInterfaceAlias);

    /**
     * Completes loading of a lazy interface alias's type parameters only
     */
    void completeTypeParameters(LazyInterfaceAlias lazyInterfaceAlias);

    /**
     * Completes loading of a lazy type alias
     */
    void complete(LazyTypeAlias lazyTypeAlias);

    /**
     * Completes loading of a lazy type alias's type parameters only
     */
    void completeTypeParameters(LazyTypeAlias lazyTypeAlias);

    /**
     * Returns a lock we can use for thread-safety
     */
    Object getLock();
} public interface ClassMirror extends AnnotatedMirror, AccessibleMirror {

    /**
     * Returns true if this is an interface
     */
    boolean isInterface();
    
    /**
     * Returns true if this is an {@code @interface}.
     */
    boolean isAnnotationType();
    
    /**
     * Returns true if this is an abstract class
     */
    boolean isAbstract();
    
    /**
     * Returns true if this is a static class
     */
    boolean isStatic();

    /**
     * Returns true if this class is an inner class
     */
    boolean isInnerClass();

    /**
     * Returns true if this class is a local class
     */
    boolean isLocalClass();

    /**
     * Returns true if this class is an anonymous class
     */
    boolean isAnonymous();
    
    /**
     * Returns true if this class is an enum class
     */
    boolean isEnum();

    /**
     * Returns the fully-qualified class name
     */
    String getQualifiedName();
    
    /**
     * Returns the fully-qualified class name in flat form: dots to separate package parts, and dollars
     * to separate inner members.
     */
    String getFlatName();
    
    /**
     * Returns this class's package
     */
    PackageMirror getPackage();
    
    /**
     * Returns the list of methods and constructors defined by this class. Does not include inherited
     * methods and constructors.
     */
    List<MethodMirror> getDirectMethods();

    /**
     * Returns the list of fields defined by this class. Does not include inherited
     * fields.
     */
    List<FieldMirror> getDirectFields();

    /**
     * Returns the list of type parameters for this class
     */
    List<TypeParameterMirror> getTypeParameters();

    /**
     * Returns the list of inner classes directly contained in this class. Does not include inherited
     * inner classes. 
     */
    List<ClassMirror> getDirectInnerClasses();

    /**
     * Returns this class's superclass, or null if it doesn't have any
     */
    TypeMirror getSuperclass();

    /**
     * Returns this class's containing class, if any
     */
    ClassMirror getEnclosingClass();

    /**
     * Returns this class's enclosing method, if any
     */
    MethodMirror getEnclosingMethod();

    /**
     * Returns the list of interfaces implemented by this class
     */
    List<TypeMirror> getInterfaces();

    /**
     * Returns true if this class represents a toplevel attribute
     */
    boolean isCeylonToplevelAttribute();

    /**
     * Returns true if this class represents a toplevel object
     */
    boolean isCeylonToplevelObject();

    /**
     * Returns true if this class represents a toplevel method
     */
    boolean isCeylonToplevelMethod();
    
    /**
     * Returns true if this class was loaded from source, false if it was loaded from a compiled form
     */
    boolean isLoadedFromSource();

    /**
     * Returns true if this class was loaded from a Java source file, false if it came from a ceylon
     * source file or from a class file
     */
    boolean isJavaSource();

    /**
     * Returns true if this class is final
     */
    boolean isFinal();

    String getCacheKey(Module module);
} public class ClassAlias extends Class {
    
    private TypeDeclaration constructor;
    
    public TypeDeclaration getConstructor() {
        return constructor;
    }
    
    public void setConstructor(TypeDeclaration constructor) {
        this.constructor = constructor;
    }
    
    @Override
    public boolean isAlias() {
        return true;
    }
    
    @Override
    public boolean isEmptyType() {
        Type et = getExtendedType();
        if (et!=null) {
            Type.checkDepth();
            Type.incDepth();
            try {
                return et.getDeclaration().isEmptyType();
            }
            finally {
                Type.decDepth();
            }
        }
        else {
            return false;
        }
    }
    
    @Override
    public boolean isTupleType() {
        Type et = getExtendedType();
        if (et!=null) {
            Type.checkDepth();
            Type.incDepth();
            try {
                return et.getDeclaration().isTupleType();
            }
            finally {
                Type.decDepth();
            }
        }
        else {
            return false;
        }
    }
    
    @Override
    public boolean isSequentialType() {
        Type et = getExtendedType();
        if (et!=null) {
            Type.checkDepth();
            Type.incDepth();
            try {
                return et.getDeclaration().isSequentialType();
            }
            finally {
                Type.decDepth();
            }
        }
        else {
            return false;
        }
    }
    
    @Override
    public boolean isSequenceType() {
        Type et = getExtendedType();
        if (et!=null) {
            Type.checkDepth();
            Type.incDepth();
            try {
                return et.getDeclaration().isSequenceType();
            }
            finally {
                Type.decDepth();
            }
        }
        else {
            return false;
        }
    }
    
    @Override
    void collectSupertypeDeclarations(
            List<TypeDeclaration> results) {
        Type et = getExtendedType();
        if (et!=null) { 
            et.getDeclaration()
                .collectSupertypeDeclarations(results);
        }
    }
    
    @Override
    public boolean inherits(TypeDeclaration dec) {
        Type et = getExtendedType();
        if (et!=null) {
            Type.checkDepth();
            Type.incDepth();
            try {
                return et.getDeclaration().inherits(dec);
            }
            finally {
                Type.decDepth();
            }
        }
        return false;
    }
    
} public abstract class Declaration 
        extends Element 
        implements Referenceable, Annotated {
    
    private static final int SHARED = 1;
    private static final int FORMAL = 1<<1;
    private static final int ACTUAL = 1<<2;
    private static final int DEFAULT = 1<<3;
    private static final int ANNOTATION = 1<<4;
    private static final int DEPRECATED = 1<<5;
    
    private static final int PROTECTED = 1<<8;
    private static final int PACKAGE = 1<<9;
    private static final int STATIC = 1<<10;
    
	private String name;
	protected int flags;
	private String qualifier;
    private Scope visibleScope;
    private Declaration refinedDeclaration = this;
    private String qualifiedNameAsStringCache;
	private Backends nativeBackends = Backends.ANY;
	private boolean otherInstanceAccess;
    private DeclarationCompleter actualCompleter;
    private List<String> aliases;

    public Scope getVisibleScope() {
        return visibleScope;
    }

    public void setVisibleScope(Scope visibleScope) {
        this.visibleScope = visibleScope;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isShared() {
        return (flags&SHARED)!=0;
    }

    public void setShared(boolean shared) {
        if (shared) {
            flags|=SHARED;
        }
        else {
            flags&=(~SHARED);
        }
    }

    public boolean isParameterized() {
        return false;
    }
    
    public boolean isDeprecated() {
		return (flags&DEPRECATED)!=0;
	}
    
    public void setDeprecated(boolean deprecated) {
        if (deprecated) {
            flags|=DEPRECATED;
        }
        else {
            flags&=(~DEPRECATED);
        }
	}

    String toStringName() {
        String name = getName();
        if (name==null) name = "";
        Scope c = getContainer();
        if (c instanceof Declaration) {
            Declaration d = (Declaration) c;
            name = d.toStringName() + "." + name;
        }
        if (this instanceof Generic) {
            Generic g = (Generic) this;
            List<TypeParameter> typeParams = 
                    g.getTypeParameters();
            if (!typeParams.isEmpty()) {
                StringBuilder params = new StringBuilder();
                params.append("<");
                boolean first = true;
                for (TypeParameter tp: typeParams) {
                    if (first) {
                        first = false;
                    }
                    else {
                        params.append(",");
                    }
                    params.append(tp.getName());
                }
                params.append(">");
                name += params;
            }
        }
        return name;
    }
    
    @Override
    public abstract String toString();
    
    @Override
    public String getQualifiedNameString() {
        if (qualifiedNameAsStringCache == null) {
            String qualifier = getContainer().getQualifiedNameString();
            String name = getName();
            if (qualifier==null || qualifier.isEmpty()) {
                qualifiedNameAsStringCache = name; 
            }
            else if (getContainer() instanceof Package) {
                qualifiedNameAsStringCache = qualifier + "::" + name;
            }
            else {
                qualifiedNameAsStringCache = qualifier + "." + name;
            }
        }
        return qualifiedNameAsStringCache;
    }
    
    public boolean isAnnotation() {
        return (flags&ANNOTATION)!=0;
    }
    
    public void setAnnotation(boolean annotation) {
        if (annotation) {
            flags|=ANNOTATION;
        }
        else {
            flags&=(~ANNOTATION);
        }
    }

    public boolean isActual() {
        if (actualCompleter != null) {
            completeActual();
        }
        return (flags&ACTUAL)!=0;
    }

    public void setActual(boolean actual) {
        if (actual) {
            flags|=ACTUAL;
        }
        else {
            flags&=(~ACTUAL);
        }
    }

    public boolean isFormal() {
        return (flags&FORMAL)!=0;
    }
    
    public void setFormal(boolean formal) {
        if (formal) {
            flags|=FORMAL;
        }
        else {
            flags&=(~FORMAL);
        }
    }

    public boolean isNative() {
        return !getNativeBackends().none();
    }
    
    public boolean isNativeHeader() {
        return getNativeBackends().header();
    }
    
    public boolean isNativeImplementation() {
        return isNative() && !isNativeHeader();
    }
    
    public Backends getNativeBackends() {
    	return nativeBackends;
    }
    
    public void setNativeBackends(Backends backends) {
    	this.nativeBackends=backends;
    }

    public Backends getScopedBackends() {
        Backends backends = getNativeBackends();
        if (backends.none()) {
            return getScope().getScopedBackends();
        } else {
            return backends;
        }
    }

    public boolean isDefault() {
        return (flags&DEFAULT)!=0;
    }

    public void setDefault(boolean def) {
        if (def) {
            flags|=DEFAULT;
        }
        else {
            flags&=(~DEFAULT);
        }
    }
    
    public Declaration getRefinedDeclaration() {
        if (actualCompleter != null) {
            completeActual();
        }
		return refinedDeclaration;
	}
    
    private void completeActual() {
        DeclarationCompleter completer = actualCompleter;
        actualCompleter = null;
        completer.completeActual(this);
    }

    public void setRefinedDeclaration(Declaration refinedDeclaration) {
		this.refinedDeclaration = refinedDeclaration;
	}
    
    /**
     * Determine if this declaration is visible
     * in the given scope, by considering if it
     * is shared or directly defined in a
     * containing scope.
     */
    public boolean isVisible(Scope scope) {
        Scope vs = getVisibleScope();
        if (vs==null) {
            return true;
        }
        else {
            return contains(vs, scope);
        }
        /*
        * Note that this implementation is not quite
        * right, since for a shared member
        * declaration it does not check if the
        * containing declaration is also visible in
        * the given scope, but this is okay for now
        * because of how this method is used.
        */
        /*if (isShared()) {
            return true;
        }
        else {
            return isDefinedInScope(scope);
        }*/
    }

    /**
     * Determine if this declaration is directly
     * defined in a containing scope of the given
     * scope.
     */
    public boolean isDefinedInScope(Scope scope) {
        while (scope!=null) {
            if (getContainer()==scope) {
                return true;
            }
            scope = scope.getContainer();
        }
        return false;
    }

    public boolean isCaptured() {
        return false;
    }

    public boolean isToplevel() {
        return getContainer() instanceof Package;
    }

    public boolean isClassMember() {
        return getContainer() instanceof Class;
    }

    public boolean isInterfaceMember() {
        return getContainer() instanceof Interface;
    }

    /**
     * Is this declaration nested inside a class or 
     * interface body. It might not be enough to make it
     * a "member" in the strict language spec sense of
     * the word.
     *  
     * @see Declaration#isMember()
     */
    public boolean isClassOrInterfaceMember() {
        return getContainer() instanceof ClassOrInterface;
    }
    
    /**
     * Is this declaration a member in the strict language
     * spec sense of the word, that is, is it a polymorphic
     * kind of declaration (an attribute, method, or class)
     * 
     * @see Declaration#isClassOrInterfaceMember()
     */
    public boolean isMember() {
    	return false;
    }
    
    @Override
    public List<Annotation> getAnnotations() {
        return emptyList();
    }
    
    public boolean isStaticallyImportable() {
        return (flags&STATIC)!=0;
    }
    
    public void setStaticallyImportable(boolean staticallyImportable) {
        if (staticallyImportable) {
            flags|=STATIC;
        }
        else {
            flags&=(~STATIC);
        }
    }
    
    public boolean isProtectedVisibility() {
        return (flags&PROTECTED)!=0;
    }
    
    public void setProtectedVisibility(boolean protectedVisibility) {
        if (protectedVisibility) {
            flags|=PROTECTED;
        }
        else {
            flags&=(~PROTECTED);
        }
    }
    
    public boolean isPackageVisibility() {
        return (flags&PACKAGE)!=0;
    }
    
    public void setPackageVisibility(boolean packageVisibility) {
        if (packageVisibility) {
            flags|=PACKAGE;
        }
        else {
            flags&=(~PACKAGE);
        }
    }

    /**
     * Get a produced reference for this declaration
     * by binding explicit or inferred type arguments
     * and type arguments of the type of which this
     * declaration is a member, in the case that this
     * is a member.
     *
     * @param outerType the qualifying produced
     * type or null if this is not a
     * nested type declaration
     * @param typeArguments arguments to the type
     * parameters of this declaration
     */
    public abstract Reference appliedReference(Type pt, 
            List<Type> typeArguments);
    
    /**
     * Obtain a reference to this declaration, as seen 
     * within the body of the declaration itself. That is,
     * within its type parameters as their own arguments.
     */
    public abstract Reference getReference();
    
    protected java.lang.Class<?> getModelClass() {
        return getClass();
    }
    
    private Scope getAbstraction(Scope container) {
        if (container instanceof Class) {
            Class c = (Class) container;
            if (isOverloadedVersion(c)) {
                return c.getExtendedType()
                        .getDeclaration();
            }
        }
        return container;
    }
    
    Type getMemberContainerType() {
        if (isMember() 
                // static inner classes in Java have no container type,
                // but Java enums are fake inner classes that require their
                // container type
                && (!isStaticallyImportable() || isJavaEnum())) {
            ClassOrInterface container = 
                    (ClassOrInterface) 
                        getContainer();
            return container.getType();
        }
        else {
            return null;
        }
    }

    public boolean isAbstraction() { 
        return false; 
    }

    public boolean isOverloaded() {
        return false;
    }

    public List<Declaration> getOverloads() {
        return Collections.emptyList();
    }
    
    @Override
    public boolean equals(Object object) {
        if (this==object) {
            return true;
        }
        
        if (object == null) {
            return false;
        }
        
        if (object instanceof Declaration) {
            Declaration that = (Declaration) object;
            String thisName = getName();
            String thatName = that.getName();
            if (!Objects.equals(getQualifier(), that.getQualifier())) {
                return false;
            }
            if (getModelClass() != that.getModelClass()) {
                return false;
            }
            Scope thisContainer = 
                    getAbstraction(getContainer());
            Scope thatContainer = 
                    getAbstraction(that.getContainer());
            if (thisName!=thatName && 
                    (thisName==null || thatName==null || 
                        !thisName.equals(thatName)) ||
                that.getDeclarationKind()!=getDeclarationKind() ||
                thisContainer==null || thatContainer==null ||
                    !thisContainer.equals(thatContainer)) {
                return false;
            }
            else if (this instanceof Functional && 
                    that instanceof Functional) {
                boolean thisIsAbstraction = 
                        this.isAbstraction();
                boolean thatIsAbstraction = 
                        that.isAbstraction();
                boolean thisIsOverloaded = 
                        this.isOverloaded();
                boolean thatIsOverloaded = 
                        that.isOverloaded();
                if (thisIsAbstraction!=thatIsAbstraction ||
                    thisIsOverloaded!=thatIsOverloaded) {
                    return false;
                }
                if (!thisIsOverloaded && !thatIsOverloaded) {
                    return true;
                }
                if (thisIsAbstraction && thatIsAbstraction) {
                    return true;
                }
                Functional thisFunction = (Functional) this;
                Functional thatFunction = (Functional) that;
                List<ParameterList> thisParamLists = 
                        thisFunction.getParameterLists();
                List<ParameterList> thatParamLists = 
                        thatFunction.getParameterLists();
                if (thisParamLists.size()!=thatParamLists.size()) {
                    return false;
                }
                for (int i=0; i<thisParamLists.size(); i++) {
                    List<Parameter> thisParams = 
                            thisParamLists.get(i)
                                .getParameters();
                    List<Parameter> thatParams = 
                            thatParamLists.get(i)
                                .getParameters();
                    if (thisParams.size()!=thatParams.size()) {
                        return false;
                    }
                    for (int j=0; j<thisParams.size(); j++) {
                        Parameter thisParam = thisParams.get(j);
                        Parameter thatParam = thatParams.get(j);
                        if (thisParam!=thatParam) {
                            if (thisParam!=null && thatParam!=null) {
                                Type thisParamType = 
                                        thisParam.getType();
                                Type thatParamType = 
                                        thatParam.getType();
                                if (thisParamType!=null && 
                                        thatParamType!=null) {
                                    if (!erase(thisParamType, unit)
                                            .equals(erase(thatParamType, unit))) {
                                        return false;
                                    }
                                }
                                else if (thisParamType!=thatParamType) {
                                    return false;
                                }
                            }
                            else if (thisParam!=thatParam) {
                                return false;
                            }
                        }
                    }
                }
                return true;
            }
            else {
                return true;
            }
        }
        else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int ret = 17;
        Scope container = getContainer();
        ret = (37 * ret) + 
                (container == null ? 0 : container.hashCode());
        String qualifier = getQualifier();
        ret = (37 * ret) + 
                (qualifier == null ? 0 : qualifier.hashCode());
        String name = getName();
        ret = (37 * ret) + (name == null ? 0 : name.hashCode());
        // make sure we don't consider getter/setter or value/anonymous-type equal
        ret = (37 * ret) + (isSetter() ? 0 : 1);
        ret = (37 * ret) + (isAnonymous() ? 0 : 1);
        return ret;
    }
    
    /**
     * Does this declaration refine the given declaration?
     * @deprecated does not take overloading into account
     */
    public boolean refines(Declaration other) {
        if (equals(other)) {
            return true;
        }
        else {
            if (isClassOrInterfaceMember()) {
                ClassOrInterface type = 
                        (ClassOrInterface) 
                            getContainer();
                return other.getName()!=null && getName()!=null &&
                        other.getName().equals(getName()) && 
                        isShared() && other.isShared() &&
                        //other.getDeclarationKind()==getDeclarationKind() &&
                        type.isMember(other);
            }
            else {
                return false;
            }
        }
    }
    
    /**
     * @return true if this is an anonymous class or 
     * anything with a rubbish system-generated name.
     * 
     * @see Declaration#isNamed()
     */
    public boolean isAnonymous() {
        return false;
    }
    
    /**
     * @return false if this declaration has a system-generated 
     * name, rather than a user-generated name. At the moment 
     * only object expressions, anonymous function expressions 
     * and anonymous type constructors are not named. This 
     * is different to {@link Declaration#isAnonymous()} 
     * because named object classes are considered anonymous 
     * but do have a user-generated name.
     */
    public boolean isNamed() {
        return true;
    }
    
    /**
     * Return true IFF this is not a real type but a 
     * pseudo-type generated by the model loader to pretend 
     * that Java enum values have an anonymous type. This is 
     * overridden and implemented in Class.
     */
    public boolean isJavaEnum() {
        return false;
    }
    
    public abstract DeclarationKind getDeclarationKind();
    
    @Override
    public String getNameAsString() {
    	return getName();
    }
    
    public String getName(Unit unit) {
    	return unit==null ? 
    	        getName() : 
    	        unit.getAliasedName(this);
    }
    
    public boolean getOtherInstanceAccess() {
    	return otherInstanceAccess;
    }

	public void setOtherInstanceAccess(boolean access) {
		otherInstanceAccess=access;
	}
	
	public boolean isParameter() {
	    return false;
	}

    public boolean isSetter() {
        return false;
    }

    public boolean isFunctional() {
        return false;
    }

    protected abstract int hashCodeForCache();

    protected abstract boolean equalsForCache(Object o);

    public String getQualifier() {
        return isParameter() ? null : qualifier;
    }

    public void setQualifier(String qualifier) {
        this.qualifier = qualifier;
    }
    
    public String getPrefixedName(){
        String qualifier = getQualifier();
        return qualifier==null || isParameter() ? 
                name : qualifier + name;
    }

    public boolean sameKind(Declaration m) {
        return m!=null && m.getModelClass()==getModelClass();
    }

    public DeclarationCompleter getActualCompleter() {
        return actualCompleter;
    }

    public void setActualCompleter(DeclarationCompleter actualCompleter) {
        this.actualCompleter = actualCompleter;
    }
    
    /**
     * Get the type parameters of this declaration as their
     * own arguments. Note: what is returned is different to 
     * {@link Util#getTypeArgumentMap(Declaration, Type, List)},
     * which also includes type arguments from qualifying 
     * types. In this case we assume they're uninteresting.
     * 
     * We do need to compensate for this in 
     * {@link Type.Substitution#substitutedType(TypeDeclaration, Type, boolean, boolean)}
     * By expanding out the type arguments of the qualifying
     * type. An alternative solution would be to just expand
     * out all the type args of the qualifying type here. 
     * 
     * @return a map from each type parameter of this 
     *         declaration to its own type
     *         
     * @see ModelUtil#typeParametersAsArgList
     */
    Map<TypeParameter, Type> getTypeParametersAsArguments() {
        if (this instanceof Generic) {
            Generic g = (Generic) this;
            List<TypeParameter> typeParameters = 
                    g.getTypeParameters();
            if (typeParameters.isEmpty()) {
                return EMPTY_TYPE_ARG_MAP;
            }
            else {
                Map<TypeParameter,Type> typeArgs = 
                        new HashMap<TypeParameter,Type>();
                for (TypeParameter p: typeParameters) {
                    Type pta = new Type();
                    if (p.isTypeConstructor()) {
                        pta.setTypeConstructor(true);
                        pta.setTypeConstructorParameter(p);
                    }
                    pta.setDeclaration(p);
                    typeArgs.put(p, pta);
                }
                return typeArgs;
            }
        }
        else {
            return EMPTY_TYPE_ARG_MAP;
        }
    }

    public List<String> getAliases(){
        return aliases != null ? aliases : Collections.<String>emptyList();
    }
    
    public void setAliases(List<String> aliases){
        this.aliases = aliases;
    }
} public class ParameterList {
    
    private List<Parameter> parameters = new ArrayList<Parameter>(2);
    private boolean supportsNamedParameters = true;
    private boolean supportsPositionalParameters = true;
    private boolean first;
    
    public List<Parameter> getParameters() {
        return parameters;
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (Parameter p: parameters) {
            if (sb.length()>1) {
                sb.append(", ");
            }
            sb.append(p);
        }
        sb.append(")");
        return parameters.toString();
    }

    public boolean isNamedParametersSupported() {
        return supportsNamedParameters;
    }

    public void setNamedParametersSupported(boolean supportsNamedParameters) {
        this.supportsNamedParameters = supportsNamedParameters;
    }
    
    public boolean isPositionalParametersSupported() {
        return supportsPositionalParameters;
    }

    public void setPositionalParametersSupported(boolean supportsPositionalParameters) {
        this.supportsPositionalParameters = supportsPositionalParameters;
    }

    public boolean hasSequencedParameter() {
        return !parameters.isEmpty() && 
                parameters.get(parameters.size()-1).isSequenced();
    }
    
    public boolean isFirst() {
		return first;
	}
    
    public void setFirst(boolean first) {
		this.first = first;
	}
    
} public abstract class Reference {
    
    Reference() {}

    private Map<TypeParameter, Type> typeArguments = 
            EMPTY_TYPE_ARG_MAP;
    
    private Type qualifyingType;
    
    //cache
    private Map<TypeParameter, Type> 
    typeArgumentsWithDefaults;
    
    public Type getQualifyingType() {
        return qualifyingType;
    }
    
    void setQualifyingType(Type qualifyingType) {
        this.qualifyingType = qualifyingType;
    }
    
    public abstract Declaration getDeclaration();

    public Map<TypeParameter, Type> getTypeArguments() {
        Declaration declaration = getDeclaration();
        if (declaration instanceof Generic) {
            if (TypeCache.isEnabled()) {
                if (typeArgumentsWithDefaults==null) {
                    typeArgumentsWithDefaults = 
                            getTypeArgumentsInternal(declaration);
                }
                return typeArgumentsWithDefaults;
            }
            else {
                return getTypeArgumentsInternal(declaration);
            }
        }
        else {
            return typeArguments;
        }
    }

    private Map<TypeParameter, Type> 
    getTypeArgumentsInternal(Declaration declaration) {
        checkDepth();
        incDepth();
        try {
            return fillInDefaultTypeArguments(
                    declaration,
                    typeArguments);
        }
        finally { 
            decDepth();
        }
    }

    private static Map<TypeParameter, Type> 
    fillInDefaultTypeArguments(Declaration declaration,
            Map<TypeParameter, Type> typeArguments) {
        Map<TypeParameter, Type> typeArgs = typeArguments;
        Generic g = (Generic) declaration;
        List<TypeParameter> typeParameters = 
                g.getTypeParameters();
        for (int i=0, l=typeParameters.size(); 
                i<l; i++) {
            TypeParameter tp = typeParameters.get(i);
            Type dta = tp.getDefaultTypeArgument();
            if (dta!=null &&
                    !typeArguments.containsKey(tp)) {
                // only make a copy of typeArguments if required
                if (typeArguments == typeArgs) {
                    // make a copy big enough to fit every type parameter
                    typeArgs = 
                            new HashMap<TypeParameter,Type>
                                (typeParameters.size());
                    typeArgs.putAll(typeArguments);
                }
                typeArgs.put(tp, 
                        dta.substitute(typeArgs, 
                                EMPTY_VARIANCE_MAP));
            }
        }
        return typeArgs;
    }
    
    void setTypeArguments
        (Map<TypeParameter,Type> typeArguments) {
        this.typeArguments = typeArguments;
    }
    
    /**
     * The type or return type of the referenced thing:
     * 
     * - for a value, this is its type,
     * - for a function, this is its return type, and
     * - for a class or constructor, it is the class type.
     * 
     * The "whole" type of the reference may be obtained
     * using {@link Reference#getFullType()}.
     * 
     * @see Reference#getTypedParameter(Parameter)
     * @see Reference#getFullType()
     */
    public abstract Type getType();
    
    /**
     * The type or callable type of the referenced thing:
     * 
     * - for a value, this is its type,
     * - for a function, class, or constructor, this is its 
     *   callable type.
     * 
     * This type encodes all the types you could assemble
     * using {@link Reference#getType()} and
     * {@link Reference#getTypedParameter(Parameter)}.
     *   
     *   @see Reference#getType()
     *   @see Reference#getTypedParameter(Parameter)
     */
    public Type getFullType() {
    	return getFullType(getType());
    }
    
    /**
     * @param wrappedType the return type of this member for
     *                    a ?. or *. expression, i.e.
     *                    T?, [T*], or [T+]
     */
    public Type getFullType(Type wrappedType) {
        //don't use this, because it is refined by Type
        Declaration declaration = getDeclaration();
        if (declaration instanceof Functional) {
            Unit unit = declaration.getUnit();
            if (isAbstraction(declaration)) {
                //for an unresolved overloaded method we don't 
                //know the parameter types, but we do know that
                //there is only one parameter list
                return appliedType(
                        unit.getCallableDeclaration(), 
                        wrappedType, 
                        new UnknownType(unit).getType());
            }
            else {
                return unit.getCallableType(this, 
                        wrappedType);
            }
        }
        else {
            return wrappedType;
        }
    }
    
    /**
     * Does this reference have parameters?
     */
    public boolean isFunctional() {
        return getDeclaration() instanceof Functional;
    }

    /**
     * Get the type of a parameter, after substitution of
     * type arguments.
     */
    public TypedReference getTypedParameter(Parameter p) {
        TypedReference ptr = 
                new TypedReference(false, true);
        FunctionOrValue model = p.getModel();
        if (model!=null) {
            ptr.setDeclaration(model);
        }
        ptr.setQualifyingType(getQualifyingType());
        ptr.setTypeArguments(getTypeArguments());
        return ptr;
    }
    
    public abstract String asString();
    
} public class Type extends Reference {
    
    private TypeDeclaration declaration;
    private String underlyingType;
    private boolean isRaw;
    private Type resolvedAliases;
    private TypeParameter typeConstructorParameter;
    private boolean typeConstructor;
    
    // cache
    private int hashCode;
//    private List<Type> typeArgumentList;
    
    private Map<TypeParameter,SiteVariance> varianceOverrides = 
            EMPTY_VARIANCE_MAP;
    
    public Map<TypeParameter, SiteVariance> getVarianceOverrides() {
        return varianceOverrides;
    }
    
    public boolean isCovariant(TypeParameter param) {
        SiteVariance override = 
                varianceOverrides.get(param);
        if (override==null) {
            return param.isCovariant();
        }
        else {
            return override==OUT;
        }
    }
    
    public boolean isContravariant(TypeParameter param) {
        SiteVariance override = 
                varianceOverrides.get(param);
        if (override==null) {
            return param.isContravariant();
        }
        else {
            return override==IN;
        }
    }
    
    public boolean isInvariant(TypeParameter param) {
        SiteVariance override = 
                varianceOverrides.get(param);
        if (override==null) {
            return param.isInvariant();
        }
        else {
            return false;
        }
    }
    
    public void setVariance(TypeParameter param, 
            SiteVariance variance) {
        if (varianceOverrides.isEmpty()) {
            varianceOverrides = 
                    new HashMap<TypeParameter,SiteVariance>();
        }
        varianceOverrides.put(param, variance);
    }
    
    public void setVarianceOverrides(
            Map<TypeParameter,SiteVariance> varianceOverrides) {
        this.varianceOverrides = varianceOverrides;
    }
    
    Type() {}
    
    @Override
    public TypeDeclaration getDeclaration() {
        return declaration;
    }
    
    void setDeclaration(TypeDeclaration declaration) {
        this.declaration = declaration;
    }
    
    /**
     * Is this type a type constructor? Warning: this is
     * not a "deep implementation", and a type constructor
     * type can be hidden behind a type alias. Therefore,
     * it's usually necessary to call 
     * {@link Type#resolveAliases()} before testing this.
     * 
     * @return true if this type is a type constructor
     */
    public boolean isTypeConstructor() {
        return typeConstructor;
    }
    
    public TypeParameter getTypeConstructorParameter() {
        return typeConstructorParameter;
    }
    
    public void setTypeConstructor(boolean typeConstructor) {
        this.typeConstructor = typeConstructor;
    }
    
    public void setTypeConstructorParameter
            (TypeParameter typeConstructorParameter) {
        this.typeConstructorParameter = typeConstructorParameter;
    }
    
    private int exactlyNothing;
    
    public boolean isExactlyNothing() {
        if (isNothing()) {
            return true;
        }
        else {
            if (exactlyNothing==0) {
                exactlyNothing =
                        isEmptySequenceType() ||
                        isEmptyTupleType() ?
                                1 : -1;
            }
            return exactlyNothing>0;
        }
    }
    
    /**
     * Is this type exactly the same type as the
     * given type? 
     */
    public boolean isExactly(Type type) {
        return type!=null && resolveAliases()
                .isExactlyInternal(type.resolveAliases());
    }
    
    private boolean isExactlyInternal(Type type) {
        checkDepth();
        incDepth();
        try {
            if (isUnknown() || type.isUnknown()) {
                return this==type;
            }
            else if (isAnything()) {
                return type.isAnything();
            }
            else if (type.isAnything()) {
                return isAnything();
            }
            else if (isExactlyNothing()) {
                return type.isExactlyNothing();
            }
            else if (type.isExactlyNothing()) {
                return isExactlyNothing();
            }
            else if (isUnion()) {
                List<Type> cases = getCaseTypes();
                if (type.isUnion()) {
                    List<Type> otherCases = 
                            type.getCaseTypes();
                    if (cases.size()!=otherCases.size()) {
                        return false;
                    }
                    else {
                        for (Type c: cases) {
                            boolean found = false;
                            for (Type oc: otherCases) {
                                if (c.isExactlyInternal(oc)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
                else if (cases.size()==1) {
                    Type st = cases.get(0);
                    return st.isExactlyInternal(type);
                }
                else {
                    return false;
                }
            }
            else if (isIntersection()) {
                List<Type> types = getSatisfiedTypes();
                if (type.isIntersection()) {
                    List<Type> otherTypes = 
                            type.getSatisfiedTypes();
                    if (types.size()!=otherTypes.size()) {
                        return false;
                    }
                    else {
                        for (Type c: types) {
                            boolean found = false;
                            TypeDeclaration cd = 
                                    c.getDeclaration();
                            for (Type oc: otherTypes) {
                                TypeDeclaration ocd = 
                                        oc.getDeclaration();
                                if (cd.equals(ocd)) {
                                    if (c.isExactlyInternal(oc)) {
                                        found = true;
                                        break;
                                    }
                                    //given a covariant type Co, and interfaces
                                    //A satisfies Co<B&Co<A>> and 
                                    //B satisfies Co<A&Co<B>>, then
                                    //A&Co<B> is equivalent A&Co<B&Co<A>> as a 
                                    //consequence of principal instantiation 
                                    //inheritance
                                    Type cst = 
                                            getSupertype(cd);
                                    Type ocst = 
                                            type.getSupertype(ocd);
                                    if (cst.isExactly(ocst)) {
                                        found = true;
                                        break;
                                    }
                                }
                            }
                            if (!found) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
                else if (types.size()==1) {
                    Type st = types.get(0);
                    return st.isExactlyInternal(type);
                }
                else {
                    return false;
                }
            }
            else if (type.isUnion()) {
                List<Type> otherCases = 
                        type.getCaseTypes();
                if (otherCases.size()==1) {
                    Type st = otherCases.get(0);
                    return isExactlyInternal(st);
                }
                else {
                    return false;
                }
            }
            else if (type.isIntersection()) {
                List<Type> otherTypes = 
                        type.getSatisfiedTypes();
                if (otherTypes.size()==1) {
                    Type st = otherTypes.get(0);
                    return isExactlyInternal(st);
                }
                else {
                    return false;
                }
            }
            else if (isObject()) {
                return type.isObject();
            }
            else if (type.isObject()) {
                return isObject();
            }
            else if (isNull()) {
                return type.isNull();
            }
            else if (type.isNull()) {
                return isNull();
            }
            else if (type.isClass()!=isClass() ||
                    type.isInterface()!=isInterface() ||
                    type.isTypeParameter()!=isTypeParameter()) {
                return false;
            }
            else if (isTypeConstructor() && 
                    type.isTypeConstructor()) {
                return isExactlyTypeConstructor(type);
            }
            else if (isTypeConstructor() ||
                    type.isTypeConstructor()) {
                return false;
            }
            else {
                TypeDeclaration dec = 
                        getDeclaration();
                TypeDeclaration otherDec = 
                        type.getDeclaration();
                if (!otherDec.equals(dec)) {
                    return false;
                }
                else {
                    if (isTuple()) {
                        return isExactlyTuple(type);
                    }
                    Type qt = trueQualifyingType();
                    Type tqt = type.trueQualifyingType();
                    if (qt==null || tqt==null) {
                        if (qt!=tqt) {
                            return false;
                        }
                    }
                    else if (qt.isUnknown() || tqt.isUnknown()) {
                        return false;
                    }
                    else {
                        Scope odc = otherDec.getContainer();
                        Scope dc = dec.getContainer();
                        if (!(odc instanceof TypeDeclaration) || 
                            !(dc instanceof TypeDeclaration)) {
                            // one of the two must be a local type, they should both be
                            if (odc instanceof TypeDeclaration || 
                                dc instanceof TypeDeclaration)
                                return false;
                            // must be the same container
                            if (!odc.equals(dc)) {
                                return false;
                            }
                            // just delegate equality
                            if (!tqt.isExactlyInternal(qt)) {
                                return false;
                            }
                        }
                        else {
                            TypeDeclaration totd = 
                                    (TypeDeclaration) odc;
                            Type tqts = 
                                    tqt.getSupertype(totd);
                            TypeDeclaration otd = 
                                    (TypeDeclaration) dc;
                            Type qts = qt.getSupertype(otd);
                            if (!qts.isExactly(tqts)) {
                                return false;
                            }
                        }
                    }
                    return isTypeArgumentListExactly(type);
                }
            }
        }
        finally {
            decDepth();
        }
    }

    private boolean isEmptySequenceType() {
        Unit unit = getDeclaration().getUnit();
        if (unit.isSequenceType(this)) {
            Type et = unit.getSequentialElementType(this);
            return et!=null && et.isExactlyNothing();
        }
        else {
            return false;
        }
    }

    private boolean isEmptyTupleType() {
        Unit unit = getDeclaration().getUnit();
        if (unit.isTupleType(this)) {
            List<Type> tal = getTypeArgumentList();
            if (tal.size()>=1) {
                Type elem = tal.get(0);
                if (elem!=null &&
                        elem.isExactlyNothing()) {
                    return true;
                }
            }
            if (tal.size()>=2) {
                Type first = tal.get(1);
                if (first!=null &&
                        first.isExactlyNothing()) {
                    return true;
                }
            }
            if (tal.size()>=3) {
                Type rest = tal.get(2);
                if (rest!=null &&
                        rest.isExactlyNothing()) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean isExactlyTuple(Type type) {
        TypeDeclaration td = 
                getDeclaration()
                    .getUnit()
                    .getTupleDeclaration();
        TypeParameter elem = td.getTypeParameters().get(0);
        TypeParameter first = td.getTypeParameters().get(1);
        TypeParameter rest = td.getTypeParameters().get(2);
        Type t1 = this;
        Type t2 = type;
        while (true) {
            Map<TypeParameter, Type> t1a = 
                    t1.getTypeArguments();
            Map<TypeParameter, Type> t2a = 
                    t2.getTypeArguments();
            Type e1 = t1a.get(elem);
            Type e2 = t2a.get(elem);
            Type f1 = t1a.get(first);
            Type f2 = t2a.get(first);
            if (e1==null || e2==null || 
                f1==null || f2==null) {
                return false;
            }
            if (!f1.isExactlyInternal(f2) || 
                !e1.isExactlyInternal(e2)) {
                return false;
            }
            Type r1 = t1a.get(rest);
            Type r2 = t2a.get(rest);
            if (r1==null || r2==null) {
                return false;
            }
            if (!r1.isTuple() || !r2.isTuple()) {
                //TODO: if both are unions containing
                //      exactly one Tuple, break the 
                //      union in two and continue
                //      iterating with the Tuples
                return r1.isExactlyInternal(r2); 
            }
            t1 = r1;
            t2 = r2;
        }
    }

    private boolean isTypeArgumentListExactly(Type type) {
        List<TypeParameter> typeParameters = 
                getDeclaration()
                    .getTypeParameters();
        for (TypeParameter p: typeParameters) {
            Type arg = getTypeArguments().get(p);
            Type otherArg = type.getTypeArguments().get(p);
            if (arg==null || otherArg==null) {
                return false;
            }
            else {
                boolean contravariant = 
                        isContravariant(p);
                boolean covariant = 
                        isCovariant(p);
                boolean invariant = 
                        !covariant && 
                        !contravariant;
                boolean otherCovariant = 
                        type.isCovariant(p);
                boolean otherContravariant = 
                        type.isContravariant(p);
                boolean otherInvariant = 
                        !otherCovariant && 
                        !otherContravariant;
                if (contravariant && otherCovariant) {
                    //Inv<in Nothing> == Inv<out Anything> 
                    if (!arg.isNothing() ||
                            !intersectionOfSupertypes(p)
                                .isSubtypeOf(otherArg)) {
                        return false;
                    }
                }
                else if (covariant && otherContravariant) {
                    //Inv<out Anything> == Inv<in Nothing>
                    if (!otherArg.isNothing() ||
                            !intersectionOfSupertypes(p)
                                .isSubtypeOf(arg)) {
                        return false;
                    }
                }
                else if (contravariant && otherInvariant ||
                        invariant && otherContravariant) {
                    //Inv<in Anything> == Inv<Anything> 
                    if (!arg.isAnything() || 
                        !otherArg.isAnything()) {
                        return false;
                    }
                }
                else if (covariant && otherInvariant ||
                        invariant && otherCovariant) {
                    //Inv<out nothing> == Inv<Nothing>
                    if (!arg.isNothing() || 
                        !otherArg.isNothing()) {
                        return false;
                    }
                }
                else {
                    //variances are same!
                    if (!arg.isExactlyInternal(otherArg)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Is this type a supertype of the given type? 
     */
    public boolean isSupertypeOf(Type type) {
        return type.isSubtypeOf(this);
    }

    /**
     * Is this type a subtype of the given type? 
     */
    public boolean isSubtypeOf(Type type) {
        return type!=null && resolveAliases()
                .isSubtypeOfInternal(type.resolveAliases());
    }

    /**
     * Is this type a subtype of the given type? Ignore
     * a certain self type constraint.
     */
    private boolean isSubtypeOfInternal(Type type) {
        checkDepth();
        incDepth();
        try {
            if (type.isAnything()) {
                return true;
            }
            else if (isExactlyNothing()) {
                return true;
            }
            else if (isUnknown() || type.isUnknown()) {
                return this==type;
            }
            else if (isAnything()) {
                return false;
            }
            else if (type.isExactlyNothing()) {
                return isExactlyNothing();
            }
            else if (isUnion()) {
                for (Type ct: 
                        getInternalCaseTypes()) {
                    if (ct==null || 
                            !ct.isSubtypeOfInternal(type)) {
                        return false;
                    }
                }
                return true;
            }
            else if (type.isUnion()) {
                for (Type ct: 
                        type.getInternalCaseTypes()) {
                    if (ct!=null && 
                            isSubtypeOfInternal(ct)) {
                        return true;
                    }
                }
                return false;
            }
            else if (type.isIntersection()) {
                for (Type ct: 
                        type.getInternalSatisfiedTypes()) {
                    if (ct!=null && 
                            !isSubtypeOfInternal(ct)) {
                        return false;
                    }
                }
                return true;
            }
            else if (isIntersection()) {
                if (type.isClassOrInterface()) {
                    TypeDeclaration otherDec = 
                            type.getDeclaration();
                    Type pst = getSupertype(otherDec);
                    if (pst!=null && 
                            pst.isSubtypeOfInternal(type)) {
                        return true;
                    }
                }
                for (Type ct: 
                        getInternalSatisfiedTypes()) {
                    if (ct==null || 
                            ct.isSubtypeOfInternal(type)) {
                        return true;
                    }
                }
                return false;
            }
            else if (isTypeConstructor() && 
                    type.isTypeConstructor()) {
                return isSubtypeOfTypeConstructor(type);
            }
            else if (isTypeConstructor()) {
                return type.isAnything() || type.isObject();
            }
            else if (type.isTypeConstructor()) {
                return false;
            }
            else if (isObject()) {
                return type.isObject();
            }
            else if (isNull()) {
                return type.isNull();
            }
            else if (isInterface() && type.isClass()) {
                return type.isObject();
            }
            else {
                if (isTuple() && type.isTuple()) {
                    return isSubtypeOfTuple(type);
                }
                TypeDeclaration otherDec = 
                        type.getDeclaration();
                Type supertype = getSupertype(otherDec);
                if (supertype==null) {
                    return false;
                }
                else {
                    supertype = supertype.resolveAliases();
                    Type stqt =
                            supertype.trueQualifyingType();
                    Type tqt =
                            type.trueQualifyingType();
                    if (stqt==null || tqt==null) {
                        if (tqt!=stqt) {
                            return false;
                        }
                    }
                    else if (tqt.isUnknown() || stqt.isUnknown()) {
                        return false;
                    }
                    else {
                        if (!otherDec.isMember()) {
                            //local types with a qualifying typed 
                            //declaration do not need to obtain the
                            //qualifying type's supertype
                            if (!stqt.isSubtypeOfInternal(tqt)) {
                                return false;
                            }
                        }
                        else {
                            //note that the qualifying type of the
                            //given type may be an invariant subtype
                            //of the type that declares the member
                            //type, as long as it doesn't refine the
                            //member type
                            TypeDeclaration totd = 
                                    (TypeDeclaration) 
                                        otherDec.getContainer();
                            Type tqts = tqt.getSupertype(totd);
                            if (tqts==null) {
                                return false;
                            }
                            else {
                                tqts = tqts.resolveAliases();
                                if(!stqt.isSubtypeOfInternal(tqts)) {
                                    return false;
                                }
                            }
                        }
                    }
                    return isTypeArgumentListAssignable(
                            supertype, type);
                }
            }
        }
        finally { 
            decDepth();
        }
    }

    private boolean isSubtypeOfTuple(Type type) {
        TypeDeclaration td = 
                getDeclaration()
                    .getUnit()
                    .getTupleDeclaration();
        TypeParameter elem = td.getTypeParameters().get(0);
        TypeParameter first = td.getTypeParameters().get(1);
        TypeParameter rest = td.getTypeParameters().get(2);
        Type t1 = this;
        Type t2 = type;
        while (true) {
            Map<TypeParameter, Type> t1a = 
                    t1.getTypeArguments();
            Map<TypeParameter, Type> t2a = 
                    t2.getTypeArguments();
            Type e1 = t1a.get(elem);
            Type e2 = t2a.get(elem);
            Type f1 = t1a.get(first);
            Type f2 = t2a.get(first);
            if (e1==null || e2==null || 
                f1==null || f2==null) {
                return false;
            }
            if (!f1.isSubtypeOfInternal(f2) || 
                !e1.isSubtypeOfInternal(e2)) {
                return false;
            }
            Type r1 = t1a.get(rest);
            Type r2 = t2a.get(rest);
            if (r1==null || r2==null) {
                return false;
            }
            if (!r1.isTuple() || !r2.isTuple()) {
                //TODO: if both are unions containing
                //      exactly one Tuple, break the 
                //      union in two and continue
                //      iterating with the Tuples
                return r1.isSubtypeOfInternal(r2); 
            }
            t1 = r1;
            t2 = r2;
        }
    }

    private static boolean isTypeArgumentListAssignable(
            Type supertype, Type type) {
        List<TypeParameter> typeParameters = 
                type.getDeclaration()
                    .getTypeParameters();
        for (TypeParameter tp: typeParameters) {
            Type arg = supertype.getTypeArguments().get(tp);
            Type otherArg = type.getTypeArguments().get(tp);
            if (arg==null || otherArg==null) {
                return false;
            }
            else if (type.isCovariant(tp)) {
                if (supertype.isContravariant(tp)) {
                    //Inv<in T> is a subtype of Inv<out Anything>
                    if (!tp.getType()
                            .isSubtypeOf(otherArg)) {
                        return false;
                    }
                }
                else if (!arg.isSubtypeOfInternal(otherArg)) {
                    return false;
                }
            }
            else if (type.isContravariant(tp)) {
                if (supertype.isCovariant(tp)) {
                    //Inv<out T> is a subtype of Inv<in Nothing>
                    if (!otherArg.isNothing()) {
                        return false;
                    }
                }
                else if (!otherArg.isSubtypeOfInternal(arg)) {
                    return false;
                }
            }
            else {
                //type is invariant in p
                //Inv<out Nothing> is a subtype of Inv<Nothing>
                //Inv<in Anything> is a subtype of Inv<Anything>
                if (supertype.isCovariant(tp) && 
                        !arg.isNothing() ||
                    supertype.isContravariant(tp) && 
                        !arg.isAnything() ||
                    !arg.isExactlyInternal(otherArg)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * For Java's static inner types, we sometimes need to
     * ignore the qualifying type, since it doesn't affect
     * the subtyping rules.
     */
    private Type trueQualifyingType() {
        return getDeclaration()
                    .isStaticallyImportable() ? null : 
                getQualifyingType();
    }
    
    private boolean isExactlyTypeConstructor(Type type) {
        TypeDeclaration dec = getDeclaration();
        TypeDeclaration otherDec = type.getDeclaration();
        if (dec.equals(otherDec)) {
            return true;
        }
        else {
            TypeParameter typeConstructorParam = 
                    getTypeConstructorParameter();
            Type qualifyingType = getQualifyingType();
            Type otherQualifyingType = type.getQualifyingType();
            if (typeConstructorParam==null) {
                List<Type> paramsAsArgs =
                        typeParametersAsArgList(otherDec);
                Type appliedType =
                        dec.appliedType(
                                qualifyingType,
                                paramsAsArgs);
                Type otherAppliedType =
                        otherDec.appliedType(
                                otherQualifyingType,
                                paramsAsArgs);
                return appliedType.isExactly(otherAppliedType) &&
                        hasExactSameUpperBounds(type,
                                paramsAsArgs);
            }
            else {
                List<Type> paramsAsArgs = 
                        typeParametersAsArgList(
                                typeConstructorParam);
                Type appliedType = 
                        dec.appliedType(
                                qualifyingType, 
                                paramsAsArgs);
                Type otherAppliedType = 
                        otherDec.appliedType(
                                otherQualifyingType, 
                                paramsAsArgs);
                //resolves aliases:
                return appliedType.isExactly(otherAppliedType);
            }
        }
    }

    private boolean isSubtypeOfTypeConstructor(Type type) {
        TypeDeclaration dec = getDeclaration();
        TypeDeclaration otherDec = type.getDeclaration();
        if (dec.equals(otherDec)) {
            return true;
        }
        else {
            Type qualifyingType = getQualifyingType();
            Type otherQualifyingType = type.getQualifyingType();
            TypeParameter typeConstructorParam = 
                    getTypeConstructorParameter();
            if (typeConstructorParam == null) {
                // occurs as the type of something
                List<Type> paramsAsArgs = 
                        typeParametersAsArgList(otherDec);
                Type appliedType =
                        dec.appliedType(
                                qualifyingType, 
                                paramsAsArgs);
                Type otherAppliedType = 
                        otherDec.appliedType(
                                otherQualifyingType, 
                                paramsAsArgs);
                return appliedType.isSubtypeOf(otherAppliedType) &&
                        acceptsUpperBounds(type, 
                                paramsAsArgs);
            }
            else {
                // occurs as a type argument to a type 
                // constructor parameter
                List<Type> paramsAsArgs = 
                        typeParametersAsArgList(
                                typeConstructorParam);
                Type appliedType = 
                        dec.appliedType(
                                qualifyingType, 
                                paramsAsArgs);
                Type otherAppliedType = 
                        otherDec.appliedType(
                                otherQualifyingType, 
                                paramsAsArgs);
                //resolves aliases:
                return appliedType.isSubtypeOf(otherAppliedType);
            }
        }
    }

    private boolean acceptsUpperBounds(Type type, 
            List<Type> paramsAsArgs) {
        TypeDeclaration declaration = getDeclaration();
        TypeDeclaration otherDeclaration = 
                type.getDeclaration();
        List<TypeParameter> typeParameters = 
                declaration.getTypeParameters();
        List<TypeParameter> otherTypeParameters = 
                otherDeclaration.getTypeParameters();
        int size = typeParameters.size();
        int otherSize = otherTypeParameters.size();
        if (size<otherSize) {
            return false;
        }
        int required = 0;
        for (int i=0; i<size; i++) {
            TypeParameter param = 
                    typeParameters.get(i);
            if (param.isDefaulted()) {
                break;
            }
            required++;
        }
        if (required>otherSize) {
            return false;
        }
        for (int i=0; i<size && i<otherSize; i++) {
            TypeParameter param = 
                    typeParameters.get(i);
            TypeParameter otherParam = 
                    otherTypeParameters.get(i);
            Map<TypeParameter, Type> otherArgs = 
                    getTypeArgumentMap(otherDeclaration, 
                            null, paramsAsArgs);
            Map<TypeParameter, Type> args = 
                    getTypeArgumentMap(declaration, 
                            null, paramsAsArgs);
            Map<TypeParameter, SiteVariance> none = 
                    EMPTY_VARIANCE_MAP;
            Type otherBound = 
                    intersectionOfSupertypes(otherParam)
                        .substitute(otherArgs, none);
            Type bound = 
                    intersectionOfSupertypes(param)
                        .substitute(args, none);
            if (!otherBound.isSubtypeOf(bound)) {
                return false;
            }
            Type otherEnumBound = 
                    unionOfCaseTypes(otherParam)
                        .substitute(otherArgs, none);
            Type enumBound = 
                    unionOfCaseTypes(param)
                        .substitute(args, none);
            if (!otherEnumBound.isSubtypeOf(enumBound)) {
                return false;
            }
        }
        return true;
    }

    private boolean hasExactSameUpperBounds(Type type, 
            List<Type> paramsAsArgs) {
        TypeDeclaration declaration = getDeclaration();
        TypeDeclaration otherDeclaration = 
                type.getDeclaration();
        List<TypeParameter> typeParameters = 
                declaration.getTypeParameters();
        List<TypeParameter> otherTypeParameters = 
                otherDeclaration.getTypeParameters();
        int size = typeParameters.size();
        int otherSize = otherTypeParameters.size();
        if (size!=otherSize) {
            return false;
        }
        int required = 0;
        int otherRequired = 0;
        for (int i=0; i<size; i++) {
            TypeParameter param = 
                    typeParameters.get(i);
            if (param.isDefaulted()) {
                break;
            }
            required++;
        }
        for (int i=0; i<otherSize; i++) {
            TypeParameter param = 
                    otherTypeParameters.get(i);
            if (param.isDefaulted()) {
                break;
            }
            otherRequired++;
        }
        if (required!=otherRequired) {
            return false;
        }
        for (int i=0; i<size && i<otherSize; i++) {
            TypeParameter param = 
                    typeParameters.get(i);
            TypeParameter otherParam = 
                    otherTypeParameters.get(i);
            Map<TypeParameter, Type> otherArgs = 
                    getTypeArgumentMap(otherDeclaration, 
                            null, paramsAsArgs);
            Map<TypeParameter, Type> args = 
                    getTypeArgumentMap(declaration, 
                            null, paramsAsArgs);
            Map<TypeParameter, SiteVariance> none = 
                    EMPTY_VARIANCE_MAP;
            Type otherBound = 
                    intersectionOfSupertypes(otherParam)
                        .substitute(otherArgs, none);
            Type bound = 
                    intersectionOfSupertypes(param)
                        .substitute(args, none);
            if (!otherBound.isExactly(bound)) {
                return false;
            }
            Type otherEnumBound = 
                    unionOfCaseTypes(otherParam)
                        .substitute(otherArgs, none);
            Type enumBound = 
                    unionOfCaseTypes(param)
                        .substitute(args, none);
            if (!otherEnumBound.isExactly(enumBound)) {
                return false;
            }
        }
        return true;
    }
    /**
     * Eliminate the given type from the union type.
     * (Performs a set complement operation.) Note
     * that this operation is not robust and only 
     * works if this is a union of the given type
     * with some other types that don't involve the
     * given type.
     */
    public Type minus(Type pt) {
        //canonicalize and then remove the type
        //from the resulting union
        return resolveAliases()
                .minusInternal(pt.resolveAliases());
    }
    
    /**
     * Eliminates unioned Null from a type in a
     * very special way that the backend prefers
     */
    public Type eliminateNull() {
        TypeDeclaration dec = getDeclaration();
        Unit unit = dec.getUnit();
        if (dec.inherits(unit.getNullDeclaration())) {
            return unit.getNothingType();
        }
        else if (isUnion()) {
            List<Type> caseTypes = getCaseTypes();
            List<Type> types = 
                    new ArrayList<Type>
                        (caseTypes.size());
            for (Type ct: caseTypes) {
                addToUnion(types, ct.eliminateNull());
            }
            return union(types, unit);
        }
        else {
            return this;
        }
    }
    
    public Type shallowMinus(Type pt) {
        TypeDeclaration dec = getDeclaration();
        Unit unit = dec.getUnit();
        if (isSubtypeOf(pt)) {
            return unit.getNothingType();
        }
        else if (isUnion()) {
            List<Type> caseTypes = getCaseTypes();
            List<Type> types = 
                    new ArrayList<Type>
                        (caseTypes.size());
            for (Type ct: caseTypes) {
                addToUnion(types, ct.shallowMinus(pt));
            }
            return union(types, unit);
        }
        else {
            return this;
        }
    }
    
    private Type minusInternal(Type pt) {
        TypeDeclaration dec = getDeclaration();
        Unit unit = dec.getUnit();
        if (pt.coversInternal(this)) { //note: coversInternal() already calls getUnionOfCases()
            return unit.getNothingType();
        }
        else {
            Type ucts = getUnionOfCases();
            if (ucts.isUnion()) {
                List<Type> cts = 
                        ucts.getCaseTypes();
                List<Type> types = 
                        new ArrayList<Type>
                            (cts.size());
                for (Type ct: cts) {
                    addToUnion(types, ct.minus(pt));
                }
                Type type = union(types, unit);
                return type.coversInternal(this) ? 
                        this : type;
            }
            else if (isIntersection()) {
                List<Type> cts = 
                        ucts.getSatisfiedTypes();
                List<Type> types = 
                        new ArrayList<Type>
                            (cts.size());
                for (Type ct: cts) {
                    addToIntersection(types, ct.minus(pt), 
                            unit);
                }
                Type type = 
                        canonicalIntersection(types, unit);
                return type.coversInternal(this) ? 
                        this : type;
            }
            else if (isTypeParameter()) {
                Type upperBoundsMinus = 
                        intersectionOfSupertypes(dec)
                                .minusInternal(pt);
                Type type = 
                        intersectionType(upperBoundsMinus, 
                                this, unit);
                return type.coversInternal(this) ? 
                        this : type;
            }
            else {
                return this;
            }
        }
    }
    
    /**
     * Substitute the type arguments and use-site variances
     * given in the given type into this type, which is
     * usually a supertype of the given type.
     * 
     * @param source the type carrying type arguments and 
     *        use-site variances to apply
     *        
     * @return this type after application of use-site 
     *         variances and substitution of type arguments
     */
    public Type substitute(Type source) {
        return substitute(source.getTypeArguments(), 
                source.getVarianceOverrides());
    }
    
    /**
     * Substitute the type arguments and use-site variances
     * given in the qualifying type and type arguments of
     * the given member reference into this type (which is
     * always the type of the member reference). 
     * 
     * @param source the member reference carrying type
     *        arguments and use-site variances to apply
     *        
     * @return this type after application of use-site 
     *         variances and substitution of type arguments
     */
    public Type substitute(TypedReference source) {
        Type receiver = source.getQualifyingType();
        return substitute(source.getTypeArguments(),
                receiver==null ? null :
                    receiver.collectVarianceOverrides(),
                    source.isCovariant(),
                    source.isContravariant());
    }
    
    /**
     * Substitute the given types for the corresponding
     * given type parameters wherever they appear in the
     * type. Has the side-effect of performing disjoint
     * type analysis, simplifying union/intersection
     * types, even when there are no substitutions. 
     */
    public Type substitute(
            Map<TypeParameter, Type> substitutions, 
            Map<TypeParameter, SiteVariance> overrides) {
        return substitute(substitutions, overrides, 
                true, false);
    }
    
    private Type substitute(
            Map<TypeParameter, Type> substitutions, 
            Map<TypeParameter, SiteVariance> overrides,
            boolean covariant, boolean contravariant) {
        if (!substitutions.isEmpty()) {
            Type type = this;
            if (overrides!=null) {
                type = applyVarianceOverrides(this, 
                        covariant, contravariant, overrides);
            }
            return new Substitution(
                    substitutions, overrides)
                .substitute(type, covariant, contravariant)
                /*.simple()*/;
        }
        else {
            return this;
        }
    }
    
    /**
     * Performs substitution of type arguments and variances
     * of the given type into this supertype of the given 
     * type.
     * 
     * @param source an extended type, satisfied type, or
     *        case type
     */
    private Type substituteFromSubtype(Type source) {
        return substituteFromSubtype(
                source.getTypeArguments(), 
                source.getVarianceOverrides());
    }

    private Type substituteFromSubtype(
            Map<TypeParameter, Type> substitutions, 
            Map<TypeParameter, SiteVariance> overrides) {
        if (!substitutions.isEmpty()) {
            Type type = this;
            if (overrides!=null) {
                type = applyVarianceOverrides(this, 
                        true, false, overrides);
            }
            return new SupertypeSubstitution(
                    substitutions, overrides)
                .substitute(type, true, false);
        }
        else {
            return this;
        }
    }

    /**
     * A member or member type of the type with actual type 
     * arguments to the receiving type and invocation.
     */
    public Reference getTypedReference(
            Declaration member, 
            List<Type> typeArguments) {
        if (member instanceof TypeDeclaration) {
            TypeDeclaration td = (TypeDeclaration) member;
            return getTypeMember(td, typeArguments);
        }
        else {
            TypedDeclaration td = (TypedDeclaration) member;
            return getTypedMember(td, typeArguments);
        }
    }
    
    public TypedReference getTypedMember(
            TypedDeclaration member, 
            List<Type> typeArguments) {
        return getTypedMember(member, typeArguments, false);
    }
    
    /**
     * A member of the type with actual type arguments
     * to the receiving type and invocation.
     * 
     * @param member the declaration of a member of
     *               this type
     * @param typeArguments the type arguments of the
     *                      invocation
     * @param assigned does this reference occur on the
     *                 RHS of an assignment operator
     */
    public TypedReference getTypedMember(
            TypedDeclaration member, 
            List<Type> typeArguments, 
            boolean assigned) {
        TypeDeclaration type = 
                (TypeDeclaration) 
                    member.getContainer();
        Type declaringType = getSupertype(type);
        TypedReference ptr = 
                new TypedReference(!assigned, assigned);
        ptr.setDeclaration(member);
        ptr.setQualifyingType(declaringType);
        Map<TypeParameter, Type> map = 
                getTypeArgumentMap(member, declaringType, 
                        typeArguments);
        ptr.setTypeArguments(map);
        return ptr;
    }

    /**
     * A member type of the type with actual type arguments
     * to the receiving type and invocation.
     * 
     * @param member the declaration of a member type of
     *               this type
     * @param typeArguments the type arguments of the
     *                      invocation
     */
    public Type getTypeMember(
            TypeDeclaration member, 
            List<Type> typeArguments) {
        TypeDeclaration type = 
                (TypeDeclaration) 
                    member.getContainer();
        Type declaringType = getSupertype(type);
        return member.appliedType(declaringType, 
                typeArguments);
    }

    /**
     * Substitute invocation type arguments into an upper bound 
     * on a type parameter of the invocation, where this type 
     * represents an upper bound.
     * 
     * @param receiver the type that receives the invocation
     * @param member the invoked member
     * @param typeArguments the explicit or inferred type 
     *                      arguments of the invocation
     * 
     * @return the upper bound of a type parameter, after 
     *         performing all type argument substitution
     */
    public Type appliedType(Type receiver, 
            Declaration member, 
            List<Type> typeArguments,
            List<SiteVariance> variances) {
        Type receivingType;
        if (receiver==null) {
            receivingType = null;
        }
        else {
            TypeDeclaration type = 
                    (TypeDeclaration) 
                        member.getContainer();
            receivingType = receiver.getSupertype(type);
        }
        Map<TypeParameter, Type> typeArgMap = 
                getTypeArgumentMap(member, receivingType, 
                        typeArguments);
        Map<TypeParameter, SiteVariance> varianceMap = 
                getVarianceMap(member, receivingType, 
                        variances);
        return new Substitution(typeArgMap, varianceMap)
                    .substitute(this, true, false);
    }

    public Type getType() {
        return this;
    }
    
    /**
     * Get all supertypes of the type by traversing the
     * whole type hierarchy. Avoid using this!
     */
    public List<Type> getSupertypes() {
        if (isUnion() || isNothing()) {
            throw new UnsupportedOperationException(
                    "getSupertypes() not defined for union types or Nothing");
        }
        return getSupertypes(new ArrayList<Type>(5));
    }
    
    private List<Type> getSupertypes(
            List<Type> list) {
        if (isWellDefined() && 
                addToSupertypes(this, list)) {
            Type extendedType = getExtendedType();
            if (extendedType!=null && 
                    !extendedType.isNothing() &&
                    !extendedType.isUnion()) {
                extendedType.getSupertypes(list);
            }
            List<Type> satisfiedTypes = getSatisfiedTypes();
            for (int i=0, l=satisfiedTypes.size(); 
                    i<l; i++) {
                Type satisfiedType = satisfiedTypes.get(i);
                if (satisfiedType!=null &&
                        !satisfiedType.isNothing() &&
                        !satisfiedType.isUnion()) {
                    satisfiedType.getSupertypes(list);
                }
            }
        }
        return list;
    }
    
    private static boolean addToSupertypes(Type st, 
            List<Type> list) {
        for (Type et: list) {
            TypeDeclaration std = st.getDeclaration();
            TypeDeclaration etd = et.getDeclaration();
            if (std.equals(etd) && //return both a type and its self type
                    st.isExactlyInternal(et)) {
                return false;
            }
        }
        list.add(st);
        return true;
    }

    /**
     * Given a type declaration, return a produced type of 
     * which this type is an invariant subtype.
     * 
     * If this type is Nothing, always return null.
     * 
     * @param dec a type declaration
     * 
     * @return a produced type of the given type declaration
     *         which is a supertype of this type, or null if
     *         there is no such supertype
     */
    public Type getSupertype(TypeDeclaration dec) {
        //we don't resolve aliases here because we want to
        //try and propagate the aliased specified in the 
        //code through to the returned supertype
        
        if (dec==null) {
            return null;
        }
        if (isNothing()) {
            //this is what the backend expects, apparently
            return null;
        }
        boolean canCache = canCacheSupertype(dec);
        if (canCache) {
            TypeCache cache = dec.getUnit().getCache();
            if (cache.containsKey(this, dec)) {
                return cache.get(this, dec);
            }
        }
        
        while (dec.isAlias()) {
            Type et = dec.getExtendedType();
            if (et==null) {
                return null;
            }
            else {
                dec = et.getDeclaration();
                if (dec==null) {
                    return null;
                }
            }
        }
        
        Type superType;
        if (isSimpleSupertypeLookup(dec)) {
            //fast!
            if (getDeclaration().inherits(dec)) {
                superType = dec.getType();
            }
            else {
                superType = null;
            }
        }
        else {
            //slow:
            superType = getSupertype(new SupertypeCriteria(dec));
        }
        
        if (canCache) {
            TypeCache cache = dec.getUnit().getCache();
            cache.put(this, dec, superType);
        }
        return superType;
    }

    private boolean canCacheSupertype(TypeDeclaration dec) {
        boolean complexType = 
                dec instanceof UnionType || 
                dec instanceof IntersectionType;
        return !complexType && 
                !hasUnderlyingType() && 
                collectVarianceOverrides().isEmpty() &&
                TypeCache.isEnabled();
    }

    private boolean isSimpleSupertypeLookup(TypeDeclaration dec) {
        return dec instanceof ClassOrInterface &&
                !isUnion() && !isIntersection() &&
                dec.getTypeParameters().isEmpty() &&
                !dec.isClassOrInterfaceMember() &&
                //this is for the runtime which uses 
                //qualifying types in a strange way
                getQualifyingType()==null;
    }
    
    private boolean hasUnderlyingType() {
        if (getUnderlyingType() != null) {
            return true;
        }
        List<Type> tal = getTypeArgumentList();
        for (int i=0, size=tal.size(); 
                i<size; i++) {
            Type ta = tal.get(i);
            if (ta!=null && ta.hasUnderlyingType()) {
                return true;
            }
        }
        return false;
    }
    
    private static final class SupertypeCriteria 
            implements Criteria {
        private TypeDeclaration dec;
        private SupertypeCriteria(TypeDeclaration dec) {
            this.dec = dec;
        }
        @Override
        public boolean satisfies(TypeDeclaration type) {
            return !(type instanceof UnionType) && 
                   !(type instanceof IntersectionType) && 
                   type.equals(dec);
        }
        @Override
        public boolean isMemberLookup() {
            return false;
        }
    };

    
    /**
     * Given a predicate, return a produced type for a 
     * declaration satisfying the predicate, of which 
     * this type is an invariant subtype. 
     */
    
    static interface Criteria {
        boolean satisfies(TypeDeclaration type);
        boolean isMemberLookup();
    }
    
    /**
     * Search for the most-specialized supertype 
     * satisfying the given predicate. 
     */
    public Type getSupertype(Criteria c) {
        checkDepth();
        incDepth();
        try {
            if (c.satisfies(getDeclaration())) {
                return qualifiedByDeclaringType();
            }
            if (isWellDefined()) {
                //now let's call the two most difficult methods
                //in the whole code base:
                Type result = getPrincipalInstantiation(c);
                result = getPrincipalInstantiationFromCases(c, result);
                if (result==null || result.isNothing()) {
                    return null;
                }
                else {
                    return result;
                }
            }
            else {
                return null;
            }
        }
        finally {
            decDepth();
        }
    }
    
    private Type getPrincipalInstantiationFromCases(
            Criteria c, Type result) {
        if (isUnion()) {
            //trying to infer supertypes of algebraic
            //types from their cases was resulting in
            //stack overflows and is not currently 
            //required by the spec
            List<Type> caseTypes = getInternalCaseTypes();
            if (caseTypes!=null && !caseTypes.isEmpty()) {
                //first find a common superclass or superinterface 
                //declaration that satisfies the criteria, ignoring
                //type arguments for now
                TypeDeclaration stc = 
                        findCommonSuperclass(c, caseTypes);
                if (stc!=null) {
                    //we found the declaration, now try to construct a 
                    //produced type that is a true common supertype
                    Type candidateResult = 
                            getCommonSupertype(caseTypes, stc);
                    if (candidateResult!=null && 
                            (result==null || 
                             candidateResult.isSubtypeOfInternal(result))) {
                        result = candidateResult;
                    }
                }
            }
        }
        return result;
    }

    private TypeDeclaration findCommonSuperclass(Criteria c,
            List<Type> types) {
        TypeDeclaration result = null;
        TypeDeclaration td = 
                types.get(0)
                    .getDeclaration();
        for (TypeDeclaration std: 
                td.getSupertypeDeclarations()) {
            if (std instanceof ClassOrInterface && 
                    c.satisfies(std)) {
                for (Type ct: types) {
                    if (!ct.getDeclaration()
                            .inherits(std)) {
                        std = null;
                        break;
                    }
                }
                if (std!=null) {
                    if (result==null) {
                        result = std;
                    }
                    else if (std.inherits(result)) {
                        result = std;
                    }
                    else if (result.inherits(std)) {
                        
                    }
                    else {
                        result = null;
                        break;
                    }
                }
            }
        }
        return result;
    }
    
    private static ThreadLocal<Integer> depth = 
            new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        }
    };
    
    public static void resetDepth(int initial) {
        depth.set(initial);
    }
    
    static void checkDepth() {
        if (depth.get()>100) {
            throw new DecidabilityException();
        }
    }
    
    static void decDepth() {
        depth.set(depth.get()-1);
    }

    static void incDepth() {
        depth.set(depth.get()+1);
    }
    
    private Type getPrincipalInstantiation(Criteria c) {
        // search for the most-specific supertype for the
        // declaration that satisfies the given Criteria
        
        Type result = null;
        Type lowerBound = null;
        
        Type extendedType = getInternalExtendedType();
        if (extendedType!=null) {
            Type possibleResult = 
                    extendedType.getSupertype(c);
            if (possibleResult!=null) {
                result = possibleResult;
                lowerBound = possibleResult;
            }
        }
        
        List<Type> satisfiedTypes = 
                getInternalSatisfiedTypes();
        for (int i=0, size=satisfiedTypes.size(); 
                i<size; i++) {
            Type satisfiedType = satisfiedTypes.get(i);
            Type possibleResult = 
                    satisfiedType.getSupertype(c);
            if (possibleResult!=null) {
                if (result==null) {
                    result = possibleResult;
                    lowerBound = possibleResult;
                }
                else if (result.isSubtypeOf(possibleResult)) {
                    //just ignore this possible result 
                }
                else if (possibleResult.isSubtypeOf(lowerBound)) {
                    result = possibleResult;
                    lowerBound = possibleResult;
                }
                else {
                    //try to find a supertype of both types
                    //and form a principal instantiation 
                    TypeDeclaration rd = 
                            result.getDeclaration();
                    TypeDeclaration prd = 
                            possibleResult.getDeclaration();
                    
                    TypeDeclaration d = null;
                    if (rd.equals(prd)) {
                        d = rd;
                    }
                    
                    Unit unit = getDeclaration().getUnit();
                    if (d!=null) {
                        result = principalInstantiation(d, 
                                possibleResult, result, unit);
                    }
                    else {
                        //ambiguous! we can't decide between 
                        //the two supertypes which both 
                        //satisfy the criteria
                        if (c.isMemberLookup() && 
                                !satisfiedTypes.isEmpty()) {
                            //for the case of a member lookup, 
                            //try to find a common supertype 
                            //by forming the union of the two 
                            //possible results (since A|B is 
                            //always a supertype of A&B)
                            List<Type> types = 
                                    new ArrayList<Type>(2);
                            types.add(lowerBound);
                            types.add(possibleResult);
                            lowerBound = 
                                    intersection(types, unit);
                            List<Type> lbsts = 
                                    lowerBound.getSatisfiedTypes();
                            List<Type> caseTypes = 
                                    new ArrayList<Type>
                                        (lbsts.size());
                            caseTypes.addAll(lbsts);
                            result = union(caseTypes, unit)
                                    .getSupertype(c);
                            if (result==null) {
                                return unit.getUnknownType();
                            }
                        }
                        else {
                            return unit.getUnknownType();
                        }
                    }
                }
            }
        }
        
        return result;
    }
    
    private Type qualifiedByDeclaringType() {
        Type qt = getQualifyingType();
        if (qt==null) {
            return this;
        }
        else {
            TypeDeclaration declaration = getDeclaration();
            if (!declaration.isMember()) {
                // local types can't have qualifying types that differ
                return this;
            }
            else{
                Type pt = new Type();
                pt.setDeclaration(declaration);
                //replace the qualifying type with
                //the supertype of the qualifying 
                //type that declares this nested
                //type, substituting type arguments
                TypeDeclaration dtd = (TypeDeclaration) 
                        declaration.getContainer();
                Type declaringType = 
                        qt.getSupertype(dtd);
                pt.setQualifyingType(declaringType);
                Map<TypeParameter, Type> tam = 
                        getTypeArgumentMap(declaration, 
                                declaringType, 
                                getTypeArgumentList());
                pt.setTypeArguments(tam);
                pt.setVarianceOverrides(getVarianceOverrides());
                return pt;
            }
        }
    }

    private Type getCommonSupertype(List<Type> caseTypes,
            TypeDeclaration dec) {
        //now try to construct a common produced
        //type that is a common supertype by taking
        //the type args and unioning them
        List<TypeParameter> typeParameters = 
                dec.getTypeParameters();
        List<Type> args = 
                new ArrayList<Type>
                    (typeParameters.size());
        Map<TypeParameter,SiteVariance> variances = 
                new HashMap<TypeParameter,SiteVariance>();
        for (TypeParameter tp: typeParameters) {
            Type result;
            Unit unit = getDeclaration().getUnit();
            if (tp.isCovariant()) {
                List<Type> union = 
                        new ArrayList<Type>
                            (caseTypes.size());
                for (Type pt: caseTypes) {
                    if (pt==null) {
                        return null;
                    }
                    if (pt.isNothing()) {
                        continue;
                    }
                    Type st = pt.getSupertype(dec);
                    if (st==null) {
                        return null;
                    }
                    addToUnion(union, 
                            st.getTypeArguments()
                                .get(tp));
                }
                result = union(union, unit);
            }
            else if (tp.isContravariant()) { 
                List<Type> intersection = 
                        new ArrayList<Type>
                            (caseTypes.size());
                for (Type pt: caseTypes) {
                    if (pt==null) {
                        return null;
                    }
                    if (pt.isNothing()) {
                        continue;
                    }
                    Type st = pt.getSupertype(dec);
                    if (st==null) {
                        return null;
                    }
                    Type arg = 
                            st.getTypeArguments()
                                .get(tp);
                    addToIntersection(intersection, arg, 
                            unit);
                }
                result = canonicalIntersection(intersection, 
                        unit);
            }
            else {
                //invariant is harder, need to account for
                //use site variances!
                List<Type> union = 
                        new ArrayList<Type>
                            (caseTypes.size());
                List<Type> intersection = 
                        new ArrayList<Type>
                            (caseTypes.size());
                boolean covariant = false;
                boolean contravariant = false;
                for (Type pt: caseTypes) {
                    if (pt==null) {
                        return null;
                    }
                    if (pt.isNothing()) {
                        continue;
                    }
                    Type st = pt.getSupertype(dec);
                    if (st==null) {
                        return null;
                    }
                    Type arg = 
                            st.getTypeArguments()
                                .get(tp);
                    if (st.isCovariant(tp)) {
                        covariant = true;
                        addToUnion(union, arg);
                    } 
                    else if (st.isContravariant(tp)) {
                        contravariant = true;
                        addToIntersection(intersection, 
                                arg, unit);
                    }
                    else {
                        addToUnion(union, arg);
                        addToIntersection(intersection, 
                                arg, unit);
                    }
                }
                Type utt = union(union, unit);
                Type itt = intersection(intersection, unit);
                if (!covariant && !contravariant) {
                    if (utt.isExactly(itt)) {
                        result = utt; //invariant!
                    }
                    else {
                        //NOTE: big asymmetry here that
                        //      privileges covariance over
                        //      contravariance. More elegant
                        //      would be to have double
                        //      bounded wildcards like
                        //      "in ITT out UTT"
                        result = utt;
                        variances.put(tp, OUT);
                    }
                }
                else if (covariant && !contravariant) {
                    result = utt;
                    variances.put(tp, OUT);
                }
                else if (contravariant && !covariant) {
                    result = itt;
                    variances.put(tp, IN);
                }
                else {
                    //we have mixed covariant and invariant
                    //instantiations - that's only OK if we
                    //have something of form
                    Type upperBound = 
                            intersectionOfSupertypes(tp);
                    result = upperBound;
                    variances.put(tp, OUT);
                    //Note: we could have used "in Nothing"
                    //      here instead
                }
            }
            if (tp.isTypeConstructor()) {
                //TODO: construct a type alias instead!
                result.setTypeConstructor(true);
                result.setTypeConstructorParameter(tp);
            }
            args.add(result);
        }
        //check that the unioned type args
        //satisfy the type constraints
        //disabled this according to #52
        /*for (int i=0; i<args.size(); i++) {
            TypeParameter tp = dec.getTypeParameters().get(i);
            for (Type ub: tp.getSatisfiedTypes()) {
                if (!args.get(i).isSubtypeOf(ub)) {
                    return null;
                }
            }
        }*/
        //recurse to the qualifying type
        Type outerType;
        if (dec.isMember() && 
                !dec.isStaticallyImportable()) {
            TypeDeclaration outer = 
                    (TypeDeclaration) 
                        dec.getContainer();
            List<Type> list = 
                    new ArrayList<Type>
                        (caseTypes.size());
            for (Type ct: caseTypes) {
                if (ct==null) {
                    return null;
                }
                List<Type> intersectedTypes;
                if (ct.isIntersection()) {
                    intersectedTypes = 
                            ct.getSatisfiedTypes();
                }
                else {
                    intersectedTypes = singletonList(ct);
                }
                for (Type it: intersectedTypes) {
                    if (it.getDeclaration().isMember()) {
                        Type st = 
                                it.getQualifyingType()
                                    .getSupertype(outer);
                        list.add(st);
                    }
                }
            }
            outerType = getCommonSupertype(list, outer);
        }
        else {
            outerType = null;
        }
        //make the resulting type
        Type candidateResult = 
                dec.appliedType(outerType, args);
        candidateResult.setVarianceOverrides(variances);
        //check the the resulting type is *really*
        //a subtype (take variance into account)
        for (Type pt: caseTypes) {
            if (!pt.isSubtypeOf(candidateResult)) {
                return null;
            }
        }
        return candidateResult;
    }
    
    
    /**
     * Get the type arguments as a tuple. 
     */
    public List<Type> getTypeArgumentList() {
        List<TypeParameter> tps = 
                getDeclaration()
                    .getTypeParameters();
        if (tps.isEmpty()) {
            return NO_TYPE_ARGS;
        }
        else {
//            if (TypeCache.isEnabled()) {
//                if (typeArgumentList==null) {
//                    typeArgumentList = 
//                            getTypeArgumentListInternal();
//                }
//                return typeArgumentList;
//            }
//            else {
                return getTypeArgumentListInternal();
            }
//        }
    }

    private List<Type> getTypeArgumentListInternal() {
        TypeDeclaration dec = getDeclaration();
        List<TypeParameter> tps = dec.getTypeParameters();
        int size = tps.size();
        List<Type> argList = new ArrayList<Type>(size);
        Map<TypeParameter, Type> args = getTypeArguments();
        for (int i=0; i<size; i++) {
            TypeParameter tp = tps.get(i);
            Type arg = args.get(tp);
            if (arg==null) {
                arg = dec.getUnit().getUnknownType();
            }
            argList.add(arg);
        }
//        argList = unmodifiableList(argList);
        return argList;
    }

    /**
     * Determine if this is a decidable supertype, i.e. if 
     * it obeys the restriction that types with contravariant 
     * type parameters may only appear in covariant positions. 
     * 
     * @return a list of type parameters which appear in  
     *         illegal positions
     */
    public List<TypeDeclaration> checkDecidability() {
        List<TypeDeclaration> errors = 
                new ArrayList<TypeDeclaration>();
        List<TypeParameter> typeParameters = 
                getDeclaration().getTypeParameters();
        Map<TypeParameter, Type> typeArguments = 
                getTypeArguments();
        for (TypeParameter tp: typeParameters) {
            Type pt = typeArguments.get(tp);
            if (pt!=null) {
                pt.checkDecidability(tp.isCovariant(), 
                        tp.isContravariant(), errors);
            }
        }
        return errors;
    }
    
    private void checkDecidability(
            boolean covariant, 
            boolean contravariant, 
            List<TypeDeclaration> errors) {
        if (isTypeParameter()) {
            //nothing to do
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                ct.checkDecidability(
                        covariant, contravariant, 
                        errors);
            }
        }
        else if (isIntersection()) {
            for (Type ct: getSatisfiedTypes()) {
                ct.checkDecidability(
                        covariant, contravariant, 
                        errors);
            }
        }
        else {
            TypeDeclaration declaration = getDeclaration();
            for (TypeParameter tp: 
                    declaration.getTypeParameters()) {
                if (!covariant && tp.isContravariant()) {
                    //a type with contravariant parameters appears at
                    //a contravariant location in satisfies / extends
                    if (!errors.contains(declaration)) {
                        errors.add(declaration);
                    }
                }
                Type pt = 
                        getTypeArguments().get(tp);
                if (pt!=null) {
                    if (tp.isCovariant()) {
                        pt.checkDecidability(
                                covariant, contravariant, 
                                errors);
                    }
                    else if (tp.isContravariant()) {
                        if (covariant|contravariant) { 
                            pt.checkDecidability(
                                    !covariant, !contravariant, 
                                    errors); 
                        }
                        else {
                            //else if we are in an invariant 
                            //position, it stays invariant
                            pt.checkDecidability(
                                    covariant, contravariant, 
                                    errors);
                        }
                    }
                    else {
                        pt.checkDecidability(
                                false, false, 
                                errors);
                    }
                }
            }
        }
    }
    
    /**
     * Check that this type can appear at a position,
     * given the variance of the position (covariant,
     * contravariant, or invariant.)
     * 
     * @param covariant true for a covariant position
     * @param contravariant true for a contravariant 
     *                      position
     * @param declaration TODO!
     * 
     * @return a list of type parameters which appear
     *         in illegal positions
     */
    public List<TypeParameter> checkVariance(
            boolean covariant, 
            boolean contravariant, 
            Declaration declaration) {
        List<TypeParameter> errors = 
                new ArrayList<TypeParameter>(3);
        checkVariance(covariant, contravariant, 
                declaration, errors);
        return errors;
    }
    
    private static boolean isTrulyCovariant(TypeParameter tp) {
        Scope container = tp.getContainer();
        if (container instanceof TypeParameter) {
            TypeParameter tcp = (TypeParameter) container;
            if (isTrulyCovariant(tcp)) {
                return tp.isCovariant();
            }
            else if (isTrulyContravariant(tcp)) {
                return tp.isContravariant();
            }
            else {
                return false;
            }
        }
        else {
            return tp.isCovariant();
        }
    }
    
    private static boolean isTrulyContravariant(TypeParameter tp) {
        Scope container = tp.getContainer();
        if (container instanceof TypeParameter) {
            TypeParameter tcp = (TypeParameter) container;
            if (isTrulyCovariant(tcp)) {
                return tp.isContravariant();
            }
            else if (isTrulyContravariant(tcp)) {
                return tp.isCovariant();
            }
            else {
                return false;
            }
        }
        else {
            return tp.isContravariant();
        }
    }
    
    private void checkVariance(
            boolean covariant, 
            boolean contravariant,
            Declaration declaration, 
            List<TypeParameter> errors) {
        TypeDeclaration dec = getDeclaration();
        if (isTypeParameter()) {
            TypeParameter tp = (TypeParameter) dec;
            Declaration parameterizedDec = 
                    tp.getDeclaration();
            if (!parameterizedDec.equals(declaration)) {
                if (//if a contravariant type parameter appears 
                    //in a covariant location
                    !covariant && 
                        isTrulyCovariant(tp) ||
                    //or a covariant type parameter appears in a 
                    //contravariant location
                    !contravariant && 
                        isTrulyContravariant(tp)) {
                    //add an error to the list
                    errors.add(tp);
                }
            }
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                ct.checkVariance(
                        covariant, contravariant, 
                        declaration, errors);
            }
        }
        else if (isIntersection()) {
            for (Type ct: getSatisfiedTypes()) {
                ct.checkVariance(
                        covariant, contravariant, 
                        declaration, errors);
            }
        }
        else {
            Type qt = getQualifyingType();
            if (qt!=null) {
                qt.checkVariance(
                        covariant, contravariant, 
                        declaration, errors);
            }
            for (TypeParameter tp: dec.getTypeParameters()) {
                Type pt = getTypeArguments().get(tp);
                if (pt!=null) {
                    if (isCovariant(tp)) {
                        pt.checkVariance(
                                covariant, contravariant, 
                                declaration, errors);
                    }
                    else if (isContravariant(tp)) {
                        if (covariant|contravariant) {
                            //flip the variance
                            pt.checkVariance(
                                    !covariant, !contravariant, 
                                    declaration, errors); 
                        }
                        else {
                            //unless we are in an invariant 
                            //position, then it stays invariant
                            pt.checkVariance(
                                    covariant, contravariant, 
                                    declaration, errors);
                        }
                    }
                    else {
                        pt.checkVariance(false, false, 
                                declaration, errors);
                    }
                }
            }
        }
    }

    /**
     * Is the type well-defined? Are any of its arguments
     * garbage nulls?
     */
    public boolean isWellDefined() {
        List<TypeParameter> tps = 
                getDeclaration().getTypeParameters();
        Type qt = getQualifyingType();
        if (qt!=null && 
                !qt.isWellDefined()) {
            return false;
        }
        List<Type> tas = getTypeArgumentList();
        for (int i=0; i<tps.size(); i++) {
            Type at = tas.get(i);
            TypeParameter tp = tps.get(i);
            if ((!tp.isDefaulted() && at==null) || 
                    (at!=null && !at.isWellDefined())) {
                return false;
            }
        }
        return true;
    }

    /**
     * Is the type fully-known? Are any of its arguments
     * unknowns?
     */
    public boolean containsUnknowns() {
        if (isUnknown()) {
            return true;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.containsUnknowns()) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.containsUnknowns()) {
                    return true;
                }
            }
        }
        else if (isNothing()) {
            return false;
        }
        else {
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.containsUnknowns()) {
                return true;
            }
            if (!isTypeConstructor()) {
                for (Type at: getTypeArgumentList()) {
                    if (at==null || 
                            at.containsUnknowns()) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    public String getFirstUnknownTypeError() {
        return getFirstUnknownTypeError(false);
    }

    public String getFirstUnknownTypeError(
            boolean includeSuperTypes) {
        if (isUnknown()) {
            TypeDeclaration dec = getDeclaration();
            UnknownType ut = (UnknownType) dec;
            ErrorReporter errorReporter = 
                    ut.getErrorReporter();
            return errorReporter == null ? null : 
                errorReporter.getMessage();
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                String error = 
                        ct.getFirstUnknownTypeError(
                                includeSuperTypes);
                if (error!=null) {
                    return error;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                String error = 
                        st.getFirstUnknownTypeError(
                                includeSuperTypes);
                if (error!=null) {
                    return error;
                }
            }
        }
        else if (isNothing()) {
            return null;
        }
        else {
            if (includeSuperTypes) {
                Type et = getExtendedType();
                if (et != null) {
                    String error = 
                            et.getFirstUnknownTypeError(
                                    includeSuperTypes);
                    if (error!=null) {
                        return error;
                    }
                }
                for (Type st: getSatisfiedTypes()) {
                    String error = 
                            st.getFirstUnknownTypeError(
                                    includeSuperTypes);
                    if (error!=null) {
                        return error;
                    }
                }
            }
            Type qt = getQualifyingType();
            if (qt!=null) {
                String error = 
                        qt.getFirstUnknownTypeError(
                                includeSuperTypes);
                if (error!=null) {
                    return error;
                }
            }
            List<Type> tas = getTypeArgumentList();
            for (Type at: tas) {
                if (at!=null) {
                    String error = 
                            at.getFirstUnknownTypeError(false);
                    if (error!=null) {
                        return error;
                    }
                }
            }
        }
        return null;
    }

    public boolean involvesDeclaration(Declaration d) {
        if (d instanceof TypeDeclaration) {
            return involvesDeclaration((TypeDeclaration) d);
        }
        else {
            return false;
        }
    }

    public boolean involvesDeclaration(TypeDeclaration td) {
        return involvesDeclaration(td, 
                new ArrayList<Type>());
    }
    private boolean involvesDeclaration(TypeDeclaration td,
            List<Type> visited) {
        if (isUnknown()) {
            return false;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.involvesDeclaration(td, visited)) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.involvesDeclaration(td, visited)) {
                    return true;
                }
            }
        }
        else if (isNothing()) {
            return false;
        }
        else {
            if (visited.contains(this)) { //might be quicker to check only for ==
                return false;
            }
            visited.add(this);
            
            if (getDeclaration().equals(td)) {
                return true;
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.involvesDeclaration(td, visited)) {
                return true;
            }
            List<Type> tas = getTypeArgumentList();
            for (Type at: tas) {
                if (at==null || //take note!
                        at.involvesDeclaration(td, visited)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean occursInvariantly(TypeParameter tp) {
        return occursInvariantly(tp, true, false);
    }

    private boolean occursInvariantly(TypeParameter tp,
            boolean covariant, boolean contravariant) {
        if (isUnknown()) {
            return false;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.occursInvariantly(tp,
                        covariant, contravariant)) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.occursInvariantly(tp,
                        covariant, contravariant)) {
                    return true;
                }
            }
        }
        else if (isNothing()) {
            return false;
        }
        else {
            TypeDeclaration dec = getDeclaration();
            if (!covariant && !contravariant && 
                    dec.equals(tp)) {
                return true;
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.occursInvariantly(tp,
                            covariant, contravariant)) {
                return true;
            }
            List<TypeParameter> tps = 
                    dec.getTypeParameters();
            List<Type> tas = getTypeArgumentList();
            for (int i=0; i<tps.size() && i<tas.size(); i++) {
                TypeParameter itp = tps.get(i);
                Type at = tas.get(i);
                if (at!=null) {
                    boolean co;
                    boolean contra;
                    if (!covariant && !contravariant) {
                        co = false;
                        contra = false;
                    }
                    else if (isCovariant(itp)) {
                        co = covariant;
                        contra = contravariant;
                    }
                    else if (isContravariant(itp)) {
                        co = !covariant;
                        contra = !contravariant;
                    }
                    else {
                        co = false;
                        contra = false;
                    }
                    if (at.occursInvariantly(tp, co, contra)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public boolean occursCovariantly(TypeParameter tp) {
        return occursCovariantly(tp, true);
    }

    private boolean occursCovariantly(TypeParameter tp,
            boolean covariant) {
        if (isUnknown()) {
            return false;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.occursCovariantly(tp,covariant)) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.occursCovariantly(tp,covariant)) {
                    return true;
                }
            }
        }
        else if (isNothing()) {
            return false;
        }
        else {
            TypeDeclaration dec = getDeclaration();
            if (covariant && dec.equals(tp)) {
                return true;
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.occursCovariantly(tp,covariant)) {
                return true;
            }
            List<TypeParameter> tps = 
                    dec.getTypeParameters();
            List<Type> tas = getTypeArgumentList();
            for (int i=0; i<tps.size() && i<tas.size(); i++) {
                TypeParameter itp = tps.get(i);
                Type at = tas.get(i);
                if (at!=null && !isInvariant(itp)) {
                    boolean co = covariant;
                    if (isContravariant(itp)) {
                        co = !co;
                    }
                    if (at.occursCovariantly(tp, co)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public boolean occursContravariantly(TypeParameter tp) {
        return occursContravariantly(tp, true);
    }

    private boolean occursContravariantly(TypeParameter tp,
            boolean covariant) {
        if (isUnknown()) {
            return false;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.occursContravariantly(tp, covariant)) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.occursContravariantly(tp, covariant)) {
                    return true;
                }
            }
        }
        else if (isNothing()) {
            return false;
        }
        else {
            TypeDeclaration dec = getDeclaration();
            if (!covariant && dec.equals(tp)) {
                return true;
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.occursContravariantly(tp, covariant)) {
                return true;
            }
            List<TypeParameter> tps = 
                    dec.getTypeParameters();
            List<Type> tas = getTypeArgumentList();
            for (int i=0; i<tps.size() && i<tas.size(); i++) {
                TypeParameter itp = tps.get(i);
                Type at = tas.get(i);
                if (at!=null && !isInvariant(itp)) {
                    boolean co = covariant;
                    if (isContravariant(itp)) {
                        co = !co;
                    }
                    if (at.occursContravariantly(tp, co)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private List<Type> getInternalSatisfiedTypes() {
        TypeDeclaration dec = getDeclaration();
        List<Type> sts = dec.getSatisfiedTypes();
        if (getTypeArguments().isEmpty()) {
            return sts;
        }
        List<Type> satisfiedTypes = 
                new ArrayList<Type>
                    (sts.size());
        for (Type st: sts) {
            satisfiedTypes.add(st.substituteFromSubtype(this));
        }
        return satisfiedTypes;
    }

    private Type getInternalExtendedType() {
        TypeDeclaration dec = getDeclaration();
        Type et = dec.getExtendedType();
        if (et==null) {
            return null;
        }
        else {
            if (getTypeArguments().isEmpty()) {
                return et;
            }
            return et.substituteFromSubtype(this);
        }
    }

    private List<Type> getInternalCaseTypes() {
        TypeDeclaration dec = getDeclaration();
        List<Type> cts = dec.getCaseTypes();
        if (cts==null) {
            return null;
        }
        else {
            if (getTypeArguments().isEmpty()) {
                return cts;
            }
            List<Type> caseTypes = 
                    new ArrayList<Type>
                        (cts.size());
            for (Type ct: cts) {
                caseTypes.add(ct.substituteFromSubtype(this));
            }
            return caseTypes;
        }
    }

    public List<Type> getSatisfiedTypes() {
        TypeDeclaration dec = getDeclaration();
        List<Type> sts = dec.getSatisfiedTypes();
        if (getTypeArguments().isEmpty()) {
            return sts; 
        }
        List<Type> satisfiedTypes = 
                new ArrayList<Type>
                    (sts.size());
        for (int i=0, l=sts.size(); i<l; i++) {
            Type st = sts.get(i);
            satisfiedTypes.add(st.substitute(this));
        }
        return satisfiedTypes;
    }

    public Type getExtendedType() {
        TypeDeclaration dec = getDeclaration();
        Type et = dec.getExtendedType();
        if (et==null) {
            return null;
        }
        else {
            if (getTypeArguments().isEmpty()) {
                return et;
            }
            else {
                return et.substitute(this);
            }
        }
    }

    public List<Type> getCaseTypes() {
        TypeDeclaration dec = getDeclaration();
        List<Type> cts = dec.getCaseTypes();
        if (cts==null) {
            return null;
        }
        else {
            if (getTypeArguments().isEmpty()) {
                return cts;
            }
            List<Type> caseTypes = 
                    new ArrayList<Type>
                        (cts.size());
            for (Type ct: cts) {
                caseTypes.add(ct.substitute(this));
            }
            return caseTypes;
        }
    }

    /**
     * Substitutes type arguments for type parameters.
     * This default strategy performs canonicalization and
     * eliminates duplicate types from unions after 
     * substituting type arguments.
     * 
     * @author Gavin King
     */
    private static class Substitution {
        
        Map<TypeParameter, Type> substitutions;
        Map<TypeParameter, SiteVariance> overrides;
        
        private Substitution(
                Map<TypeParameter, Type> substitutions, 
                Map<TypeParameter, SiteVariance> overrides) {
            this.substitutions = substitutions;
            this.overrides = overrides;
        }

        Type substitute(Type type, 
                boolean covariant, boolean contravariant) {
            //Need to guard against stack overflows here,
            //since DefaultTypeArgVisitor catches our
            //thrown DecidabilityException
            checkDepth();
            try {
                incDepth();
            TypeDeclaration dec;
            TypeDeclaration ptd = type.getDeclaration();
            Unit unit = ptd.getUnit();
            if (type.isUnion()) {
                List<Type> cts = type.getCaseTypes();
                List<Type> types = 
                        new ArrayList<Type>
                            (cts.size());
                for (Type ct: cts) {
                    if (ct!=null) {
                        addTypeToUnion(substitute(ct,
                                covariant, contravariant),
                                covariant, contravariant,
                                types);
                    }
                }
                return union(types, unit);
            }
            else if (type.isIntersection()) {
                List<Type> sts = type.getSatisfiedTypes();
                List<Type> types = 
                        new ArrayList<Type>
                            (sts.size());
                for (Type st: sts) {
                    if (st!=null) {
                        addTypeToIntersection(substitute(st,
                                covariant, contravariant),
                                covariant, contravariant,
                                types, unit);
                    }
                }
                return canonicalIntersection(types, unit);
            }
            else if (type.isTypeParameter()) {
                TypeParameter tp = (TypeParameter) ptd;
                Type sub = substitutions.get(tp);
                if (sub==null) {
                    if (tp.isTypeConstructor()) {
                        //if this is an applied type 
                        //constructor parameter, we still 
                        //might need to substitute into its 
                        //arguments!
                        dec = tp;
                    }
                    else {
                        //a regular type parameter, easy,
                        //nothing more to do with it
                        return type;
                    }
                }
                else {
                    if (type.isTypeConstructor()) {
                        //an unapplied generic type 
                        //parameter (a type constructor 
                        //parameter) in a higher-order
                        //abstraction - in this case, the 
                        //argument itself must be an 
                        //assignable type constructor
                        return sub;
                    }
                    else if (tp.getTypeParameters().isEmpty()) {
                        //a regular type parameter
                        //easy, no type arguments or 
                        //qualifying type to substitute
                        return sub;
                    }
                    else {
                        //an applied generic type parameter 
                        //in a higher-order generic 
                        //abstraction - in this case we need
                        //to substitute both the type 
                        //constructor and its arguments
                        return substitutedAppliedTypeConstructor(
                                type, sub, tp,
                                covariant, contravariant, 
                                unit);
                    }
                }
            }
            else {
                dec = ptd;
            }
            if (type.isTypeConstructor()) {
                //a type constructor occurring in a higher
                //rank generic abstraction
                return substitutedTypeConstructor(type,
                        covariant, contravariant);
            }
            else {
                //a plain olde type
                return substitutedType(dec, type,
                        covariant, contravariant);
            }
            }
            finally {
                decDepth();
            }
        }

        private Type substitutedAppliedTypeConstructor(
                Type type, Type sub, 
                TypeParameter typeConstructorParameter,
                boolean covariant, boolean contravariant, 
                Unit unit) {
            List<Type> tal = type.getTypeArgumentList();
            List<TypeParameter> tpl = 
                    typeConstructorParameter.getTypeParameters();
            List<Type> sta = 
                    new ArrayList<Type>
                        (tal.size());
            for (int i=0; 
                    i<tal.size() && 
                    i<tpl.size(); 
                    i++) {
                Type ta = tal.get(i);
                TypeParameter itp = tpl.get(i);
                boolean co = false;
                boolean contra = false;
                if (type.isContravariant(itp)) {
                    co = contravariant;
                    contra = covariant;
                }
                else if (type.isCovariant(itp)) {
                    co = covariant;
                    contra = contravariant;
                }
                sta.add(ta.substitute(
                        substitutions,
                        overrides, 
                        co, contra));
            }
            return substituteIntoTypeConstructors(
                    sub, sta,
                    covariant, contravariant,
                    unit, type);
        }
        
        private Type substituteIntoTypeConstructors(
                Type sub, List<Type> args,
                boolean covariant, boolean contravariant,
                Unit unit, Type tc) {
            if (sub.isUnion()) {
                List<Type> list =
                        new ArrayList<Type>();
                for (Type ct: sub.getCaseTypes()) {
                    addToUnion(list, 
                            substituteIntoTypeConstructors(
                                    ct,args,
                                    covariant, contravariant, 
                                    unit, tc));
                }
                return union(list, unit);
            }
            else if (sub.isIntersection()) {
                List<Type> list =
                        new ArrayList<Type>();
                for (Type st: sub.getSatisfiedTypes()) {
                    addToIntersection(list, 
                            substituteIntoTypeConstructors(st,
                                    args,
                                    covariant, contravariant, 
                                    unit, tc),
                            unit);
                }
                return canonicalIntersection(list, unit);
            }
            else {
                Type sqt = sub.getQualifyingType();
                Type qt = sqt==null ? null : 
                    substitute(sqt,
                            covariant, contravariant);
                Type result = 
                        sub.getDeclaration()
                            .appliedType(qt, args);
                substituteVarianceOverridesInTypeConstructor(
                        tc, result);
                return result;
            }
        }

        private void substituteVarianceOverridesInTypeConstructor(
                Type typeConstructor, 
                Type result) {
            TypeDeclaration sd = result.getDeclaration();
            Map<TypeParameter, SiteVariance> map = 
                    new HashMap<TypeParameter, SiteVariance>();
            map.putAll(result.getVarianceOverrides());
            List<TypeParameter> sdtps = 
                    sd.getTypeParameters();
            List<TypeParameter> tctps = 
                    typeConstructor.getDeclaration()
                        .getTypeParameters();
            for (int i=0; 
                    i<tctps.size() && i<sdtps.size(); 
                    i++) {
                TypeParameter tctp = tctps.get(i);
                TypeParameter sdtp = sdtps.get(i);
                SiteVariance var = 
                        typeConstructor.getVarianceOverrides()
                            .get(tctp);
                if (var!=null) {
                    map.put(sdtp, var);
                }
            }
            result.setVarianceOverrides(map);
        }
        
        void addTypeToUnion(Type ct,
                boolean covariant, boolean contravariant, 
                List<Type> types) {
            addToUnion(types, ct);
        }

        void addTypeToIntersection(Type st,
                boolean covariant, boolean contravariant, 
                List<Type> types, Unit unit) {
            addToIntersection(types, st, unit);
        }
        
        private Type substitutedType(
                TypeDeclaration dec, Type type,
                boolean covariant, boolean contravariant) {
            Type result;
            Type receiverType = type.getQualifyingType();
            if (receiverType!=null) {
                receiverType = 
                        substitute(receiverType, 
                                covariant, contravariant);
                List<Type> typeArgumentList =
                        type.getTypeArgumentList();
                List<Type> args = new ArrayList<Type>();
                for (int i=0, s=typeArgumentList.size();
                        i<s; i++) {
                    args.add(substitute(
                            typeArgumentList.get(i), 
                            covariant, contravariant));
                }
                result = dec.appliedType(receiverType, args);
            }
            else {
                result = new Type();
                result.setDeclaration(dec);
                Map<TypeParameter, Type> typeArguments = 
                        type.getTypeArguments();
                Map<TypeParameter, Type> typeArgs = 
                        new HashMap<TypeParameter, Type>
                            (typeArguments.size());
                for (Map.Entry<TypeParameter, Type> e: 
                        typeArguments.entrySet()) {
                    substituteTypeArgument(typeArgs, type, 
                            covariant, contravariant, 
                            e.getKey(), e.getValue());
                }
                result.setTypeArguments(typeArgs);
            }
            result.setTypeConstructor(
                    type.isTypeConstructor());
            result.setTypeConstructorParameter(
                    type.getTypeConstructorParameter());
            result.setVarianceOverrides(
                    type.getVarianceOverrides());
            result.setUnderlyingType(
                    type.getUnderlyingType());
            return result;
        }

        private void substituteTypeArgument(
                Map<TypeParameter, Type> typeArgs, 
                Type type, 
                boolean covariant, boolean contravariant, 
                TypeParameter tp, Type arg) {
            if (arg!=null) {
                boolean co = false;
                boolean contra = false;
                if (type.isContravariant(tp)) {
                    co = contravariant;
                    contra = covariant;
                }
                else if (type.isCovariant(tp)) {
                    co = covariant;
                    contra = contravariant;
                }
                typeArgs.put(tp, 
                        substitute(arg, co, contra));
            }
        }
        
        private Type substitutedTypeConstructor(
                Type type,
                boolean covariant, boolean contravariant) {
            TypeDeclaration dec = type.getDeclaration();            
            if (dec.isAlias() && substitutions!=null) {
                //necessary for subtyping of rank > 2 
                //polymorphic types and of any higher rank
                //parameter of a generic function ... and
                //possibly also other cases
                Unit unit = dec.getUnit();
                Type et = dec.getExtendedType();
                if (et==null) {
                    return unit.getUnknownType();
                }
                TypeAlias ta = new TypeAlias();
                ta.setAnonymous(dec.isAnonymous());
                ta.setName(dec.getName());
                ta.setExtendedType(et.substitute(
                        substitutions, overrides,
                        covariant, contravariant));
                ta.setScope(dec.getScope());
                ta.setContainer(dec.getContainer());
                ta.setUnit(unit);
                List<TypeParameter> tps = 
                        new ArrayList<TypeParameter>();
                //compute the right bounds for a higher
                //rank parameter of a generic function
                //by substituting the type arguments of
                //the function into the bounds of the
                //parameter
                for (TypeParameter tp: 
                        dec.getTypeParameters()) {
                    TypeParameter stp = new TypeParameter();
                    stp.setName(tp.getName());
                    stp.setScope(tp.getScope());
                    stp.setContainer(tp.getContainer());
                    stp.setUnit(tp.getUnit());
                    stp.setDeclaration(ta);
                    List<Type> sts = tp.getSatisfiedTypes();
                    List<Type> ssts = 
                            new ArrayList<Type>
                                (sts.size());
                    for (Type st: sts) {
                        ssts.add(st.substitute(
                                substitutions, overrides,
                                covariant, contravariant));
                    }
                    stp.setSatisfiedTypes(ssts);
                    List<Type> cts = tp.getCaseTypes();
                    if (cts!=null) {
                        List<Type> scts = 
                                new ArrayList<Type>
                                    (cts.size());
                        for (Type ct: cts) {
                            scts.add(ct.substitute(
                                    substitutions, overrides,
                                    covariant, contravariant));
                        }
                        stp.setCaseTypes(scts);
                    }
                    tps.add(stp);
                }
                ta.setTypeParameters(tps);
                Type result = ta.getType();
                Type qt = type.getQualifyingType();
                if (qt!=null) {
                    result.setQualifyingType(qt.substitute(
                            substitutions, overrides,
                            covariant, contravariant));
                }
                result.setTypeConstructor(true);
                result.setTypeConstructorParameter(
                        type.getTypeConstructorParameter());
                return result;
            }
            else {
                return type;
            }
        }
    }
    
    /**
     * This special strategy for internal use by the 
     * containing class does not perform canonicalization
     * nor eliminate duplicate types from unions after 
     * substituting type arguments.
     * 
     * @author Gavin King
     */
    private static class SupertypeSubstitution extends Substitution {
    
        private SupertypeSubstitution(
                Map<TypeParameter, Type> substitutions, 
                Map<TypeParameter, SiteVariance> overrides) {
            super(substitutions, overrides);
        }
        
        @Override 
        void addTypeToUnion(Type ct,
                boolean covariant, boolean contravariant,
                List<Type> types) {
            types.add(ct);
        }

        @Override 
        void addTypeToIntersection(Type st,
                boolean covariant, boolean contravariant,
                List<Type> types, Unit unit) {
            types.add(st);
        }
        
    }

    @Override
    public String toString() {
        String result = asString();
        return isTypeConstructor() ?
                result + " (type constructor)" :
                result + " (type)";
    }
    
    @Override
    public String asString() {
        return asString(null);
    }

    public String asString(Unit unit) {
        return TypePrinter.DEFAULT.print(this, unit);
    }
    
    public String asSourceCodeString(Unit unit) {
        return TypePrinter.ESCAPED.print(this, unit);
    }
    
    public String asString(boolean abbreviate) {
        return asString(abbreviate, null);
    }

    public String asString(boolean abbreviate, Unit unit) {
        return new TypePrinter(abbreviate)
                        .print(this, unit);
    }

    private String qualifiedName() {
        StringBuilder ptn = new StringBuilder();
        if (isTypeConstructor()) {
            return asString();
        }
        Type qt = getQualifyingType();
        TypeDeclaration declaration = getDeclaration();
        if (qt!=null) {
            ptn.append(qt.asQualifiedString())
               .append(".")
               .append(declaration.getName());
        }
        //}
        else {
            ptn.append(declaration.getQualifiedNameString());
        }
        if (!getTypeArgumentList().isEmpty()) {
            ptn.append("<");
            boolean first = true;
            for (Type t: getTypeArgumentList()) {
                if (first) {
                    first = false;
                }
                else {
                    ptn.append(",");
                }
                if (t==null) {
                    ptn.append("unknown");
                }
                else {
                    ptn.append(t.asQualifiedString());
                }
            }
            ptn.append(">");
        }
        return ptn.toString();
    }

    public String asQualifiedString() {
        TypeDeclaration declaration = getDeclaration();
        if (declaration==null) {
            //unknown type
            return null;
        }
        if (isUnion()) {
            StringBuilder name = new StringBuilder();
            boolean first = true;
            for (Type pt: getCaseTypes()) {
                if (first) {
                    first = false;
                }
                else {
                    name.append("|");
                }
                if (pt==null) {
                    name.append("unknown");
                }
                else {
                    name.append(pt.asQualifiedString());
                }
            }
            return name.toString();
        }
        else if (isIntersection()) {
            StringBuilder name = new StringBuilder();
            boolean first = true;
            for (Type pt: getSatisfiedTypes()) {
                if (first) {
                    first = false;
                }
                else {
                    name.append("&");
                }
                if (pt==null) {
                    name.append("unknown");
                }
                else {
                    name.append(pt.asQualifiedString());
                }
            }
            return name.toString();
        }
        else {            
            return qualifiedName();
        }
    }

    /**
     * Form a union type of all cases of the type, 
     * recursively reducing cases to their cases
     */
    public Type getUnionOfCases() {
        Unit unit = getDeclaration().getUnit();
        //if X is an intersection type A&B, and A is an
        //enumerated type with cases U and V, then the cases
        //of X are the intersection (U|V)&B canonicalized to
        //the union U&B|V&B
        if (isIntersection()) {
            List<Type> sts = getSatisfiedTypes();
            List<Type> list = 
                    new ArrayList<Type>
                        (sts.size());
            for (Type st: sts) {
                addToIntersection(list, 
                        st.getUnionOfCases(), 
                        unit);
            }
            return canonicalIntersection(list, unit);
        }
        else {
            List<Type> cts = getCaseTypes();
            if (cts==null) {
                return narrowToUpperBounds();
            }
            //otherwise, if X is a union A|B, or an enumerated 
            //type, with cases A and B, and A is an enumerated 
            //type with cases U and V, then the cases of X are
            //the union U|V|B
            else {
                //build a union of all the cases
                List<Type> list = 
                        new ArrayList<Type>
                            (cts.size());
                for (Type ct: cts) {
                    if (ct.isExactly(this)) {
                        //we hit a self type
                        return this;
                    }
                    addToUnion(list,
                            ct.narrowToUpperBounds()
                                .getUnionOfCases()); //note recursion
                }
                return union(list, unit);
            }
        }
    }

    /**
     * If a covariant type argument is a supertype of the
     * upper bounds on the type parameter, replace it with
     * the upper bounds.
     *  
     * @return a new instantiation of the generic type that 
     *         covers this instantiation, but whose type 
     *         arguments satisfy the upper bounds
     */
    Type narrowToUpperBounds() {
        if (isClassOrInterface()) {
            TypeDeclaration declaration = getDeclaration();
            List<TypeParameter> params = 
                    declaration.getTypeParameters();
            if (!params.isEmpty()) {
                List<Type> args = getTypeArgumentList();
                List<Type> bounded =
                        new ArrayList<Type>
                            (args.size());
                boolean found = false;
                for (int i=0, 
                        s1 = params.size(),
                        s2 = args.size();
                        i<s1 && i<s2; 
                        i++) {
                    TypeParameter tp = params.get(i);
                    Type arg = args.get(i);
                    if (isCovariant(tp)) {
                        Type bound =
                                intersectionOfSupertypes(tp)
                                    .substitute(this);
                        if (!arg.isSubtypeOf(bound)) {
                            arg = bound;
                            found = true;
                        }
                    }
                    bounded.add(arg);
                }
                if (found) {
                    Type type = 
                            declaration.appliedType(
                                    getQualifyingType(), 
                                    bounded);
                    type.setVarianceOverrides(
                            getVarianceOverrides());
                    return type;
                }
            }
        }
        return this;
    }
    
    public void setUnderlyingType(String underlyingType) {
        this.underlyingType = underlyingType;
        // if we have a resolvedAliases cache, update it too
        if (resolvedAliases != null && 
            resolvedAliases != this) {
            resolvedAliases.setUnderlyingType(underlyingType);
        }
    }
    
    public String getUnderlyingType() {
        return underlyingType;
    }
    
    /**
     * Does this type cover the given type?
     */
    public boolean covers(Type st) {
        return resolveAliases()
                .coversInternal(st.resolveAliases());
    }
    
    /*public boolean coversInternal(Type t, Stack<TypeDeclaration> stack) {
        Type uoc = t.getUnionOfCases();
        //X covers Y if the union of cases of Y is 
        //a subtype of X
        if (uoc.isSubtypeOfInternal(this)) {
            return true;
        }
        TypeDeclaration dec = t.getDeclaration();
        boolean decHasEquals = 
                dec instanceof ClassOrInterface || 
                dec instanceof TypeParameter;
        Map<TypeParameter, Type> args = t.getTypeArguments();
        List<Type> cts = uoc.getCaseTypes();
        if (cts!=null && !(decHasEquals && stack.contains(dec))) {
            //X covers Y if Y has the cases A|B|C and 
            //X covers all of A, B, and C
            for (Type ct: cts) {
                if (decHasEquals) stack.push(dec);
                if (!coversInternal(ct.substituteInternal(args), stack)) {
                    if (decHasEquals) stack.pop();
                    return false;
                }
                else {
                    if (decHasEquals) stack.pop();
                }
            }
            return true;
        }
        else {
            //X covers Y if Y extends Z and X covers Z
            Type et = dec.getExtendedType();
            if (et!=null && coversInternal(et.substituteInternal(args), stack)) {
                return true;
            }
            //X covers Y if Y satisfies Z and X covers Z
            for (Type st: dec.getSatisfiedTypes()) {
                if (coversInternal(st.substituteInternal(args), stack)) {
                    return true;
                }
            }
            return false;
        }
    }*/
    
    //This alternative algorithm, without the stack
    //fails in one tiny little corner case which we
    //might be able to disallow - Algebraic.ceylon:345
    private boolean coversInternal(Type type) {
        Type uoc = type.getUnionOfCases();
        //X covers Y if the union of cases of Y is 
        //a subtype of X
        if (uoc.isSubtypeOfInternal(this)) {
            return true;
        }
        if (uoc.isUnion()) {
            //X covers Y if Y has the cases A|B|C and 
            //X covers all of A, B, and C
            for (Type ct: uoc.getCaseTypes()) {
                if (!coversInternal(ct)) {
                    return false;
                }
            }
            return true;
        }
        else {
            TypeDeclaration dec = type.getDeclaration();
            //X covers Y if Y extends Z and X covers Z
            Type et = type.getExtendedType();
            Unit unit = dec.getUnit();
            if (et!=null) {
//                if (coversInternal(et)) {
//                    return true;
//                }
                //decompose a type T extends U for an 
                //enumerated type U of A|B|... to 
                //the union type T&A|T&B|...
                Type stu = et.getUnionOfCases();
                if (stu.isUnion() && 
                        intersectionType(stu, type, unit)
                            .isSubtypeOf(this)) {
                  return true;
               }
            }
            //X covers Y if Y satisfies Z and X covers Z
            for (Type st: type.getSatisfiedTypes()) {
//                if (coversInternal(st)) {
//                    return true;
//                }
                //decompose a type T satisfies U
                //for an enumerated type U of A|B|... to 
                //the union type T&A|T&B|...
                Type stu = st.getUnionOfCases();
                if (stu.isUnion() && 
                        intersectionType(stu, type, unit)
                            .isSubtypeOf(this)) {
                  return true;
               }
            }
            return false;
        }
    }
    
    public Type withoutUnderlyingType() {
        Type pt = new Type();
        pt.setDeclaration(getDeclaration());
        pt.setQualifyingType(getQualifyingType());
        pt.setTypeArguments(getTypeArguments());
        pt.setVarianceOverrides(getVarianceOverrides());
        pt.setTypeConstructor(isTypeConstructor());
        pt.setTypeConstructorParameter(getTypeConstructorParameter());
        return pt;
    }

    public boolean isRaw() {
        return isRaw;
    }

    public void setRaw(boolean isRaw) {
        this.isRaw = isRaw;
        // if we have a resolvedAliases cache, update it too
        if (resolvedAliases != null && 
            resolvedAliases != this) {
            resolvedAliases.setRaw(isRaw);
        }
    }
    
    /**
     * Eliminate type aliases from this type, recursively
     * expanding them into their aliased types. Canonicalize
     * the type by side-effect!
     * 
     * @return this type, with type aliases expanded, and 
     *         then canonicalized
     */
    public Type resolveAliases() {
        if (resolvedAliases == null) {
            // really compute it
            checkDepth();
            if (!isTuple()) {
                incDepth();
            }
            try {
                resolvedAliases = resolveAliasesInternal();
                /*if (resolvedAliases.isExactlyNothing()) {
                    resolvedAliases = 
                            getDeclaration().getUnit()
                                .getNothingType();
                }*/
            }
            finally {
                if (!isTuple()) {
                    decDepth();
                }
            }
            // mark it as resolved so it doesn't get resolved again
            resolvedAliases.resolvedAliases = resolvedAliases;
            if (resolvedAliases != this) {
                // inherit whatever underlying type we had
                resolvedAliases.underlyingType = underlyingType;
                resolvedAliases.isRaw = isRaw;
            }
        }
        return resolvedAliases;
    }

    private Type resolveAliasesInternal() {
        TypeDeclaration dec = getDeclaration();
        Unit unit = dec.getUnit();
        if (isClassOrInterface() &&
                getQualifyingType()==null &&
                !dec.isAlias() &&
                dec.getTypeParameters().isEmpty()) {
            return this;
        }
        else if (isTypeConstructor()) {
            return this;
        }
        else if (isUnion()) {
            List<Type> caseTypes = 
                    getCaseTypes();
            List<Type> list = 
                    new ArrayList<Type>
                        (caseTypes.size());
            for (Type pt: caseTypes) {
                addToUnion(list, 
                        pt.resolveAliases());
            }
            return union(list, unit);
        }
        else if (isIntersection()) {
            List<Type> satisfiedTypes = 
                    getSatisfiedTypes();
            List<Type> list = 
                    new ArrayList<Type>
                        (satisfiedTypes.size());
            for (Type pt: satisfiedTypes) {
                addToIntersection(list, 
                        pt.resolveAliases(), 
                        unit);
            }
            return canonicalIntersection(list, unit);
        }
        else {
            Type qt = getQualifyingType();
            Type aliasedQualifyingType = 
                    qt==null ? null : 
                        qt.resolveAliases();
            
            List<Type> args = getTypeArgumentList();
            List<Type> aliasedArgs;
            if (args.isEmpty()) {
                aliasedArgs = NO_TYPE_ARGS;
            }
            else {
                aliasedArgs = 
                        new ArrayList<Type>
                            (args.size());
                for (Type arg: args) {
                    Type aliasedArg = 
                            arg==null ? null : 
                                arg.resolveAliases();
                    aliasedArgs.add(aliasedArg);
                }
            }
            if (dec.isAlias()) {
                Type et = dec.getExtendedType();
                if (et == null) {
                    return unit.getUnknownType();
                }
                else {
                    return et.resolveAliases()
                            .substitute(getTypeArgumentMap(
                                    dec, 
                                    aliasedQualifyingType, 
                                    aliasedArgs),
                                    getVarianceOverrides());
                }
            }
            else {
                Type result = 
                        dec.appliedType(
                                aliasedQualifyingType, 
                                aliasedArgs);
                result.setVarianceOverrides(
                        getVarianceOverrides());
                return result;
            }
        }
    }
    
    /*private Type simple() {
        TypeDeclaration d = getDeclaration();
        if (d instanceof UnionType) {
            List<Type> cts = d.getCaseTypes();
            if (cts.size()==1) {
                return cts.get(0);
            }
        }
        if (d instanceof IntersectionType) {
            List<Type> sts = d.getSatisfiedTypes();
            if (sts.size()==1) {
                return sts.get(0);
            }
        }
        List<Type> args = getTypeArgumentList();
        List<Type> simpleArgs;
        Type qt = getQualifyingType();
        if (args.isEmpty()) {
            if (qt == null) {
                return this; // we have nothing to simplify
            }
            simpleArgs = NO_TYPE_ARGS;
        }
        else {
            simpleArgs = 
                    new ArrayList<Type>
                        (args.size());
            for (Type arg: args) {
                Type sarg = 
                        arg==null ? null : arg.simple();
                simpleArgs.add(sarg);
            }
        }
        Type sqt = 
                qt==null ? null : qt.simple();
        Type ret = 
                d.getProducedType(sqt, simpleArgs);
        ret.setUnderlyingType(getUnderlyingType());
        ret.setTypeConstructor(isTypeConstructor());
        ret.setTypeConstructorParameter(getTypeConstructorParameter());
        ret.setRaw(isRaw());
        ret.setVarianceOverrides(getVarianceOverrides());
        return ret;
    }*/
    
    public boolean involvesTypeParameters() {
        if (isTypeParameter()) {
            return true;
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.involvesTypeParameters()) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.involvesTypeParameters()) {
                    return true;
                }
            }
        }
        else {
            for (Type at: getTypeArgumentList()) {
                if (at!=null && 
                        at.involvesTypeParameters()) {
                    return true;
                }
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.involvesTypeParameters()) {
                return true;
            }
        }
        return false;
    }
    
    public boolean involvesTypeParameters(Generic g) {
        return involvesTypeParameters(g.getTypeParameters());
    }
    
    public boolean involvesTypeParameters(
            Collection<TypeParameter> params) {
        TypeDeclaration d = getDeclaration();
        if (isTypeParameter()) {
            if (params.contains(d)) {
                return true;
            }
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                if (ct.involvesTypeParameters(params)) {
                    return true;
                }
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                if (st.involvesTypeParameters(params)) {
                    return true;
                }
            }
        }
        else {
            for (Type at: getTypeArgumentList()) {
                if (at!=null && 
                        at.involvesTypeParameters(params)) {
                    return true;
                }
            }
            Type qt = getQualifyingType();
            if (qt!=null && 
                    qt.involvesTypeParameters(params)) {
                return true;
            }
        }
        return false;
    }
    
    /*public boolean containsTypeAliases() {
        TypeDeclaration d = getDeclaration();
        if (d instanceof TypeAlias||
            d instanceof ClassAlias||
            d instanceof InterfaceAlias) {
            return true;
        }
        else if (d instanceof UnionType) {
            for (Type ct: getCaseTypes()) {
                if (ct.containsTypeAliases()) return true;
            }
        }
        else if (d instanceof IntersectionType) {
            for (Type st: getSatisfiedTypes()) {
                if (st.containsTypeAliases()) return true;
            }
        }
        else {
            for (Type at: getTypeArgumentList()) {
                if (at!=null && at.containsTypeAliases()) return true;
            }
            Type qt = getQualifyingType();
            if (qt!=null && qt.containsTypeAliases()) return true;
        }
        return false;
    }*/
    
    private Set<TypeDeclaration> extend(TypeDeclaration td, 
            Set<TypeDeclaration> visited) {
        HashSet<TypeDeclaration> set = 
                new HashSet<TypeDeclaration>(visited);
        set.add(td);
        return set;
    }
    
    private List<TypeDeclaration> extend(TypeDeclaration td, 
            List<TypeDeclaration> results) {
        if (!results.contains(td)) {
            results.add(td);
        }
        return results;
    }
    
    public List<TypeDeclaration> isRecursiveTypeAliasDefinition(
            Set<TypeDeclaration> visited) {
        TypeDeclaration dec = getDeclaration();
        if (dec.isAlias()) {
            if (visited.contains(dec)) {
                return new ArrayList<TypeDeclaration>(
                        singletonList(dec));
            }
            Type et = dec.getExtendedType();
            if (et!=null) {
                List<TypeDeclaration> l = 
                        et.isRecursiveTypeAliasDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
            for (Type bt: dec.getBrokenSupertypes()) {
                List<TypeDeclaration> l = 
                        bt.isRecursiveTypeAliasDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                List<TypeDeclaration> l = 
                        ct.isRecursiveTypeAliasDefinition(
                                visited);
                if (!l.isEmpty()) return l;
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                List<TypeDeclaration> l = 
                        st.isRecursiveTypeAliasDefinition(
                                visited);
                if (!l.isEmpty()) return l;
            }
        }
        else {
            for (Type at: getTypeArgumentList()) {
                if (at!=null) {
                    List<TypeDeclaration> l = 
                            at.isRecursiveTypeAliasDefinition(
                                    visited);
                    if (!l.isEmpty()) return l;
                }
            }
            Type qt = getQualifyingType();
            if (qt!=null) {
                List<TypeDeclaration> l = 
                        qt.isRecursiveTypeAliasDefinition(
                                visited);
                if (!l.isEmpty()) return l;
            }
        }
        return emptyList();
    }
    
    public List<TypeDeclaration> isRecursiveRawTypeDefinition(
            Set<TypeDeclaration> visited) {
        TypeDeclaration dec = getDeclaration();
        if (dec.isAlias()) {
            if (visited.contains(dec)) {
                return new ArrayList<TypeDeclaration>(
                        singletonList(dec));
            }
            Type et = dec.getExtendedType();
            if (et!=null) {
                List<TypeDeclaration> l = 
                        et.isRecursiveRawTypeDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
            for (Type bt: dec.getBrokenSupertypes()) {
                List<TypeDeclaration> l = 
                        bt.isRecursiveRawTypeDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
        }
        else if (isUnion()) {
            for (Type ct: getCaseTypes()) {
                List<TypeDeclaration> l = 
                        ct.isRecursiveRawTypeDefinition(
                                visited);
                if (!l.isEmpty()) return l;
            }
        }
        else if (isIntersection()) {
            for (Type st: getSatisfiedTypes()) {
                List<TypeDeclaration> l = 
                        st.isRecursiveRawTypeDefinition(
                                visited);
                if (!l.isEmpty()) return l;
            }
        }
        else {
            if (visited.contains(dec)) {
                return new ArrayList<TypeDeclaration>(
                        singletonList(dec));
            }
            Type et = dec.getExtendedType();
            if (et!=null) {
                List<TypeDeclaration> i = 
                        et.isRecursiveRawTypeDefinition(
                                extend(dec, visited));
                if (!i.isEmpty()) {
                    i.add(0, dec);
                    return i;
                }
            }
            for (Type bt: dec.getBrokenSupertypes()) {
                List<TypeDeclaration> l = 
                        bt.isRecursiveRawTypeDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
            for (Type st: getSatisfiedTypes()) {
                List<TypeDeclaration> l = 
                        st.isRecursiveRawTypeDefinition(
                                extend(dec, visited));
                if (!l.isEmpty()) {
                    return extend(dec, l);
                }
            }
        }
        return emptyList();
    }
    
    public boolean isUnknown() {
        return getDeclaration() instanceof UnknownType;
    }
    
    public boolean isNothing() {
        return getDeclaration() instanceof NothingType;
    }
    
    public boolean isUnion() {
        return getDeclaration() instanceof UnionType;
    }
    
    public boolean isIntersection() {
        return getDeclaration() instanceof IntersectionType;
    }
    
    public boolean isClassOrInterface() {
        return getDeclaration() instanceof ClassOrInterface;
    }

    public boolean isFunctionOrValueInterface() {
        return getDeclaration() instanceof FunctionOrValueInterface;
    }

    public boolean isClass() {
        return getDeclaration() instanceof Class;
    }
    
    public boolean isInterface() {
        return getDeclaration() instanceof Interface;
    }
    
    public boolean isTypeParameter() {
        return getDeclaration() instanceof TypeParameter;
    }
    
    public boolean isTypeAlias() {
        return getDeclaration() instanceof TypeAlias;
    }
    
    public boolean isAnything() {
        return getDeclaration().isAnything();
    }
    
    public boolean isObject() {
        return getDeclaration().isObject();
    }
    
    public boolean isNull() {
        return getDeclaration().isNull();
    }
    
    public boolean isNullValue() {
        return getDeclaration().isNullValue();
    }
    
    public boolean isBasic() {
        return getDeclaration().isBasic();
    }
    
    public boolean isBoolean() {
        return getDeclaration().isBoolean();
    }
    
    public boolean isString() {
        return getDeclaration().isString();
    }
    
    public boolean isCharacter() {
        return getDeclaration().isCharacter();
    }
    
    public boolean isFloat() {
        return getDeclaration().isFloat();
    }
    
    public boolean isInteger() {
        return getDeclaration().isInteger();
    }
    
    public boolean isByte() {
        return getDeclaration().isByte();
    }
    
    public boolean isIterable() {
        return getDeclaration().isIterable();
    }
    
    public boolean isSequence() {
        return getDeclaration().isSequence();
    }
    
    public boolean isSequential() {
        return getDeclaration().isSequential();
    }
    
    public boolean isRange() {
        return getDeclaration().isRange();
    }
    
    public boolean isEmpty() {
        return getDeclaration().isEmpty();
    }
    
    public boolean isEmptyValue() {
        return getDeclaration().isEmptyValue();
    }
    
    public boolean isTuple() {
        return getDeclaration().isTuple();
    }
    
    public boolean isEntry() {
        return getDeclaration().isEntry();
    }
    
    public int getMemoisedHashCode() {
        if (hashCode == 0) {
            int ret = 17;
            Type qualifyingType = 
                    getQualifyingType();
            ret = (37 * ret) + 
                    (qualifyingType != null ? 
                            qualifyingType.hashCode() : 0);
            TypeDeclaration declaration = 
                    getDeclaration();
            ret = (37 * ret) + 
                    declaration.hashCodeForCache();
            
            Map<TypeParameter, Type> typeArguments = 
                    getTypeArguments();
            if (!typeArguments.isEmpty()) {
                List<TypeParameter> typeParameters = 
                        declaration.getTypeParameters();
                for (int i=0, l=typeParameters.size(); 
                        i<l; i++) {
                    TypeParameter typeParameter = 
                            typeParameters.get(i);
                    Type typeArgument = 
                            typeArguments.get(typeParameter);
                    ret = (37 * ret) + 
                            (typeArgument != null ? 
                                    typeArgument.hashCode() : 0);
                }
            }
            ret = (37 * ret) + 
                    varianceOverrides.hashCode();

            hashCode = ret;
        }
        return hashCode;
    }
    
    private Map<TypeParameter,SiteVariance> collectVarianceOverrides() {
        Type qt = getQualifyingType();
        Map<TypeParameter,SiteVariance> qualifyingOverrides = 
                qt==null ? EMPTY_VARIANCE_MAP :
                    qt.collectVarianceOverrides();
        if (varianceOverrides.isEmpty()) {
            return qualifyingOverrides;
        }
        else if (qualifyingOverrides.isEmpty()) {
            return varianceOverrides;
        }
        else {
            Map<TypeParameter,SiteVariance> overrides = 
                    new HashMap<TypeParameter,SiteVariance>
                        (varianceOverrides);
            overrides.putAll(qualifyingOverrides);
            return overrides;
        }
    }
    
    /**
     * Given a set of use site variance overrides, adjust 
     * the given type to account for these variances.
     * 
     * Performs replacement of type parameters with their
     * bounds when the use-site variance is opposite to the
     * variance of the position in which the type parameter
     * occurs, adds use-site variance annotations to the 
     * given type, etc.
     * 
     * This operation must happen before any substitution 
     * operation, as a pre-processing phase before we
     * actually subtitute the type arguments.
     * 
     * @param type the type to which we're applying the 
     *        variance overrides
     * @param covariant true if the given type occurs in a 
     *        covariant position
     * @param contravariant false if the given type occurs 
     *        in a contravariant position
     * @param overrides the variance overrides
     *        
     * @return the new type after application of the rules
     *         for use-site variance substitution
     */
    private static Type applyVarianceOverrides(
            Type type, 
            boolean covariant, boolean contravariant, 
            Map<TypeParameter, SiteVariance> overrides) {
        if (overrides.isEmpty()) {
            return type;
        }
        TypeDeclaration dec = type.getDeclaration();
        Unit unit = dec.getUnit();
        if (type.isTypeParameter()) {
            SiteVariance siteVariance = overrides.get(dec);
            if (contravariant && siteVariance==OUT) {
                //if a type parameter occurs in a 
                //contravariant position, and the specified
                //use-site variance is "out", replace the
                //type parameter with its lower bound
                //Nothing, throwing away the use-site 
                //upper bound
                return unit.getNothingType();
            }
            else if (covariant && siteVariance==IN) {
                TypeParameter tp = (TypeParameter) dec;
                //if a type parameter occurs in a 
                //covariant position, and the specified
                //use-site variance is "in", replace the
                //type parameter with its upper bounds,
                //throwing away the use-site lower bound
                List<Type> bounds = tp.getSatisfiedTypes();
                List<Type> list = 
                        new ArrayList<Type>
                            (bounds.size()+1);
                for (Type bound: bounds) {
                    //ignore bounds in which the type 
                    //parameter itself occurs covariantly
                    //because they would result in a 
                    //stack overflow here
                    //TODO: perhaps we should just 
                    //      substitute Anything for the
                    //      type parameter in such bounds,
                    //      which would be a little more
                    //      precise
                    if (!bound.occursCovariantly(tp)) {
                        Type applied = 
                                applyVarianceOverrides(
                                        bound, 
                                        covariant, contravariant, 
                                        overrides);
                        addToIntersection(list, applied, unit);
                    }
                }
                return canonicalIntersection(list, unit);
            }
            else {
                return type;
            }
        }
        else if (type.isUnion()) {
            List<Type> list = 
                    new ArrayList<Type>();
            for (Type ut: type.getCaseTypes()) {
                addToUnion(list, 
                        applyVarianceOverrides(ut, 
                                covariant, contravariant,
                                overrides));
            }
            return union(list, unit);
        }
        else if (type.isIntersection()) {
            List<Type> list = 
                    new ArrayList<Type>();
            for (Type it: type.getSatisfiedTypes()) {
                addToIntersection(list, 
                        applyVarianceOverrides(it, 
                                covariant, contravariant,
                                overrides),
                        unit);
            }
            return canonicalIntersection(list, unit);
        }
        else {
            List<Type> args = 
                    type.getTypeArgumentList();
            List<TypeParameter> params = 
                    dec.getTypeParameters();
            if (params.isEmpty()) {
                // we have variance overrides from a 
                // qualifying type 
                // optimize, since no work to do
                return type;
            }
            List<Type> resultArgs = 
                    new ArrayList<Type>
                        (args.size());
            Map<TypeParameter,SiteVariance> varianceResults = 
                    new HashMap<TypeParameter,SiteVariance>
                            (type.getVarianceOverrides());
            for (int i = 0; i<args.size(); i++) {
                Type arg = args.get(i);
                if (arg==null) {
                    resultArgs.add(null);
                    continue;
                }
                TypeDeclaration argDec = 
                        arg.getDeclaration();
                TypeParameter param = params.get(i);
                if (type.isCovariant(param)) {
                    resultArgs.add(
                            applyVarianceOverrides(arg, 
                                    covariant, contravariant,
                                    overrides));
                }
                else if (type.isContravariant(param)) {
                    resultArgs.add(
                            applyVarianceOverrides(arg, 
                                    !covariant, !contravariant,
                                    overrides));
                }
                else {
                    if (contravariant) {
                        if (arg.isTypeParameter() &&
                                overrides.containsKey(argDec)) {
                            return unit.getNothingType();
                        }
                    }
                    else if (covariant) {
                        if (arg.isTypeParameter() &&
                                overrides.containsKey(argDec)) {
                            varianceResults.put(param,
                                    overrides.get(argDec));
                            resultArgs.add(arg);
                            continue;
                        }
                    }
                    Type resultArg = 
                            applyVarianceOverrides(arg, 
                                    covariant, contravariant,
                                    overrides);
                    if (resultArg.isNothing()) {
                        return resultArg;
                    }
                    resultArgs.add(resultArg);
                    if (arg.involvesTypeParameters(
                            overrides.keySet())) {
                        varianceResults.put(param, OUT);
                    }
                }
            }
            Type result = 
                    dec.appliedType(
                            type.getQualifyingType(), 
                            resultArgs);
            result.setVarianceOverrides(varianceResults);
            result.setUnderlyingType(type.getUnderlyingType());
            return result;
        }
    }
    
    @Override
    public int hashCode() {
        return getMemoisedHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this==obj) {
            return true;
        }
        if (obj == null || 
                !(obj instanceof Type)) {
            return false;
        }
        Type other = (Type) obj;
        Type qA = getQualifyingType();
        Type qB = other.getQualifyingType();
        if (qA!=qB && (qA==null || qB==null || !qA.equals(qB))) {
            return false;
        }
        TypeDeclaration aDecl = getDeclaration();
        TypeDeclaration bDecl = other.getDeclaration();
        if (!aDecl.equalsForCache(bDecl)) {
            return false;
        }
        
        Map<TypeParameter, Type> typeArgumentsA = 
                getTypeArguments();
        Map<TypeParameter, Type> typeArgumentsB = 
                other.getTypeArguments();
        if (typeArgumentsA.size() != typeArgumentsB.size()) {
            return false;
        }
        if (!typeArgumentsA.isEmpty()) {
            List<TypeParameter> typeParametersA = 
                    aDecl.getTypeParameters();
            for (int i=0, l=typeParametersA.size(); i<l; i++) {
                TypeParameter typeParameter = 
                        typeParametersA.get(i);
                Type typeArgumentA = 
                        typeArgumentsA.get(typeParameter);
                Type typeArgumentB = 
                        typeArgumentsB.get(typeParameter);
                if (typeArgumentA!=typeArgumentB &&
                        (typeArgumentA==null || 
                         typeArgumentB==null || 
                            !typeArgumentA.equals(typeArgumentB))) {
                    return false;
                }
            }
        }
        return getVarianceOverrides()
                .equals(other.getVarianceOverrides());
    }
    
    @Override
    public Type getFullType(Type wrappedType) {
        //TODO: put this back in, and find another way to
        //      propagate the type in static method refs
        //      of form Interface.member in
        //      visitQualifiedMemberExpression() and
        //      visitQualifiedTypeExpression()
//        TypeDeclaration declaration = getDeclaration();
//        if (!(declaration instanceof Class)) {           
//            return unknownType();
//        }
//        else {
            return super.getFullType(wrappedType);
//        }
    }

} public interface Scope {
    
    /**
     * A period-separated name uniquely representing this 
     * scope. 
     */
    public String getQualifiedNameString();

    public Type getDeclaringType(Declaration d);
    
    /**
     * Get a member declared directly in this scope.
     * 
     * @param name the name of the member
     * @param signature the signature of the parameter list,
     *        or null if we have no parameter list
     * @param ellipsis true if we are looking for a member
     *        with a variadic parameter
     * 
     * @return the best matching member
     */
    public Declaration getDirectMember(String name, 
            List<Type> signature, 
            boolean ellipsis);

    /**
     * Get a member declared directly in this scope for any of the given backends
     * 
     * @param name the name of the member
     * @param backends the native backends
     * 
     * @return the best matching member
     */
    public Declaration getDirectMemberForBackend(String name, Backends backends);

    /**
     * Resolve a qualified reference.
     * 
     * @param name the name of the member
     * @param signature the signature of the parameter list,
     *        or null if we have no parameter list
     * @param ellipsis true if we are looking for a member
     *        with a variadic parameter
     * 
     * @return the best matching member
     */
    public Declaration getMember(String name, 
            List<Type> signature, 
            boolean ellipsis);
    
    /**
     * Resolve an unqualified reference.
     * 
     * @param name the name of the member
     * @param signature the signature of the parameter list,
     *        or null if we have no parameter list
     * @param ellipsis true if we are looking for a member
     *        with a variadic parameter
     * 
     * @return the best matching member
     */
    //TODO: should be renamed getBase() since it also looks 
    //      in containing scopes
    public Declaration getMemberOrParameter(Unit unit, 
            String name, 
            List<Type> signature, 
            boolean ellipsis);
    
    /**
     * Is the given declaration inherited from
     * a supertype of this type or an outer
     * type?
     * 
     * @return true if it is
     */
    public boolean isInherited(Declaration d);
    
    /**
     * Get the containing type which inherits the given 
     * declaration.
     * 
     * @return null if the declaration is not inherited!!
     */
    public TypeDeclaration getInheritingDeclaration(Declaration d);

    /**
     * The "real" containing scope, ignoring that conditions 
     * (in an assert, if, or while) each have their own "fake" 
     * scope.
     * 
     * @see ConditionScope
     */
    public Scope getContainer();
    
    /**
     * The containing scope, taking into account that conditions 
     * (in an assert, if, or while) each have their own "fake" 
     * scope.
     * 
     * @see ConditionScope
     */
    public Scope getScope();
    
    /**
     * Get a set of proposals for use in IDE completion.
     * 
     * @param unit
     * @param startingWith a pattern to match the name against
     * @param proximity the "distance" from the carat
     * @return
     */
    public Map<String, DeclarationWithProximity> 
    getMatchingDeclarations(Unit unit, 
            String startingWith,
            int proximity);
    
    /**
     * Get a list of all declarations belonging directly to
     * this scope, even declarations which aren't normally
     * visible. Calling this method necessarily loads the
     * whole containing scope, so please don't, at least not
     * unless you really have to.
     * 
     * @return a list of everything
     * @deprecated to discourage you from calling this
     */
    @Deprecated 
    public List<Declaration> getMembers();
    
    /**
     * Add a member to this scope.
     * 
     * @param declaration the member to add
     */
    public void addMember(Declaration declaration);
    
    public boolean isToplevel();

    /**
     * Returns the native backend defined for this scope
     * or any of its containing scopes. Will return
     * <code>null</code> if no specific backend was
     * defined.
     */
    public Backends getScopedBackends();
} public class TypeParameter extends TypeDeclaration {

    private boolean covariant;
    private boolean contravariant;
    private Declaration declaration;
    private TypeDeclaration selfTypedDeclaration;
    private Type defaultTypeArgument;
    private boolean defaulted;
    private boolean constrained;
    private boolean typeConstructor;
    private Boolean hasNonErasedBounds;
    private List<Declaration> members = new ArrayList<Declaration>(0);
    private boolean captured;
    private List<TypeParameter> typeParameters = emptyList();

    @Override
    public List<Declaration> getMembers() {
        return members;
    }
    
    @Override
    public void addMember(Declaration declaration) {
        members.add(declaration);
    }
    
    public boolean isInvariant() {
    	return !covariant && !contravariant;
    }
    
    public boolean isCovariant() {
        return covariant;
    }

    public void setCovariant(boolean covariant) {
        this.covariant = covariant;
    }

    public boolean isContravariant() {
        return contravariant;
    }

    public void setContravariant(boolean contravariant) {
        this.contravariant = contravariant;
    }
    
    public boolean isTypeConstructor() {
        return typeConstructor;
    }
    
    public void setTypeConstructor(boolean typeConstructor) {
        this.typeConstructor = typeConstructor;
    }
    
    @Override
    public boolean isOverloaded() {
    	return false;
    }
    
    @Override
    public boolean isSelfType() {
        return selfTypedDeclaration!=null;
    }
    
    public TypeDeclaration getSelfTypedDeclaration() {
        return selfTypedDeclaration;
    }
    
    public void setSelfTypedDeclaration(TypeDeclaration selfTypedDeclaration) {
        this.selfTypedDeclaration = selfTypedDeclaration;
    }

    public Declaration getDeclaration() {
        return declaration;
    }

    public void setDeclaration(Declaration declaration) {
        this.declaration = declaration;
    }
    
    @Override
    public DeclarationKind getDeclarationKind() {
        return DeclarationKind.TYPE_PARAMETER;
    }
    
    @Override
    public String getQualifiedNameString() {
    	return getName();
    }
    
    @Override
    public boolean isAbstraction() {
        return false;
    }
    
    @Override
    public boolean isParameterized() {
        return !typeParameters.isEmpty();
    }

    public List<TypeParameter> getTypeParameters() {
        return typeParameters;
    }

    public void setTypeParameters(List<TypeParameter> typeParameters) {
        this.typeParameters = typeParameters;
    }
    
    @Override
    public List<Declaration> getOverloads() {
        return null;
    }
    
    public Type getDefaultTypeArgument() {
		return defaultTypeArgument;
	}
    
    public void setDefaultTypeArgument(Type defaultTypeArgument) {
		this.defaultTypeArgument = defaultTypeArgument;
	}
    
    public boolean isDefaulted() {
		return defaulted;
	}
    
    public void setDefaulted(boolean defaulted) {
		this.defaulted = defaulted;
	}
    
    public boolean isConstrained() {
		return constrained;
	}
    
    public void setConstrained(boolean constrained) {
		this.constrained = constrained;
	}

    public Boolean hasNonErasedBounds() {
        return hasNonErasedBounds;
    }

    public void setNonErasedBounds(boolean hasNonErasedBounds) {
        this.hasNonErasedBounds = hasNonErasedBounds;
    }
    
    @Override
    public boolean isEmptyType() {
        for (Type st: getSatisfiedTypes()) {
            if (st.getDeclaration().isEmptyType()) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public boolean isTupleType() {
        for (Type st: getSatisfiedTypes()) {
            if (st.getDeclaration().isTupleType()) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public boolean isSequenceType() {
        for (Type st: getSatisfiedTypes()) {
            if (st.getDeclaration().isSequenceType()) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public boolean isSequentialType() {
        for (Type st: getSatisfiedTypes()) {
            if (st.getDeclaration().isSequentialType()) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public List<Type> getSatisfiedTypes() {
        List<Type> satisfiedTypes = 
                super.getSatisfiedTypes();
        List<Type> sts = satisfiedTypes;
        for (int i=0, size=sts.size(); i<size; i++) {
            Type st = sts.get(i);
            if (st!=null) {
                TypeDeclaration std = st.getDeclaration();
                if (std==this || st.isTypeAlias()) {
                    if (sts == satisfiedTypes) {
                        sts = new ArrayList<Type>(sts);
                    }
                    sts.remove(i);
                    size--;
                    i--; 
                }
            }
        }
        return sts;
    }
    
    @Override
    public List<Type> getCaseTypes() {
        List<Type> caseTypes = 
                super.getCaseTypes();
        List<Type> cts = caseTypes;
        if (cts!=null) {
            for (int i=0, size=cts.size(); i<size; i++) {
                Type ct = cts.get(i);
                if (ct!=null) {
                    TypeDeclaration ctd = ct.getDeclaration();
                    if (ctd==this || ct.isTypeAlias()) {
                        if (cts==caseTypes) {
                            cts = new ArrayList<Type>(cts);
                        }
                        cts.remove(i);
                        i--;
                        size--;
                    }
                }
            }
        }
        return cts;
    }
    
    @Override
    void collectSupertypeDeclarations(
            List<TypeDeclaration> results) {
        List<Type> stds = getSatisfiedTypes();
        for (int i=0, l=stds.size(); i<l; i++) {
            Type st = stds.get(i);
            st.getDeclaration()
                .collectSupertypeDeclarations(results);
        }
    }
    
    @Override
    public boolean inherits(TypeDeclaration dec) {
        if (dec.isAnything()) {
            return true;
        }
        else {
            List<Type> sts = getSatisfiedTypes();
            for (int i=0, s=sts.size(); i<s; i++) {
                Type st = sts.get(i);
                if (st.getDeclaration().inherits(dec)) {
                    return true;
                }
            }
            return false;
        }
    }
    
    @Override
    public boolean isFunctional() {
        return true;
    }

    @Override
    protected int hashCodeForCache() {
        int ret = 17;
        ret = (37 * ret) + getDeclaration().hashCodeForCache();
        ret = (37 * ret) + Objects.hashCode(getName());
        return ret;
    }

    @Override
    protected boolean equalsForCache(Object o) {
        if(o == null || !(o instanceof TypeParameter))
            return false;
        TypeParameter b = (TypeParameter) o;
        return getDeclaration().equalsForCache(b.getDeclaration())
                && Objects.equals(getName(),b.getName());
    }

    @Override
    public void clearProducedTypeCache() {
        ModelUtil.clearProducedTypeCache(this);
    }

    public void setCaptured(boolean captured) {
        this.captured = captured;
    }
    
    @Override
    public boolean isCaptured() {
        return captured;
    }
    
    @Override
    public String toString() {
        return "given " + toStringName();
    }
} public class Unit {

    private Package pkg;
    private List<Import> imports = new ArrayList<Import>();
    private List<Declaration> declarations = new ArrayList<Declaration>();
    private String filename;
    private List<ImportList> importLists = new ArrayList<ImportList>();
    private Set<Declaration> duplicateDeclarations = new HashSet<Declaration>();
    private final Set<String> dependentsOf = new HashSet<String>();
    private String fullPath;
    private String relativePath;
    
    public List<Import> getImports() {
        return imports;
    }

    public List<ImportList> getImportLists() {
        return importLists;
    }

    /**
     * @return the dependentsOf
     */
    public Set<String> getDependentsOf() {
        return dependentsOf;
    }
    
    public Set<Declaration> getDuplicateDeclarations() {
        return duplicateDeclarations;
    }

    public Package getPackage() {
        return pkg;
    }

    public void setPackage(Package p) {
        pkg = p;
    }

    public List<Declaration> getDeclarations() {
        synchronized (declarations) {
            return new ArrayList<Declaration>(declarations);
        }
    }
    
    public void addDeclaration(Declaration declaration) {
        synchronized (declarations) {
            declarations.add(declaration);
        }
    }

    public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
    }

    public String getFullPath() {
        return fullPath;
    }

    public void setFullPath(String fullPath) {
        this.fullPath = fullPath;
    }

    public String getRelativePath() {
        return relativePath;
    }

    public void setRelativePath(String relativePath) {
        this.relativePath = relativePath;
    }

    @Override
    public String toString() {
        return filename;
    }

    public Import getImport(String name) {
        for (Import i: getImports()) {
            if (!i.isAmbiguous() &&
                    i.getTypeDeclaration()==null &&
                    i.getAlias().equals(name)) {
                return i;
            }
        }
        return null;
    }
    
    public String getAliasedName(Declaration dec) {
        return getAliasedName(dec, dec.getName());
    }

    public String getAliasedName(Declaration dec, String defaultValue) {
        for (Import i: getImports()) {
            if (!i.isAmbiguous() &&
                    i.getDeclaration().equals(getAbstraction(dec))) {
                return i.getAlias();
            }
        }
        return defaultValue;
    }

    public static Declaration getAbstraction(Declaration dec){
        if (isOverloadedVersion(dec)) {
            return dec.getContainer()
                    .getDirectMember(dec.getName(), 
                            null, false);
        }
        else {
            return dec;
        }
    }
    
    /**
     * Search the imports of a compilation unit for the 
     * named toplevel declaration.
     */
    public Declaration getImportedDeclaration(String name, 
            List<Type> signature, boolean ellipsis) {
        for (Import i: getImports()) {
            if (!i.isAmbiguous() && 
                    i.getAlias().equals(name)) {
                //in case of an overloaded member, this will
                //be the "abstraction", so search for the 
                //correct overloaded version
                Declaration d = i.getDeclaration();
                if (isToplevelImport(i, d)) {
                    return d.getContainer()
                            .getMember(d.getName(), 
                                    signature, ellipsis);
                }
            }
        }
        return null;
    }
    
    /**
     * Does this import element import a new name directly 
     * into the toplevel namespace of the compilation unit.
     * 
     * @return true if the imported name or alias is added
     *         to the namespace of the compilation unit
     */
    static boolean isToplevelImport(Import i, Declaration d) {
        return d.isToplevel() || 
            d.isStaticallyImportable() ||
            isToplevelClassConstructor(i.getTypeDeclaration(), d) ||
            isToplevelAnonymousClass(i.getTypeDeclaration());
    }
    
    /**
     * Search the imports of a compilation unit for the 
     * named member declaration.
     */
    public Declaration getImportedDeclaration(TypeDeclaration td, 
            String name, List<Type> signature, 
            boolean ellipsis) {
        for (Import i: getImports()) {
            TypeDeclaration itd = i.getTypeDeclaration();
            if (itd!=null && itd.equals(td) && 
                    !i.isAmbiguous() &&
                    i.getAlias().equals(name)) {
                //in case of an overloaded member, this will
                //be the "abstraction", so search for the 
                //correct overloaded version
                Declaration d = i.getDeclaration();
                return d.getContainer()
                        .getMember(d.getName(), 
                                signature, ellipsis);
            }
        }
        return null;
    }
    
    public Map<String, DeclarationWithProximity> 
    getMatchingImportedDeclarations(String startingWith, 
            int proximity) {
        Map<String, DeclarationWithProximity> result = 
                new TreeMap<String, DeclarationWithProximity>();
        for (Import i: new ArrayList<Import>(getImports())) {
            if (i.getAlias()!=null && 
                    !i.isAmbiguous() &&
                    isNameMatching(startingWith, i)) {
                Declaration d = i.getDeclaration();
                if (isToplevelImport(i, d)) {
                    result.put(i.getAlias(), 
                            new DeclarationWithProximity(i, 
                                    proximity));
                }
            }
        }
        return result;
    }
    
    public Map<String, DeclarationWithProximity> 
    getMatchingImportedDeclarations(TypeDeclaration td, 
            String startingWith, int proximity) {
        Map<String, DeclarationWithProximity> result = 
                new TreeMap<String, DeclarationWithProximity>();
        for (Import i: new ArrayList<Import>(getImports())) {
            TypeDeclaration itd = i.getTypeDeclaration();
            if (i.getAlias()!=null && 
                    !i.isAmbiguous() &&
                    itd!=null && itd.equals(td) &&
                    isNameMatching(startingWith, i)) {
                result.put(i.getAlias(), 
                        new DeclarationWithProximity(i, 
                                proximity));
            }
        }
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Unit) {
            Unit that = (Unit) obj;
            return that==this ||
                    that.getPackage()
                        .equals(getPackage()) && 
                    Objects.equals(getFilename(), 
                            that.getFilename()) &&
                    Objects.equals(that.getFullPath(), 
                            getFullPath());
        }
        else {
            return false;
        }
    }
    
    @Override
    public int hashCode() {
        return getFullPath().hashCode();
    }
    
    private Module languageModule;
    private Package languagePackage;

    /**
     * Search for a declaration in the language module. 
     */
    public Declaration getLanguageModuleDeclaration(String name) {
        //all elements in ceylon.language are auto-imported
        //traverse all default module packages provided they 
        //have not been traversed yet
        Module languageModule = getLanguageModule();
        if (languageModule!=null && 
                languageModule.isAvailable()) {
            if ("Nothing".equals(name)) {
                return getNothingDeclaration();
            }
            if (languagePackage==null) {
                languagePackage = 
                        languageModule.getPackage(LANGUAGE_MODULE_NAME);
            }
            if (languagePackage != null) {
                Declaration d = 
                        languagePackage.getMember(name, 
                                null, false);
                if (d != null && d.isShared()) {
                    return d;
                }
            }
        }
        return null;
    }
    
    private Module getLanguageModule() {
        if (languageModule==null) {
            languageModule = 
                    getPackage().getModule()
                        .getLanguageModule();
        }
        return languageModule;
    }

    /**
     * Search for a declaration in {@code ceylon.language.meta.model} 
     */
    public Declaration getLanguageModuleModelDeclaration(String name) {
        Module languageModule = 
                getPackage().getModule()
                    .getLanguageModule();
        if (languageModule!=null && 
                languageModule.isAvailable()) {
            Package languageScope = 
                    languageModule.getPackage("ceylon.language.meta.model");
            if (languageScope!=null) {
                Declaration d = 
                        languageScope.getMember(name, 
                                null, false);
                if (d!=null && d.isShared()) {
                    return d;
                }
            }
        }
        return null;
    }
    
    /**
     * Search for a declaration in {@code ceylon.language.meta.declaration} 
     */
    public Declaration getLanguageModuleDeclarationDeclaration(String name) {
        Module languageModule = 
                getPackage().getModule()
                    .getLanguageModule();
        if (languageModule!=null && 
                languageModule.isAvailable()) {
            Package languageScope = 
                    languageModule.getPackage("ceylon.language.meta.declaration");
            if (languageScope!=null) {
                Declaration d = 
                        languageScope.getMember(name, 
                                null, false);
                if (d!=null && d.isShared()) {
                    return d;
                }
            }
        }
        return null;
    }
    
    /**
     * Search for a declaration in {@code ceylon.language.serialization} 
     */
    public Declaration getLanguageModuleSerializationDeclaration(String name) {
        Module languageModule = 
                getPackage().getModule()
                    .getLanguageModule();
        if (languageModule!=null && 
                languageModule.isAvailable()) {
            Package languageScope = 
                    languageModule.getPackage("ceylon.language.serialization");
            if (languageScope!=null) {
                Declaration d = 
                        languageScope.getMember(name, 
                                null, false);
                if (d != null && d.isShared()) {
                    return d;
                }
            }
        }
        return null;
    }
    
    public Interface getCorrespondenceDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Correspondence");
    }
    
    public Class getAnythingDeclaration() {
        return (Class) getLanguageModuleDeclaration("Anything");
    }
    
    public Class getNullDeclaration() {
        return (Class) getLanguageModuleDeclaration("Null");
    }
    
    public Value getNullValueDeclaration() {
        return (Value) getLanguageModuleDeclaration("null");
    }
    
    public Interface getEmptyDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Empty");
    }
    
    public Interface getSequenceDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Sequence");
    }
    
    public Class getObjectDeclaration() {
        return (Class) getLanguageModuleDeclaration("Object");
    }
    
    public Class getBasicDeclaration() {
        return (Class) getLanguageModuleDeclaration("Basic");
    }
    
    public Interface getIdentifiableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Identifiable");
    }
    
    public Class getThrowableDeclaration() {
        return (Class) getLanguageModuleDeclaration("Throwable");
    }
    
    public Class getErrorDeclaration() {
        return (Class) getLanguageModuleDeclaration("Error");
    }
    
    public Class getExceptionDeclaration() {
        return (Class) getLanguageModuleDeclaration("Exception");
    }
    
    public Interface getCategoryDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Category");
    }
    
    public Interface getIterableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Iterable");
    }
    
    public Interface getSequentialDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Sequential");
    }
    
    public Interface getListDeclaration() {
        return (Interface) getLanguageModuleDeclaration("List");
    }
    
    public Interface getCollectionDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Collection");
    }
    
    public Interface getIteratorDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Iterator");
    }
    
    public Interface getCallableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Callable");
    }
    
    public Interface getScalableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Scalable");
    }
    
    public Interface getSummableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Summable");
    }
     
    public Interface getNumericDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Numeric");
    }
    
    public Interface getIntegralDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Integral");
    }
    
    public Interface getInvertableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Invertible");
    }
    
    public Interface getExponentiableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Exponentiable");
    }
    
    public Interface getSetDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Set");
    }
    
    public TypeDeclaration getComparisonDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Comparison");
    }
    
    public TypeDeclaration getBooleanDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Boolean");
    }
    
    public Value getTrueValueDeclaration() {
        return (Value) getLanguageModuleDeclaration("true");
    }
    
    public Value getFalseValueDeclaration() {
        return (Value) getLanguageModuleDeclaration("false");
    }
    
    public TypeDeclaration getStringDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("String");
    }
    
    public TypeDeclaration getFloatDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Float");
    }
    
    public TypeDeclaration getIntegerDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Integer");
    }
    
    public TypeDeclaration getCharacterDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Character");
    }
    
    public TypeDeclaration getByteDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Byte");
    }
    
    public Interface getComparableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Comparable");
    }
    
    public Interface getUsableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Usable");
    }
    
    public Interface getDestroyableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Destroyable");
    }
    
    public Interface getObtainableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Obtainable");
    }
    
    public Interface getOrdinalDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Ordinal");
    }
        
    public Interface getEnumerableDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Enumerable");
    }
        
    public Class getRangeDeclaration() {
        return (Class) getLanguageModuleDeclaration("Range");
    }
    
    public Class getSpanDeclaration() {
        return (Class) getLanguageModuleDeclaration("Span");
    }
    
    public Class getMeasureDeclaration() {
        return (Class) getLanguageModuleDeclaration("Measure");
    }
    
    public Class getTupleDeclaration() {
        return (Class) getLanguageModuleDeclaration("Tuple");
    }
    
    public TypeDeclaration getArrayDeclaration() {
        return (Class) getLanguageModuleDeclaration("Array");
    }
    
    public Interface getRangedDeclaration() {
        return (Interface) getLanguageModuleDeclaration("Ranged");
    }
        
    public Class getEntryDeclaration() {
        return (Class) getLanguageModuleDeclaration("Entry");
    }
    
    Type getCallableType(Reference ref, Type rt) {
        Type result = rt;
        Declaration declaration = ref.getDeclaration();
        if (declaration instanceof Functional) {
            Functional fd = (Functional) declaration;
            List<ParameterList> pls = fd.getParameterLists();
            for (int i=pls.size()-1; i>=0; i--) {
                boolean hasSequenced = false;
                boolean atLeastOne = false;
                int firstDefaulted = -1;
                List<Parameter> ps = 
                        pls.get(i).getParameters();
                List<Type> args = 
                        new ArrayList<Type>
                            (ps.size());
                for (int j=0; j<ps.size(); j++) {
                    Parameter p = ps.get(j);
                    if (p.getModel()==null) {
                        args.add(getUnknownType());
                    }
                    else {
                        TypedReference np = 
                                ref.getTypedParameter(p);
                        Type npt = np.getType();
                        if (npt==null) {
                            args.add(getUnknownType());
                        }
                        else {
                            if (p.isDefaulted() && 
                                    firstDefaulted==-1) {
                                firstDefaulted = j;
                            }
                            if (np.getDeclaration() 
                                    instanceof Functional) {
                                args.add(getCallableType(np, npt));
                            }
                            else if (p.isSequenced()) {
                                args.add(getIteratedType(npt));
                                hasSequenced = true;
                                atLeastOne = p.isAtLeastOne();
                            }
                            else {
                                args.add(npt);
                            }
                        }
                    }
                }
                Type paramListType = 
                        getTupleType(args, 
                                hasSequenced, atLeastOne, 
                                firstDefaulted);
                result = appliedType(getCallableDeclaration(), 
                        result, paramListType);
            }
        }
        return result;
    }

    public Type getTupleType(
            List<Type> elemTypes, 
            Type variadicTailType, 
            int firstDefaulted) {
        boolean hasVariadicTail = variadicTailType!=null;
        Type result = hasVariadicTail ?
                variadicTailType : 
                getEmptyType();
        Type union = hasVariadicTail ?
                getSequentialElementType(variadicTailType) :
                getNothingType();
        return getTupleType(elemTypes, 
                false, false, 
                firstDefaulted, 
                result, union);
    }

    public Type getTupleType(
            List<Type> elemTypes, 
            boolean variadic, boolean atLeastOne, 
            int firstDefaulted) {
        return getTupleType(elemTypes, 
                variadic, atLeastOne, 
                firstDefaulted,
                getEmptyType(), 
                getNothingType());
    }

    private Type getTupleType(
            List<Type> elemTypes,
            boolean variadic, boolean atLeastOne, 
            int firstDefaulted,
            Type result, Type union) {
        int last = elemTypes.size()-1;
        for (int i=last; i>=0; i--) {
            Type elemType = elemTypes.get(i);
            union = unionType(union, elemType, this);
            if (variadic && i==last) {
                result = atLeastOne ? 
                        getSequenceType(elemType) : 
                        getSequentialType(elemType);
            }
            else {
                result = appliedType(getTupleDeclaration(), 
                        union, elemType, result);
                if (firstDefaulted>=0 && i>=firstDefaulted) {
                    result = unionType(result, 
                            getEmptyType(), this);
                }
            }
        }
        return result;
    }

    public Type getEmptyType(Type pt) {
        return pt==null ? null : 
            unionType(pt, getEmptyType(), this);
    }
    
    public Type getPossiblyEmptyType(Type pt) {
        return pt==null ? null :
            appliedType(getSequentialDeclaration(),
                    getSequentialElementType(pt));
    }
    
    public Type getOptionalType(Type pt) {
        return pt==null ? null :
            unionType(pt, getNullType(), this);
    }

    public Type getUnknownType() {
        return new UnknownType(this).getType();
    }
    
    public Type getNothingType() {
        return getType(getNothingDeclaration());
    }

    public Type getEmptyType() {
        return getType(getEmptyDeclaration());
    }
    
    public Type getAnythingType() {
        return getType(getAnythingDeclaration());
    }
    
    public Type getObjectType() {
        return getType(getObjectDeclaration());
    }
    
    public Type getIdentifiableType() {
        return getType(getIdentifiableDeclaration());
    }
    
    public Type getBasicType() {
        return getType(getBasicDeclaration());
    }

    public Type getNullType() {
        return getType(getNullDeclaration());
    }
    
    public Type getThrowableType() {
        return getType(getThrowableDeclaration());
    }
    
    public Type getExceptionType() {
        return getType(getExceptionDeclaration());
    }
    
    public Type getBooleanType() {
        return getType(getBooleanDeclaration());
    }
    
    public Type getStringType() {
        return getType(getStringDeclaration());
    }
    
    public Type getIntegerType() {
        return getType(getIntegerDeclaration());
    }
    
    public Type getFloatType() {
        return getType(getFloatDeclaration());
    }
    
    public Type getCharacterType() {
        return getType(getCharacterDeclaration());
    }
    
    public Type getByteType() {
        return getType(getByteDeclaration());
    }
    
    public Type getComparisonType() {
        return getType(getComparisonDeclaration());
    }
    
    public Type getDestroyableType() {
        return getType(getDestroyableDeclaration());
    }

    public Type getObtainableType() {
        return getType(getObtainableDeclaration());
    }
    
    public Type getSequenceType(Type et) {
        return appliedType(getSequenceDeclaration(), et);
    }
    
    public Type getSequentialType(Type et) {
        return appliedType(getSequentialDeclaration(), et);
    }
    
    public Type getIterableType(Type et) {
        return appliedType(getIterableDeclaration(), et, 
                getNullType());
    }

    public Type getNonemptyIterableType(Type et) {
        return appliedType(getIterableDeclaration(), et, 
                getNothingType());
    }

    public Type getSetType(Type et) {
        return appliedType(getSetDeclaration(), et);
    }

    /**
     * Returns a Type corresponding to {@code Iterator<T>}
     * @param et The Type corresponding to {@code T}
     * @return The Type corresponding to {@code Iterator<T>}
     */
    public Type getIteratorType(Type et) {
        return appliedType(getIteratorDeclaration(), et);
    }

    /**
     * Returns a Type corresponding to {@code Span<T>}
     * @param rt The Type corresponding to {@code T}
     * @return The Type corresponding to {@code Span<T>}
     */
    public Type getSpanType(Type rt) {
        return appliedType(getRangeDeclaration(), rt);
    }

    /**
     * Returns a Type corresponding to {@code SizedRange<T>|[]}
     * @param rt The Type corresponding to {@code T}
     * @return The Type corresponding to {@code SizedRange<T>|[]}
     */
    public Type getMeasureType(Type rt) {
        return unionType(appliedType(getRangeDeclaration(), rt), 
                getEmptyType(), 
                this);
    }

    public Type getEntryType(Type kt, Type vt) {
        return appliedType(getEntryDeclaration(), kt, vt);
    }

    public Type getKeyType(Type type) {
        Type st = type.getSupertype(getEntryDeclaration());
        if (st!=null && 
                st.getTypeArguments().size()==2) {
            return st.getTypeArgumentList().get(0);
        }
        else {
            return null;
        }
    }

    public Type getValueType(Type type) {
        Type st = type.getSupertype(getEntryDeclaration());
        if (st!=null && 
                st.getTypeArguments().size()==2) {
            return st.getTypeArgumentList().get(1);
        }
        else {
            return null;
        }
    }

    public Type getIteratedType(Type type) {
        Interface id = getIterableDeclaration();
        Type st = type.getSupertype(id);
        if (st!=null && 
                st.getTypeArguments().size()>0) {
            return st.getTypeArgumentList().get(0);
        }
        else {
            return null;
        }
    }

    public Type getAbsentType(Type type) {
        Interface id = getIterableDeclaration();
        Type st = type.getSupertype(id);
        if (st!=null && 
                st.getTypeArguments().size()>1) {
            return st.getTypeArgumentList().get(1);
        }
        else {
            return null;
        }
    }
    
    public boolean isNonemptyIterableType(Type type) {
        Type ft = getAbsentType(type);
        return ft!=null && ft.isExactlyNothing();
    }

    public Type getSetElementType(Type type) {
        Type st = type.getSupertype(getSetDeclaration());
        if (st!=null && 
                st.getTypeArguments().size()==1) {
            return st.getTypeArgumentList().get(0);
        }
        else {
            return null;
        }
    }

    public Type getSequentialElementType(Type type) {
        Interface sd = getSequentialDeclaration();
        Type st = type.getSupertype(sd);
        if (st!=null && 
                st.getTypeArguments().size()==1) {
            Type et = st.getTypeArgumentList().get(0);
            if (type.isTuple() && et.isUnion()) {
                //total hack to accommodate the fact that
                //tuple types are created with unsimplified
                //unions
                Unit unit = et.getDeclaration().getUnit();
                List<Type> types = new ArrayList<Type>();
                for (Type ct: et.getCaseTypes()) {
                    addToUnion(types, ct);
                }
                return union(types, unit).getType();
            }
            return et;
        }
        else {
            return null;
        }
    }

    public Type getDefiniteType(Type pt) {
        return intersectionType(getObjectType(), pt, this);
    }

    public Type getNonemptyType(Type pt) {
        Type st = 
                appliedType(getSequenceDeclaration(), 
                        getSequentialElementType(pt));
        return intersectionType(st, pt, this);
    }

    public Type getNonemptyDefiniteType(Type pt) {
        return getNonemptyType(getDefiniteType(pt));
    }
    
    public boolean isIterableType(Type pt) {
        return pt.getDeclaration()
                .inherits(getIterableDeclaration());
    }
    
    public boolean isUsableType(Type pt) {
        return pt.getDeclaration()
                .inherits(getUsableDeclaration());
    }
    
    public boolean isEntryType(Type pt) {
        return pt.getDeclaration()
                .inherits(getEntryDeclaration());
    }
    
    public boolean isSequentialType(Type pt) {
        return pt.getDeclaration().isSequentialType();
    }
    
    public boolean isSequenceType(Type pt) {
        return pt.getDeclaration().isSequenceType();
    }
    
    public boolean isEmptyType(Type pt) {
        return pt.getDeclaration().isEmptyType();
    }
    
    public boolean isTupleType(Type pt) {
        return pt.getDeclaration().isTupleType();
    }
    
    public boolean isOptionalType(Type pt) {
        //must have non-empty intersection with Null
        //and non-empty intersection with Value
        return !intersectionType(getNullType(), 
                        pt, this)
                    .isNothing() &&
                !intersectionType(getObjectType(), 
                        pt, this)
                    .isNothing();
    }
    
    public boolean isPossiblyEmptyType(Type pt) {
        //must be a subtype of Sequential<Anything>
        return isSequentialType(getDefiniteType(pt)) &&
        //must have non-empty intersection with Empty
        //and non-empty intersection with Sequence<Nothing>
               !intersectionType(getEmptyType(), 
                           pt, this)
                        .isNothing() &&
               !intersectionType(getSequentialType(getNothingType()), 
                           pt, this)
                        .isNothing();
    }
    
    public boolean isCallableType(Type pt) {
        return pt!=null && 
                pt.getDeclaration()
                    .inherits(getCallableDeclaration());
    }
    
    public NothingType getNothingDeclaration() {
        return new NothingType(this);
    }
    
    public Type denotableType(Type type) {
        if (type!=null) {
            if (type.isUnion()) {
                List<Type> cts = type.getCaseTypes();
                List<Type> list = 
                        new ArrayList<Type>
                            (cts.size()+1);
                for (Type ct: cts) {
                    addToUnion(list, 
                            denotableType(ct));
                }
                return union(list, this);
            }
            if (type.isIntersection()) {
                List<Type> sts = type.getSatisfiedTypes();
                List<Type> list = 
                        new ArrayList<Type>
                            (sts.size()+1);
                for (Type st: sts) {
                    addToIntersection(list, 
                            denotableType(st), 
                            this);
                }
                return canonicalIntersection(list, this);
            }
            TypeDeclaration dec = type.getDeclaration();
            Type et = dec.getExtendedType();
            TypeDeclaration ed = 
                    et==null ? null : 
                        et.getDeclaration();
            if (dec.isOverloaded()) {
                type = type.getSupertype(ed);
            }
            if (dec instanceof Constructor) {
                return type.getSupertype(ed);
            }
            if (dec instanceof Class && dec.isAnonymous()) {
                List<Type> sts = dec.getSatisfiedTypes();
                List<Type> list = 
                        new ArrayList<Type>
                            (sts.size()+1);
                if (et!=null) {
                    TypeDeclaration etd = 
                            et.getDeclaration();
                    addToIntersection(list, 
                            type.getSupertype(etd), 
                            this);
                }
                for (Type st: sts) {
                    if (st!=null) {
                        TypeDeclaration std = 
                                st.getDeclaration();
                        addToIntersection(list, 
                                type.getSupertype(std), 
                                this);
                    }
                }
                return canonicalIntersection(list, this);
            }
            else {
                List<Type> typeArgList = 
                        type.getTypeArgumentList();
                if (typeArgList.isEmpty()) {
                    return type;
                }
                else {
                    dec = type.getDeclaration();
                    List<TypeParameter> typeParamList = 
                            dec.getTypeParameters();
                    List<Type> typeArguments = 
                            new ArrayList<Type>
                                (typeArgList.size());
                    for (int i=0; 
                            i<typeParamList.size() && 
                            i<typeArgList.size(); 
                            i++) {
                        Type at = typeArgList.get(i);
                        TypeParameter tp = 
                                typeParamList.get(i);
                        typeArguments.add(tp.isCovariant() ? 
                                denotableType(at) : at);
                    }
                    Type qt = type.getQualifyingType();
                    Type dt = dec.appliedType(qt, typeArguments);
                    dt.setUnderlyingType(type.getUnderlyingType());
                    dt.setVarianceOverrides(type.getVarianceOverrides());
                    dt.setTypeConstructor(type.isTypeConstructor());
                    dt.setTypeConstructorParameter(
                            type.getTypeConstructorParameter());
                    dt.setRaw(type.isRaw());
                    return dt;
                }
            }
        }
        else {
            return null;
        }
    }
    
    public Type nonemptyArgs(Type args) {
        return getEmptyType().isSubtypeOf(args) ? 
                getNonemptyType(args) : args;
    }
    
    public boolean isHomogeneousTuple(Type args) {
        if (args!=null) {
            Class td = getTupleDeclaration();
            Type tuple = args.getSupertype(td);
            if (tuple!=null) {
                List<Type> tal = tuple.getTypeArgumentList();
                Type elemType;
                if (tal.size()>=3) {
                    elemType = tal.get(0);
                }
                else {
                    return false;
                }
                Type emptyType = getEmptyType();
                while (true) {
                    tal = tuple.getTypeArgumentList();
                    if (tal.size()>=3) {
                        Type first = tal.get(1);
                        if (first==null) {
                            return false;
                        }
                        else if (!first.isExactly(elemType)) {
                            return false;
                        }
                        else {
                            Type rest = tal.get(2);
                            if (rest==null) {
                                return false;
                            }
                            else if (rest.isExactly(emptyType)) {
                                return true;
                            }
                            else {
                                tuple = rest.getSupertype(td);
                                if (tuple==null) {
                                    return false;
                                }
                            }
                        }
                    }
                    else {
                        return false;
                    }
                }
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
    
    /*public Type getHomogeneousTupleElementType(Type args) {
        if (args!=null) {
            Class td = getTupleDeclaration();
            Type tuple = args.getSupertype(td);
            if (tuple!=null) {
                return getSequentialElementType(tuple);
            }
            else {
                return null;
            }
        }
        else {
            return null;
        }
    }*/
    
    public int getHomogeneousTupleLength(Type args) {
        if (args!=null) {
            Class td = getTupleDeclaration();
            Type tuple = args.getSupertype(td);
            if (tuple!=null) {
                List<Type> tal = tuple.getTypeArgumentList();
                Type elemType;
                if (tal.size()>=1) {
                    elemType = tal.get(0);
                }
                else {
                    return -1;
                }
                int size = 0;
                Type emptyType = getEmptyType();
                while (true) {
                    size++;
                    tal = tuple.getTypeArgumentList();
                    if (tal.size()>=3) {
                        Type first = tal.get(1);
                        if (first==null) {
                            return -1;
                        }
                        else if (!first.isExactly(elemType)) {
                            return -1;
                        }
                        else {
                            Type rest = tal.get(2);
                            if (rest==null) {
                                return -1;
                            }
                            else if (rest.isExactly(emptyType)) {
                                return size;
                            }
                            else if (rest.isTuple()) {
                                tuple = rest;
                                //continue looping
                            }
                            else {
                                return -1;
                            }
                        }
                    }
                    else {
                        return -1;
                    }
                }
            }
            else {
                return -1;
            }
        }
        else {
            return -1;
        }
    }

    public List<Type> getTupleElementTypes(Type args) {
        if (args!=null) {
            /*List<Type> simpleResult = 
                    getSimpleTupleElementTypes(args, 0);
            if (simpleResult!=null) {
                return simpleResult;
            }*/
            if (isEmptyType(args)) {
                return NO_TYPE_ARGS;
            }
            Class td = getTupleDeclaration();
            Type tuple =
                    nonemptyArgs(args)
                        .getSupertype(td);
            if (tuple!=null) {
                List<Type> result = 
                        new LinkedList<Type>();
                while (true) {
                    List<Type> tal = 
                            tuple.getTypeArgumentList();
                    if (tal.size()>=3) {
                        Type first = tal.get(1);
                        if (first==null) {
                            first = getUnknownType();
                        }
                        result.add(first);
                        Type rest = tal.get(2);
                        if (rest==null) {
                            result.add(getUnknownType());
                            return result;
                        }
                        else if (isEmptyType(rest)) {
                            return result;
                        }
                        else {
                            tuple = nonemptyArgs(rest)
                                    .getSupertype(td);
                            if (tuple==null) {
                                if (isSequentialType(rest)) {
                                    //this is pretty weird: return the whole
                                    //tail type as the element of the list! 
                                    result.add(rest);
                                    return result;
                                }
                                else {
                                    result.add(getUnknownType());
                                    return result; 
                                }
                            }
                            //else continue the loop!
                        }
                    }
                    else {
                        result.add(getUnknownType());
                        return result;
                    }
                }
            }
            else if (isSequentialType(args)) {
                //this is pretty weird: return the whole
                //tail type as the element of the list! 
                return singleton(args);
            }
        }
        return singleton(getUnknownType());
    }
    
    private static List<Type> singleton(Type pt) {
        List<Type> result = 
                new ArrayList<Type>(1);
        result.add(pt);
        return result;
    }
    
    /*private List<Type> getSimpleTupleElementTypes(
            Type args, int count) {
        // can be a defaulted tuple of Empty|Tuple
        if (args.isUnion()) {
            List<Type> caseTypes = 
                    args.getCaseTypes();
            if (caseTypes == null || caseTypes.size() != 2) {
                return null;
            }
            Type caseA = caseTypes.get(0);
            Type caseB = caseTypes.get(1);
            if (!caseA.isClassOrInterface() || 
                !caseB.isClassOrInterface()) {
                    return null;
            }
            TypeDeclaration caseADecl = 
                    caseA.getDeclaration();
            TypeDeclaration caseBDecl = 
                    caseB.getDeclaration();
            String caseAName = 
                    caseADecl.getQualifiedNameString();
            String caseBName = 
                    caseBDecl.getQualifiedNameString();
            if (caseAName.equals("ceylon.language::Empty") && 
                caseBName.equals("ceylon.language::Tuple")) {
                return getSimpleTupleElementTypes(caseB, count);
            }
            if (caseBName.equals("ceylon.language::Empty") && 
                caseAName.equals("ceylon.language::Tuple")) {
                return getSimpleTupleElementTypes(caseA, count);
            }
            return null;
        }
        // can be Tuple, Empty, Sequence or Sequential
        if (!(args.isClassOrInterface())) {
            return null;
        }
        String name = 
                args.getDeclaration()
                    .getQualifiedNameString();
        if (name.equals("ceylon.language::Tuple")){
            List<Type> tal = 
                    args.getTypeArgumentList();
            Type first = tal.get(1);
            Type rest = tal.get(2);
            List<Type> ret = 
                    getSimpleTupleElementTypes(rest, count+1);
            if (ret == null)
                return null;
            ret.set(count, first);
            return ret;
        }
        if (name.equals("ceylon.language::Empty")){
            ArrayList<Type> ret = 
                    new ArrayList<Type>(count);
            for (int i=0;i<count;i++) {
                ret.add(null);
            }
            return ret;
        }
        if (name.equals("ceylon.language::Sequential")
                || name.equals("ceylon.language::Sequence")
                || name.equals("ceylon.language::Range")) {
            ArrayList<Type> ret = 
                    new ArrayList<Type>(count+1);
            for (int i=0;i<count;i++) {
                ret.add(null);
            }
            ret.add(args);
            return ret;
        }
        return null;
    }*/

    public boolean isTupleLengthUnbounded(Type args) {
        if (args!=null) {
            /*Boolean simpleTupleLengthUnbounded = 
                    isSimpleTupleLengthUnbounded(args);
            if (simpleTupleLengthUnbounded != null) {
                return simpleTupleLengthUnbounded.booleanValue();
            }*/
            if (args.isSubtypeOf(getEmptyType())) {
                return false;
            }
            //TODO: this doesn't account for the case where
            //      a tuple occurs in a union with []
            Class td = getTupleDeclaration();
            Type tst = 
                    nonemptyArgs(args)
                        .getSupertype(td);
            if (tst==null) {
                return true;
            }
            else {
                while (true) {
                    List<Type> tal = 
                            tst.getTypeArgumentList();
                    if (tal.size()>=3) {
                        Type rest = tal.get(2);
                        if (rest==null) {
                            return false;
                        }
                        else if (rest.isSubtypeOf(getEmptyType())) {
                            return false;
                        }
                        else {
                            tst = nonemptyArgs(rest)
                                    .getSupertype(td);
                            if (tst==null) {
                                return true;
                            }
                            //else continue the loop!
                        }
                    }
                    else {
                        return false;
                    }
                }
            }
        }
        return false;
    }
    
    /*protected Boolean isSimpleTupleLengthUnbounded(Type args) {
        // can be a defaulted tuple of Empty|Tuple
        if (args.isUnion()) {
            List<Type> caseTypes = 
                    args.getCaseTypes();
            if (caseTypes == null || caseTypes.size() != 2) {
                return null;
            }
            Type caseA = caseTypes.get(0);
            Type caseB = caseTypes.get(1);
            if (!caseA.isClassOrInterface() || 
                !caseB.isClassOrInterface()) {
                    return null;
            }
            TypeDeclaration caseADecl = 
                    caseA.getDeclaration();
            TypeDeclaration caseBDecl = 
                    caseB.getDeclaration();
            String caseAName = 
                    caseADecl.getQualifiedNameString();
            String caseBName = 
                    caseBDecl.getQualifiedNameString();
            if (caseAName.equals("ceylon.language::Empty") && 
                caseBName.equals("ceylon.language::Tuple")) {
                return isSimpleTupleLengthUnbounded(caseB);
            }
            if (caseBName.equals("ceylon.language::Empty") && 
                caseAName.equals("ceylon.language::Tuple")) {
                return isSimpleTupleLengthUnbounded(caseA);
            }
            return null;
        }
        // can be Tuple, Empty, Sequence or Sequential
        if (!(args.isClassOrInterface())) {
            return null;
        }
        String name = 
                args.getDeclaration()
                    .getQualifiedNameString();
        if (name.equals("ceylon.language::Tuple")) {
            Type rest = 
                    args.getTypeArgumentList().get(2);
            return isSimpleTupleLengthUnbounded(rest);
        }
        if (name.equals("ceylon.language::Empty")) {
            return false;
        }
        if (name.equals("ceylon.language::Range")) {
            return true;
        }
        if (name.equals("ceylon.language::Sequential") || 
            name.equals("ceylon.language::Sequence")) {
            return true;
        }
        return null;
    }*/

    public boolean isTupleVariantAtLeastOne(Type args) {
        if (args!=null) {
            /*Boolean simpleTupleVariantAtLeastOne = 
                    isSimpleTupleVariantAtLeastOne(args);
            if (simpleTupleVariantAtLeastOne != null) {
                return simpleTupleVariantAtLeastOne.booleanValue();
            }*/
            if (getEmptyType().isSubtypeOf(args)) {
                return false;
            }
            Class td = getTupleDeclaration();
            Type tuple = 
                    nonemptyArgs(args)
                        .getSupertype(td);
            if (tuple == null) {
                return isSequenceType(args);
            }
            else {
                while (true) {
                    List<Type> tal = 
                            tuple.getTypeArgumentList();
                    if (tal.size()>=3) {
                        Type rest = tal.get(2);
                        if (rest==null) {
                            return false;
                        }
                        else if (getEmptyType()
                                .isSubtypeOf(rest)) {
                            return false;
                        }
                        else if (isSequenceType(rest) && 
                                !isTupleType(args)) {
                            return true;
                        }
                        else {
                            tuple = nonemptyArgs(rest)
                                    .getSupertype(td);
                            if (tuple==null) {
                                return isSequenceType(args);
                            }
                            //else continue the loop!
                        }
                    }
                    else {
                        return false;
                    }
                }
            }
        }
        return false;
    }
    
    /*private Boolean isSimpleTupleVariantAtLeastOne(Type args) {
        // can be a defaulted tuple of Empty|Tuple
        if (args.isUnion()) {
            List<Type> caseTypes = 
                    args.getCaseTypes();
            if (caseTypes == null || caseTypes.size() != 2) {
                return null;
            }
            Type caseA = caseTypes.get(0);
            Type caseB = caseTypes.get(1);
            if (!caseA.isClassOrInterface() || 
                !caseB.isClassOrInterface()) {
                    return null;
            }
            TypeDeclaration caseADecl = 
                    caseA.getDeclaration();
            TypeDeclaration caseBDecl = 
                    caseB.getDeclaration();
            String caseAName = 
                    caseADecl.getQualifiedNameString();
            String caseBName = 
                    caseBDecl.getQualifiedNameString();
            if (caseAName.equals("ceylon.language::Empty") && 
                caseBName.equals("ceylon.language::Tuple")) {
                return isSimpleTupleVariantAtLeastOne(caseB);
            }
            if (caseBName.equals("ceylon.language::Empty") && 
                caseAName.equals("ceylon.language::Tuple")) {
                return isSimpleTupleVariantAtLeastOne(caseA);
            }
            return null;
        }
        // can be Tuple, Empty, Sequence or Sequential
        if (!(args.isClassOrInterface())) {
            return null;
        }
        String name = 
                args.getDeclaration()
                    .getQualifiedNameString();
        if (name.equals("ceylon.language::Tuple")) {
            Type rest = 
                    args.getTypeArgumentList().get(2);
            return isSimpleTupleVariantAtLeastOne(rest);
        }
        if (name.equals("ceylon.language::Empty")) {
            return false;
        }
        if (name.equals("ceylon.language::Range")) {
            return true;
        }
        if (name.equals("ceylon.language::Sequential")) {
            return false;
        }
        if (name.equals("ceylon.language::Sequence")) {
            return true;
        }
        return null;
    }*/

    public int getTupleMinimumLength(Type args) {
        if (args!=null) {
            /*int simpleMinimumLength = 
                    getSimpleTupleMinimumLength(args);
            if (simpleMinimumLength != -1) {
                return simpleMinimumLength;
            }*/
            if (getEmptyType().isSubtypeOf(args)) {
                return 0;
            }
            Class td = getTupleDeclaration();
            Type tuple = 
                    nonemptyArgs(args)
                        .getSupertype(td);
            if (tuple == null) {
                return isSequenceType(args) ? 
                        1 : 0;
            }
            else {
                int size = 0;
                while (true) {
                    List<Type> tal = 
                            tuple.getTypeArgumentList();
                    size++;
                    if (tal.size()>=3) {
                        Type rest = tal.get(2);
                        if (rest==null) {
                            return size;
                        }
                        else if (getEmptyType()
                                .isSubtypeOf(rest)) {
                            return size;
                        }
                        else {
                            tuple = nonemptyArgs(rest)
                                    .getSupertype(td);
                            if (tuple==null) {
                                return isSequenceType(args) ? 
                                        size+1 : size;
                            }
                            //else continue the loop!
                        }
                    }
                    else {
                        return size;
                    }
                }
            }
        }
        return 0;
    }
    
    /*private int getSimpleTupleMinimumLength(Type args) {
        // can be a defaulted tuple of Empty|Tuple
        if (args.isUnion()){
            List<Type> caseTypes = 
                    args.getCaseTypes();
            if (caseTypes == null || caseTypes.size() != 2) {
                return -1;
            }
            Type caseA = caseTypes.get(0);
            Type caseB = caseTypes.get(1);
            if (!caseA.isClassOrInterface() || 
                !caseB.isClassOrInterface()) {
                    return -1;
            }
            TypeDeclaration caseADecl = 
                    caseA.getDeclaration();
            TypeDeclaration caseBDecl = 
                    caseB.getDeclaration();
            String caseAName = 
                    caseADecl.getQualifiedNameString();
            String caseBName = 
                    caseBDecl.getQualifiedNameString();
            if (caseAName.equals("ceylon.language::Empty") && 
                caseBName.equals("ceylon.language::Tuple")) {
                return 0;
            }
            if (caseBName.equals("ceylon.language::Empty") && 
                caseAName.equals("ceylon.language::Tuple")) {
                return 0;
            }
            return -1;
        }
        // can be Tuple, Empty, Sequence or Sequential
        if (!(args.isClassOrInterface())) {
            return -1;
        }
        String name = 
                args.getDeclaration()
                    .getQualifiedNameString();
        if (name.equals("ceylon.language::Tuple")) {
            Type rest = 
                    args.getTypeArgumentList().get(2);
            int ret = getSimpleTupleMinimumLength(rest);
            return ret == -1 ? -1 : ret + 1;
        }
        if (name.equals("ceylon.language::Empty")) {
            return 0;
        }
        if (name.equals("ceylon.language::Range")) {
            return 1;
        }
        if (name.equals("ceylon.language::Sequential")) {
            return 0;
        }
        if (name.equals("ceylon.language::Sequence")) {
            return 1;
        }
        return -1;
    }*/

    public List<Type> getCallableArgumentTypes(Type t) {
        Type tuple = getCallableTuple(t);
        if (tuple == null) {
            return Collections.emptyList();
        }
        else {
            return getTupleElementTypes(tuple);
        }
    }
    
    public Type getCallableTuple(Type t) {
        if (t==null) return null;
        Interface cd = getCallableDeclaration();
        Type ct = t.getSupertype(cd);
        if (ct!=null) {
            List<Type> typeArgs = 
                    ct.getTypeArgumentList();
            if (typeArgs.size()>=2) {
                return typeArgs.get(1);
            }
        }
        return null;
    }
    
    public Type getCallableReturnType(Type t) {
        if (t==null) return null;
        if (t.isNothing()) return t;
        Interface cd = getCallableDeclaration();
        Type ct = t.getSupertype(cd);
        if (ct!=null) {
            List<Type> typeArgs = ct.getTypeArgumentList();
            if (typeArgs.size()>=1) {
                return typeArgs.get(0);
            }
        }
        return null;
    }
    
    public boolean isIterableParameterType(Type t) {
        return t.getDeclaration().isIterable();
    }
    
    public TypeDeclaration 
    getLanguageModuleModelTypeDeclaration(String name) {
        return (TypeDeclaration) 
                getLanguageModuleModelDeclaration(name);
    }
    public TypeDeclaration 
    getLanguageModuleDeclarationTypeDeclaration(String name) {
        return (TypeDeclaration) 
                getLanguageModuleDeclarationDeclaration(name);
    }
    
    private final Map<String,String> modifiers = 
            new HashMap<String,String>();
    private void put(String modifier) {
        modifiers.put(modifier, modifier);
    }
    {
        put("shared");
        put("default");
        put("formal");
        put("native");
        put("actual");
        put("abstract");
        put("final");
        put("sealed");
        put("variable");
        put("late");
        put("deprecated");
        put("annotation");
        put("optional");
        put("serializable");
    }
    public Map<String, String> getModifiers() {
        return modifiers;
    }
    
    public Type getValueMetatype(TypedReference reference) {
        TypedDeclaration declaration = 
                reference.getDeclaration();
        boolean variable = declaration.isVariable();
        Type getType;
        Type setType = getNothingType();
        Type qualifyingType = reference.getQualifyingType();
        if (declaration.getTypeDeclaration() 
                instanceof Constructor) {
            getType = denotableType(reference.getType());
            if (qualifyingType!=null) {
                qualifyingType = 
                        qualifyingType.getQualifyingType();
            }            
        }
        else {
            getType = reference.getType();
            if (variable) {
                setType = getType;
            }
        }
        if (qualifyingType!=null) {
            TypeDeclaration ad = 
                    getLanguageModuleModelTypeDeclaration(
                            "Attribute");
            return appliedType(ad, qualifyingType, 
                    getType, setType);
        }
        else {
            TypeDeclaration vd = 
                    getLanguageModuleModelTypeDeclaration(
                            "Value");
            return appliedType(vd, getType, setType);
        }
    }
    
    public Type getFunctionMetatype(TypedReference reference) {
        TypedDeclaration dec = reference.getDeclaration();
        Functional fun = (Functional) dec;
        if (fun.getParameterLists().isEmpty()) {
            return null;
        }
        ParameterList fpl = fun.getFirstParameterList();
        List<Parameter> params = fpl.getParameters();
        Type parameterTuple = 
                getParameterTypesAsTupleType(params, 
                        reference);
        Type fullType = reference.getFullType();
        Type returnType = getCallableReturnType(fullType);
        if (returnType == null) {
            return null;
        }
        else {
            Type qualifyingType = 
                    reference.getQualifyingType();
            if (qualifyingType!=null) {
                TypeDeclaration md = 
                        getLanguageModuleModelTypeDeclaration(
                                "Method");
                return appliedType(md, qualifyingType, 
                        returnType, parameterTuple);
            }
            else {
                TypeDeclaration fd = 
                        getLanguageModuleModelTypeDeclaration(
                                "Function");
                return appliedType(fd, returnType, 
                        parameterTuple);
            }
        }
    }
    
    public Type getConstructorMetatype(TypedReference reference) {
        TypeDeclaration d = 
                reference.getDeclaration()
                    .getTypeDeclaration();
        if (d==null) {
            return null;
        }
        return getConstructorMetatype(reference, d);
    }

    public Type getConstructorMetatype(Type type) {
        TypeDeclaration d = type.getDeclaration();
        if (d==null) {
            return null;
        }
        return getConstructorMetatype(type, d);
    }

    private Type getConstructorMetatype(
            Reference reference, 
            TypeDeclaration d) {
        Functional f = (Functional) d;
        ParameterList fpl = f.getFirstParameterList();
        if (fpl==null) {
            return null;
        }
        List<Parameter> params = fpl.getParameters();
        Type parameterTuple = getNothingType();
        Scope scope = d.getContainer();
        if (scope instanceof Class) {
            Class c = (Class) scope;
            if (c.isClassOrInterfaceMember() || c.isToplevel()) {
                parameterTuple = 
                        getParameterTypesAsTupleType(params, 
                                reference);
            }
            else {
                parameterTuple = getNothingType();
            }
        }
        Type fullType = reference.getFullType();
        Type returnType = 
                denotableType(getCallableReturnType(fullType));
        if (returnType == null) {
            return null;
        }
        else {
            Type qualifyingType = 
                    reference.getQualifyingType();
            if (qualifyingType!=null && 
                    qualifyingType.getDeclaration()
                        .isClassOrInterfaceMember()) {
                Type qqt = qualifyingType.getQualifyingType();
                TypeDeclaration mccd = 
                        getLanguageModuleModelTypeDeclaration(
                                "MemberClassCallableConstructor");
                return appliedType(mccd, qqt, returnType, 
                        parameterTuple);
            }
            else {
                TypeDeclaration cd = 
                        getLanguageModuleModelTypeDeclaration(
                                "CallableConstructor");
                return appliedType(cd, returnType, 
                        parameterTuple);
            }
        }
    }
    
    public Type getValueConstructorMetatype(
            Reference reference) {
        Type fullType = reference.getFullType();
        Type returnType = 
                fullType;
        if (returnType == null) {
            return null;
        }
        else {
            Type qualifyingType = 
                    reference.getQualifyingType();
            if (qualifyingType!=null && 
                    qualifyingType.getDeclaration()
                        .isClassOrInterfaceMember()) {
                Type qqt = qualifyingType.getQualifyingType();
                TypeDeclaration mccd = 
                        getLanguageModuleModelTypeDeclaration(
                                "MemberClassValueConstructor");
                return appliedType(mccd, qqt, returnType);
            }
            else {
                TypeDeclaration cd = 
                        getLanguageModuleModelTypeDeclaration(
                                "ValueConstructor");
                return appliedType(cd, returnType);
            }
        }
    }
    
    public Type getClassMetatype(Type type) {
        Class c = (Class) type.getDeclaration();
        ParameterList parameterList = c.getParameterList();
        Type parameterTuple;
        if ((c.isClassOrInterfaceMember() || c.isToplevel()) &&
                parameterList!=null) {
            List<Parameter> params = 
                    parameterList.getParameters();
            parameterTuple = 
                    getParameterTypesAsTupleType(params, type);
        }
        else {
            parameterTuple = getNothingType();
        }
        Type qualifyingType = type.getQualifyingType();
        if (qualifyingType!=null) {
            TypeDeclaration mcd = 
                    getLanguageModuleModelTypeDeclaration(
                            "MemberClass");
            return appliedType(mcd, qualifyingType, 
                    type, parameterTuple);
        }
        else {
            TypeDeclaration cd = 
                    getLanguageModuleModelTypeDeclaration(
                            "Class");
            return appliedType(cd, type, 
                    parameterTuple);
        }
    }
    
    public Type getInterfaceMetatype(Type type) {
        Type qualifyingType = type.getQualifyingType();
        if (qualifyingType!=null) {
            TypeDeclaration mid = 
                    getLanguageModuleModelTypeDeclaration(
                            "MemberInterface");
            return appliedType(mid, qualifyingType, type);
        }
        else {
            TypeDeclaration id = 
                    getLanguageModuleModelTypeDeclaration(
                            "Interface");
            return appliedType(id, type);
        }
    }

    public Type getTypeMetaType(Type type) {
        if (type.isUnion()) {
            TypeDeclaration utd = 
                    getLanguageModuleModelTypeDeclaration(
                            "UnionType");
            return appliedType(utd, type);
        }
        else if (type.isIntersection()) {
            TypeDeclaration itd = 
                    getLanguageModuleModelTypeDeclaration(
                            "IntersectionType");
            return appliedType(itd, type);
        }
        else {
            TypeDeclaration td = 
                    getLanguageModuleModelTypeDeclaration(
                            "Type");
            return appliedType(td, type);
        }
    }
    
    public Type getParameterTypesAsTupleType(
            List<Parameter> params, Reference reference) {
        List<Type> paramTypes = 
                new ArrayList<Type>
                    (params.size());
        int max = params.size()-1;
        int firstDefaulted = -1;
        boolean sequenced = false;
        boolean atLeastOne = false;
        for (int i=0; i<=max; i++) {
            Parameter p = params.get(i);
            Type fullType;
            if (p.getModel() == null) {
                fullType = getUnknownType();
            }
            else {
                if (reference==null) {
                    //this special case is here because
                    //TypeArgumentInference abuses this
                    //API by passing a qualifying type
                    //which does not actually own the
                    //given parameters directly
                    fullType =
                            p.getModel()
                                .getReference()
                                .getFullType();
                }
                else {
                    fullType = 
                            reference.getTypedParameter(p)
                                .getFullType();
                }
                if (firstDefaulted<0 && p.isDefaulted()) {
                    firstDefaulted = i;
                }
                if (i==max && p.isSequenced()) {
                    sequenced = true;
                    atLeastOne = p.isAtLeastOne();
                    if (fullType!=null) {
                        fullType = getIteratedType(fullType);
                    }
                }
            }
            paramTypes.add(fullType);
        }
        return getTupleType(paramTypes, 
                sequenced, atLeastOne, 
                firstDefaulted);
    }
    
    public Type getTailType(Type sequenceType, 
            int fixedLength) {
        int i=0;
        Type tail = sequenceType;
        while (i++<fixedLength && tail!=null) {
            if (isTupleType(tail)) {
                List<Type> list = 
                        tail.getTypeArgumentList();
                if (list.size()>=3) {
                    tail = list.get(2);
                }
                else {
                    tail = null;
                }
            }
            else {
                tail = null;
            }
        }
        return tail;
    }
    
    public Type getType(TypeDeclaration td) {
        return td==null ?
                getUnknownType() :
                td.getType();
    }
    
    public Type getPackageDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("Package"));
    }
    
    public Type getModuleDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("Module"));
    }
    
    public Type getImportDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("Import"));
    }
    
    public Type getClassDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("ClassDeclaration"));
    }
    
    public Type getClassDeclarationType(Class clazz) {
        return clazz.hasConstructors() || clazz.hasEnumerated() || clazz.isAnonymous() ?
                getType(getLanguageModuleDeclarationTypeDeclaration("ClassWithConstructorsDeclaration")) :
                getType(getLanguageModuleDeclarationTypeDeclaration("ClassWithInitializerDeclaration"));
    }
    
    public Type getConstructorDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("ConstructorDeclaration"));
    }
    
    public Type getCallableConstructorDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("CallableConstructorDeclaration"));
    }
    
    public Type getValueConstructorDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("ValueConstructorDeclaration"));
    }
    
    public Type getInterfaceDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("InterfaceDeclaration"));
    }
    
    public Type getAliasDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("AliasDeclaration"));
    }
    
    public Type getTypeParameterDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("TypeParameter"));
    }
    
    public Type getFunctionDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("FunctionDeclaration"));
    }
    
    public Type getValueDeclarationType() {
        return getType(getLanguageModuleDeclarationTypeDeclaration("ValueDeclaration"));
    }
    
    public TypeDeclaration getAnnotationDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("Annotation");
    }
    
    public TypeDeclaration getConstrainedAnnotationDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("ConstrainedAnnotation");
    }
    
    public TypeDeclaration getSequencedAnnotationDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("SequencedAnnotation");
    }
    
    public TypeDeclaration getOptionalAnnotationDeclaration() {
        return (TypeDeclaration) getLanguageModuleDeclaration("OptionalAnnotation");
    }
    
    public TypeDeclaration getDeclarationDeclaration() {
        return getLanguageModuleDeclarationTypeDeclaration("Declaration");
    }
    
    public TypeCache getCache() {
        Module module = getPackage().getModule();
        return module != null ? module.getCache() : null;
    }

}
import java.util.List;
import com.redhat.ceylon.model.loader.JvmBackendUtil;
import com.redhat.ceylon.model.loader.ModelCompleter;
import com.redhat.ceylon.model.loader.mirror.ClassMirror;
import com.redhat.ceylon.model.typechecker.model.Annotation;
import com.redhat.ceylon.model.typechecker.model.ClassAlias;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Reference;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.Unit;
package com.redhat.ceylon.model.loader.model;



/**
 * Represents a lazy class alias.
 *
 * @author Stéphane Épardaud <stef@epardaud.fr>
 */
public class LazyClassAlias extends ClassAlias implements LazyContainer {

    public ClassMirror classMirror;
    private ModelCompleter completer;
    
    private boolean isLoaded = false;
    private boolean isLoaded2 = false;
    private boolean isTypeParamsLoaded = false;
    private boolean isTypeParamsLoaded2 = false;
    private boolean local;

    @Override
    protected java.lang.Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyClassAlias(ClassMirror classMirror, ModelCompleter completer) {
        this.classMirror = classMirror;
        this.completer = completer;
        setName(JvmBackendUtil.getMirrorName(classMirror));