public static Type intersection(
        List<Type> types, Unit unit) {
    if (types.size()==1) {
        return types.get(0);
    }
    IntersectionType it = new IntersectionType(unit);
    it.setSatisfiedTypes(types);
    return it.getType();
} public static Type union(
        List<Type> types, Unit unit) {
    if (types.size()==1) {
        return types.get(0);
    }
    UnionType ut = new UnionType(unit);
    ut.setCaseTypes(types);
    return ut.getType();
} public interface ArtifactResult {
    /**
     * Get name.
     *
     * @return the artifact name.
     */
    String name();

    /**
     * Get version.
     *
     * @return the version.
     */
    String version();

    /**
     * Get import type.
     *
     * @return the import type
     */
    ImportType importType();

    /**
     * The result type.
     *
     * @return the type
     */
    ArtifactResultType type();

    /**
     * Get visibility type.
     *
     * @return visibility type
     */
    VisibilityType visibilityType();

    /**
     * The requested artifact.
     *
     * @return the requested artifact
     * @throws RepositoryException for any I/O error
     */
    File artifact() throws RepositoryException;

    /**
     * The resources filter.
     *
     * @return the path filter or null if there is none
     */
    PathFilter filter();

    /**
     * Dependencies.
     * <p/>
     * They could be lazily recursively fetched
     * or they could be fetched in one go.
     *
     * @return dependencies, empty list if none
     * @throws RepositoryException for any I/O error
     */
    List<ArtifactResult> dependencies() throws RepositoryException;

    /**
     * Get the display string of the repository this
     * result comes from
     *
     * @return the repository display string.
     */
    String repositoryDisplayString();
    
    /**
     * Get the repository this result was resolved from.
     *
     * @return the repository this result was resolved from.
     */
    Repository repository();
} public class JDKUtils {
    
    public enum JDK {
        JDK7("package-list.jdk7", "package-list.oracle.jdk7", "7"),
        JDK8("package-list.jdk8", "package-list.oracle.jdk8", "8");
        
        public final String packageList;
        public final String packageListOracle;
        public final String version;
        
        private JDK(String packageList, String packageListOracle, String version) {
            this.packageList = packageList;
            this.packageListOracle = packageListOracle;
            this.version = version;
        }
        
        public boolean providesVersion(String version){
            if(this.version.equals(version))
                return true;
            // also provides every smaller version
            EnumSet<JDK> smaller = EnumSet.range(JDK7, this);
            // we want strictly smaller
            smaller.remove(this);
            for(JDK smallerJDK : smaller){
                if(smallerJDK.version.equals(version))
                    return true;
            }
            return false;
        }

        public boolean isLowerVersion(String version) {
            EnumSet<JDK> smaller = EnumSet.range(JDK7, this);
            // we want strictly smaller
            smaller.remove(this);
            for(JDK smallerJDK : smaller){
                if(smallerJDK.version.equals(version))
                    return true;
            }
            return false;
        }
    }
    
    public final static JDK jdk;
    
    static {
        String version = System.getProperty("java.version");
        if(version != null && version.startsWith("1.8")){
            jdk = JDK.JDK8;
        }else{
            jdk = JDK.JDK7;
        }
    }

    private static Map<String, Tuple> jdkModules;
    private static Map<String, Tuple> jdkOracleModules;
    private static HashMap<String, String> jdkPackageToModule;

    private static class Tuple {
        private Set<String> packages;
        private Set<String> paths;

        private Tuple() {
            packages = new HashSet<String>();
            paths = new HashSet<String>();
        }

        boolean isEmpty() {
            return packages.isEmpty();
        }

        Tuple finish() {
            packages = Collections.unmodifiableSet(packages);
            paths = Collections.unmodifiableSet(paths);
            return this;
        }
    }

    private static synchronized void loadPackageList() {
        if (jdkModules != null)
            return;
        jdkModules = loadModularPackageList(jdk.packageList);
        jdkOracleModules = loadModularPackageList(jdk.packageListOracle);
        jdkPackageToModule = new HashMap<String,String>();
        for(Entry<String, Tuple> entry : jdkModules.entrySet()){
            for(String pkg : entry.getValue().packages)
                jdkPackageToModule.put(pkg, entry.getKey());
        }
        for(Entry<String, Tuple> entry : jdkOracleModules.entrySet()){
            for(String pkg : entry.getValue().packages)
                jdkPackageToModule.put(pkg, entry.getKey());
        }
    }

    private static Map<String, Tuple> loadModularPackageList(String file) {
        try {
            // not thread-safe, but that's OK because the caller is thread-safe
            Map<String, Tuple> jdkPackages = new HashMap<String, Tuple>();
            InputStream inputStream = JDKUtils.class.getResourceAsStream(file);
            if (inputStream == null) {
                throw new RuntimeException("Failed to read JDK package list file from " + file + ": your Ceylon installation is broken.");
            }
            BufferedReader bis = new BufferedReader(new InputStreamReader(inputStream, "ASCII"));
            Tuple tuple = null;
            String moduleName = null;
            String pkg;
            while ((pkg = bis.readLine()) != null) {
                // strip comments
                int commentStart = pkg.indexOf('#');
                if (commentStart > -1)
                    pkg = pkg.substring(0, commentStart);
                // strip whitespace
                pkg = pkg.trim();
                // ignore empty lines
                if (pkg.isEmpty())
                    continue;
                // see if we start a new module
                if (pkg.startsWith("=")) {
                    String name = pkg.substring(1).trim();
                    if (name.isEmpty())
                        throw new RuntimeException("Failed to read JDK module list file from " + file + ": module has empty name");
                    // close previous module
                    if (tuple != null) {
                        if (tuple.isEmpty())
                            throw new RuntimeException("Failed to read JDK module list file from " + file + ": module " + moduleName + " is empty");
                        // save previous module
                        jdkPackages.put(moduleName, tuple.finish());
                    }
                    // start the new module
                    moduleName = name;
                    tuple = new Tuple();
                    continue;
                }
                // add a package to the current module
                if (tuple == null)
                    throw new RuntimeException("Failed to read JDK module list file from " + file + ": adding package to undefined module");

                tuple.packages.add(pkg);
                tuple.paths.add(pkg.replace('.', '/'));
            }
            bis.close();
            // close previous module
            if (tuple != null) {
                if (tuple.isEmpty())
                    throw new RuntimeException("Failed to read JDK module list file from " + file + ": module " + moduleName + " is empty");
                // save previous module
                jdkPackages.put(moduleName, tuple.finish());
            }
            // sanity check
            if (jdkPackages.size() == 0)
                throw new RuntimeException("Failed to read JDK package list file from " + file + "(empty package set): your Ceylon installation is broken.");
            return Collections.unmodifiableMap(jdkPackages);
        } catch (IOException x) {
            throw new RuntimeException("Failed to read JDK package list file from " + file + ": your Ceylon installation is broken.", x);
        }
    }

    public static boolean isJDKModule(String mod) {
        loadPackageList();
        return jdkModules.containsKey(mod);
    }

    public static boolean isJDKPackage(String mod, String pkg) {
        loadPackageList();
        Tuple tuple = jdkModules.get(mod);
        return tuple != null && tuple.packages.contains(pkg);
    }

    public static boolean isJDKAnyPackage(String pkg) {
        loadPackageList();
        for (Tuple tuple : jdkModules.values()) {
            if (tuple.packages.contains(pkg))
                return true;
        }
        return false;
    }

    public static Set<String> getJDKModuleNames() {
        loadPackageList();
        return jdkModules.keySet();
    }

    public static Set<String> getJDKPackagesByModule(String module) {
        loadPackageList();
        Tuple tuple = jdkModules.get(module);
        return tuple != null ? tuple.packages : null;
    }

    public static Set<String> getJDKPathsByModule(String module) {
        loadPackageList();
        Tuple tuple = jdkModules.get(module);
        return tuple != null ? tuple.paths : null;
    }

    public static boolean isOracleJDKModule(String pkg) {
        loadPackageList();
        return jdkOracleModules.containsKey(pkg);
    }

    public static boolean isOracleJDKPackage(String mod, String pkg) {
        loadPackageList();
        Tuple tuple = jdkOracleModules.get(mod);
        return tuple != null && tuple.packages.contains(pkg);
    }

    public static boolean isOracleJDKAnyPackage(String pkg) {
        loadPackageList();
        for (Tuple tuple : jdkOracleModules.values()) {
            if (tuple.packages.contains(pkg))
                return true;
        }
        return false;
    }

    public static Set<String> getOracleJDKModuleNames() {
        loadPackageList();
        return jdkOracleModules.keySet();
    }

    public static Set<String> getOracleJDKPackagesByModule(String module) {
        loadPackageList();
        Tuple tuple = jdkOracleModules.get(module);
        return tuple != null ? tuple.packages : null;
    }

    public static Set<String> getOracleJDKPathsByModule(String module) {
        loadPackageList();
        Tuple tuple = jdkOracleModules.get(module);
        return tuple != null ? tuple.paths : null;
    }

    public static String getJDKModuleNameForPackage(String pkgName) {
        loadPackageList();
        return jdkPackageToModule.get(pkgName);
    }
} 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 interface FieldMirror extends AnnotatedMirror, AccessibleMirror {

    /**
     * Returns true if this field is static
     */
    boolean isStatic();

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

    /**
     * Returns the type of this field 
     */
    TypeMirror getType();
} public interface MethodMirror extends AnnotatedMirror, AccessibleMirror {

    /**
     * Returns true if this method is static
     */
    boolean isStatic();

    /**
     * Returns true if this method is a constructor
     */
    boolean isConstructor();

    /**
     * Returns true if this method is abstract
     */
    boolean isAbstract();
    
    /**
     * Returns true if this method is final
     */
    boolean isFinal();

    /**
     * Returns true if this method is a static initialiser
     */
    boolean isStaticInit();

    /**
     * Returns true if this method is variadic
     */
    boolean isVariadic();
    
    /**
     * Returns the list of parameters
     */
    List<VariableMirror> getParameters();

    /**
     * Returns the return type for this method 
     */
    TypeMirror getReturnType();
    
    boolean isDeclaredVoid();

    /**
     * Returns the list of type parameters for this method
     */
    List<TypeParameterMirror> getTypeParameters();
    
    /**
     * If this is a method on an annotation type, whether the method has a 
     * {@code default} expression;
     */
    boolean isDefault();

    /**
     * Return this method's enclosing class.
     */
    ClassMirror getEnclosingClass();
} public interface VariableMirror extends AnnotatedMirror {
    
    /**
     * Returns this parameter's type
     */
    TypeMirror getType();
} public class AnnotationProxyClass extends Class {

    public final LazyInterface iface;

    public AnnotationProxyClass(LazyInterface iface) {
        this.iface = iface;
    }
    
    /**
     * The elements in the {@code @Target} annotation, or null if 
     * the annotation type lacks the {@code @Target} annotation.
     */
    @Override
    public EnumSet<AnnotationTarget> getAnnotationTarget() {
        return AnnotationTarget.getAnnotationTarget(iface);
    }
} public class LazyClass extends Class implements LazyContainer {

    public final ClassMirror classMirror;
    private ModelCompleter completer;
    private Class superClass;
    private MethodMirror constructor;
    private String realName;
    private boolean isStatic;
    private boolean isCeylon;
    private boolean isValueType;
    private Map<String,Declaration> localDeclarations;
    
    private boolean isLoaded = false;
    private boolean isLoaded2 = false;
    private boolean isTypeParamsLoaded = false;
    private boolean isTypeParamsLoaded2 = false;
    private boolean local = false;
    private boolean hasJpaConstructor;

    @Override
    protected java.lang.Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyClass(ClassMirror classMirror, ModelCompleter completer, Class superClass, MethodMirror constructor) {
        this.classMirror = classMirror;
        this.completer = completer;
        this.superClass = superClass;
        this.constructor = constructor;
        this.realName = classMirror.getName();
        setName(JvmBackendUtil.getMirrorName(classMirror));
        this.isStatic = classMirror.isStatic();
        this.isCeylon = classMirror.getAnnotation(AbstractModelLoader.CEYLON_CEYLON_ANNOTATION) != null;
        this.isValueType = classMirror.getAnnotation(AbstractModelLoader.CEYLON_VALUETYPE_ANNOTATION) != null;
    }

    @Override
    public boolean isErasedTypeArguments() {
        return !isCeylon();
    }
    
    @Override
    public boolean isCeylon() {
        return isCeylon;
    }

    public boolean isValueType() {
        return isValueType;
    }

    public boolean isStatic() {
        return isStatic;
    }

    public MethodMirror getConstructor() {
        return constructor;
    }
    
    public String getRealName() {
        return this.realName;
    }
    
    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                loadTypeParams();
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }

    private void loadTypeParams() {
        if(!isTypeParamsLoaded2){
            synchronized(completer.getLock()){
                if(!isTypeParamsLoaded){
                    isTypeParamsLoaded = true;
                    completer.completeTypeParameters(this);
                    isTypeParamsLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public ParameterList getParameterList() {
        load();
        return super.getParameterList();
    }

    @Override
    public boolean isStaticallyImportable() {
        // no lazy loading since it is set before completion
        return super.isStaticallyImportable();
    }

    @Override
    public Parameter getParameter(String name) {
        load();
        return super.getParameter(name);
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    public Type getType() {
        loadTypeParams();
        return super.getType();
    }

    @Override
    public Type getExtendedType() {
        if (superClass == null) {
            load();
            return super.getExtendedType();
        } else {
            return superClass.getType();
        }
    }
    
    @Override
    public List<TypeParameter> getTypeParameters() {
        loadTypeParams();
        return super.getTypeParameters();
    }
    
    @Override
    public List<ParameterList> getParameterLists() {
        load();
        return super.getParameterLists();
    }

    @Override
    public boolean isMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isMember();
    }

    @Override
    public boolean hasConstructors() {
        load();
        return super.hasConstructors();
    }
    
    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<Type> getSatisfiedTypes() {
        load();
        return super.getSatisfiedTypes();
    }

    @Override
    public List<Type> getCaseTypes() {
        load();
        return super.getCaseTypes();
    }

    @Override
    public Reference appliedReference(Type pt,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public Type appliedType(Type outerType,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedType(outerType, typeArguments);
    }

    @SuppressWarnings("deprecation")
    @Override
    public List<Declaration> getInheritedMembers(String name) {
        load();
        return super.getInheritedMembers(name);
    }

    @Override
    public Declaration getRefinedMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getRefinedMember(name, signature, ellipsis);
    }
    
    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public boolean isAlias() {
        // does not require lazy loading since it depends on class
        return super.isAlias();
    }

    @Override
    public Type getSelfType() {
        load();
        return super.getSelfType();
    }

    @Override
    public Scope getVisibleScope() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.getVisibleScope();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public String getQualifiedNameString() {
        // never need to complete since the container is set before completion
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public Backends getNativeBackends() {
        return super.getNativeBackends();
    }
    
    @Override
    public void setNativeBackends(Backends backends) {
        super.setNativeBackends(backends);
    }

    @Override
    public boolean isVisible(Scope scope) {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public Unit getUnit() {
        // this doesn't require to load the model
        return super.getUnit();
    }

    @Override
    public Scope getContainer() {
        // NO lazy-loading since this is set before lazy-loading
        return super.getContainer();
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public boolean isAnnotation() {
//        load();
        return super.isAnnotation();
    }
    
    @Override
    public void addMember(Declaration decl) {
        // do this without lazy-loading
        super.addMember(decl);
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public void setLocal(boolean local) {
        this.local = local;
    }
    
    @Override
    public boolean isLocal(){
        return this.local ;
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        load();
        if(localDeclarations == null)
            return null;
        return localDeclarations.get(name);
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        if(localDeclarations == null)
            localDeclarations = new HashMap<String, Declaration>();
        localDeclarations.put(declaration.getPrefixedName(), declaration);
    }
    
    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    public boolean hasJpaConstructor() {
        load();
        return hasJpaConstructor;
    }
    
    public void setHasJpaConstructor(boolean hasJpaConstructor) {
        this.hasJpaConstructor = hasJpaConstructor;
    }
} 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));
    }

    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                loadTypeParams();
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }

    private void loadTypeParams() {
        if(!isTypeParamsLoaded2){
            synchronized(completer.getLock()){
                if(!isTypeParamsLoaded){
                    isTypeParamsLoaded = true;
                    completer.completeTypeParameters(this);
                    isTypeParamsLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public ParameterList getParameterList() {
        load();
        return super.getParameterList();
    }

    @Override
    public boolean isStaticallyImportable() {
        load();
        return super.isStaticallyImportable();
    }

    @Override
    public Parameter getParameter(String name) {
        load();
        return super.getParameter(name);
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    public Type getType() {
        loadTypeParams();
        return super.getType();
    }

    @Override
    public Type getExtendedType() {
        load();
        return super.getExtendedType();
    }
    
    @Override
    public List<TypeParameter> getTypeParameters() {
        loadTypeParams();
        return super.getTypeParameters();
    }
    
    @Override
    public List<ParameterList> getParameterLists() {
        load();
        return super.getParameterLists();
    }

    @Override
    public boolean isMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isMember();
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<Type> getSatisfiedTypes() {
        load();
        return super.getSatisfiedTypes();
    }

    @Override
    public List<Type> getCaseTypes() {
        load();
        return super.getCaseTypes();
    }

    @Override
    public Reference appliedReference(Type pt,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public Type appliedType(Type outerType,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedType(outerType, typeArguments);
    }

    @SuppressWarnings("deprecation")
    @Override
    public List<Declaration> getInheritedMembers(String name) {
        load();
        return super.getInheritedMembers(name);
    }

    @Override
    public Declaration getRefinedMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getRefinedMember(name, signature, ellipsis);
    }
    
    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public boolean isAlias() {
        // does not require lazy loading since it depends on class
        return super.isAlias();
    }

    @Override
    public Type getSelfType() {
        load();
        return super.getSelfType();
    }

    @Override
    public Scope getVisibleScope() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.getVisibleScope();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public String getQualifiedNameString() {
        load();
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public boolean isVisible(Scope scope) {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public Unit getUnit() {
        // this doesn't require to load the model
        return super.getUnit();
    }

    @Override
    public Scope getContainer() {
        // NO lazy-loading since this is set before lazy-loading
        return super.getContainer();
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public void addMember(Declaration decl) {
        // do this without lazy-loading
        super.addMember(decl);
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public void setLocal(boolean local) {
        this.local = local;
    }
    
    @Override
    public boolean isLocal(){
        return this.local ;
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        return null;
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        throw new RuntimeException("type aliases do not contain any local declarations");
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    @Override
    public boolean isCeylon() {
        // there are no aliases in Java
        return true;
    }
} public interface LazyContainer extends LazyElement, LocalDeclarationContainer {
    public void addMember(Declaration decl);

} public interface LazyElement {

    public boolean isLoaded();
    
    public boolean isLocal();
    
    public void setLocal(boolean local);
    
    public boolean isCeylon();
} public class LazyFunction extends Function implements LazyElement, LocalDeclarationContainer {

    private MethodMirror methodMirror;
    public final ClassMirror classMirror;
    private ModelCompleter completer;
    private String realName;
    private String realMethodName;
    
    private boolean isLoaded = false;
    private boolean isLoaded2 = false;
    
    private Map<String,Declaration> localDeclarations;
    
    @Override
    protected Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyFunction(ClassMirror classMirror, ModelCompleter completer) {
        this.classMirror = classMirror;
        this.completer = completer;
        this.realName = classMirror.getName();
        setName(JvmBackendUtil.getMirrorName(classMirror));
    }

    public void setMethodMirror(MethodMirror methorMirror) {
        this.methodMirror = methorMirror;
    }
    
    public MethodMirror getMethodMirror(){
        load();
        return this.methodMirror;
    }

    public String getRealName() {
        return this.realName;
    }

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

    public String getRealMethodName(){
        return this.realMethodName;
    }
    
    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public Type getType() {
        load();
        return super.getType();
    }
    
    @Override
    public boolean isDeclaredVoid() {
        load();
        return super.isDeclaredVoid();
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<TypeParameter> getTypeParameters() {
        load();
        return super.getTypeParameters();
    }

    @Override
    public List<ParameterList> getParameterLists() {
        load();
        return super.getParameterLists();
    }
    
    @Override
    public Object getAnnotationConstructor() {
        load();
        return super.getAnnotationConstructor();
    }

    @Override
    public DeclarationKind getDeclarationKind() {
        load();
        return super.getDeclarationKind();
    }

    @Override
    public TypeDeclaration getTypeDeclaration() {
        load();
        return super.getTypeDeclaration();
    }

    @Override
    public TypedReference appliedTypedReference(Type qualifyingType, 
            List<Type> typeArguments, boolean assignment) {
        load();
        return super.appliedTypedReference(qualifyingType, typeArguments, assignment);
    }

    @Override
    public Reference appliedReference(Type pt, List<Type> typeArguments) {
        load();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public boolean isMember() {
        load();
        return super.isMember();
    }

    @Override
    public boolean isVariable() {
        load();
        return super.isVariable();
    }

    @Override
    public Map<String, DeclarationWithProximity> getMatchingDeclarations(Unit unit, String startingWith, int proximity) {
        load();
        return super.getMatchingDeclarations(unit, startingWith, proximity);
    }

    @Override
    public TypedDeclaration getOriginalDeclaration() {
        load();
        return super.getOriginalDeclaration();
    }

    @Override
    public Boolean getUnboxed() {
        load();
        return super.getUnboxed();
    }

    @Override
    public Scope getVisibleScope() {
        load();
        return super.getVisibleScope();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public Backends getNativeBackends() {
        return super.getNativeBackends();
    }
    
    @Override
    public void setNativeBackends(Backends backends) {
        super.setNativeBackends(backends);
    }

    @Override
    public Declaration getRefinedDeclaration() {
        load();
        return super.getRefinedDeclaration();
    }

    @Override
    public boolean isVisible(Scope scope) {
        load();
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public void setCaptured(boolean local) {
        load();
        super.setCaptured(local);
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean refines(Declaration other) {
        load();
        return super.refines(other);
    }

    @Override
    public Unit getUnit() {
//        load();
        return super.getUnit();
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    protected Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public boolean isInherited(Declaration d) {
        load();
        return super.isInherited(d);
    }

    @Override
    public TypeDeclaration getInheritingDeclaration(Declaration d) {
        load();
        return super.getInheritingDeclaration(d);
    }

    @Override
    public boolean isAnnotation() {
        load();
        return super.isAnnotation();
    }
    
    @Override
    public boolean isLoaded() {
        return isLoaded;
    }
    
    @Override
    public void addMember(Declaration declaration) {
        // do this without lazy-loading
        super.addMember(declaration);
    }

    @Override
    public boolean isLocal() {
        // FIXME: this may be wrong now, but is it used?
        return false;
    }

    @Override
    public void setLocal(boolean local) {
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        load();
        if(localDeclarations == null)
            return null;
        return localDeclarations.get(name);
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        if(localDeclarations == null)
            localDeclarations = new HashMap<String, Declaration>();
        localDeclarations.put(declaration.getPrefixedName(), declaration);
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    @Override
    public boolean isCeylon() {
        // there are no toplevels in Java
        return true;
    }
} public class LazyInterface extends Interface implements LazyContainer {

    public final ClassMirror classMirror;
    private ModelCompleter completer;
    private String realName;
    private boolean isStatic;
    private boolean isCeylon;
    private Map<String,Declaration> localDeclarations;
    
    private boolean isLoaded = false;
    private boolean isLoaded2 = false;
    private boolean isTypeParamsLoaded = false;
    private boolean isTypeParamsLoaded2 = false;
    private boolean isAnnotationType = false;
    private boolean local;
    public ClassMirror companionClass;

    @Override
    protected Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyInterface(ClassMirror classMirror, ModelCompleter completer) {
        this.classMirror = classMirror;
        this.completer = completer;
        this.realName = classMirror.getName();
        setName(JvmBackendUtil.getMirrorName(classMirror));
        this.isStatic = classMirror.isStatic();
        this.isAnnotationType  = classMirror.isAnnotationType();
        this.isCeylon = classMirror.getAnnotation(AbstractModelLoader.CEYLON_CEYLON_ANNOTATION) != null;
    }

    @Override
    public boolean isErasedTypeArguments() {
        return !isCeylon();
    }
    
    @Override
    public boolean isCeylon() {
        return isCeylon;
    }

    public boolean isStatic() {
        return isStatic;
    }
    
    public boolean isAnnotationType() {
        return isAnnotationType;
    }

    public String getRealName() {
        return this.realName;
    }
    
    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                loadTypeParams();
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }

    private void loadTypeParams() {
        if(!isTypeParamsLoaded2){
            synchronized(completer.getLock()){
                if(!isTypeParamsLoaded){
                    isTypeParamsLoaded = true;
                    completer.completeTypeParameters(this);
                    isTypeParamsLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    public Type getType() {
        loadTypeParams();
        return super.getType();
    }

    @Override
    public Type getExtendedType() {
        load();
        return super.getExtendedType();
    }

    @Override
    public List<TypeParameter> getTypeParameters() {
        loadTypeParams();
        return super.getTypeParameters();
    }

    @Override
    public boolean isMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isMember();
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<Type> getSatisfiedTypes() {
        load();
        return super.getSatisfiedTypes();
    }

    @Override
    public List<Type> getCaseTypes() {
        load();
        return super.getCaseTypes();
    }

    @Override
    public Reference appliedReference(Type pt,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public Type appliedType(Type outerType,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedType(outerType, typeArguments);
    }

    @SuppressWarnings("deprecation")
    @Override
    public List<Declaration> getInheritedMembers(String name) {
        load();
        return super.getInheritedMembers(name);
    }

    @Override
    public Declaration getRefinedMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getRefinedMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public boolean isAlias() {
        // does not require lazy loading since it depends on class
        return super.isAlias();
    }

    @Override
    public Type getSelfType() {
        load();
        return super.getSelfType();
    }

    @Override
    public Scope getVisibleScope() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.getVisibleScope();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public String getQualifiedNameString() {
        // never need to complete since the container is set before completion
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public boolean isVisible(Scope scope) {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public Unit getUnit() {
        // this doesn't require to load the model
        return super.getUnit();
    }

    @Override
    public Scope getContainer() {
        // NO lazy-loading since this is set before lazy-loading
        return super.getContainer();
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public void addMember(Declaration decl) {
        // do this without lazy-loading
        super.addMember(decl);
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public void setLocal(boolean local) {
        this.local = local;
    }
    
    @Override
    public boolean isLocal(){
        return this.local ;
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        load();
        if(localDeclarations == null)
            return null;
        return localDeclarations.get(name);
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        if(localDeclarations == null)
            localDeclarations = new HashMap<String, Declaration>();
        localDeclarations.put(declaration.getPrefixedName(), declaration);
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }
} public class LazyInterfaceAlias extends InterfaceAlias 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 Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyInterfaceAlias(ClassMirror classMirror, ModelCompleter completer) {
        this.classMirror = classMirror;
        this.completer = completer;
        setName(JvmBackendUtil.getMirrorName(classMirror));
    }

    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                loadTypeParams();
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }

    private void loadTypeParams() {
        if(!isTypeParamsLoaded2){
            synchronized(completer.getLock()){
                if(!isTypeParamsLoaded){
                    isTypeParamsLoaded = true;
                    completer.completeTypeParameters(this);
                    isTypeParamsLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    public Type getType() {
        loadTypeParams();
        return super.getType();
    }

    @Override
    public Type getExtendedType() {
        load();
        return super.getExtendedType();
    }

    @Override
    public List<TypeParameter> getTypeParameters() {
        loadTypeParams();
        return super.getTypeParameters();
    }

    @Override
    public boolean isMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isMember();
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<Type> getSatisfiedTypes() {
        load();
        return super.getSatisfiedTypes();
    }

    @Override
    public List<Type> getCaseTypes() {
        load();
        return super.getCaseTypes();
    }

    @Override
    public Reference appliedReference(Type pt,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public Type appliedType(Type outerType,
            List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedType(outerType, typeArguments);
    }

    @SuppressWarnings("deprecation")
    @Override
    public List<Declaration> getInheritedMembers(String name) {
        load();
        return super.getInheritedMembers(name);
    }

    @Override
    public Declaration getRefinedMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getRefinedMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public boolean isAlias() {
        // does not require lazy loading since it depends on class
        return super.isAlias();
    }

    @Override
    public Type getSelfType() {
        load();
        return super.getSelfType();
    }

    @Override
    public Scope getVisibleScope() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.getVisibleScope();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public String getQualifiedNameString() {
        load();
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public boolean isVisible(Scope scope) {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public Unit getUnit() {
        // this doesn't require to load the model
        return super.getUnit();
    }

    @Override
    public Scope getContainer() {
        // NO lazy-loading since this is set before lazy-loading
        return super.getContainer();
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public void addMember(Declaration decl) {
        // do this without lazy-loading
        super.addMember(decl);
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public void setLocal(boolean local) {
        this.local = local;
    }
    
    @Override
    public boolean isLocal(){
        return this.local ;
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        return null;
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        throw new RuntimeException("type aliases do not contain any local declarations");
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    @Override
    public boolean isCeylon() {
        // there are no aliases in Java
        return true;
    }
} public abstract class LazyModule extends Module {

    private boolean isJava = false;
    protected Set<String> jarPackages = new HashSet<String>();

    public LazyModule() {
    }

    @Override
    public Package getDirectPackage(String name) {
        return findPackageInModule(this, name);
    }
    
    @Override
    public Package getPackage(String name) {
        // try here first
        Package pkg = null;
        
        // unless we're the default module, in which case we have to check this at the end,
        // since every package can be part of the default module
        boolean defaultModule = isDefault();
        if(!defaultModule){
            pkg = findPackageInModule(this, name);
            if(pkg != null)
                return pkg;
        }
        // then try in dependencies
        Set<Module> visited = new HashSet<Module>();
        for(ModuleImport dependency : getImports()){
            // we don't have to worry about the default module here since we can't depend on it
            pkg = findPackageInImport(name, dependency, visited);
            if(pkg != null)
                return pkg;
        }
        // The JDK uses arrays, which we pretend are in java.lang, and ByteArray needs ceylon.language.Byte,
        // so we pretend the JDK imports the language module
        if(JDKUtils.isJDKModule(getNameAsString()) || JDKUtils.isOracleJDKModule(getNameAsString())){
            Module languageModule = getModelLoader().getLanguageModule();
            if(languageModule instanceof LazyModule){
                pkg = findPackageInModule((LazyModule) languageModule, name);
                if(pkg != null)
                    return pkg;
            }
        }
        // never try to load java packages from the default module because it would
        // work and appear to come from there
        if(JDKUtils.isJDKAnyPackage(name) || JDKUtils.isOracleJDKAnyPackage(name)){
            return null;
        }
        // do the lookup of the default module last
        if(defaultModule)
            pkg = getModelLoader().findExistingPackage(this, name);
        return pkg;
    }

    private Package findPackageInImport(String name, ModuleImport dependency, Set<Module> visited) {
        Module module = dependency.getModule();
        // only visit modules once
        if(!visited.add(module))
            return null;
        if (module instanceof LazyModule) {
            // this is the equivalent of getDirectPackage, it does not recurse
            Package pkg =  findPackageInModule((LazyModule) dependency.getModule(), name);
            if(pkg != null)
                return pkg;
            // not found, try in its exported dependencies
            for(ModuleImport dep : module.getImports()){
                if(!dep.isExport())
                    continue;
                pkg = findPackageInImport(name, dep, visited);
                if(pkg != null)
                    return pkg;
            }
            // not found
            return null;
        }
        else
            return module.getPackage(name);
    }

    private Package findPackageInModule(LazyModule module, String name) {
        if(module.containsPackage(name)){
            // first try the already loaded packages from that module
            AbstractModelLoader modelLoader = getModelLoader();
            synchronized(modelLoader.getLock()){
                for(Package pkg : module.getPackages()){
                    if(pkg.getNameAsString().equals(name))
                        return pkg;
                }
                // create/load a new one
                return getModelLoader().findExistingPackage(module, name);
            }
        }
        return null;
    }

    public Package findPackageNoLazyLoading(String name) {
        // make sure we don't call any overloaded getPackages() that might trigger lazy loading
        for(Package pkg : super.getPackages()){
            if(pkg.getNameAsString().equals(name))
                return pkg;
        }
        return null;
    }

    protected abstract AbstractModelLoader getModelLoader();

    public boolean isJava() {
        return isJava;
    }

    public void setJava(boolean isJava) {
        this.isJava = isJava;
    }

    public void loadPackageList(ArtifactResult artifact) {
        if (artifact instanceof ContentAwareArtifactResult) {
            for (String entry : ((ContentAwareArtifactResult) artifact).getEntries()) {
                addPackageForPath(entry, artifact.filter());
            }
        } else {
            File file = artifact.artifact();
            if (file != null) {
                ZipFile zipFile;
                try {
                    zipFile = new ZipFile(artifact.artifact());
                } catch (IOException e) {
                    throw new RuntimeException("Error accessing artifact: " + artifact.artifact(), e);
                }
                try{
                    Enumeration<? extends ZipEntry> entries = zipFile.entries();
                    while(entries.hasMoreElements())
                        loadPackageList(entries.nextElement(), artifact.filter());
                }finally{
                    try {
                        zipFile.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        }
    }
    
    private void loadPackageList(ZipEntry entry, PathFilter pathFilter) {
        if(!entry.isDirectory()){
            String path = entry.getName();
            addPackageForPath(path, pathFilter);
        }
    }

    private void addPackageForPath(String path, PathFilter pathFilter) {
        if(path.toLowerCase().endsWith(".class")){
            int sep = path.lastIndexOf('/');
            if(sep != -1)
                path = path.substring(0, sep);
            else
                path = "";// default package
            String pkg = path;
            // make sure we unquote any package part
            pkg = pkg.replace("$", "");
            String pathQuery;
            if(path.isEmpty())
                pathQuery = pkg;
            else
                pathQuery = pkg+"/";
            if(pathFilter == null || pathFilter.accept(pathQuery)){
                pkg = pkg.replace('/', '.');
                jarPackages.add(pkg);
            }
        }
    }

    public boolean containsPackage(String pkgName){
        String moduleName = getNameAsString();
        if(!isJava){
            List<Package> superPackages = super.getPackages();
            for(int i=0,l=superPackages.size();i<l;i++){
                if(superPackages.get(i).getNameAsString().equals(pkgName))
                    return true;
            }
            // The language module is in the classpath and does not have its jarPackages loaded
            if(moduleName.equals(Module.LANGUAGE_MODULE_NAME)){
                return JvmBackendUtil.isSubPackage(moduleName, pkgName)
                        || pkgName.startsWith("com.redhat.ceylon.compiler.java.runtime")
                        || pkgName.startsWith("com.redhat.ceylon.compiler.java.language")
                        || pkgName.startsWith("com.redhat.ceylon.compiler.java.metadata");
            }
            return jarPackages.contains(pkgName);
        }else{
            // special rules for the JDK which we don't load from the repo
            if(JDKUtils.isJDKPackage(moduleName, pkgName)
                    || JDKUtils.isOracleJDKPackage(moduleName, pkgName))
                return true;
            // otherwise we have the list of packages contained in that module jar
            return jarPackages.contains(pkgName);
        }
    }
    
    @Override
    protected boolean isJdkModule(String moduleName) {
        return JDKUtils.isJDKModule(moduleName) || JDKUtils.isOracleJDKModule(moduleName);
    }
    
    @Override
    protected boolean isJdkPackage(String moduleName, String packageName) {
        return JDKUtils.isJDKPackage(moduleName, packageName)
                || JDKUtils.isOracleJDKPackage(moduleName, packageName);
    }

    public void addPackage(Package pkg){
        // make sure we don't call any overloaded getPackages() that might trigger lazy loading
        super.getPackages().add(pkg);
    }
} public class LazyPackage extends Package {
    
    private AbstractModelLoader modelLoader;
    private List<Declaration> compiledDeclarations = new ArrayList<Declaration>(3);
    private Set<Unit> lazyUnits = new HashSet<Unit>();
    private Map<String,Declaration> cache = new HashMap<String,Declaration>();
    
    public LazyPackage(AbstractModelLoader modelLoader){
        this.modelLoader = modelLoader;
    }
    
    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        // FIXME: what use is this method in the type checker?
        return getDirectMember(name, signature, ellipsis);
    }
    
    // FIXME: redo this method better: https://github.com/ceylon/ceylon-spec/issues/90
    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
//        System.err.println("getMember "+name+" "+signature+" "+ellipsis);
        boolean canCache = (signature == null && !ellipsis);
        if(canCache){
            if(cache.containsKey(name)) {
                Declaration cachedDeclaration = cache.get(name);
                if (! modelLoader.searchAgain(cachedDeclaration, this, name)) {
                    return cachedDeclaration;
                }

            }
        }
        Declaration ret = getDirectMemberMemoised(name, signature, ellipsis, Backends.ANY);
        if(canCache){
            cache.put(name, ret);
        }
        return ret;
    }
    
    @Override
    public Declaration getDirectMemberForBackend(String name, Backends backends) {
        // FIXME: do we want to cache those calls too? If yes we need to add the backend type in the cache key
        // and invalidate properly in flushCache()
        return getDirectMemberMemoised(name, null, false, backends);
    }
    
    private Declaration getDirectMemberMemoised(String name, List<Type> signature, boolean ellipsis, Backends backends) {
        synchronized(modelLoader.getLock()){

            String pkgName = getQualifiedNameString();
            Module module = getModule();
            
            // we need its package ready first
            modelLoader.loadPackage(module, pkgName, false);

            // make sure we iterate over a copy of compiledDeclarations, to avoid lazy loading to modify it and
            // cause a ConcurrentModificationException: https://github.com/ceylon/ceylon-compiler/issues/399
            Declaration d = !backends.none()
                    ? lookupMemberForBackend(compiledDeclarations, name, backends)
                    : lookupMember(compiledDeclarations, name, signature, ellipsis);
            if (d != null) {
                return d;
            }

            String className = getQualifiedName(pkgName, name);
            ClassMirror classSymbol = modelLoader.lookupClassMirror(module, className);

            // only get it from the classpath if we're not compiling it, unless
            // it happens to be a java source
            if(classSymbol != null && (!classSymbol.isLoadedFromSource() || classSymbol.isJavaSource())) {
                d = modelLoader.convertToDeclaration(module, className, DeclarationType.VALUE);
                if (d instanceof Class) {
                    Class c = (Class) d;
                    if (c.isAbstraction() && signature != null) {
                        ArrayList<Declaration> list = new ArrayList<Declaration>(c.getOverloads());
                        list.add(c);
                        return !backends.none()
                                ? lookupMemberForBackend(list, name, backends)
                                : lookupMember(list, name, signature, ellipsis);
                    }
                }
                // if we're not looking for a backend, or we found the right backend, fine
                // if not, keep looking
                if(isForBackend(d, backends))
                    return d;
            }
            d = getDirectMemberFromSource(name, backends);
            
            if (d == null
                    && Character.isLowerCase(name.codePointAt(0))
                    && Character.isUpperCase(Character.toUpperCase(name.codePointAt(0)))) {
                // Might be trying to get an annotation constructor for a Java annotation type
                // So try to find the annotation type with two strategies:
                // - urlDecoder -> UrlDecover and url -> Url
                // - urlDecoder -> URLDecoder and url -> URL
                for(String annotationName : Arrays.asList(NamingBase.capitalize(name), NamingBase.getReverseJavaBeanName(name), NamingBase.capitalize(name).replaceFirst("__(CONSTRUCTOR|TYPE|PACKAGE|FIELD|METHOD|ANNOTATION_TYPE|LOCAL_VARIABLE|PARAMETER|SETTER|GETTER)$", ""))){
                    Declaration possibleAnnotationType = getDirectMember(annotationName, signature, ellipsis);
                    if (possibleAnnotationType != null
                            && possibleAnnotationType instanceof LazyInterface
                            && ((LazyInterface)possibleAnnotationType).isAnnotationType()) {
                        // addMember() will have added a Function if we found an annotation type
                        // so now we can look for the constructor again
                        d = !backends.none()
                                ? lookupMemberForBackend(compiledDeclarations, name, backends)
                                : lookupMember(compiledDeclarations, name, signature, ellipsis);
                    }
                }
            }
            return d;
        }
    }

    private boolean isForBackend(Declaration d, Backends backends) {
        return backends.none() 
                || d.getNativeBackends().none() 
                || backends.supports(d.getNativeBackends());
    }

    private Declaration getDirectMemberFromSource(String name, Backends backends) {
        for (Declaration d: super.getMembers()) {
            if (com.redhat.ceylon.model.typechecker.model.ModelUtil.isResolvable(d) /* && d.isShared() */ 
            && com.redhat.ceylon.model.typechecker.model.ModelUtil.isNamed(name, d)
            && isForBackend(d, backends)) {
                return d;
            }
        }
        return null;
    }

    public String getQualifiedName(final String pkgName, String name) {
        // no need to quote the name itself as java keywords are lower-cased and we append a _ to every
        // lower-case toplevel so they can never be java keywords
        String className = pkgName.isEmpty() ? name : JVMModuleUtil.quoteJavaKeywords(pkgName) + "." + name;
        return className;
    }
    
    // FIXME: This is only here for wildcard imports, and we should be able to make it lazy like the rest
    // with a bit of work in the typechecker
    // FIXME: redo this method better: https://github.com/ceylon/ceylon-spec/issues/90
    @Override
    public List<Declaration> getMembers() {
        synchronized(modelLoader.getLock()){
            // make sure the package is loaded
            modelLoader.loadPackage(getModule(), getQualifiedNameString(), true);
            List<Declaration> sourceDeclarations = super.getMembers();
            LinkedList<Declaration> ret = new LinkedList<Declaration>();
            ret.addAll(sourceDeclarations);
            ret.addAll(compiledDeclarations);
            return ret;
        }
    }

    @Override
    public void addMember(Declaration declaration) {
        super.addMember(declaration);
        flushCache(declaration);
    }
    
    private void flushCache(Declaration declaration) {
        cache.remove(declaration.getName());
    }

    public void addCompiledMember(Declaration d) {
        synchronized(modelLoader.getLock()){
            flushCache(d);
            compiledDeclarations.add(d);
            if (d instanceof LazyInterface
                    && !((LazyInterface)d).isCeylon()
                    && ((LazyInterface)d).isAnnotationType()) {
                makeInteropAnnotation((LazyInterface)d);
                
            }
            if ((d instanceof LazyClass ||
                    d instanceof LazyInterface)  && d.getUnit().getFilename() != null) {
                lazyUnits.add(d.getUnit());
            }
        }
    }

    /**
     * Adds extra members to the package for annotation interop.
     * For a Java declaration {@code @interface Annotation} we generate 
     * a model corresponding to:
     * <pre>
     *   annotation class Annotation$Proxy(...) satisfies Annotation {
     *       // a `shared` class parameter for each method of Annotation
     *   }
     *   annotation JavaAnnotation javaAnnotation(...) => JavaAnnotation$Proxy(...);
     * </pre>
     * 
     * We also make a {@code *__method}, {@code *__field} etc version for each
     * {@code @Target} program element
     * @param iface The model of the annotation @interface
     */
    private void makeInteropAnnotation(LazyInterface iface) {
        AnnotationProxyClass klass = modelLoader.makeInteropAnnotationClass(iface, this);
        
        compiledDeclarations.add(modelLoader.makeInteropAnnotationConstructor(iface, klass, null, this));
        
        for (OutputElement target : AnnotationTarget.outputTargets(klass)) {
            compiledDeclarations.add(modelLoader.makeInteropAnnotationConstructor(iface, klass, target, this));
        }
        
        compiledDeclarations.add(klass);
    }

    @Override
    public Iterable<Unit> getUnits() {
        synchronized(modelLoader.getLock()){
            Iterable<Unit> sourceUnits = super.getUnits();
            LinkedList<Unit> ret = new LinkedList<Unit>();
            for (Unit unit : sourceUnits) {
                ret.add(unit);
            }
            ret.addAll(lazyUnits);
            return ret;
        }
    }

    @Override
    public void removeUnit(Unit unit) {
        synchronized(modelLoader.getLock()){
            for (Declaration d : unit.getDeclarations()) {
                flushCache(d);
                if (d instanceof TypeDeclaration) {
                    ((TypeDeclaration)d).clearProducedTypeCache();
                }
            }
            if (unit.getFilename().endsWith(".class") || unit.getFilename().endsWith(".java")) {
                lazyUnits.remove(unit);
                for (Declaration d : unit.getDeclarations()) {
                    compiledDeclarations.remove(d);
                    // TODO : remove the declaration from the declaration map in AbstractModelLoader
                }
                modelLoader.removeDeclarations(unit.getDeclarations());
            } else {            
                super.removeUnit(unit);
            }
        }
    }
} public class LazyTypeAlias extends TypeAlias 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 Class<?> getModelClass() {
        return getClass().getSuperclass(); 
    }
    
    public LazyTypeAlias(ClassMirror classMirror, ModelCompleter completer) {
        this.classMirror = classMirror;
        this.completer = completer;
        setName(JvmBackendUtil.getMirrorName(classMirror));
    }

    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                loadTypeParams();
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }

    private void loadTypeParams() {
        if(!isTypeParamsLoaded2){
            synchronized(completer.getLock()){
                if(!isTypeParamsLoaded){
                    isTypeParamsLoaded = true;
                    completer.completeTypeParameters(this);
                    isTypeParamsLoaded2 = true;
                }
            }
        }
    }


    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public void addMember(Declaration decl) {
        // do this without lazy-loading
        super.getMembers().add(decl);
    }

    @Override
    public Type appliedType(Type outerType, List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedType(outerType, typeArguments);
    }

    @Override
    public DeclarationKind getDeclarationKind() {
        load();
        return super.getDeclarationKind();
    }

    @Override
    public boolean isAlias() {
        // does not require lazy loading since it depends on class
        return super.isAlias();
    }

    @Override
    protected TypeDeclaration clone() {
        load();
        return super.clone();
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public List<TypeParameter> getTypeParameters() {
        loadTypeParams();
        return super.getTypeParameters();
    }

    @Override
    public Type getExtendedType() {
        load();
        return super.getExtendedType();
    }

    @Override
    public List<Type> getSatisfiedTypes() {
        load();
        return super.getSatisfiedTypes();
    }

    @Override
    public List<Type> getCaseTypes() {
        load();
        return super.getCaseTypes();
    }

    @Override
    public Reference appliedReference(Type pt, List<Type> typeArguments) {
        loadTypeParams();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public Type getType() {
        loadTypeParams();
        return super.getType();
    }

    @SuppressWarnings("deprecation")
    @Override
    public List<Declaration> getInheritedMembers(String name) {
        load();
        return super.getInheritedMembers(name);
    }

    @Override
    public boolean isMember(Declaration dec) {
        load();
        return super.isMember(dec);
    }

    @Override
    public boolean inherits(TypeDeclaration dec) {
        load();
        return super.inherits(dec);
    }

    @Override
    public Declaration getRefinedMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getRefinedMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, Unit unit, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, unit, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public boolean isInherited(Declaration d) {
        load();
        return super.isInherited(d);
    }

    @Override
    public TypeDeclaration getInheritingDeclaration(Declaration d) {
        load();
        return super.getInheritingDeclaration(d);
    }

    @Override
    public boolean isInheritedFromSupertype(Declaration member) {
        load();
        return super.isInheritedFromSupertype(member);
    }

    @Override
    public Type getSelfType() {
        load();
        return super.getSelfType();
    }

    @Override
    public Map<String, DeclarationWithProximity> getImportableDeclarations(Unit unit, String startingWith, List<Import> imports, int proximity) {
        load();
        return super.getImportableDeclarations(unit, startingWith, imports, proximity);
    }

    @Override
    public Map<String, DeclarationWithProximity> getMatchingDeclarations(Unit unit, String startingWith, int proximity) {
        load();
        return super.getMatchingDeclarations(unit, startingWith, proximity);
    }

    @Override
    public Map<String, DeclarationWithProximity> getMatchingMemberDeclarations(Unit unit, Scope scope, String startingWith, int proximity) {
        load();
        return super.getMatchingMemberDeclarations(unit, scope, startingWith, proximity);
    }

    @Override
    public Scope getVisibleScope() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.getVisibleScope();
    }

    @Override
    public String getName() {
        // no need to load model
        return super.getName();
    }

    @Override
    public boolean isShared() {
        // no need to load model
        return super.isShared();
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public String getQualifiedNameString() {
        load();
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public Declaration getRefinedDeclaration() {
        load();
        return super.getRefinedDeclaration();
    }

    @Override
    public boolean isVisible(Scope scope) {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public boolean isMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isMember();
    }

    @Override
    public boolean isStaticallyImportable() {
        load();
        return super.isStaticallyImportable();
    }

    @Override
    public boolean isProtectedVisibility() {
        load();
        return super.isProtectedVisibility();
    }

    @Override
    public boolean isPackageVisibility() {
        load();
        return super.isPackageVisibility();
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean refines(Declaration other) {
        load();
        return super.refines(other);
    }

    @Override
    public boolean isAnonymous() {
        load();
        return super.isAnonymous();
    }

    @Override
    public String getNameAsString() {
        load();
        return super.getNameAsString();
    }

    @Override
    public Unit getUnit() {
        // this doesn't require to load the model
        return super.getUnit();
    }

    @Override
    public Scope getContainer() {
        // this doesn't require to load the model
        return super.getContainer();
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public void setLocal(boolean local) {
        this.local = local;
    }
    
    @Override
    public boolean isLocal(){
        return this.local ;
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        return null;
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        throw new RuntimeException("type aliases do not contain any local declarations");
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    @Override
    public boolean isCeylon() {
        // there are no aliases in Java
        return true;
    }
} public class LazyValue extends Value implements LazyElement, LocalDeclarationContainer {
    
    public ClassMirror classMirror;
    private ModelCompleter completer;
    private String realName;

    private Map<String,Declaration> localDeclarations;

    private boolean isLoaded = false;
    private boolean isLoaded2 = false;

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

    public String getRealName() {
        return this.realName;
    }

    private void load() {
        if(!isLoaded2){
            synchronized(completer.getLock()){
                if(!isLoaded){
                    isLoaded = true;
                    completer.complete(this);
                    isLoaded2 = true;
                }
            }
        }
    }
    
    @Override
    public String toString() {
        if (!isLoaded) {
            return "UNLOADED:" + super.toString();
        }
        return super.toString();
    }

    @Override
    public boolean isVariable() {
        load();
        return super.isVariable();
    }
    
    @Override
    public Type getType() {
        load();
        return super.getType();
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    @Override
    public List<Annotation> getAnnotations() {
        load();
        return super.getAnnotations();
    }

    @Override
    public void setVariable(boolean variable) {
        load();
        super.setVariable(variable);
    }

    @Override
    public boolean isCaptured() {
        load();
        return super.isCaptured();
    }

    @Override
    public void setCaptured(boolean local) {
        load();
        super.setCaptured(local);
    }

    @Override
    public DeclarationKind getDeclarationKind() {
        load();
        return super.getDeclarationKind();
    }

    @Override
    public TypeDeclaration getTypeDeclaration() {
        load();
        return super.getTypeDeclaration();
    }

    @Override
    public void setType(Type type) {
        load();
        super.setType(type);
    }

    @Override
    public TypedReference appliedTypedReference(Type qualifyingType, 
            List<Type> typeArguments, boolean assignment) {
        load();
        return super.appliedTypedReference(qualifyingType, typeArguments, assignment);
    }

    @Override
    public Reference appliedReference(Type pt, List<Type> typeArguments) {
        load();
        return super.appliedReference(pt, typeArguments);
    }

    @Override
    public boolean isMember() {
        load();
        return super.isMember();
    }

    @Override
    public Map<String, DeclarationWithProximity> getMatchingDeclarations(Unit unit, String startingWith, int proximity) {
        load();
        return super.getMatchingDeclarations(unit, startingWith, proximity);
    }

    @Override
    public TypedDeclaration getOriginalDeclaration() {
        load();
        return super.getOriginalDeclaration();
    }

    @Override
    public void setOriginalDeclaration(TypedDeclaration originalDeclaration) {
        load();
        super.setOriginalDeclaration(originalDeclaration);
    }

    @Override
    public Boolean getUnboxed() {
        load();
        return super.getUnboxed();
    }

    @Override
    public Scope getVisibleScope() {
        load();
        return super.getVisibleScope();
    }

    @Override
    public void setVisibleScope(Scope visibleScope) {
        load();
        super.setVisibleScope(visibleScope);
    }

    @Override
    public boolean isParameterized() {
        load();
        return super.isParameterized();
    }

    @Override
    public String getQualifiedNameString() {
//        load();
        return super.getQualifiedNameString();
    }

    @Override
    public boolean isActual() {
        load();
        return super.isActual();
    }

    @Override
    public void setActual(boolean actual) {
        load();
        super.setActual(actual);
    }

    @Override
    public boolean isFormal() {
        load();
        return super.isFormal();
    }

    @Override
    public void setFormal(boolean formal) {
        load();
        super.setFormal(formal);
    }

    @Override
    public boolean isDefault() {
        load();
        return super.isDefault();
    }

    @Override
    public void setDefault(boolean def) {
        load();
        super.setDefault(def);
    }

    @Override
    public Backends getNativeBackends() {
        return super.getNativeBackends();
    }
    
    @Override
    public void setNativeBackends(Backends backends) {
        super.setNativeBackends(backends);
    }

    @Override
    public Declaration getRefinedDeclaration() {
        load();
        return super.getRefinedDeclaration();
    }

    @Override
    public void setRefinedDeclaration(Declaration refinedDeclaration) {
        load();
        super.setRefinedDeclaration(refinedDeclaration);
    }

    @Override
    public boolean isVisible(Scope scope) {
        load();
        return super.isVisible(scope);
    }

    @Override
    public boolean isDefinedInScope(Scope scope) {
        load();
        return super.isDefinedInScope(scope);
    }

    @Override
    public boolean isToplevel() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isToplevel();
    }

    @Override
    public boolean isClassMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassMember();
    }

    @Override
    public boolean isInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isInterfaceMember();
    }

    @Override
    public boolean isClassOrInterfaceMember() {
        // NO lazy-loading since this uses getContainer() which is set before lazy-loading
        return super.isClassOrInterfaceMember();
    }

    @Override
    public boolean equals(Object object) {
        // NO lazy-loading since this uses only things set before lazy-loading
        return super.equals(object);
    }

    @Override
    public int hashCode() {
        // NO lazy-loading since this uses only things set before lazy-loading
        return super.hashCode();
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean refines(Declaration other) {
        load();
        return super.refines(other);
    }

    @Override
    public List<Declaration> getMembers() {
        load();
        return super.getMembers();
    }

    @Override
    protected Declaration getMemberOrParameter(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(name, signature, ellipsis);
    }

    @Override
    public Declaration getMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getDirectMember(String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        load();
        return super.getDeclaringType(d);
    }

    @Override
    public Declaration getMemberOrParameter(Unit unit, String name, List<Type> signature, boolean ellipsis) {
        load();
        return super.getMemberOrParameter(unit, name, signature, ellipsis);
    }

    @Override
    public boolean isInherited(Declaration d) {
        load();
        return super.isInherited(d);
    }

    @Override
    public TypeDeclaration getInheritingDeclaration(Declaration d) {
        load();
        return super.getInheritingDeclaration(d);
    }
    
    @Override
    public void addMember(Declaration declaration) {
        // do this without lazy-loading
        super.addMember(declaration);
    }

    @Override
    public boolean isLocal() {
        // FIXME: this may be wrong now, but is it used?
        return false;
    }

    @Override
    public void setLocal(boolean local) {
    }

    @Override
    public Declaration getLocalDeclaration(String name) {
        load();
        if(localDeclarations == null)
            return null;
        return localDeclarations.get(name);
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        if(localDeclarations == null)
            localDeclarations = new HashMap<String, Declaration>();
        localDeclarations.put(declaration.getPrefixedName(), declaration);
    }

    @Override
    public boolean isDeprecated() {
        // requires no lazy-loading
        return super.isDeprecated();
    }

    @Override
    public boolean isCeylon() {
        // there are no toplevels in Java
        return true;
    }
} public enum OutputElement {
    TYPE,
    FIELD,
    METHOD,
    GETTER,
    SETTER,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE;
} public class SetterWithLocalDeclarations extends Setter implements LocalDeclarationContainer {

    private Map<String,Declaration> localDeclarations;
    public final ClassMirror classMirror;
    
    public SetterWithLocalDeclarations(ClassMirror classMirror) {
        this.classMirror = classMirror;
    }
    
    @Override
    public Declaration getLocalDeclaration(String name) {
        if(localDeclarations == null)
            return null;
        return localDeclarations.get(name);
    }

    @Override
    public void addLocalDeclaration(Declaration declaration) {
        if(localDeclarations == null)
            localDeclarations = new HashMap<String, Declaration>();
        localDeclarations.put(declaration.getPrefixedName(), declaration);
    }

} public class Class extends ClassOrInterface implements Functional {
    
    private static final int CONSTRUCTORS = 1<<11;
    private static final int ENUMERATED = 1<<12;
    private static final int ABSTRACT = 1<<13;
    private static final int FINAL = 1<<14;
    private static final int SERIALIZABLE = 1<<15;
    private static final int ANONYMOUS = 1<<16;
    private static final int JAVA_ENUM = 1<<17;
    private static final int VALUE_CONSTRUCTOR = 1<<18;
    private static final int OVERLOADED = 1<<19;
    private static final int ABSTRACTION = 1<<20;
    private static final int NO_NAME = 1<<24;
    
    private ParameterList parameterList;
    private List<Declaration> overloads;
    private List<Reference> unimplementedFormals = 
            Collections.<Reference>emptyList();

    public boolean hasConstructors() {
        return (flags&CONSTRUCTORS)!=0;
    }
    
    public void setConstructors(boolean constructors) {
        if (constructors) {
            flags|=CONSTRUCTORS;
        }
        else {
            flags&=(~CONSTRUCTORS);
        }
    }
    
    public boolean hasEnumerated() {
        return (flags&ENUMERATED)!=0;
    }

    public void setEnumerated(boolean enumerated) {
        if (enumerated) {
            flags|=ENUMERATED;
        }
        else {
            flags&=(~ENUMERATED);
        }
    }
    
    @Override
    public boolean isValueConstructor() {
        return (flags&VALUE_CONSTRUCTOR)!=0;
    }
    
    public void setValueConstructor(boolean valueConstructor) {
        if (valueConstructor) {
            flags|=VALUE_CONSTRUCTOR;
        }
        else {
            flags&=(~VALUE_CONSTRUCTOR);
        }
    }
    
    @Override
    public boolean isAnonymous() {
        return (flags&ANONYMOUS)!=0;
    }
    
    public void setAnonymous(boolean anonymous) {
        if (anonymous) {
            flags|=ANONYMOUS;
        }
        else {
            flags&=(~ANONYMOUS);
        }
    }
    
    @Override
    public boolean isObjectClass() {
        return (flags&ANONYMOUS)!=0;
    }
    
    /**
     * Return true if we have are anonymous and have a name 
     * which is not system-generated. Currently only object 
     * expressions have no name.
     */
    @Override
    public boolean isNamed() {
        return (flags&NO_NAME)==0;
    }
    
    public void setNamed(boolean named) {
        if (!named) {
            flags|=NO_NAME;
        }
        else {
            flags&=(~NO_NAME);
        }

    }
    
    @Override
    public boolean isAbstract() {
        return (flags&ABSTRACT)!=0;
    }

    public void setAbstract(boolean abstr) {
        if (abstr) {
            flags|=ABSTRACT;
        }
        else {
            flags&=(~ABSTRACT);
        }

    }
    
    public Constructor getDefaultConstructor() {
        if (hasConstructors()) {
            for (Declaration dec: getMembers()) {
                if (dec instanceof Constructor &&
                        dec.getName()==null) {
                    return (Constructor) dec;
                }
            }
            return null;
        }
        else {
            return null;
        }
    }
    
    public FunctionOrValue getDefaultConstructorFunctionOrValue() {
        if (hasConstructors()) {
            for (Declaration dec: getMembers()) {
                if (dec instanceof FunctionOrValue &&
                        dec.getName()==null) {
                    return (FunctionOrValue) dec;
                }
            }
            return null;
        }
        else {
            return null;
        }
    }
    
    @Override
    public boolean isSealed() {
        if (parameterList==null) {
            Constructor defaultConstructor = 
                    getDefaultConstructor();
            return defaultConstructor!=null &&
                    defaultConstructor.isSealed();
        }
        else {
            return super.isSealed();
        }
    }
    
    @Override
    public ParameterList getFirstParameterList() {
        return getParameterList();
    }
    
    public ParameterList getParameterList() {
        if (hasConstructors()) {
            Constructor defaultConstructor = 
                    getDefaultConstructor();
            if (defaultConstructor==null) {
                return null;
            }
            else {
                if (defaultConstructor.getParameterLists().isEmpty()) {
                    return null;
                }
                else {
                    return defaultConstructor.getFirstParameterList();
                }
            }
        }
        else {
            return parameterList;
        }
    }

    public void setParameterList(ParameterList parameterList) {
        this.parameterList = parameterList;
    }

    @Override
    public List<ParameterList> getParameterLists() {
        ParameterList parameterList = getParameterList();
        if (parameterList==null) {
            return emptyList();
        }
        else {
            return singletonList(parameterList);
        }
    }

    @Override
    public void addParameterList(ParameterList pl) {
        parameterList = pl;
    }

    public Parameter getParameter(String name) {
        for (Declaration d : getMembers()) {
            if (d.isParameter() && 
                    ModelUtil.isNamed(name, d)) {
                FunctionOrValue fov = (FunctionOrValue) d;
                return fov.getInitializerParameter();
            }
        }
        return null;
    }
    
    @Override
    public boolean isOverloaded() {
    	return (flags&OVERLOADED)!=0;
    }
    
    public void setOverloaded(boolean overloaded) {
        if (overloaded) {
            flags|=OVERLOADED;
        }
        else {
            flags&=(~OVERLOADED);
        }

	}
    
    @Override
    public Type getExtendedType() {
        Type et = super.getExtendedType();
        if (et==null) {
            //for Anything
            return null;
        }
        else if (et.isUnknown()) {
            //used by the model loader to indicate a
            //superclass that could not be loaded
            //leave it intact because the UnknownType 
            //carries error information 
            return et;
        }
        else if (et.isClass()) {
            return et;
        }
        else {
            return unit.getAnythingType();
        }
    }
    
    public void setAbstraction(boolean abstraction) {
        if (abstraction) {
            flags|=ABSTRACTION;
        }
        else {
            flags&=(~ABSTRACTION);
        }
    }
    
    @Override
    public boolean isAbstraction() {
        return (flags&ABSTRACTION)!=0;
    }
    
    @Override
    public boolean isFinal() {
		return (flags&FINAL)!=0||(flags&ANONYMOUS)!=0;
	}
    
    public void setFinal(boolean fin) {
		if (fin) {
		    flags|=FINAL;
		}
		else {
		    flags&=(~FINAL);
		}
	}

    @Override
    public List<Declaration> getOverloads() {
        return overloads;
    }

    public void setOverloads(List<Declaration> overloads) {
        this.overloads = overloads;
    }

    public void initOverloads(Class... initial) {
        overloads = 
                new ArrayList<Declaration>
                    (initial.length+1);
        for (Declaration d: initial) {
            overloads.add(d);
        }
    }
    
    @Override
    public boolean isDeclaredVoid() {
        return false;
    }

    @Override
    public boolean isFunctional() {
        return true;
    }
    
    @Override
    public boolean isAnything() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Anything");
    }
    
    @Override
    public boolean isObject() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Object");
    }
    
    @Override
    public boolean isNull() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Null");
    }

    @Override
    public boolean isNullValue() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::null");
    }

    @Override
    boolean isEmptyValue() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::empty");
    }

    @Override
    public boolean isBasic() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Basic");
    }

    @Override
    public boolean isBoolean() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Boolean");
    }

    @Override
    public boolean isString() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::String");
    }

    @Override
    public boolean isCharacter() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Character");
    }

    @Override
    public boolean isFloat() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Float");
    }

    @Override
    public boolean isInteger() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Integer");
    }

    @Override
    public boolean isByte() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Byte");
    }

    @Override
    public boolean isTuple() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Tuple");
    }
    
    @Override
    public boolean isEntry() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Entry");
    }
    
    @Override
    public boolean isRange() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Range");
    }
    
    @Override
    public boolean inherits(TypeDeclaration dec) {
        if (dec.isAnything()) {
            return true;
        }
        else if (dec.isObject()) {
            return !isAnything() && 
                !isNull() && !isNullValue();
        }
        else if (dec.isNull()) {
            return isNull() || isNullValue();
        }
        else if (dec instanceof Class && equals(dec)) {
            return true;
        }
        else if (dec.isFinal() &&
                //take into account constructors 
                //of final Java classes
                !dec.isAbstraction()) {
            //cannot possibly be true, 
            //since dec is nonequal
            return false;
        }
        else {
            //TODO: optimize this to avoid walking the
            //      same supertypes multiple times
            Type et = getExtendedType();
            if (et!=null && 
                    et.getDeclaration().inherits(dec)) {
                return true;
            }
            if (dec instanceof Interface) {
                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;
        }
    }

    public List<Reference> getUnimplementedFormals() {
        return unimplementedFormals;
    }
    
    public void setUnimplementedFormals(
            List<Reference> unimplementedFormals) {
        this.unimplementedFormals = unimplementedFormals;
    }

    public boolean isSerializable() {
        return (flags&SERIALIZABLE)!=0;
    }

    public void setSerializable(boolean serializable) {
        if (serializable) {
            flags|=SERIALIZABLE;
        }
        else {
            flags&=(~SERIALIZABLE);
        }
    }

    public boolean isJavaEnum() {
        return (flags&JAVA_ENUM)!=0;
    }

    public void setJavaEnum(boolean javaEnum) {
        if (javaEnum) {
            flags|=JAVA_ENUM;
        }
        else {
            flags&=(~JAVA_ENUM);
        }
    }
    
    @Override
    public boolean isEmptyType() {
        return isEmptyValue();
    }
    
    @Override
    public boolean isTupleType() {
        return isTuple();
    }
    
    private int sequentialType;
    
    private int sequenceType;
    
    @Override
    public boolean isSequentialType() {
        if (sequentialType==0) {
            sequentialType = 
                    isSequentialTypeInternal() ? 1 : -1;
        }
        return sequentialType>0;
    }

    @Override
    public boolean isSequenceType() {
        if (sequenceType==0) {
            sequenceType = 
                    isSequenceTypeInternal() ? 1 : -1;
        }
        return sequenceType>0;
    }

    private boolean isSequentialTypeInternal() {
        Package pack = getUnit().getPackage();
        if (!pack.getNameAsString()
                .equals(Module.LANGUAGE_MODULE_NAME)) {
            return false;
        }
        else if (isAnything() || isObject() || 
                isNull() || isBasic()) {
            return false;
        }
        else if (isEmptyValue() || isRange() || isTuple()) {
            return true;
        }
        else {
            //handles Measure/Span
            Type et = getExtendedType();
            if (et!=null && et.isRange()) {
                return true;
            }
            //handles misc direct impls of Sequence
            List<Type> sts = getSatisfiedTypes();
            for (Type st: sts) {
                if (st.isSequence()) {
                    return true;
                }
            }
            return false;
        }
    }
    
    private boolean isSequenceTypeInternal() {
        Package pack = getUnit().getPackage();
        if (!pack.getNameAsString()
                .equals(Module.LANGUAGE_MODULE_NAME)) {
            return false;
        }
        else if (isAnything() || isObject() || 
                isNull() || isBasic()) {
            return false;
        }
        else if (isRange() || isTuple()) {
            return true;
        }
        else {
            //handles Measure/Span
            Type et = getExtendedType();
            if (et!=null && et.isRange()) {
                return true;
            }
            //handles misc direct impls of Sequence
            List<Type> sts = getSatisfiedTypes();
            for (Type st: sts) {
                if (st.isSequence()) {
                    return true;
                }
            }
            return false;
        }
    }
    
    @Override
    public String toString() {
        StringBuilder params = new StringBuilder();
        ParameterList list = getParameterList();
        if (list!=null) {
            params.append("(");
            boolean first = true;
            for (Parameter p: list.getParameters()) {
                if (first) {
                    first = false;
                }
                else {
                    params.append(", ");
                }
                if (p.getType()!=null) {
                    params.append(p.getType().asString());
                    params.append(" ");
                }
                params.append(p.getName());
            }
            params.append(")");
        }
        return "class " + toStringName() + params;
    }

} public class Constructor extends TypeDeclaration implements Functional {

    private static final int ABSTRACT = 1<<13;
    private static final int OVERLOADED = 1<<19;
    private static final int ABSTRACTION = 1<<20;
    
    private ParameterList parameterList;
    private List<Declaration> overloads;
    private List<Declaration> members = new ArrayList<Declaration>(3);
    private List<Annotation> annotations = new ArrayList<Annotation>(4);
    
    public boolean isValueConstructor() {
        return parameterList==null;
    }

    @Override
    public boolean isAbstract() {
        return (flags&ABSTRACT)!=0;
    }
    
    public void setAbstract(boolean abstr) {
        if (abstr) {
            flags|=ABSTRACT;
        }
        else {
            flags&=(~ABSTRACT);
        }
    }
    
    public ParameterList getParameterList() {
        return parameterList;
    }
    
    @Override
    public ParameterList getFirstParameterList() {
        return getParameterList();
    }

    @Override
    public List<ParameterList> getParameterLists() {
        ParameterList parameterList = getParameterList();
        if (parameterList==null) {
            return emptyList();
        }
        else {
            return singletonList(parameterList);
        }
    }

    @Override
    public void addParameterList(ParameterList pl) {
        parameterList = pl;
    }
    
    @Override
    public boolean isOverloaded() {
    	return (flags&OVERLOADED)!=0;
    }
    
    public void setOverloaded(boolean overloaded) {
        if (overloaded) {
            flags|=OVERLOADED;
        }
        else {
            flags&=(~OVERLOADED);
        }
	}
    
    public void setAbstraction(boolean abstraction) {
        if (abstraction) {
            flags|=ABSTRACTION;
        }
        else {
            flags&=(~ABSTRACTION);
        }
    }
    
    @Override
    public boolean isAbstraction() {
        return (flags&ABSTRACTION)!=0;
    }
    
    @Override
    public List<Declaration> getOverloads() {
        return overloads;
    }
    
    public void setOverloads(List<Declaration> overloads) {
        this.overloads = overloads;
    }
    
    public Parameter getParameter(String name) {
        for (Declaration d : getMembers()) {
            if (d.isParameter() && ModelUtil.isNamed(name, d)) {
                return ((FunctionOrValue) d).getInitializerParameter();
            }
        }
        return null;
    }
    
    @Override
    public boolean isFunctional() {
        return true;
    }

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

    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }
    
    @Override
    public List<Declaration> getMembers() {
        return members;
    }
    
    @Override
    public void addMember(Declaration declaration) {
        members.add(declaration);
    }
    
    @Override
    public boolean isMember() {
        return getContainer() instanceof ClassOrInterface;
    }

    @Override
    public DeclarationKind getDeclarationKind() {
        return CONSTRUCTOR;
    }
    
    protected Declaration getMemberOrParameter(String name, 
            List<Type> signature, boolean ellipsis) {
        return getDirectMember(name, signature, ellipsis);
    }
    
    @Override
    public TypeDeclaration getInheritingDeclaration(
            Declaration member) {
        if (member.getContainer().equals(this)) {
            return null;
        }
        else if (getContainer()!=null) {
            return getContainer()
                    .getInheritingDeclaration(member);
        }
        else {
            return null;
        }
    }
    
    @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) { 
            return et.getDeclaration().inherits(dec);
        }
        else {
            return false;
        }
    }

    @Override
    protected int hashCodeForCache() {
        int ret = 17;
        Scope container = getContainer();
        if (container instanceof Declaration) {
            ret = (37 * ret) + ((Declaration) container).hashCodeForCache();
        }
        else {
            ret = (37 * ret) + container.hashCode();
        }
        String qualifier = getQualifier();
        ret = (37 * ret) + (qualifier == null ? 0 : qualifier.hashCode());
        ret = (37 * ret) + Objects.hashCode(getName());
        return ret;
    }

    @Override
    protected boolean equalsForCache(Object o) {
        if (o == null || o instanceof Constructor == false) {
            return false;
        }
        Constructor b = (Constructor) o;
        Scope container = getContainer();
        if (container instanceof Declaration) {
            if (!((Declaration) container).equalsForCache(b.getContainer())) {
                return false;
            }
        }
        else {
            if (!container.equals(b.getContainer())) {
                return false;
            }
        }
        if (!Objects.equals(getQualifier(), b.getQualifier())) {
            return false;
        }
        return Objects.equals(getName(), b.getName());
    }
    
    @Override
    public String toString() {
        StringBuilder params = new StringBuilder();
        for (ParameterList pl: getParameterLists()) {
            params.append("(");
            boolean first = true;
            for (Parameter p: pl.getParameters()) {
                if (first) {
                    first = false;
                }
                else {
                    params.append(", ");
                }
                if (p.getType()!=null) {
                    params.append(p.getType().asString());
                    params.append(" ");
                }
                params.append(p.getName());
            }
            params.append(")");
        }
        return "new " + toStringName() + params;
    }
    
    @Override
    public boolean isAnonymous() {
        return true;
    }
    
    @Override
    public boolean isFinal() {
        return true;
    }
    
} 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 abstract class Element {
    
    Element() {}
    
	private Scope container;
	private Scope scope;
	protected Unit unit;
    
	public List<Declaration> getMembers() {
        return emptyList();
    }
    
    public Unit getUnit() {
        return unit;
    }

    public void setUnit(Unit compilationUnit) {
        this.unit = compilationUnit;
    }

    /**
     * The "real" scope of the element, ignoring that
     * conditions (in an assert, if, or while) each have
     * their own "fake" scope.
     * 
     * @see ConditionScope
     */
    public Scope getContainer() {
        return container;
    }

    public void setContainer(Scope scope) {
        this.container = scope;
    }
    
    /**
     * The scope of the element, taking into account that
     * conditions (in an assert, if, or while) each have
     * their own "fake" scope.
     * 
     * @see ConditionScope
     */
    public Scope getScope() {
		return scope;
	}
    
    public void setScope(Scope scope) {
    	this.scope = scope;
    }
    
    public String getQualifiedNameString() {
        return getContainer().getQualifiedNameString();
    }
    
    /**
     * Search only directly inside this scope.
     */
    public Declaration getDirectMember(String name, 
            List<Type> signature, boolean ellipsis) {
        return getDirectMember(name, signature, ellipsis, 
                false);
    }

    /**
     * Search only directly inside this scope.
     */
    public Declaration getDirectMember(String name, 
            List<Type> signature, boolean ellipsis, 
            boolean onlyExactMatches) {
        return lookupMember(getMembers(), 
                name, signature, ellipsis, 
                onlyExactMatches);
    }

    /**
     * Search only directly inside this scope for a member
     * with the given name and any of the given backends
     */
    public Declaration getDirectMemberForBackend(String name, 
            Backends backends) {
        return lookupMemberForBackend(getMembers(), 
                name, backends);
    }

    /**
     * Search only this scope, including members inherited 
     * by the scope, without considering containing scopes 
     * or imports. We're not looking for un-shared direct 
     * members, but return them anyway, to let the caller 
     * produce a nicer error.
     */
    public Declaration getMember(String name, 
            List<Type> signature, boolean ellipsis, 
            boolean onlyExactMatches) {
        return getDirectMember(name, signature, ellipsis, 
                onlyExactMatches);
    }

    /**
     * Search only this scope, including members inherited 
     * by the scope, without considering containing scopes 
     * or imports. We're not looking for un-shared direct 
     * members, but return them anyway, to let the caller 
     * produce a nicer error.
     */
    public Declaration getMember(String name, 
            List<Type> signature, boolean ellipsis) {
        return getMember(name, signature, ellipsis, false);
    }
    
    /**
     * Search in this scope, taking into account containing 
     * scopes, imports, and members inherited by this scope
     * and containing scopes, returning even un-shared 
     * declarations of this scope and containing scopes.
     */
    public Declaration getMemberOrParameter(Unit unit, String name, 
            List<Type> signature, boolean ellipsis) {
        return getMemberOrParameter(unit, name, signature, 
                ellipsis, false);
    }
    
    /**
     * Search in this scope, taking into account containing 
     * scopes, imports, and members inherited by this scope
     * and containing scopes, returning even un-shared 
     * declarations of this scope and containing scopes.
     */
    public Declaration getMemberOrParameter(Unit unit, String name, 
            List<Type> signature, boolean ellipsis, 
            boolean onlyExactMatches) {
        Declaration d = 
                getMemberOrParameter(name, signature, ellipsis);
        if (d!=null) {
            return d;
        }
        else if (getScope()!=null) {
            return getScope()
                    .getMemberOrParameter(unit, name, 
                            signature, ellipsis);
        }
        else {
            //union type or bottom type 
            return null;
        }
    }
    
    /**
     * Search only this scope, including members inherited 
     * by this scope, without considering containing scopes 
     * or imports. We are even interested in un-shared 
     * direct members.
     */
    protected Declaration getMemberOrParameter(String name, 
            List<Type> signature, boolean ellipsis) {
        return getMemberOrParameter(name, signature, ellipsis, 
                false);
    }

    /**
     * Search only this scope, including members inherited 
     * by this scope, without considering containing scopes 
     * or imports. We are even interested in un-shared 
     * direct members.
     */
    protected Declaration getMemberOrParameter(String name, 
            List<Type> signature, boolean ellipsis, 
            boolean onlyExactMatches) {
        return getDirectMember(name, signature, ellipsis, 
                onlyExactMatches);
    }

    public boolean isInherited(Declaration d) {
        if (d.getContainer()==this) {
            return false;
        }
        else if (getContainer()!=null) {
            return getContainer().isInherited(d);
        }
        else {
            return false;
        }
    }
    
    public TypeDeclaration getInheritingDeclaration(Declaration d) {
        if (d.getContainer()==this) {
            return null;
        }
        else if (getContainer()!=null) {
            return getContainer().getInheritingDeclaration(d);
        }
        else {
            return null;
        }
    }
    
    public Type getDeclaringType(Declaration d) {
        if (d.isMember()) {
            return getContainer().getDeclaringType(d);
        }
        else {
            return null;
        }
    }
    
    public Map<String, DeclarationWithProximity> 
    getMatchingDeclarations(Unit unit, String startingWith, 
            int proximity) {
    	Map<String, DeclarationWithProximity> result = 
    	        getScope()
    			    .getMatchingDeclarations(unit, 
    			            startingWith, proximity+1);
        for (Declaration d: getMembers()) {
            if (isResolvable(d) && !isOverloadedVersion(d)){
                if(isNameMatching(startingWith, d)) {
                    result.put(d.getName(unit), 
                            new DeclarationWithProximity(d, 
                                    proximity));
                }
                for(String alias : d.getAliases()){
                    if(isNameMatching(startingWith, alias)){
                        result.put(alias, 
                                new DeclarationWithProximity(
                                        alias, d, proximity));
                    }
                }
            }
        }
    	return result;
    }

} public class Function extends FunctionOrValue implements Generic, Scope, Functional {

    private static final int VOID = 1<<22;
    private static final int DEFERRED = 1<<23;
    private static final int NO_NAME = 1<<24;
    
    private List<TypeParameter> typeParameters = emptyList();
    private List<ParameterList> parameterLists = new ArrayList<ParameterList>(1);
    private Object annotationConstructor;
    
    public Object getAnnotationConstructor() {
        return annotationConstructor;
    }

    public void setAnnotationConstructor(Object annotationInstantiation) {
        this.annotationConstructor = annotationInstantiation;
    }

    @Override
    public boolean isParameterized() {
        return !typeParameters.isEmpty();
    }

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

    public void setTypeParameters(List<TypeParameter> typeParameters) {
        this.typeParameters = typeParameters;
    }
    
    @Override
    public ParameterList getFirstParameterList() {
        return getParameterLists().get(0);
    }

    @Override
    public List<ParameterList> getParameterLists() {
        return parameterLists;
    }

    @Override
    public void addParameterList(ParameterList pl) {
        parameterLists.add(pl);
    }

    @Override
    public boolean isDeclaredVoid() {
        return (flags&VOID)!=0;
    }
    
    public void setDeclaredVoid(boolean declaredVoid) {
        if (declaredVoid) {
            flags|=VOID;
        }
        else {
            flags&=(~VOID);
        }
    }
    
    public boolean isDeferred() {
        return (flags&DEFERRED)!=0;
    }
    
    public void setDeferred(boolean deferred) {
        if (deferred) {
            flags|=DEFERRED;
        }
        else {
            flags&=(~DEFERRED);
        }
    }
    
    public Parameter getParameter(String name) {
        for (Declaration d : getMembers()) {
            if (d.isParameter() && ModelUtil.isNamed(name, d)) {
                FunctionOrValue mod = (FunctionOrValue) d;
                return mod.getInitializerParameter();
            }
        }
        return null;
    }
    
    @Override
    public boolean isFunctional() {
        return true;
    }

    //TODO: replace with setNamed()
    public void setAnonymous(boolean anonymous) {
        if (anonymous) {
            flags|=NO_NAME;
        }
        else {
            flags&=(~NO_NAME);
        }
    }
    
    @Override
    public boolean isAnonymous() {
        return (flags&NO_NAME)!=0;
    }
    
    /**
     * Returns true if this method is anonymous.
     */
    @Override
    public boolean isNamed() {
        return (flags&NO_NAME)==0;
    }

    @Override
    public Backends getScopedBackends() {
        return super.getScopedBackends();
    }
    
    @Override
    public String toString() {
        Type type = getType();
        if (type==null) {
            return "function " + toStringName();
        }
        else {
            StringBuilder params = new StringBuilder();
            for (ParameterList pl: getParameterLists()) {
                params.append("(");
                boolean first = true;
                for (Parameter p: pl.getParameters()) {
                    if (first) {
                        first = false;
                    }
                    else {
                        params.append(", ");
                    }
                    if (p.getType()!=null) {
                        params.append(p.getType().asString());
                        params.append(" ");
                    }
                    params.append(p.getName());
                }
                params.append(")");
            }
            return "function " + toStringName() + params + 
                    " => " + type.asString();
        }
    }
    
} public abstract class FunctionOrValue extends TypedDeclaration {
    
    private static final int CAPTURED = 1<<17;
    private static final int SHORTCUT_REFINEMENT = 1<<18;
    private static final int OVERLOADED = 1<<19;
    private static final int ABSTRACTION = 1<<20;
    private static final int IMPLEMENTED = 1<<21;
    
    private Parameter initializerParameter;
    private List<Declaration> members = new ArrayList<Declaration>(3);
    private List<Annotation> annotations = new ArrayList<Annotation>(4);
    private List<Declaration> overloads;
    
    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }
    
    @Override
    public List<Declaration> getMembers() {
        return members;
    }
    
    public void addMember(Declaration declaration) {
        members.add(declaration);
    }
    
    public boolean isShortcutRefinement() {
        return (flags&SHORTCUT_REFINEMENT)!=0;
    }
    
    public void setShortcutRefinement(boolean shortcutRefinement) {
        if (shortcutRefinement) {
            flags|=SHORTCUT_REFINEMENT;
        }
        else {
            flags&=(~SHORTCUT_REFINEMENT);
        }
    }
    
    @Override
    public DeclarationKind getDeclarationKind() {
        return DeclarationKind.MEMBER;
    }
    
    public Parameter getInitializerParameter() {
        return initializerParameter;
    }

    public void setInitializerParameter(Parameter d) {
        initializerParameter = d;
    }
    
    @Override
    public boolean isParameter() {
        return initializerParameter!=null;
    }

    public boolean isTransient() {
        return true;
    }

    @Override
    public boolean isCaptured() {
        return (flags&CAPTURED)!=0;
    }

    public void setCaptured(boolean captured) {
        if (captured) {
            flags|=CAPTURED;
        }
        else {
            flags&=(~CAPTURED);
        }
    }

    @Override
    public boolean isOverloaded() {
        return (flags&OVERLOADED)!=0;
    }
    
    public void setOverloaded(boolean overloaded) {
        if (overloaded) {
            flags|=OVERLOADED;
        }
        else {
            flags&=(~OVERLOADED);
        }
    }
    
    public void setAbstraction(boolean abstraction) {
        if (abstraction) {
            flags|=ABSTRACTION;
        }
        else {
            flags&=(~ABSTRACTION);
        }
    }
    
    @Override
    public boolean isAbstraction() {
        return (flags&ABSTRACTION)!=0;
    }
    
    @Override
    public List<Declaration> getOverloads() {
        return overloads;
    }

    public void setOverloads(List<Declaration> overloads) {
        this.overloads = overloads;
    }

    public void initOverloads(FunctionOrValue... initial) {
        overloads = 
                new ArrayList<Declaration>
                    (initial.length+1);
        for (Declaration d: initial) {
            overloads.add(d);
        }
    }
    
    public boolean isImplemented() {
        return (flags&IMPLEMENTED)!=0;
    }
    
    public void setImplemented(boolean implemented) {
        if (implemented) {
            flags|=IMPLEMENTED;
        }
        else {
            flags&=(~IMPLEMENTED);
        }
    }

} public interface Functional {

    public ParameterList getFirstParameterList();
    
    public List<ParameterList> getParameterLists();

    public void addParameterList(ParameterList pl);

    public Type getType();

    public String getName();
    public String getName(Unit unit);
    
    public Parameter getParameter(String name);

//    public List<TypeParameter> getTypeParameters();
    
    public boolean isDeclaredVoid();

} public class Interface extends ClassOrInterface {

    private String javaCompanionClassName;
    private Boolean companionClassNeeded;
    
    @Override
    public boolean isAbstract() {
        return true;
    }

    @Override
    public boolean isEmpty() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Empty");
    }

    @Override
    public boolean isSequence() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Sequence");
    }

    @Override
    public boolean isSequential() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Sequential");
    }

    @Override
    public boolean isIterable() {
        return Objects.equals(getQualifiedNameString(),
                "ceylon.language::Iterable");
    }

    @Override
    public boolean inherits(TypeDeclaration dec) {
        if (dec.isAnything() || dec.isObject()) {
            return true;
        }
        else if (dec instanceof Class) {
            //interface can't inherit any other class
            return false;
        }
        else if (dec instanceof Interface && equals(dec)) {
            return true;
        }
        else {
            //TODO: optimize this to avoid walking the
            //      same supertypes multiple times
            if (dec instanceof Interface) {
                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;
        }
    }

    public void setJavaCompanionClassName(String name) {
        this.javaCompanionClassName = name;
    }
    
    public String getJavaCompanionClassName() {
        return javaCompanionClassName;
    }

    public Boolean isCompanionClassNeeded() {
        return companionClassNeeded;
    }

    public void setCompanionClassNeeded(Boolean companionClassNeeded) {
        this.companionClassNeeded = companionClassNeeded;
    }
    
    @Override
    public boolean isEmptyType() {
        return isEmpty();
    }
    
    @Override
    public boolean isSequentialType() {
        return isSequential() || isSequence() || isEmpty();
    }
    
    @Override
    public boolean isSequenceType() {
        return isSequence();
    }
    
    @Override
    public String toString() {
        return "interface " + toStringName();
    }
    
} public class ModelUtil {

    static final List<Type> NO_TYPE_ARGS = 
            Collections.<Type>emptyList();    
    
    static final Map<TypeParameter, Type> EMPTY_TYPE_ARG_MAP = 
            Collections.<TypeParameter,Type>emptyMap();
    
    static final Map<TypeParameter, SiteVariance> EMPTY_VARIANCE_MAP = 
            emptyMap();
    /**
     * Is the second scope contained by the first scope?
     */
    public static boolean contains(Scope outer, Scope inner) {
        if (outer != null) {
            while (inner!=null) {
                if (inner.equals(outer)) {
                    return true;
                }
                inner = inner.getScope();
            }
        }
        return false;
    }
    
    /**
     * Get the nearest containing scope that is not a
     * ConditionScope. 
     */
    public static Scope getRealScope(Scope scope) {
        while (!(scope instanceof Package)) {
            if (!(scope instanceof ConditionScope)) {
                return scope;
            }
            scope = scope.getContainer();
        }
        return null;
    }
    
    /**
     * Get the class or interface that "this" and "super" 
     * refer to. 
     */
    public static ClassOrInterface getContainingClassOrInterface(Scope scope) {
        while (!(scope instanceof Package)) {
            if (scope instanceof ClassOrInterface) {
                return (ClassOrInterface) scope;
            }
            scope = scope.getContainer();
        }
        return null;
    }
    
    /**
     * Get the declaration that contains the specified declaration, if any.
     */
    public static Declaration getContainingDeclaration(Declaration d) {
        if (d.isToplevel()) return null;
        Scope scope = d.getContainer();
        while (!(scope instanceof Package)) {
            if (scope instanceof Declaration) {
                return (Declaration) scope;
            }
            scope = scope.getContainer();
        }
        return null;
    }

    /**
     * Get the declaration that contains the specified scope, if any.
     */
    public static Declaration getContainingDeclarationOfScope(Scope scope) {
        while (!(scope instanceof Package)) {
            if (scope instanceof Declaration) {
                return (Declaration) scope;
            }
            scope = scope.getContainer();
        }
        return null;
    }

    /**
     * Get the class or interface that "outer" refers to. 
     */
    public static Type getOuterClassOrInterface(Scope scope) {
        Boolean foundInner = false;
        while (!(scope instanceof Package)) {
            if (scope instanceof ClassOrInterface) {
                if (foundInner) {
                    ClassOrInterface ci = 
                            (ClassOrInterface) scope;
                    return ci.getType();
                }
                else {
                    foundInner = true;
                }
            }
            scope = scope.getContainer();
        }
        return null;
    }
    
    /**
     * Convenience method to bind a single type argument 
     * to a toplevel type declaration.  
     */
    public static Type appliedType(
            TypeDeclaration declaration, 
            Type typeArgument) {
        if (declaration==null) return null;
        return declaration.appliedType(null, 
                singletonList(typeArgument));
    }

    /**
     * Convenience method to bind a list of type arguments
     * to a toplevel type declaration.  
     */
    public static Type appliedType(
            TypeDeclaration declaration, 
            Type... typeArguments) {
        if (declaration==null) return null;
        return declaration.appliedType(null, 
                asList(typeArguments));
    }

    public static boolean isResolvable(Declaration declaration) {
        return declaration.getName()!=null &&
            !declaration.isSetter() && //return getters, not setters
            !declaration.isAnonymous(); //don't return the type associated with an object dec 
    }
    
    public static boolean isAbstraction(Declaration d) {
        return d!=null && d.isAbstraction();
    }

    public static boolean notOverloaded(Declaration d) {
        if (d==null || !d.isFunctional()) {
            return true;
        }
        else {
            return !d.isOverloaded() || d.isAbstraction();
        }
    }
    
    public static boolean isOverloadedVersion(Declaration decl) {
        return decl!=null && 
                (decl.isOverloaded() && 
                !decl.isAbstraction());
    }

    static boolean hasMatchingSignature(
            Declaration dec, 
            List<Type> signature, boolean ellipsis) {
        return hasMatchingSignature(dec, 
                signature, ellipsis, true);
    }
    
    static boolean hasMatchingSignature(
            Declaration dec, 
            List<Type> signature, boolean spread, 
            boolean excludeAbstractClasses) {
        if (excludeAbstractClasses && 
                dec instanceof Class && 
                ((Class) dec).isAbstract()) {
            return false;
        }
        if (dec instanceof Functional) {
            if (dec.isAbstraction()) {
                return false;
            }
            Functional f = (Functional) dec;
            Unit unit = dec.getUnit();
            List<ParameterList> pls = f.getParameterLists();
            if (pls!=null && !pls.isEmpty()) {
                ParameterList pl = pls.get(0);
                List<Parameter> params = pl.getParameters();
                int size = params.size();
                boolean hasSeqParam = 
                        pl.hasSequencedParameter();
                int sigSize = signature.size();
                if (hasSeqParam) {
                    size--;
                    if (sigSize<size) {
                        return false;
                    }
                }
                else if (sigSize!=size) {
                    return false;
                }
                for (int i=0; i<size; i++) {
                    FunctionOrValue pm = 
                            params.get(i).getModel();
                    if (pm==null) {
                        return false;
                    }
                    Type pdt = 
                            pm.appliedReference(null, 
                                    NO_TYPE_ARGS)
                                .getFullType();
                    if (pdt==null) {
                        return false;
                    }
                    Type sdt = signature.get(i);
                    if (!matches(sdt, pdt, unit)) {
                        return false;
                    }
                }
                if (hasSeqParam) {
                    FunctionOrValue model = 
                            params.get(size).getModel();
                    Type pdt = 
                            model.appliedReference(null, 
                                    NO_TYPE_ARGS)
                            .getFullType();
                    if (pdt==null || 
                            pdt.getTypeArgumentList()
                                .isEmpty()) {
                        return false;
                    }
                    //Note: don't use Unit.getIterableType() 
                    //      because this gets called from 
                    //      model loader out-of-phase
                    Type ipdt = 
                            pdt.getTypeArgumentList()
                                .get(0);  
                    for (int i=size; i<sigSize; i++) {
                        if (spread && i==sigSize-1) {
                            Type sdt = signature.get(i);
                            Type isdt = 
                                    unit.getIteratedType(sdt);
                            if (!matches(isdt, ipdt, unit)) {
                                return false;
                            }
                        }
                        else {
                            Type sdt = signature.get(i);
                            if (!matches(sdt, ipdt, unit)) {
                                return false;
                            }
                        }
                    }
                }
                else if (spread) {
                    // if the method doesn't take sequenced 
                    // params and we have an ellipsis let's 
                    // not use it since we expect a variadic 
                    // method
                    // TODO: this is basically wrong now that 
                    //       we can spread tuples
                    return false;
                }
                return true;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
    
    public static boolean matches(
            Type argType, 
            Type paramType, 
            Unit unit) {
        if (paramType==null || argType==null) {
            return false;
        }
        //Ignore optionality for resolving overloads, since
        //all Java parameters are treated as optional,
        //except primitive-typed parameters
        Type nvt = unit.getNullType();
        if (nvt.isSubtypeOf(argType) && 
                !nvt.isSubtypeOf(paramType)) {
            return false; //only for primitives
        }
        Type defParamType = unit.getDefiniteType(paramType);
        Type defArgType = unit.getDefiniteType(argType);
        Type nt = unit.getNullType();
        if (defArgType.isSubtypeOf(nt)) {
            return true;
        }
        if (isTypeUnknown(defArgType) || 
                isTypeUnknown(defParamType)) {
            return false;
        }
        if (!erase(defArgType, unit)
                .inherits(erase(defParamType, unit)) &&
                notUnderlyingTypesEqual(defParamType, 
                        defArgType)) {
            return false;
        }
        return true;
    }

    private static boolean notUnderlyingTypesEqual(
            Type paramType, 
            Type sigType) {
        String sut = sigType.getUnderlyingType();
        String put = paramType.getUnderlyingType();
        return sut==null || put==null || !sut.equals(put);
    }
    
    static boolean betterMatch(Declaration d, Declaration r, 
            List<Type> signature) {
        if (d instanceof Functional && 
            r instanceof Functional) {
            Functional df = (Functional) d;
            Functional rf = (Functional) r;
            List<ParameterList> dpls = df.getParameterLists();
            List<ParameterList> rpls = rf.getParameterLists();
            if (dpls!=null && !dpls.isEmpty() && 
                    rpls!=null && !rpls.isEmpty()) {
                ParameterList dpls0 = dpls.get(0);
                ParameterList rpls0 = rpls.get(0);
                List<Parameter> dpl = dpls0.getParameters();
                List<Parameter> rpl = rpls0.getParameters();
                int dplSize = dpl.size();
                int rplSize = rpl.size();
                //ignore sequenced parameters
                boolean dhsp = dpls0.hasSequencedParameter();
                boolean rhsp = rpls0.hasSequencedParameter();
                //always prefer a signature without varargs 
                //over one with a varargs parameter
                if (!dhsp && rhsp) {
                    return true;
                }
                if (dhsp && !rhsp) {
                    return false;
                }
                //ignore sequenced parameters
                if (dhsp) dplSize--;
                if (rhsp) rplSize--;
                if (dplSize==rplSize) {
                    //if all parameters are of more specific
                    //or equal type, prefer it
                    Unit unit = d.getUnit();
                    for (int i=0; i<dplSize; i++) {
                        Type dplt =
                                dpl.get(i).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type paramType = 
                                unit.getDefiniteType(dplt);
                        Type rplt = 
                                rpl.get(i).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type otherType = 
                                unit.getDefiniteType(rplt);
                        Type argumentType = 
                                signature != null && 
                                signature.size() >= i ? 
                                        signature.get(i) : 
                                        null;
                        if (isTypeUnknown(otherType) || 
                            isTypeUnknown(paramType)) {
                            return false;
                        }
                        TypeDeclaration ptd = 
                                erase(paramType, unit);
                        TypeDeclaration otd = 
                                erase(otherType, unit);
                        if(paramType.isExactly(otherType) && 
                                supportsCoercion(ptd) &&
                                // do we have different scores?
                                hasWorseScore(
                                        getCoercionScore(
                                                argumentType, 
                                                paramType), 
                                        getCoercionScore(
                                                argumentType, 
                                                otherType))) {
                            return false;
                        }
                        if (!ptd.inherits(otd) &&
                                notUnderlyingTypesEqual(
                                        paramType, 
                                        otherType)) {
                            return false;
                        }
                    }
                    // check sequenced parameters last
                    if (dhsp && rhsp){
                        Type dplt = 
                                dpl.get(dplSize).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type paramType = 
                                unit.getDefiniteType(dplt);
                        Type rplt = 
                                rpl.get(dplSize).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type otherType = 
                                unit.getDefiniteType(rplt);
                        if (isTypeUnknown(otherType) || 
                                isTypeUnknown(paramType)) {
                            return false;
                        }
                        paramType = 
                                unit.getIteratedType(paramType);
                        otherType = 
                                unit.getIteratedType(otherType);
                        if (isTypeUnknown(otherType) || 
                                isTypeUnknown(paramType)) {
                            return false;
                        }
                        TypeDeclaration ptd = 
                                erase(paramType, unit);
                        TypeDeclaration otd = 
                                erase(otherType, unit);
                        if (paramType.isExactly(otherType) && 
                                supportsCoercion(ptd)) {
                            Type widerArgumentType = 
                                    getWiderArgumentType(
                                            paramType, 
                                            signature, 
                                            dplSize);
                            // do we have different scores?
                            int pscore = 
                                    getCoercionScore(
                                            widerArgumentType, 
                                            paramType);
                            int oscore = 
                                    getCoercionScore(
                                            widerArgumentType, 
                                            otherType);
                            if (hasWorseScore(pscore, oscore)) {
                                return false;
                            }
                        }
                        if (!ptd.inherits(otd) &&
                                notUnderlyingTypesEqual(
                                        paramType, 
                                        otherType)) {
                            return false;
                        }
                    }
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean supportsCoercion(
            TypeDeclaration decl) {
        return decl.isInteger() || decl.isFloat();
    }

    private static boolean hasWorseScore(
            int underlyingTypeCoercionScoreA, 
            int underlyingTypeCoercionScoreB) {
        if (underlyingTypeCoercionScoreA != 
                underlyingTypeCoercionScoreB) {
            if (underlyingTypeCoercionScoreA > 0 && 
                    underlyingTypeCoercionScoreB > 0) {
                // both truncations, prefer the smaller 
                // truncation
                if (underlyingTypeCoercionScoreA > 
                underlyingTypeCoercionScoreB) {
                    return true;
                }
            }
            else if(underlyingTypeCoercionScoreA > 0) {
                // A is a truncation, B is a widening, 
                // prefer widening
                return true;
            }
            else if(underlyingTypeCoercionScoreA == 0) {
                // A is a perfect match, it's not worse
                return false;
            }
            else if(underlyingTypeCoercionScoreB == 0) {
                // B is a perfect match but A is not, 
                // so it's worse
                return true;
            }
            else if(underlyingTypeCoercionScoreB > 0) {
                // A is a widening, B is a truncation, 
                // so it's not worse
                return false;
            }
            else {
                // A is a widening and B is a widening too, 
                // A is worse than B if it widens more than B
                return underlyingTypeCoercionScoreA < 
                        underlyingTypeCoercionScoreB;
            }
        }
        return false;// same score or we don't know
    }

    private static Type getWiderArgumentType(
            Type paramType, 
            List<Type> signature, 
            int startAt) {
        if (startAt >= signature.size()) {
            return null;
        }
        TypeDeclaration decl = paramType.getDeclaration();
        Unit unit = decl.getUnit();
        if (decl.isInteger()) {
            int bestScore = 0;
            Type ret = null;
            for(int i=startAt; i<signature.size(); i++){
                Type argType = signature.get(i);
                String underlyingType = 
                        argType.getUnderlyingType();
                int score = 0;
                if (underlyingType == null || 
                        underlyingType.equals("long")) {
                    return argType; // found the wider sort
                }
                else if (underlyingType.equals("int")) {
                    score = 2;
                }
                else if (underlyingType.equals("short")) {
                    score = 1;
                }
                // keep the widest argument type
                if (score > bestScore) {
                    bestScore = score;
                    ret = argType;
                }
            }
            return ret;
        }
        else if (decl.equals(unit.getFloatDeclaration())) {
            int bestScore = 0;
            Type ret = null;
            for (int i=startAt; i<signature.size(); i++) {
                Type argType = signature.get(i);
                String underlyingType = 
                        argType.getUnderlyingType();
                int score = 0;
                if (underlyingType == null || 
                        underlyingType.equals("double")) {
                    return argType; // found the wider sort
                }
                else if (underlyingType.equals("float")) {
                    score = 1;
                }
                // keep the widest argument type
                if (score > bestScore){
                    bestScore = score;
                    ret = argType;
                }
            }
            return ret;
        }
        // not relevant
        return null;
    }

    /**
     * Returns 0 of there's no coercion, > 0 if we have to 
     * truncate the argument type to fit the param type,
     * the higher for the worse truncation, or < 0 if we 
     * have to widen the argument type to fit the param type, 
     * the lower for the worse widening.
     */
    private static int getCoercionScore(
            Type argumentType, 
            Type paramType) {
        if (argumentType == null) {
            return 0;
        }
        // only consider types of Integer of Float
        if (paramType.isExactly(argumentType)) {
            String aType = argumentType.getUnderlyingType();
            String pType = paramType.getUnderlyingType();
            if (aType == null && pType == null) {
                return 0;
            }
            Unit unit = argumentType.getDeclaration().getUnit();
            TypeDeclaration decl = paramType.getDeclaration();
            if (decl.isInteger()) {
                if (aType == null) {
                    aType = "long";
                }
                if (pType == null) {
                    pType = "long";
                }
                int aScore = getPrimitiveScore(aType);
                int bScore = getPrimitiveScore(pType);
                /*
                 * aType aTypeScore pType pTypeScore score
                 * short 0          short 0          0
                 * short 0          int   1          -1 (widening)
                 * short 0          long  2          -2 (widening)
                 * int   1          short 0          1 (truncation)
                 * int   1          int   1          0
                 * int   1          long  2          -1 (widening)
                 * long  2          short 0          2 (truncation)
                 * long  2          int   1          1 (truncation)
                 * long  2          long  2          0
                 */
                return aScore - bScore;
            }
            else if (decl.equals(unit.getFloatDeclaration())) {
                if (aType == null) {
                    aType = "double";
                }
                if (pType == null) {
                    pType = "double";
                }
                int aScore = getPrimitiveScore(aType);
                int bScore = getPrimitiveScore(pType);
                /*
                 * aType  aTypeScore pType  pTypeScore score
                 * float  0          float  0          0
                 * float  0          double 1          -1 (widening)
                 * double 1          float  0          1 (truncation)
                 * double 1          double 1          0
                 */
                return aScore - bScore;
            }
        }
        // no truncation for the rest
        return 0;
    }

    private static int getPrimitiveScore(String underlyingType) {
        if (underlyingType.equals("long")) {
            return 2;
        }
        if (underlyingType.equals("int") || 
            underlyingType.equals("double")) {
            return 1;
        }
        if (underlyingType.equals("short") || 
            underlyingType.equals("float")) {
            return 0;
        }
        return 0;
    }

    static boolean strictlyBetterMatch(Declaration d, Declaration r) {
        if (d instanceof Functional && 
            r instanceof Functional) {
            Functional fd = (Functional) d;
            Functional fr = (Functional) r;
            List<ParameterList> dpls = fd.getParameterLists();
            List<ParameterList> rpls = fr.getParameterLists();
            if (dpls!=null && !dpls.isEmpty() && 
                    rpls!=null && !rpls.isEmpty()) {
                ParameterList dpls0 = dpls.get(0);
                ParameterList rpls0 = rpls.get(0);
                List<Parameter> dpl = dpls0.getParameters();
                List<Parameter> rpl = rpls0.getParameters();
                int dplSize = dpl.size();
                int rplSize = rpl.size();
                //ignore sequenced parameters
                boolean dhsp = dpls0.hasSequencedParameter();
                boolean rhsp = rpls0.hasSequencedParameter();
                //always prefer a signature without varargs 
                //over one with a varargs parameter
                if (!dhsp && rhsp) {
                    return true;
                }
                if (dhsp && !rhsp) {
                    return false;
                }
                //ignore sequenced parameters
                if (dhsp) dplSize--;
                if (rhsp) rplSize--;
                if (dplSize==rplSize) {
                    //if all parameters are of more specific
                    //or equal type, prefer it
                    boolean atLeastOneBetter = false;
                    Unit unit = d.getUnit();
                    for (int i=0; i<dplSize; i++) {
                        Type dplt = 
                                dpl.get(i).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type paramType = 
                                unit.getDefiniteType(dplt);
                        Type rplt = 
                                rpl.get(i).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type otherType = 
                                unit.getDefiniteType(rplt);
                        if (isTypeUnknown(otherType) || 
                                isTypeUnknown(paramType)) {
                            return false;
                        }
                        TypeDeclaration ptd = 
                                erase(paramType, unit);
                        TypeDeclaration otd = 
                                erase(otherType, unit);
                        if (!ptd.inherits(otd) &&
                                notUnderlyingTypesEqual(
                                        paramType, otherType)) {
                            return false;
                        }
                        if (ptd.inherits(otd) && 
                            !otd.inherits(ptd) &&
                                notUnderlyingTypesEqual(
                                        paramType, otherType)) {
                            atLeastOneBetter = true;
                        }
                        
                    }
                    // check sequenced parameters last
                    if (dhsp && rhsp) {
                        Type dplt = 
                                dpl.get(dplSize).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type paramType = 
                                unit.getDefiniteType(dplt);
                        Type rplt = 
                                rpl.get(dplSize).getModel()
                                .appliedReference(null, 
                                        NO_TYPE_ARGS)
                                .getFullType();
                        Type otherType = 
                                unit.getDefiniteType(rplt);
                        if (isTypeUnknown(otherType) || 
                                isTypeUnknown(paramType)) {
                            return false;
                        }
                        paramType = 
                                unit.getIteratedType(paramType);
                        otherType = 
                                unit.getIteratedType(otherType);
                        if (isTypeUnknown(otherType) || 
                                isTypeUnknown(paramType)) {
                            return false;
                        }
                        TypeDeclaration ptd = 
                                erase(paramType, unit);
                        TypeDeclaration otd = 
                                erase(otherType, unit);
                        if (!ptd.inherits(otd) &&
                                notUnderlyingTypesEqual(
                                        paramType, 
                                        otherType)) {
                            return false;
                        }
                        if (ptd.inherits(otd) && 
                            !otd.inherits(ptd) &&
                                notUnderlyingTypesEqual(
                                        paramType, 
                                        otherType)) {
                            atLeastOneBetter = true;
                        }
                    }
                    return atLeastOneBetter;
                }
            }
        }
        return false;
    }

    public static boolean isNamed(String name, Declaration d) {
        String dname = d.getName();
        return dname!=null && dname.equals(name);
    }
    
    static TypeDeclaration erase(Type paramType, Unit unit) {
        if (paramType.isTypeParameter()) {
            if (paramType.getSatisfiedTypes().isEmpty()) {
                Type et = 
                        paramType.getExtendedType();
                return et==null ? null : 
                    et.getDeclaration();
            }
            else {
                //TODO: Is this actually correct? 
                //      What is Java's rule here?
                Type st = 
                        paramType.getSatisfiedTypes()
                            .get(0);
                return st==null ? null : 
                    st.getDeclaration();
            }
        }
        else if (paramType.isUnion()) {
            //TODO: this is very sucky, cos in theory a
            //      union might be assignable to the 
            //      parameter type with a typecast
            return unit.getObjectDeclaration();
        }
        else if (paramType.isIntersection()) {
            List<Type> sts = paramType.getSatisfiedTypes();
            if (sts.size()==2) {
                //attempt to eliminate Basic from the 
                //intersection - very useful for anonymous
                //classes, whose denotableType is often an 
                //intersection with Basic
                Type first = sts.get(0);
                Type second = sts.get(1);
                if (first!=null && first.isBasic()) {
                    return erase(second, unit);
                }
                else if (second!=null && second.isBasic()) {
                    return erase(first, unit);
                }
            }
            //TODO: this is very sucky, cos in theory an
            //      intersection might be assignable to the 
            //      parameter type with a typecast
            return unit.getObjectDeclaration();
        }
        else {
            return paramType.getDeclaration();
        }
    }
    
    /**
     * Match the name of the given declaration to the given
     * pattern. A name matches if:
     * 
     * - it starts with the pattern, ignoring case, or
     * - the pattern consists of all uppercase after the
     *   first character, and its uppercase "humps" match 
     *   the pattern.
     */
    public static boolean isNameMatching(
            String startingWith, Declaration d) {
        return isNameMatching(startingWith, d.getName());
    }
    
    public static boolean isNameMatching(
            String startingWith, Import i) {
        return isNameMatching(startingWith, i.getAlias());
    }
    
    public static boolean isNameMatching(
            String startingWith, String name) {
        if (startingWith==null || startingWith.isEmpty()) {
            return true;
        }
        if (name==null || name.isEmpty()) {
            return false;
        }
        int nameLength = name.length();
        int startingWithLength = startingWith.length();
        if (nameLength<startingWithLength) {
            return false;
        }
        if (name.regionMatches(true,0,startingWith,0,startingWithLength)) {
            return true;
        }
        int c = startingWith.codePointAt(0); 
        int d = name.codePointAt(0);
        if (c!=d) {
            return false;
        }
        //camel hump matching, starting from second character:
        int i=1, j=1;
        while (i<startingWithLength) {
            if (j>=nameLength) {
                return false;
            }
            while (i<startingWithLength && 
                    isLowerCase(c=startingWith.codePointAt(i))) {
                d = name.codePointAt(j);
                if (c==d) {
                    i+=charCount(c);
                    j+=charCount(d); 
                    if (i>=startingWithLength) {
                        return true;
                    }
                    if (j>=nameLength) {
                        return false;
                    }
                }
                else {
                    return false;
                }
            }
            while (j<nameLength && 
                    isLowerCase(d=name.codePointAt(j))) {
                j+=charCount(d);
                if (j>=nameLength) {
                    return false;
                }
            }
            c = startingWith.codePointAt(i);
            d = name.codePointAt(j);
            i+=charCount(c);
            j+=charCount(d); 
            if (d!=c) {
                return false;
            }
        }
        return true;
    }
    
    private static int countTypeParameters(
            Declaration declaration, Type receivingType) {
        int count = 0;
        boolean containsFunctionOrValueInterface = containsFunctionOrValueInterface(receivingType);
        while (true) {
            if (declaration instanceof Generic) {
                Generic g = (Generic) declaration;
                count += g.getTypeParameters().size();
            }
            if (declaration.isClassOrInterfaceMember()) {
                declaration = 
                        (Declaration) 
                            declaration.getContainer();
            }
            else if(containsFunctionOrValueInterface){
                // we are at runtime and we care about local types' generic ancestors
                declaration = getFirstGenericContainer(declaration);
                if(declaration == null)
                    break;
            }
            else {
                break;
            }
        }
        return count;
    }

    private static Declaration getFirstGenericContainer(Declaration declaration) {
        Scope container = declaration.getContainer();
        while(container != null && container instanceof Generic == false)
            container = container.getContainer();
        // must be Generic or null, and every Generic is a Declaration
        return (Declaration)container;
    }

    private static boolean containsFunctionOrValueInterface(Type receivingType) {
        while(receivingType != null){
            if(receivingType.isFunctionOrValueInterface())
                return true;
            receivingType = receivingType.getQualifyingType();
        }
        return false;
    }

    /**
     * Given a declaration, a list of type arguments to the 
     * declaration, and a receiving type, collect together
     * all interesting type arguments. The resulting map 
     * includes all type arguments from the receiving type 
     * and all its qualifying types. That's useful, because
     * {@link Type.Substitution} works with a single
     * aggregated map, and so for performance
     * {@link Type#substitute(Type)} and
     * {@link Type#substitute(TypedReference)}
     * assume that the given type or reference holds such a
     * single aggregated map.
     * 
     * @return a map of type parameter to type argument
     *  
     * @param declaration a declaration
     * @param receivingType the receiving produced type 
     *        of which the declaration is a member
     * @param typeArguments all the explicit or inferred 
     *        type arguments of the declaration, including
     *        those from qualifying types
     */
    public static Map<TypeParameter,Type> 
    getTypeArgumentMap(Declaration declaration, 
            Type receivingType, 
            List<Type> typeArguments) {        
        int count = countTypeParameters(declaration, receivingType);
        if (count==0) {
            return EMPTY_TYPE_ARG_MAP;
        }
        else {
            return aggregateTypeArguments(receivingType, 
                    typeArguments, declaration, count);
        }
    }

    private static Map<TypeParameter, Type> 
    aggregateTypeArguments(Type receivingType, 
            List<Type> typeArguments,
            Declaration declaration,
            int count) {
        List<TypeParameter> typeParameters = 
                getTypeParameters(declaration);
        Map<TypeParameter,Type> map = 
                new HashMap<TypeParameter,Type>
                    (count);
        //make sure we collect all type arguments
        //from the whole qualified type!
        if (receivingType!=null) {
            if (receivingType.isIntersection()) {
                for (Type supertype: 
                        receivingType.getSatisfiedTypes()) {
                    aggregateTypeArguments(map, 
                            supertype, declaration);
                }
            }
            else {
                aggregateTypeArguments(map, 
                        receivingType, declaration);
            }
        }
        if (typeArguments!=null) {
            //now turn the type argument tuple into a
            //map from type parameter to argument
            for (int i=0; 
                    i<typeParameters.size() && 
                    i<typeArguments.size(); 
                    i++) {
                map.put(typeParameters.get(i), 
                        typeArguments.get(i));
            }
        }
        return map;
    }

    private static void aggregateTypeArguments(
            Map<TypeParameter, Type> map, 
            Type dt, Declaration d) {
        while (dt!=null){
            Type aqt;
            if(d.isClassOrInterfaceMember()) {
                TypeDeclaration declaringType = 
                        (TypeDeclaration) 
                            d.getContainer();
                aqt = dt.getSupertype(declaringType);
                if (aqt==null) {
                    break;
                }
            }else if(dt.isFunctionOrValueInterface()){
                // just take it as-is
                aqt = dt;
            }else{
                break;
            }
            map.putAll(aqt.getTypeArguments());
            dt = aqt.getQualifyingType();
            d = aqt.getDeclaration();
        }
    }
    
    public static Map<TypeParameter,SiteVariance> 
    getVarianceMap(Declaration declaration, 
            Type receivingType, 
            List<SiteVariance> variances) {     
        if (variances==null) {
            return EMPTY_VARIANCE_MAP;
        }
        else {
            if (countTypeParameters(declaration, receivingType)==0) {
                return EMPTY_VARIANCE_MAP;
            }
            else {
                return aggregateVariances(receivingType, 
                        variances, declaration);
            }
        }
    }

    private static Map<TypeParameter, SiteVariance> 
    aggregateVariances(Type receivingType, 
            List<SiteVariance> variances,
            Declaration declaration) {
        Map<TypeParameter,SiteVariance> map = 
                new HashMap<TypeParameter,SiteVariance>();
        //make sure we collect all type arguments
        //from the whole qualified type!
        if (receivingType!=null) {
            if (receivingType.isIntersection()) {
                for (Type supertype: 
                        receivingType.getSatisfiedTypes()) {
                    aggregateVariances(map, supertype, 
                            declaration);
                }
            }
            else {
                aggregateVariances(map, receivingType, 
                        declaration);
            }
        }
        List<TypeParameter> typeParameters = 
                getTypeParameters(declaration);
        for (int i=0; 
                i<typeParameters.size() && 
                i<variances.size(); 
                i++) {
            SiteVariance var = variances.get(i);
            if (var!=null) {
                map.put(typeParameters.get(i), var);
            }
        }
        return map;
    }

    private static void aggregateVariances(
            Map<TypeParameter,SiteVariance> map, 
            Type dt, Declaration d) {
        while (dt!=null) {
            Type aqt;
            if (d.isClassOrInterfaceMember()) {
                TypeDeclaration declaringType = 
                        (TypeDeclaration) 
                            d.getContainer();
                aqt = dt.getSupertype(declaringType);
                if (aqt==null) {
                    break;
                }
            }else if(dt.isFunctionOrValueInterface()){
                // take it as-is
                aqt = dt;
            }else{
                break;
            }
            map.putAll(dt.getVarianceOverrides());
            dt = aqt.getQualifyingType();
            d = aqt.getDeclaration();
        }
    }

    /*private static int countTypeParameters(
            Type receivingType,
            List<TypeParameter> typeParameters) {
        int count = typeParameters.size();
        //make sure we count all type arguments
        //from the whole qualified type!
        if (receivingType!=null) {
            if (receivingType.isIntersection()) {
                for (Type supertype: 
                        receivingType.getSatisfiedTypes()) {
                    while (supertype!=null) {
                        count += supertype.getTypeArguments().size();
                        supertype = supertype.getQualifyingType();
                    }
                }
            }
            else {
                Type dt = receivingType;
                while (dt!=null) {
                    count += dt.getTypeArguments().size();
                    dt = dt.getQualifyingType();
                }
            }
        }
        return count;
    }*/

    public static List<TypeParameter> getTypeParameters(
            Declaration declaration) {
        if (declaration instanceof Generic) {
            Generic g = (Generic) declaration;
            return g.getTypeParameters();
        }
        else {
            return emptyList();
        }
    }
    
    static <T> List<T> list(List<T> list, T element) {
        List<T> result = new ArrayList<T>(list.size()+1);
        result.addAll(list);
        result.add(element);
        return result;
    }

    /**
     * Helper method for eliminating duplicate types from
     * lists of types that form a union type, taking into
     * account that a subtype is a "duplicate" of its
     * supertype.
     */
    public static void addToUnion(List<Type> list, 
            Type pt) {
        if (pt==null || 
                !list.isEmpty() && 
                pt.isExactlyNothing()) {
            return;
        }
        else if (pt.isAnything()) {
            list.clear();
            list.add(pt);
        }
        else if (pt.isUnion()) {
            List<Type> caseTypes = 
                    pt.getCaseTypes();
            for ( int i=0, size=caseTypes.size(); 
                    i<size; i++ ) {
                Type t = caseTypes.get(i);
                addToUnion(list, t.substitute(pt));
            }
        }
        else if (pt.isWellDefined()) {
            boolean add=true;
            for (int i=0; i<list.size(); i++) {
                Type t = list.get(i);
                if (pt.isSubtypeOf(t)) {
                    add=false;
                    break;
                }
                else if (pt.isSupertypeOf(t)) {
                    list.remove(i);
                    i--; // redo this index
                }
            }
            if (add) {
                list.add(pt);
            }
        }
    }
    
    /**
     * Helper method for eliminating duplicate types from
     * lists of types that form an intersection type, taking 
     * into account that a supertype is a "duplicate" of its
     * subtype.
     */
    public static void addToIntersection(List<Type> list, 
            Type type, Unit unit) {
        if (type==null || 
                !list.isEmpty() && 
                type.isAnything()) {
            return;
        }
        else if (type.isExactlyNothing()) {
            list.clear();
            list.add(type);
        }
        else if (type.isIntersection()) {
            List<Type> satisfiedTypes = 
                    type.getSatisfiedTypes();
            for (int i=0, 
                    size=satisfiedTypes.size(); 
                    i<size; i++) {
                Type t = satisfiedTypes.get(i);
                addToIntersection(list, t, unit);
            }
        }
        else {
            if (type.isWellDefined()) {
                TypeDeclaration dec = type.getDeclaration();
                for (int i=0; i<list.size(); i++) {
                    Type t = list.get(i);
                    if (t.isSubtypeOf(type)) {
                        return;
                    }
                    else if (type.isSubtypeOf(t)) {
                        list.remove(i);
                        i--; // redo this index
                    }
                    else if (disjoint(type, t, unit)) {
                        list.clear();
                        list.add(unit.getNothingType());
                        return;
                    } 
                    else {
                        if (type.isClassOrInterface() && 
                            t.isClassOrInterface() && 
                            t.getDeclaration().equals(dec) &&
                                !type.containsUnknowns() &&
                                !t.containsUnknowns()) {
                            //canonicalize a type of form
                            //T<InX,OutX>&T<InY,OutY> to 
                            //T<InX|InY,OutX&OutY>
                            Type pi = 
                                    principalInstantiation(
                                            dec, type, t, 
                                            unit);
                            if (!pi.containsUnknowns()) {
                                list.remove(i);
                                list.add(pi);
                                return;
                            }
                        }
                    }
                }
                if (list.size()>1) {
                    //it is possible to have a type that is
                    //a supertype of the intersection, even 
                    //though it is not a supertype of any of  
                    //the intersected types!
                    Type t = canonicalIntersection(list, unit);
                    if (type.isSupertypeOf(t)) {
                        return;
                    }
                }
                list.add(type);
            }
        }
    }
    
    /**
     * Are the given types disjoint?
     * 
     * @param p the first type
     * @param q the second type
     *        enumerated type are disjoint
     * @param unit
     * 
     * @return true if the types are disjoint
     */
    private static boolean disjoint(Type p, Type q, 
            Unit unit) {
        if (q.getDeclaration()
                .isDisjoint(p.getDeclaration())) {
            return true;
        }
        else {
            //we have to resolve aliases here, or computing
            //supertype declarations gets incredibly slow 
            //for the big stack of union type aliases in 
            //ceylon.ast
            Type ps = p.resolveAliases();
            Type qs = q.resolveAliases();
            return emptyMeet(ps, qs, unit) ||
                    hasEmptyIntersectionOfInvariantInstantiations(ps, qs);

        }
    }

    /**
     * implement the rule that Foo&Bar==Nothing if 
     * here exists some enumerated type Baz with
     * 
     *    Baz of Foo | Bar 
     * 
     * (the intersection of disjoint types is empty)
     * 
     * @param type a type which might be disjoint from
     *        a list of other given types
     * @param list the list of other types
     * @param unit
     * 
     * @return true of the given type was disjoint from
     *         the given list of types
     */
    /*private static boolean reduceIfDisjoint(Type type, 
            List<Type> list, Unit unit) {
        if (list.isEmpty()) { 
            return false;
        }
        TypeDeclaration typeDec = type.getDeclaration();
        List<TypeDeclaration> supertypes = 
                typeDec.getSupertypeDeclarations();
        for (int i=0, l=supertypes.size(); i<l; i++) {
            TypeDeclaration supertype = 
                    supertypes.get(i);
            List<Type> cts = supertype.getCaseTypes();
            if (cts!=null) {
                TypeDeclaration ctd=null;
                for (int cti=0, 
                        ctl=cts.size(); 
                        cti<ctl; 
                        cti++) {
                    TypeDeclaration ct = 
                            cts.get(cti)
                                .getDeclaration();
                    if (typeDec.inherits(ct)) {
                        ctd = ct;
                        break;
                    }
                }
                if (ctd!=null) {
                    for (int cti=0, ctl=cts.size(); 
                            cti<ctl; 
                            cti++) {
                        TypeDeclaration ct = 
                                cts.get(cti)
                                    .getDeclaration();
                        if (ct!=ctd) {
                            for (int ti=0, 
                                    tl=list.size(); 
                                    ti<tl; 
                                    ti++) {
                                Type t = list.get(ti);
                                if (t.getDeclaration()
                                        .inherits(ct)) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }*/

    /**
     * The meet of two classes unrelated by inheritance,
     * or of Null with an interface type is empty. The meet
     * of an anonymous class with a type to which it is not
     * assignable is empty.
     */
    private static boolean emptyMeet(
            Type p, Type q, Unit unit) {
        if (p==null || q==null) {
            return false;
        }
        if (p.isExactlyNothing() || 
            q.isExactlyNothing()) {
            return true;
        }
        TypeDeclaration pd = p.getDeclaration();
        TypeDeclaration qd = q.getDeclaration();
        if (p.isTypeParameter()) {
            p = canonicalIntersection(
                    p.getSatisfiedTypes(), 
                    unit);
            pd = p.getDeclaration();
        }
        if (q.isTypeParameter()) {
            q = canonicalIntersection(
                    q.getSatisfiedTypes(), 
                    unit);
            qd = q.getDeclaration();
        }
        if (q.isIntersection()) {
            for (Type t: q.getSatisfiedTypes()) {
                if (emptyMeet(p,t,unit)) {
                    return true;
                }
            }
            return false;
        }
        if (p.isIntersection()) {
            for (Type t: p.getSatisfiedTypes()) {
                if (emptyMeet(q,t,unit)) {
                    return true;
                }
            }
            return false;
        }
        if (q.isUnion()) {
            for (Type t: q.getCaseTypes()) {
                if (!emptyMeet(p,t,unit)) {
                    return false;
                }
            }
            return true;
        }
        else if (qd.getCaseTypes()!=null) {
            boolean all = true;
            for (Type t: qd.getCaseTypes()) {
                if (t.getDeclaration().isSelfType() || 
                        !emptyMeet(p,t,unit)) {
                    all = false; 
                    break;
                }
            }
            if (all) return true;
        }
        if (p.isUnion()) {
            for (Type t: p.getCaseTypes()) {
                if (!emptyMeet(q,t,unit)) {
                    return false;
                }
            }
            return true;
        }
        else if (p.getCaseTypes()!=null) {
            boolean all = true;
            for (Type t: pd.getCaseTypes()) {
                if (t.getDeclaration().isSelfType() || 
                        !emptyMeet(q,t,unit)) {
                    all = false; 
                    break;
                }
            }
            if (all) return true;
        }
        if (p.isClass() && q.isClass() ||
            p.isInterface() && q.isNull() ||
            q.isInterface() && p.isNull()) {
            if (!qd.inherits(pd) &&
                !pd.inherits(qd)) {
                return true;
            }
        }
        if (pd.isFinal()) {
            if (pd.getTypeParameters().isEmpty() &&
                    !q.involvesTypeParameters() &&
                    !p.isSubtypeOf(q) &&
                    !(qd instanceof UnknownType)) {
                return true;
            }
            if (q.isClassOrInterface() &&
                    !pd.inherits(qd)) {
                return true;
            }
        }
        if (qd.isFinal()) { 
            if (qd.getTypeParameters().isEmpty() &&
                    !p.involvesTypeParameters() &&
                    !q.isSubtypeOf(p) &&
                    !(p.isUnknown())) {
                return true;
            }
            if (p.isClassOrInterface() &&
                    !qd.inherits(pd)) {
                return true;
            }
        }
//        Interface ed = unit.getEmptyDeclaration();
//        Interface id = unit.getIterableDeclaration();
//        if (pd.inherits(ed) && qd.inherits(id) &&
//                unit.isNonemptyIterableType(q) ||
//            pd.inherits(id) && qd.inherits(ed) &&
//                unit.isNonemptyIterableType(p)) {
//            return true;
//        }
        Interface st = unit.getSequentialDeclaration();
        if (q.isClassOrInterface() &&
                pd.inherits(st) && !qd.inherits(st) && 
                !st.inherits(qd) ||
            p.isClassOrInterface() &&
                qd.inherits(st) && !pd.inherits(st) && 
                !st.inherits(pd) && 
                !p.involvesTypeParameters()) {
            return true;
        }
        
        Interface nst = unit.getSequenceDeclaration();
        if (pd.inherits(nst) && qd.inherits(st) ||
            qd.inherits(nst) && pd.inherits(st)) {
            Type pet = unit.getSequentialElementType(p);
            Type qet = unit.getSequentialElementType(q);
            if (emptyMeet(pet, qet, unit)) {
                return true;
            }
        }
        Class td = unit.getTupleDeclaration();
        if (pd.inherits(td) && qd.inherits(td)) {
            List<Type> pal = p.getTypeArgumentList();
            List<Type> qal = q.getTypeArgumentList();
            if (pal.size()>=3 && qal.size()>=3) {
                if (emptyMeet(pal.get(1), qal.get(1), unit) ||
                    emptyMeet(pal.get(2), qal.get(2), unit)) {
                    return true;
                }
            }
        }
        if (pd.inherits(td) && qd.inherits(st)) {
            List<Type> pal = p.getTypeArgumentList();
            Type qet = unit.getSequentialElementType(q);
            if (pal.size()>=3) {
                if (emptyMeet(pal.get(1), qet, unit) ||
                    emptyMeet(pal.get(2), 
                            unit.getSequentialType(qet), 
                            unit)) {
                    return true;
                }
            }
        }
        if (qd.inherits(td) && pd.inherits(st)) {
            List<Type> qal = q.getTypeArgumentList();
            Type pet = unit.getSequentialElementType(p);
            if (qal.size()>=3) {
                if (emptyMeet(qal.get(1), pet, unit) ||
                    emptyMeet(qal.get(2), 
                            unit.getSequentialType(pet),
                            unit)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Given two instantiations of a qualified type 
     * constructor, determine the qualifying type of the 
     * principal instantiation of that type constructor for 
     * the intersection of the two types.
     * 
     * @param p the first instantiation
     * @param q the second instantiation
     * @param td the type constructor
     */
    static Type principalQualifyingType(
            Type p, Type q, Declaration td, Unit unit) {
        Type pqt = p.getQualifyingType();
        Type qqt = q.getQualifyingType();
        Scope tdc = td.getContainer();
        if (pqt!=null && qqt!=null) {
            if (tdc instanceof TypeDeclaration) {
                TypeDeclaration qtd = (TypeDeclaration) tdc;
                Type pst = pqt.getSupertype(qtd);
                Type qst = qqt.getSupertype(qtd);
                if (pst!=null && qst!=null) {
                    return principalInstantiation(qtd, pst, qst, 
                            unit);
                }
            }
            else {
                if (pqt.isExactly(qqt)) {
                    return pqt;
                }
            }
        }
        return null;
    }
    
    /**
     * Determine if a type of form X<P>&X<Q> is equivalent
     * to Nothing where X<T> is invariant in T.
     * 
     * @param p the argument type P
     * @param q the argument type Q
     */
    private static boolean hasEmptyIntersectionOfInvariantInstantiations(
            Type p, Type q) {
        List<TypeDeclaration> pstds = 
                p.getDeclaration()
                    .getSupertypeDeclarations();
        List<TypeDeclaration> qstds =
                q.getDeclaration()
                    .getSupertypeDeclarations();
        Set<TypeDeclaration> set = 
                new HashSet<TypeDeclaration>
                    (pstds.size()+qstds.size());
        set.addAll(pstds); 
        set.retainAll(qstds);
        for (TypeDeclaration std: pstds) {
            Type pst = null;
            Type qst = null;
            for (TypeParameter tp: std.getTypeParameters()) {
                if (tp.isInvariant()) {
                    if (pst==null) {
                        pst = p.getSupertype(std);
                    }
                    if (qst==null) { 
                        qst = q.getSupertype(std);
                    }
                    if (pst!=null && qst!=null) {
                        Type psta = 
                                pst.getTypeArguments()
                                    .get(tp);
                        Type qsta = 
                                qst.getTypeArguments()
                                    .get(tp);
                        //TODO: why isWellDefined() instead of isTypeUnknown() ?
                        if (psta!=null && 
                                psta.isWellDefined() && 
                                !pst.involvesTypeParameters() && 
                            qsta!=null && 
                                qsta.isWellDefined() && 
                                !qst.involvesTypeParameters()) {
                            boolean psti = 
                                    pst.isInvariant(tp);
                            boolean pstcov = 
                                    pst.isCovariant(tp);
                            boolean pstcontra = 
                                    pst.isContravariant(tp);
                            boolean qsti = 
                                    qst.isInvariant(tp);
                            boolean qstcov = 
                                    qst.isCovariant(tp);
                            boolean qstcontra = 
                                    qst.isContravariant(tp);
                            if (psti && qsti && 
                                    !psta.isExactly(qsta) ||
                                pstcov && qsti && 
                                    !qsta.isSubtypeOf(psta) ||
                                qstcov && psti && 
                                    !psta.isSubtypeOf(qsta) ||
                                pstcontra && qsti && 
                                    !psta.isSubtypeOf(qsta) ||
                                qstcontra && psti && 
                                    !qsta.isSubtypeOf(psta)) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }
    
    public static String formatPath(List<String> path, 
            char separator) {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<path.size(); i++) {
            String pathPart = path.get(i);
            if (! pathPart.isEmpty()) {
                sb.append(pathPart);
                if (i<path.size()-1) sb.append(separator);
            }
        }
        return sb.toString();
    }

    public static String formatPath(List<String> path) {
        return formatPath(path, '.');
    }
    
    /**
     * Form the union of the given types, eliminating 
     * duplicates. 
     */
    public static Type unionType(
            Type lhst, Type rhst, Unit unit) {
        List<Type> list = new ArrayList<Type>(2);
        addToUnion(list, rhst);
        addToUnion(list, lhst);
        UnionType ut = new UnionType(unit);
        ut.setCaseTypes(list);
        return ut.getType();
    }

    /**
     * Form the intersection of the given types, 
     * canonicalizing, and eliminating duplicates. 
     */
    public static Type intersectionType(
            Type lhst, Type rhst, Unit unit) {
        Type simpleIntersection = 
                getSimpleIntersection(lhst, rhst);
        if (simpleIntersection != null) {
            return simpleIntersection;
        }
        List<Type> list = new ArrayList<Type>(2);
        addToIntersection(list, rhst, unit);
        addToIntersection(list, lhst, unit);
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(list);
        return it.canonicalize().getType();
    }
    
    /**
     * Form the union of the given types, without 
     * eliminating duplicates.
     */
    public static Type union(
            List<Type> types, Unit unit) {
        if (types.size()==1) {
            return types.get(0);
        }
        UnionType ut = new UnionType(unit);
        ut.setCaseTypes(types);
        return ut.getType();
    }
    
    /**
     * Form the intersection of the given types, without
     * eliminating duplicates nor canonicalizing.
     */
    public static Type intersection(
            List<Type> types, Unit unit) {
        if (types.size()==1) {
            return types.get(0);
        }
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(types);
        return it.getType();
    }

    /**
     * Form the canonical intersection of the given types, 
     * without eliminating duplicates.
     */
    public static Type canonicalIntersection(
            List<Type> types, Unit unit) {
        if (types.size()==1) {
            return types.get(0);
        }
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(types);
        return it.canonicalize().getType();
    }

    private static Type getSimpleIntersection(
            Type a, Type b) {
        if (a == null || b == null) {
            return null;
        }
        TypeDeclaration ad = a.getDeclaration();
        TypeDeclaration bd = b.getDeclaration();
        if (ad == null || bd == null) {
            return null;
        }
        if (!a.isClassOrInterface()) {
            if (a.isUnion() && b.isClassOrInterface()) {
                return getSimpleIntersection(b, 
                        (ClassOrInterface) bd, a);
            }
            return null;
        }
        else if (!b.isClassOrInterface()) {
            // here aDecl MUST BE a ClassOrInterface as per flow
            if (b.isUnion()) {
                return getSimpleIntersection(a, 
                        (ClassOrInterface) ad, b);
            }
            return null;
        }
        String an = ad.getQualifiedNameString();
        String bn = bd.getQualifiedNameString();
        if (an.equals(bn)
                && ad.getTypeParameters().isEmpty()
                && bd.getTypeParameters().isEmpty())
            return a;
        if (a.isAnything()) {
            // everything is an Anything
            return b;
        }
        if (b.isAnything()) {
            // everything is an Anything
            return a;
        }
        if (a.isObject()) {
            // every ClassOrInterface is an object except Null
            if (b.isNull() || b.isNullValue()) {
                return ad.getUnit().getNothingType();
            }
            else {
                return b;
            }
        }
        if (b.isObject()) {
            // every ClassOrInterface is an object except Null
            if (a.isNull() || a.isNullValue()) {
                return bd.getUnit().getNothingType();
            }
            else {
                return a;
            }
        }
        if (a.isNull()) {
            // only null is null
            if (b.isNull() || b.isNullValue()) {
                return b;
            }
            else {
                return ad.getUnit().getNothingType();
            }
        }
        if (b.isNull()) {
            // only null is null
            if (a.isNull() || a.isNullValue()) {
                return a;
            }
            else {
                return bd.getUnit().getNothingType();
            }
        }
        // not simple
        return null;
    }

    private static Type getSimpleIntersection(
            Type a, ClassOrInterface aDecl, 
            Type b) {
        // we only handle Foo|Null
        if (b.getCaseTypes().size() != 2) {
            return null;
        }

        // we only handle Object and Null intersections
        boolean aIsObject = a.isObject();
        boolean aIsNull = a.isNull();
        if (!aIsObject && !aIsNull) {
            return null;
        }
        
        Type caseA = b.getCaseTypes().get(0);
        Type caseB = b.getCaseTypes().get(1);
        boolean isANull = caseA.isNull();
        boolean isBNull = caseB.isNull();
        
        if (aIsObject) {
            if (isANull) {
                return simpleObjectIntersection(aDecl, caseB);
            }
            if (isBNull) {
                return simpleObjectIntersection(aDecl, caseA);
            }
            // too complex
            return null;
        }
        if (aIsNull) {
            if (isANull) {
                return caseA;
            }
            if (isBNull) {
                return caseB;
            }
            // too complex
            return null;
        }
        // too complex
        return null;
    }

    private static Type simpleObjectIntersection(
            ClassOrInterface objectDecl, Type type) {
        if (type.isClassOrInterface()) {
            return type;
        }
        else if (type.isTypeParameter()) {
            List<Type> satisfiedTypes = 
                    type.getSatisfiedTypes();
            if (satisfiedTypes.isEmpty()) {
                // trivial intersection TP&Object
                Unit unit = objectDecl.getUnit();
                List<Type> types = new ArrayList<Type>(2);
                types.add(type);
                types.add(objectDecl.getType());
                return canonicalIntersection(types, unit);
            }
            for (Type sat: satisfiedTypes) {
                if (sat.isObject()) {
                    // it is already an Object
                    return type;
                }
            }
            // too complex
            return null;
        }
        // too complex
        return null;
    }

    public static boolean isElementOfUnion(
            Type unionType, 
            ClassOrInterface ci) {
        for (Type ct: unionType.getCaseTypes()) {
            if (ct.isClassOrInterface() && 
                    ct.getDeclaration().equals(ci)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Find the member which best matches the given signature
     * among the given list of members. In the case that
     * there are multiple matching declarations, attempt to
     * return the "best" match, according to some ad-hoc 
     * rules that roughly follow how Java resolves 
     * overloaded methods.
     * 
     * @param members a list of members to search
     * @param name the name of the member to find
     * @param signature the parameter types to match, or
     *        null if we're not matching on parameter types
     * @param ellipsis true of we want to find a declaration
     *        which supports varags, or false otherwise
     *        
     * @return the best matching declaration
     */
    public static Declaration lookupMember(
            List<Declaration> members, String name,
            List<Type> signature, boolean ellipsis) {
        return lookupMember(members, name, signature, ellipsis, false);
    }

    /**
     * Find the member which best matches the given signature
     * among the given list of members. In the case that
     * there are multiple matching declarations, attempt to
     * return the "best" match, according to some ad-hoc 
     * rules that roughly follow how Java resolves 
     * overloaded methods.
     * 
     * @param members a list of members to search
     * @param name the name of the member to find
     * @param signature the parameter types to match, or
     *        null if we're not matching on parameter types
     * @param ellipsis true of we want to find a declaration
     *        which supports varags, or false otherwise
     * @param onlyExactMatches only consider exact matches
     *        if a signature is provided. This means that if
     *        true, members whose signature does not match
     *        will not be returned. If the signature is null
     *        this parameter has no effect as the signature
     *        will not be checked.
     *        
     * @return the best matching declaration
     */
    public static Declaration lookupMember(
            List<Declaration> members, String name,
            List<Type> signature, boolean ellipsis,
            boolean onlyExactMatches) {
        List<Declaration> results = null;
        Declaration result = null;
        Declaration inexactMatch = null;
        for (int i=0, size=members.size(); i<size ; i++) {
            Declaration d = members.get(i);
            if (isResolvable(d) && isNamed(name, d)) {
                if (signature==null) {
                    //no argument types: either a type 
                    //declaration, an attribute, or a method 
                    //reference - don't return overloaded
                    //forms of the declaration (instead
                    //return the "abstraction" of them)
                    if (notOverloaded(d)) {
                        return d;
                    }
                }
                else {
                    if (notOverloaded(d)) {
                        //we have found either a non-overloaded
                        //declaration, or the "abstraction" 
                        //which of all the overloaded forms 
                        //of the declaration
                        //Note: I could not do this optimization
                        //      because then it could not distinguish
                        //      between Java open() and isOpen()
                        /*if (!isAbstraction(d)) {
                            return d;
                        }*/
                        inexactMatch = d;
                    }
                    if (hasMatchingSignature(d, signature, ellipsis)) {
                        //we have found an exactly matching 
                        //overloaded declaration
                        if (result == null) {
                            result = d; // first match
                        }
                        else {
                            // more than one match, move to array
                            if (results == null) {
                                results = new ArrayList<Declaration>(2);
                                results.add(result);
                            }
                            addIfBetterMatch(results, d, signature);
                        }
                    }
                }
            }
        }
        // if we never needed a results array
        if (results == null) {
            // single result
            if (result != null) {
                return result;
            }
            // no exact match
            return onlyExactMatches ? null : inexactMatch;
        }
        switch (results.size()) {
        case 0:
            //no exact match, so return the non-overloaded
            //declaration or the "abstraction" of the 
            //overloaded declaration
            return onlyExactMatches ? null : inexactMatch;
        case 1:
            //exactly one exact match, so return it
            return results.get(0);
        default:
            //more than one matching overloaded declaration,
            //so return the "abstraction" of the overloaded
            //declaration
            return onlyExactMatches ? null : inexactMatch;
        }
    }

    private static void addIfBetterMatch(
            List<Declaration> results, Declaration d, 
            List<Type> signature) {
        boolean add=true;
        for (Iterator<Declaration> i = results.iterator(); 
                i.hasNext();) {
            Declaration o = i.next();
            if (betterMatch(d, o, signature)) {
                i.remove();
            }
            else if (betterMatch(o, d, signature)) { //TODO: note asymmetry here resulting in nondeterminate behavior!
                add=false;
            }
        }
        if (add) results.add(d);
    }
    
    public static Declaration findMatchingOverloadedClass(
            Class abstractionClass, 
            List<Type> signature, boolean ellipsis) {
        List<Declaration> results = 
                new ArrayList<Declaration>(1);
        if (!abstractionClass.isAbstraction()) {
            return abstractionClass;
        }
        for (Declaration overloaded: 
                abstractionClass.getOverloads()) {
            if (hasMatchingSignature(overloaded, 
                    signature, ellipsis, false)) {
                addIfBetterMatch(results, 
                        overloaded, signature);
            }
        }
        if (results.size() == 1) {
            return results.get(0);
        }
        return abstractionClass;
    }

    public static boolean isTypeUnknown(Type type) {
        return type==null || type.getDeclaration()==null ||
                type.containsUnknowns();
    }

    public static List<Type> getSignature(
            Declaration dec) {
        if (!(dec instanceof Functional)) {
            return null;
        }
        Functional fun = (Functional) dec;
        List<ParameterList> parameterLists = 
                fun.getParameterLists();
        if (parameterLists == null || 
                parameterLists.isEmpty()) {
            return null;
        }
        ParameterList parameterList = 
                parameterLists.get(0);
        if (parameterList == null) {
            return null;
        }
        List<Parameter> parameters = 
                parameterList.getParameters();
        if (parameters == null) {
            return null;
        }
        List<Type> signature = 
                new ArrayList<Type>
                    (parameters.size());
        Unit unit = dec.getUnit();
        for (Parameter param: parameters) {
            FunctionOrValue model = param.getModel();
            Type t;
            if (model instanceof Value) {
                t = model.getType();
            }
            else if (model instanceof Function) {
                t = appliedType(
                        unit.getCallableDeclaration(),
                        model.getType(),
                        unit.getUnknownType());
            }
            else {
                t = unit.getUnknownType();
            }
            signature.add(t);
        }
        return signature;
    }

    public static List<Type> getSignature(
            Reference ref) {
        Declaration dec = ref.getDeclaration();
        if (!(dec instanceof Functional)) {
            return null;
        }
        Functional fun = (Functional) dec;
        List<ParameterList> parameterLists = 
                fun.getParameterLists();
        if (parameterLists == null || 
                parameterLists.isEmpty()) {
            return null;
        }
        ParameterList parameterList = 
                parameterLists.get(0);
        if (parameterList == null) {
            return null;
        }
        List<Parameter> parameters = 
                parameterList.getParameters();
        if (parameters == null) {
            return null;
        }
        List<Type> signature = 
                new ArrayList<Type>
                    (parameters.size());
        Unit unit = dec.getUnit();
        for (Parameter param: parameters) {
            TypedReference typedParameter = 
                    ref.getTypedParameter(param);
            Type t = 
                    typedParameter==null ? 
                        unit.getUnknownType() : 
                        typedParameter.getType();
            signature.add(t);
        }
        return signature;
    }

    public static boolean isCompletelyVisible(
            Declaration member, Type pt) {
        if (pt.isUnion()) {
            for (Type ct: pt.getCaseTypes()) {
                if (!isCompletelyVisible(member, 
                        ct.substitute(pt))) {
                    return false;
                }
            }
            return true;
        }
        else if (pt.isIntersection()) {
            for (Type ct: pt.getSatisfiedTypes()) {
                if (!isCompletelyVisible(member, 
                        ct.substitute(pt))) {
                    return false;
                }
            }
            return true;
        }
        else if (pt.isTypeAlias() && 
                pt.getDeclaration().isAnonymous()) {
            //for anonymous type constructors
            return isCompletelyVisible(member,
                    pt.getExtendedType());
        }
        else {
            if (!isVisible(member, pt.getDeclaration())) {
                return false;
            }
            for (Type at: pt.getTypeArgumentList()) {
                if (at!=null && 
                        !isCompletelyVisible(member, at)) {
                    return false;
                }
            }
            return true;
        }
    }

    static boolean isVisible(Declaration member, 
            TypeDeclaration type) {
        return type instanceof TypeParameter || 
                type.isVisible(member.getVisibleScope()) &&
                (member.getVisibleScope()!=null || 
                !member.getUnit().getPackage().isShared() || 
                type.getUnit().getPackage().isShared());
    }

    /**
     * Given two instantiations of the same type constructor,
     * construct a principal instantiation that is a supertype
     * of both. This is impossible in the following special
     * cases:
     * 
     * - an abstract class which does not obey the principal
     *   instantiation inheritance rule
     * - an intersection between two instantiations of the
     *   same type where one argument is a type parameter
     * 
     * Nevertheless, we give it our best shot!
     * 
     * @param dec the type constructor
     * @param first the first instantiation
     * @param second the second instantiation
     * 
     */
    public static Type principalInstantiation(
            TypeDeclaration dec, 
            Type first, Type second, 
            Unit unit) {
        List<TypeParameter> tps = dec.getTypeParameters();
        List<Type> args = new ArrayList<Type>(tps.size());
        Map<TypeParameter,SiteVariance> varianceOverrides =
                new HashMap<TypeParameter,SiteVariance>(1);
        for (TypeParameter tp: tps) {
            Type firstArg = 
                    first.getTypeArguments().get(tp);
            Type secondArg = 
                    second.getTypeArguments().get(tp);
            Type arg;
            if (firstArg==null || secondArg==null) {
                arg = unit.getUnknownType();
            }
            else {
                boolean firstCo = first.isCovariant(tp);
                boolean secondCo = second.isCovariant(tp);
                boolean firstContra = first.isContravariant(tp);
                boolean secondContra = second.isContravariant(tp);
                boolean firstInv = !firstCo && !firstContra;
                boolean secondInv = !secondCo && !secondContra;
                boolean parameterized = 
                        firstArg.involvesTypeParameters() ||
                        secondArg.involvesTypeParameters();
                if (firstContra && secondContra) {
                    arg = unionType(
                            firstArg, secondArg, unit);
                    if (!tp.isContravariant()) {
                        varianceOverrides.put(tp, IN);
                    }
                }
                else if (firstCo && secondCo) {
                    arg = intersectionType(
                            firstArg, secondArg, unit);
                    if (!tp.isCovariant()) {
                        varianceOverrides.put(tp, OUT);
                    }
                }
                else if (firstContra && secondInv) {
                    if (firstArg.isSubtypeOf(secondArg)) {
                        arg = secondArg;
                    }
                    else if (parameterized) {
                       //irreconcilable instantiations
                       arg = unit.getUnknownType();
                    }
                    else {
                        return unit.getNothingType();
                    }
                }
                else if (firstCo && secondInv) {
                    if (secondArg.isSubtypeOf(firstArg)) {
                        arg = secondArg;
                    }
                    else if (parameterized) {
                       //irreconcilable instantiations
                       arg = unit.getUnknownType();
                    }
                    else {
                        return unit.getNothingType();
                    }
                }
                else if (secondCo && firstInv) {
                   if (firstArg.isSubtypeOf(secondArg)) {
                       arg = firstArg;
                   }
                   else if (parameterized) {
                      //irreconcilable instantiations
                      arg = unit.getUnknownType();
                   }
                   else {
                       return unit.getNothingType();
                   }
                }
                else if (secondContra && firstInv) {
                    if (secondArg.isSubtypeOf(firstArg)) {
                        arg = firstArg;
                    }
                    else if (parameterized) {
                        //irreconcilable instantiations
                        arg = unit.getUnknownType();
                    }
                    else {
                        return unit.getNothingType();
                    }
                }
                else if (firstInv && secondInv) {
                    if (firstArg.isExactly(secondArg)) {
                        arg = firstArg;
                    }
                    else if (parameterized) {
                        //type parameters that might represent 
                        //equivalent types at runtime, 
                        //irreconcilable instantiations
                        //TODO: detect cases where we know for
                        //      sure that the types are disjoint
                        //      because the type parameters only
                        //      occur as type args
                        arg = unit.getUnknownType();
                    }
                    else {
                        //the type arguments are distinct, and the
                        //intersection is Nothing, so there is
                        //no reasonable principal instantiation
                        return unit.getNothingType();
                    }
                }
                else {
                    //opposite variances
                    //irreconcilable instantiations
                    arg = unit.getUnknownType();
                }
            }
            args.add(arg);
        }
        Type result =
                dec.appliedType(principalQualifyingType(
                        first, second, dec, unit), 
                        args);
        result.setVarianceOverrides(varianceOverrides);
        return result;
    }
    
    public static boolean areConsistentSupertypes(
            Type st1, Type st2, Unit unit) {
        //can't inherit two instantiations of an invariant type
        //Note: I don't think we need to check type parameters of 
        //      the qualifying type, since you're not allowed to
        //      subtype an arbitrary instantiation of a nested
        //      type - only supertypes of the outer type
        //      Nor do we need to check variance overrides since
        //      supertypes can't have them.
        List<TypeParameter> typeParameters = 
                st1.getDeclaration().getTypeParameters();
        for (TypeParameter tp: typeParameters) {
            if (!tp.isCovariant() && !tp.isContravariant()) {
                Type ta1 = st1.getTypeArguments().get(tp);
                Type ta2 = st2.getTypeArguments().get(tp);
                if (ta1!=null && ta2!=null && 
                        !ta1.isExactly(ta2)) {
                    return false;
                }
            }
        }
        return !intersectionType(st1, st2, unit).isNothing();
    }
    
    /**
     * The intersection of the types inherited of the given
     * declaration. No need to worry about canonicalization
     * because:
     * 
     * 1. an inherited type can't be a union, and
     * 2. they are prevented from being disjoint types.
     */
    public static Type intersectionOfSupertypes(
            TypeDeclaration td) {
        Type extendedType = td.getExtendedType();
        List<Type> satisfiedTypes = td.getSatisfiedTypes();
        List<Type> list = 
                new ArrayList<Type>
                    (satisfiedTypes.size()+1);
        if (extendedType!=null) {
            list.add(extendedType);
        }
        list.addAll(satisfiedTypes);
        Unit unit = td.getUnit();
        IntersectionType it = new IntersectionType(unit);
        it.setSatisfiedTypes(list);
        return it.getType();
    }

    /**
     * The union of the case types of the given declaration.
     */
    public static Type unionOfCaseTypes(
            TypeDeclaration td) {
        List<Type> caseTypes = td.getCaseTypes();
        Unit unit = td.getUnit();
        if (caseTypes==null) {
            return unit.getAnythingType();
        }
        List<Type> list =
                new ArrayList<Type>
                    (caseTypes.size()+1);
        list.addAll(caseTypes);
        UnionType it = new UnionType(unit);
        it.setCaseTypes(list);
        return it.getType();
    }

    public static int addHashForModule(int ret, Declaration decl) {
        Module module = getModule(decl);
        return (37 * ret) + 
                (module != null ? module.hashCode() : 0);
    }

    public static boolean sameModule(Declaration a, Declaration b) {
        Module aMod = getModule(a);
        Module bMod = getModule(b);
        return aMod.equals(bMod);
    }

    public static void clearProducedTypeCache(TypeDeclaration decl) {
        Module module = getModule(decl);
        if(module != null){
            module.clearCache(decl);
        }
    }
    
    public static List<Declaration> getInterveningRefinements(
            String name, List<Type> signature, 
            Declaration root,
            TypeDeclaration bottom, TypeDeclaration top) {
        boolean rootOverloaded = isOverloadedVersion(root);
        List<Declaration> result = 
                new ArrayList<Declaration>(2);
        for (TypeDeclaration std: 
                bottom.getSupertypeDeclarations()) {
            if (std.inherits(top) && !std.equals(bottom)) {
                Declaration member = 
                        std.getDirectMember(name,
                                //TODO: should we use the 
                                //signature of root here?
                                signature, false,
                                rootOverloaded);
                if (member!=null && 
                        member.isShared() && 
                        !isAbstraction(member)) {
                    TypeDeclaration td = 
                            (TypeDeclaration) 
                                member.getContainer();
                    boolean overloaded = 
                            rootOverloaded ||
                            isOverloadedVersion(member);
                    Declaration refined = 
                            td.getRefinedMember(name, 
                                    signature, false,
                                    overloaded);
                    if (refined!=null && 
                            refined.equals(root)) {
                        result.add(member);
                    }
                }
            }
        }
        return result;
    }
    
    public static List<Declaration> getInheritedDeclarations(
            String name, TypeDeclaration bottom) {
        List<Declaration> result = 
                new ArrayList<Declaration>(2);
        for (TypeDeclaration std: 
                bottom.getSupertypeDeclarations()) {
            if (!std.equals(bottom)) {
                Declaration member = 
                        std.getDirectMember(name, 
                                null, false);
                if (member!=null && 
                        member.isShared() && 
                        !isAbstraction(member)) {
                    result.add(member);
                }
            }
        }
        return result;
    }
    
    /**
     * Is the given declaration a constructor or singleton
     * constructor of a toplevel class?
     * 
     * Constructors of toplevel classes can be directly
     * imported into the toplevel namespace of a compilation
     * unit.
     */
    public static boolean isToplevelClassConstructor(
            TypeDeclaration td, Declaration dec) {
        return td.isToplevel() && 
                (dec instanceof Constructor ||
                dec instanceof FunctionOrValue && 
                ((FunctionOrValue) dec).getTypeDeclaration() 
                        instanceof Constructor);
    }

    /**
     * Is the given declaration a toplevel anonymous class?
     * 
     * Members of toplevel anonymous classes can be directly
     * imported into the toplevel namespace of a compilation
     * unit.
     */
    public static boolean isToplevelAnonymousClass(Scope s) {
        if (s instanceof Class) {
            Class td = (Class) s;
            return td.isAnonymous() && td.isToplevel();
        }
        else {
            return false;
        }
    }
    
    public static boolean isNativeHeader(Declaration dec) {
        return dec != null && dec.isNativeHeader();
    }
    
    public static boolean isNativeImplementation(Declaration dec) {
        return dec != null && dec.isNativeImplementation();
    }
    
    public static Declaration getNativeDeclaration(
            Declaration dec, Backend backend) {
        return getNativeDeclaration(dec, backend.asSet());
    }
    
    public static Declaration getNativeDeclaration(
            Declaration dec, Backends backends) {
        if (dec.isNative() && 
                backends != null) {
            Declaration abstraction = null;
            if (backends.none() &&
                    !dec.isNativeHeader()
                || backends.supports(
                        dec.getNativeBackends())) {
                abstraction = dec;
            }
            else {
                List<Declaration> overloads = 
                        dec.getOverloads();
                if (overloads != null) {
                    for (Declaration d: overloads) {
                        if (backends.none() &&
                                !d.isNativeHeader()
                            || backends.supports(
                                    d.getNativeBackends())) {
                            abstraction = d;
                            break;
                        }
                    }
                }
            }
            return abstraction;
        }
        else {
            return dec;
        }
    }

    public static boolean isForBackend(Backends backends,
            Backend supported) {
        return isForBackend(backends, supported.asSet());
    }
    
    public static boolean isForBackend(Backends backends,
            BackendSupport support) {
        return isForBackend(backends, support.getSupportedBackends());
    }
    
    public static boolean isForBackend(Backends backends,
            Backends supported) {
        return backends.none() || supported.supports(backends);
    }
    
    /**
     * The list of type parameters of the given generic
     * declaration as types. (As viewed within the body of
     * the generic declaration.)
     * 
     * @param dec a generic declaration or type constructor
     * @return a list of types of its type parameters
     * 
     * @see Declaration#getTypeParametersAsArguments
     */
    public static List<Type> typeParametersAsArgList(Generic dec) {
        List<TypeParameter> params = 
                dec.getTypeParameters();
        if (params.isEmpty()) {
            return NO_TYPE_ARGS;
        }
        int size = params.size();
        List<Type> paramsAsArgs = 
                new ArrayList<Type>(size);
        for (int i=0; i<size; i++) {
            TypeParameter param = params.get(i);
            paramsAsArgs.add(param.getType());
        }
        return paramsAsArgs;
    }
    
    /**
     * Find the declaration with the given name that occurs
     * in the list of members, taking into account the
     * given backends, if any. Does not take into account
     * Java overloading
     *  
     * @param members a list of member declarations
     * @param name the name of a declaration occurring 
     *        directly in the scope, and not overloaded
     * @param backends the native backends
     * 
     * @return the matching declaration
     */
    public static Declaration lookupMemberForBackend(
            List<Declaration> members, String name, Backends backends) {
        for (Declaration dec: members) {
            if (isResolvable(dec) && isNamed(name, dec)) {
                Backends bs = dec.getNativeBackends();
                if (bs.none()) {
                    return dec;
                }
                else if (backends.supports(bs)) {
                    return dec;
                }
            }
        }
        return null;
    }
    
    /**
     * Find the all the declarations with the given
     * name that occur in the given list of members
     *  
     * @param members a list of member declarations
     * @param name the name of a declaration
     * 
     * @return the matching declarations
     */
    public static List<Declaration> lookupOverloadedByName(
            List<Declaration> members, String name) {
        List<Declaration> result = null;
        for (Declaration dec: members) {
            if (isResolvable(dec) && isNamed(name, dec)) {
                if (result == null) {
                    result = new ArrayList<Declaration>(3);
                }
                result.add(dec);
            }
        }
        if (result == null) {
            result = Collections.emptyList();
        }
        return result;
    }
    
    public static Declaration getNativeHeader(Declaration dec) {
        Declaration header = 
                getNativeHeader(dec.getContainer(), 
                        dec.getName());
        // In case of objects make sure we return the same type of
        // declaration we were called with
        if (dec instanceof ClassOrInterface && 
                header instanceof Value) {
            Value value = (Value) header;
            if (isObject(value)) {
                return value.getType().getDeclaration();
            }
        }
        // In case of constructors we make sure we return the same
        // type of declaration we were called with
        if (dec instanceof Constructor
                && header instanceof FunctionOrValue
                && isConstructor(header)) {
            return getConstructor(header);
        }
        return header;
    }
    
    public static Declaration getNativeHeader(Scope container, String name) {
        return getNativeDeclaration(container, name, Backends.HEADER);
    }
    
    /**
     * Find the declaration with the given name and backend
     * that occurs directly in the given scope or if that
     * scope is itself native first look up the scope's
     * proper backend declaration and find the requested
     * declaration there.
     *  
     * @param scope any scope
     * @param name the name of a declaration
     * @param backends the backends to obtain
     * 
     * @return the matching declaration
     */
    public static Declaration getNativeDeclaration(Scope container, String name, Backends backends) {
        if (container instanceof Declaration) {
            Declaration cd = (Declaration) container;
            if (backends.header() && cd.isNativeImplementation() ||
                    !backends.header() && cd.isNativeHeader()) {
                // The container is a native implementation so
                // we first need to find _its_ header
                Declaration c = getNativeDeclaration(cd.getContainer(), cd.getName(), backends);
                if (c != null) {
                    // Is this the Value part of an object?
                    if (c instanceof Value) {
                        Value v = (Value) c;
                        if (isObject(v)) {
                            // Then use the Class part as the container
                            c = v.getType().getDeclaration();
                        }
                    }
                    container = (Scope) c;
                }
            }
        }
        // Find the declaration
        Declaration decl;
        if (container instanceof Class && name == null) {
            // Special case for the default constructor
            Class c = (Class) container;
            decl = c.getDefaultConstructorFunctionOrValue();
        }
        else {
            decl =
                    container.getDirectMemberForBackend(
                            name,
                            backends);
        }
        return decl;
    }
    
    // Check if the Value is part of an object or value constructor
    public static boolean isObject(Value value) {
        Type type = value.getType();
        // Check type because in case of compile errors it can be null
        return type != null &&
                type.getDeclaration()
                    .isAnonymous();
    }

    public static boolean isImplemented(Declaration dec) {
        if (dec instanceof FunctionOrValue) {
            FunctionOrValue fov = (FunctionOrValue) dec;
            return fov.isImplemented();
        }
        else if (dec instanceof Class) {
            Class coi = (Class) dec;
            if (dec.isNativeHeader()) {
                for (Declaration m : coi.getMembers()) {
                    if (!isImplemented(m)) {
                        return false;
                    }
                }
                return true;
            } else {
                return coi.isAbstract();
            }
        }
        else if (dec instanceof Constructor ||
                dec instanceof Interface) {
            // For now consider these implemented
            return true;
        }
        return false;
    }
    
    public static boolean eq(Object decl, Object other) {
        if (decl == null) {
            return other == null;
        }
        else {
            return decl.equals(other);
        }
    }

    public static boolean equal(Declaration decl, Declaration other) {
        if (decl instanceof UnionType || 
            decl instanceof IntersectionType || 
            other instanceof UnionType || 
            other instanceof IntersectionType) {
            return false;
        }
        return ModelUtil.eq(decl, other);
    }

    public static boolean equalModules(Module scope, Module other) {
        return eq(scope, other);
    }

    public static Module getModule(Declaration decl) {
        return decl.getUnit().getPackage().getModule();
    }

    public static Package getPackage(Declaration decl) {
        return decl.getUnit().getPackage();
    }

    public static Package getPackageContainer(Scope scope) {
        // stop when null or when it's a Package
        while(scope != null
                && !(scope instanceof Package)){
            // stop if the container is not a Scope
            if(!(scope.getContainer() instanceof Scope))
                return null;
            scope = (Scope) scope.getContainer();
        }
        return (Package) scope;
    }

    public static Module getModuleContainer(Scope scope) {
        Package pkg = getPackageContainer(scope);
        return pkg != null ? pkg.getModule() : null;
    }

    public static ClassOrInterface getClassOrInterfaceContainer(
            Element decl) {
        return getClassOrInterfaceContainer(decl, true);
    }
    
    public static ClassOrInterface getClassOrInterfaceContainer(
            Element decl, boolean includingDecl) {
        if (!includingDecl) {
            if(decl.getContainer() instanceof Element == false)
                return null;
            decl = (Element) decl.getContainer();
        }
        // stop when null or when it's a ClassOrInterface
        while(decl != null
                && !(decl instanceof ClassOrInterface)){
            // stop if the container is not an Element
            if(!(decl.getContainer() instanceof Element))
                return null;
            decl = (Element) decl.getContainer();
        }
        return (ClassOrInterface) decl;
    }

    public static void setVisibleScope(Declaration model) {
        Scope s=model.getContainer();
        while (s!=null) {
            if (s instanceof Declaration) {
                if (model.isShared()) {
                    if (!((Declaration) s).isShared()) {
                        model.setVisibleScope(s.getContainer());
                        break;
                    }
                }
                else {
                    model.setVisibleScope(s);
                    break;
                }
            }
            else if (s instanceof Package) {
                //TODO: unshared packages!
                /*if (!((Package) s).isShared()) {
                    model.setVisibleScope(s);
                }*/
                if (!model.isShared()) {
                    model.setVisibleScope(s);
                }
                //null visible scope means visible everywhere
                break;
            }
            else {
                model.setVisibleScope(s);
                break;
            }    
            s = s.getContainer();
        }
    }

    /**
     * Determines whether the declaration's containing scope is a class or interface
     * @param decl The declaration
     * @return true if the declaration is within a class or interface
     */
    public static boolean withinClassOrInterface(Declaration decl) {
        return decl.getContainer() instanceof ClassOrInterface;
    }

    public static boolean withinClass(Declaration decl) {
        return decl.getContainer() instanceof Class;
    }

    public static boolean isLocalToInitializer(Declaration decl) {
        return withinClass(decl) && !isCaptured(decl);
    }

    public static boolean isCaptured(Declaration decl) {
        // Shared elements are implicitely captured although the typechecker doesn't mark them that way
        return decl.isCaptured() || decl.isShared();
    }

    public static boolean isNonTransientValue(Declaration decl) {
        return (decl instanceof Value)
                && !((Value)decl).isTransient();
    }

    /**
     * Is the given scope a local scope but not an initializer scope?
     */
    public static boolean isLocalNotInitializerScope(Scope scope) {
        return scope instanceof FunctionOrValue 
                || scope instanceof Constructor
                || scope instanceof ControlBlock
                || scope instanceof NamedArgumentList
                || scope instanceof Specification;
    }

    /**
     * Determines whether the declaration is local to a method,
     * getter or setter, but <strong>returns {@code false} for a declaration 
     * local to a Class initializer.</strong>
     * @param decl The declaration
     * @return true if the declaration is local
     */
    public static boolean isLocalNotInitializer(Declaration decl) {
        return isLocalNotInitializerScope(decl.getContainer());
    }

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

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

        return false;
    }

    public static boolean isBooleanTrue(Declaration d) {
        return d!=null && d.getQualifiedNameString()
                .equals("ceylon.language::true");
    }

    public static boolean isBooleanFalse(Declaration d) {
        return d!=null && d.getQualifiedNameString()
                .equals("ceylon.language::false");
    }

    public static Type genericFunctionType(
            Generic generic, Scope scope, 
            Declaration member, Reference reference, 
            Unit unit) {
        List<TypeParameter> typeParameters = 
                generic.getTypeParameters();
        TypeAlias ta = new TypeAlias();
        ta.setContainer(scope);
        ta.setName("Anonymous#" + member.getName());
        ta.setAnonymous(true);
        ta.setScope(scope);
        ta.setUnit(unit);
        ta.setExtendedType(reference.getFullType());
        ta.setTypeParameters(typeParameters);
        Type type = ta.getType();
        type.setTypeConstructor(true);
        return type;
    }

    public static boolean isConstructor(Declaration member) {
        if (member instanceof Constructor) {
            return true;
        }
        else if (member instanceof FunctionOrValue) {
            FunctionOrValue fov = (FunctionOrValue) member;
            return fov.getTypeDeclaration() 
                    instanceof Constructor;
        }
        else {
            return false;
        }
    }
    
    public static Constructor getConstructor(Declaration member) {
        if (member instanceof Constructor) {
            return (Constructor) member;
        }
        else if (member instanceof FunctionOrValue) {
            FunctionOrValue fov = (FunctionOrValue) member;
            TypeDeclaration td = fov.getTypeDeclaration();
            if (td instanceof Constructor) {
                return (Constructor) td;                
            }
            else {
                return null;
            }
        }
        else {
            return null;
        }
    }
    
    public static boolean isCeylonDeclaration(Declaration declaration) {
        if (declaration instanceof LazyElement) {
            LazyElement lazy = (LazyElement) declaration;
            return lazy.isCeylon();
        }
        else {
            // if it's not one of those it must be from source (Ceylon)
            return true;
        }
    }
} public class Module 
        implements Referenceable, Annotated, Comparable<Module> {

    public static final String LANGUAGE_MODULE_NAME = "ceylon.language";
    public static final String DEFAULT_MODULE_NAME = "default";

    private List<String> name;
    private String version;
    private int major;
    private int minor;
    private List<Package> packages = 
            new ArrayList<Package>();
    private List<ModuleImport> imports = 
            new ArrayList<ModuleImport>();
    private Module languageModule;
    private boolean available;
    private boolean isDefault;
    private List<Annotation> annotations = 
            new ArrayList<Annotation>();
    private Unit unit;
    private String memoisedName;
    private TypeCache cache = new TypeCache();
    private String signature;
    private List<ModuleImport> overridenImports = null;
    private Backends nativeBackends = Backends.ANY;

    public Module() {}
    
    /**
     * Whether or not the module is available in the
     * source path or the repository
     */
    public boolean isAvailable() {
        return available;
    }

    public void setAvailable(boolean available) {
        this.available = available;
    }

    public List<String> getName() {
        return name;
    }

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

    public List<Package> getPackages() {
        return packages;
    }

    public List<ModuleImport> getImports() {
        return Collections.unmodifiableList(imports);
    }
    
    public void addImport(ModuleImport modImport) {
        imports.add(modImport);
    }
    
    public Module getLanguageModule() {
        return languageModule;
    }
    
    public void setLanguageModule(Module languageModule) {
        this.languageModule = languageModule;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
    
    /**
     * Get all packages belonging to this module and modules 
     * transitively imported by this module that are visible
     * to this module. 
     */
    public List<Package> getAllVisiblePackages() {
        List<Package> list = 
                new ArrayList<Package>(getPackages());
        addVisiblePackagesOfTransitiveDependencies(list, 
                new HashSet<String>(), true);
        return list;
    }
    
    private void addVisiblePackagesOfTransitiveDependencies(
            List<Package> list, 
            Set<String> alreadyScannedModules, 
            boolean firstLevel) {
        for (ModuleImport mi: getImports()) {
            if (firstLevel || mi.isExport()) {
                Module importedModule = mi.getModule();
                String imn = importedModule.getNameAsString();
                if (alreadyScannedModules.add(imn)) {
                    for (Package p: 
                            importedModule.getPackages()) {
                        if (p.isShared()) {
                            list.add(p);
                        }
                    }
                    importedModule.addVisiblePackagesOfTransitiveDependencies(
                            list, alreadyScannedModules, false);
                }
            }
        }
    }
    
    /**
     * Get all packages belonging to this module and modules 
     * transitively imported by this module, including 
     * packages that aren't visible to this module. 
     */
    public List<Package> getAllReachablePackages() {
        List<Package> list = new ArrayList<Package>();
        list.addAll(getPackages());
        addAllPackagesOfTransitiveDependencies(list, 
                new HashSet<String>());
        return list;
    }

    private void addAllPackagesOfTransitiveDependencies(
            List<Package> list, 
            Set<String> alreadyScannedModules) {
        for (ModuleImport mi: getImports()) {
            Module importedModule = mi.getModule();
            String imn = importedModule.getNameAsString();
            if (alreadyScannedModules.add(imn)) {
                for (Package p: importedModule.getPackages()) {
                    list.add(p);
                }
                importedModule.addVisiblePackagesOfTransitiveDependencies(
                        list, alreadyScannedModules, false);
            }
        }
    }
    
    public Map<String, DeclarationWithProximity> 
    getAvailableDeclarations(String startingWith, 
            int proximity) {
        Map<String, DeclarationWithProximity> result = 
                new TreeMap<String,DeclarationWithProximity>();
        for (Package p: getAllVisiblePackages()) {
            String packageName = 
                    p.getNameAsString();
            boolean isLanguageModule = 
                    packageName.equals(LANGUAGE_MODULE_NAME);
            boolean isDefaultPackage = 
                    packageName.isEmpty();
            if (!isDefaultPackage) {
                for (Declaration d: p.getMembers()) {
                    try {
                        if (isResolvable(d) && 
                                d.isShared() && 
                                !isOverloadedVersion(d) &&
                                isNameMatching(startingWith, d)) {
                            String name = d.getName();
                            boolean isSpecialValue = 
                                    isLanguageModule &&
                                        name.equals("true") || 
                                        name.equals("false") || 
                                        name.equals("null");
                            boolean isSpecialType = 
                                    isLanguageModule &&
                                        name.equals("String") ||
                                        name.equals("Integer") ||
                                        name.equals("Float") ||
                                        name.equals("Character") ||
                                        name.equals("Boolean") ||
                                        name.equals("Byte") ||
                                        name.equals("Object") ||
                                        name.equals("Anything");
                            int prox;
                            if (isSpecialValue) {
                                prox = -1;
                            }
                            else if (isSpecialType) {
                                //just less than toplevel
                                //declarations of the package
                                prox = proximity+2;
                            }
                            else if (isLanguageModule) {
                                //just less than toplevel
                                //declarations of the package
                                prox = proximity+3;
                            }
                            else {
                                //unimported declarations
                                //that may be imported
                                prox = proximity+4;
                            }
                            result.put(d.getQualifiedNameString(), 
                                    new DeclarationWithProximity(d, 
                                            prox, !isLanguageModule));
                        }
                    }
                    catch (Exception e) {}
                }
            }
        }
        if ("Nothing".startsWith(startingWith)) {
            result.put("Nothing", 
                    new DeclarationWithProximity(
                            new NothingType(unit),
                            //same as other "special" 
                            //language module declarations
                            proximity+2));
        }
        return result;
    }

    protected boolean isJdkModule(String moduleName) {
        // overridden by subclasses
        return false;
    }

    protected boolean isJdkPackage(String moduleName, 
            String packageName) {
        // overridden by subclasses
        return false;
    }

    public Package getDirectPackage(String name) {
        for (Package pkg: packages) {
            if (pkg.getQualifiedNameString().equals(name)) {
                return pkg;
            }
        }
        return null;
    }
    
    public Package getPackage(String name) {
        Package pkg = getDirectPackage(name);
        if(pkg != null)
            return pkg;
        for (ModuleImport mi: imports) {
            pkg = mi.getModule().getDirectPackage(name);
            if(pkg != null)
                return pkg;
        }
        return null;
    }
    
    public Package getRootPackage() {
        return getPackage(getNameAsString());
    }
    
    public String getNameAsString() {
        if (memoisedName == null){
            StringBuilder sb = new StringBuilder();
            for (int i=0, s=name.size(); i<s; i++) {
                sb.append(name.get(i));
                if (i < name.size()-1) {
                    sb.append('.');
                }
            }
            memoisedName = sb.toString();
        }
        return memoisedName;
    }

    @Override
    public String toString() {
        return "module " + getNameAsString() + 
                " \"" + getVersion() + "\"";
    }
    
    /**
     * Is this the default module hosting all units outside 
     * of an explicit module
     */
    public boolean isDefault() {
        return isDefault;
    }

    public void setDefault(boolean isDefault) {
        this.isDefault = isDefault;
    }

    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }

    public boolean isJava() {
        return false;
    }

    public boolean isNative() {
        return !getNativeBackends().none();
    }
    
    public Backends getNativeBackends() {
        return nativeBackends;
    }
    
    public void setNativeBackends(Backends backends) {
        this.nativeBackends=backends;
    }
    
    @Override
    public Unit getUnit() {
        return unit;
    }
    
    public void setUnit(Unit unit) {
        this.unit = unit;
    }

    public int getMajor() {
        return major;
    }

    public void setMajor(int major) {
        this.major = major;
    }

    public int getMinor() {
        return minor;
    }

    public void setMinor(int minor) {
        this.minor = minor;
    }

    @Override
    public int compareTo(Module other) {
        if (this == other) {
            return 0;
        }
        // default first
        if (isDefault()) {
            return -1;
        }
        String name = this.getNameAsString();
        String otherName = other.getNameAsString();
        int cmp = name.compareTo(otherName);
        if (cmp != 0) {
            return cmp;
        }
        // we don't care about how versions are compared, we 
        // just care that the order is consistent
        String version = this.getVersion();
        String otherVersion = other.getVersion();
        return version.compareTo(otherVersion);
    }

    public TypeCache getCache(){
        return cache;
    }

    public void clearCache(TypeDeclaration declaration) {
        TypeCache cache = getCache();
        if (cache != null){
            cache.clearForDeclaration(declaration);
        }
        // FIXME: propagate to modules that import this module transitively
        // Done in the IDE JDTModule
    }
    
    public String getSignature() {
        if (signature == null) {
            if (isDefault()) {
                signature = getNameAsString();
            }
            else {
                signature = getNameAsString() + 
                        "/" + getVersion();
            }
        }
        return signature;
    }

    public List<ModuleImport> getOverridenImports() {
        return overridenImports == null ? null :
            unmodifiableList(overridenImports);
    }

    public boolean overrideImports(
            List<ModuleImport> newModuleImports) {
        if (overridenImports == null 
                && newModuleImports != null) {
            overridenImports  = imports;
            imports = newModuleImports;
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return getSignature().hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Module) {
            Module b = (Module) obj;
            return getSignature().equals(b.getSignature());
        }
        else {
            return false;
        }
    }
} public class ModuleImport implements Annotated {
    private boolean optional;
    private boolean export;
    private Module module;
    private Backends nativeBackends;
    private List<Annotation> annotations = new ArrayList<Annotation>();
    private ModuleImport overridenModuleImport = null;

    public ModuleImport(Module module, boolean optional, boolean export) {
        this(module, optional, export, (Backend)null);
    }

    public ModuleImport(Module module, boolean optional, boolean export, Backend backend) {
        this(module, optional, export, backend != null ? backend.asSet() : Backends.ANY);
    }

    public ModuleImport(Module module, boolean optional, boolean export, Backends backends) {
        this.module = module;
        this.optional = optional;
        this.export = export;
        this.nativeBackends = backends;
    }

    public boolean isOptional() {
        return optional;
    }

    public boolean isExport() {
        return export;
    }

    public Module getModule() {
        return module;
    }
    
    public boolean isNative() {
        return !getNativeBackends().none();
    }
    
    public Backends getNativeBackends() {
        return nativeBackends;
    }
    
    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }

    public ModuleImport getOverridenModuleImport() {
        return overridenModuleImport;
    }

    public boolean override(ModuleImport moduleImportOverride) {
        if (overridenModuleImport == null
        		&& moduleImportOverride != null) {
            this.overridenModuleImport = new ModuleImport(module, optional, export, nativeBackends);
            module = moduleImportOverride.getModule();
            optional = moduleImportOverride.isOptional();
            export = moduleImportOverride.isExport();
            nativeBackends = moduleImportOverride.getNativeBackends();
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        if (export) sb.append("shared ");
        if (optional) sb.append("optional ");
        if (!nativeBackends.none()) {
            sb.append("native(")
              .append(nativeBackends)
              .append(") ");
        }
        sb.append("import ")
          .append(module.getNameAsString())
          .append(" \"")
          .append(module.getVersion())
          .append("\"");
        return sb.toString();
    }
} public class Package 
        implements ImportableScope, Referenceable, Annotated {

    private List<String> name;
    private Module module;
    private List<Unit> units = 
            new ArrayList<Unit>();
    private boolean shared = false;
    private List<Annotation> annotations = 
            new ArrayList<Annotation>();
    private Unit unit;
    private String nameAsString;
    
    @Override
    public boolean isToplevel() {
        return false;
    }
    
    public Module getModule() {
        return module;
    }

    public void setModule(Module module) {
        this.module = module;
    }

    public List<String> getName() {
        return name;
    }

    public void setName(List<String> name) {
        this.name = name;
    }
    
    public Iterable<Unit> getUnits() {
        synchronized (units) {
            return new ArrayList<Unit>(units);
        }
    }
    
    public void addUnit(Unit unit) {
        synchronized (units) {
            units.add(unit);
            members=null;
        }
    }
    
    public void removeUnit(Unit unit) {
        synchronized (units) {
            units.remove(unit);
            members=null;
        }
    }
    
    public boolean isShared() {
        return shared;
    }
    
    public void setShared(boolean shared) {
        this.shared = shared;
    }
    
    private List<Declaration> members;
    
    @Override
    public List<Declaration> getMembers() {
        synchronized (units) {
            //return getMembersInternal();
            if (members==null) {
                members = getMembersInternal();
            }
            return members;
        }
    }
    
    @Override
    public void addMember(Declaration declaration) {
        members=null;
    }
    
    private List<Declaration> getMembersInternal() {
        List<Declaration> result = 
                new ArrayList<Declaration>();
        for (Unit unit: units) {
            for (Declaration d: unit.getDeclarations()) {
                if (d.getContainer().equals(this)) {
                    result.add(d);
                }
            }
        }
        return result;
    }

    @Override
    public Scope getContainer() {
        return null;
    }

    @Override
    public Scope getScope() {
        return null;
    }

    public String getNameAsString() {
        if (nameAsString == null){
            nameAsString = formatPath(name);
        }
        return nameAsString;
    }

    @Override
    public String toString() {
        return "package " + getNameAsString();
    }
    
    @Override
    public String getQualifiedNameString() {
        return getNameAsString();
    }
    
    /**
     * Search only inside the package, ignoring imports
     */
    @Override
    public Declaration getMember(String name, 
            List<Type> signature, boolean ellipsis) {
        return getDirectMember(name, signature, ellipsis);
    }

    @Override
    public Declaration getDirectMember(String name, 
            List<Type> signature, boolean ellipsis) {
        return lookupMember(getMembers(), 
                name, signature, ellipsis);
    }

    @Override
    public Declaration getDirectMemberForBackend(String name, 
            Backends backends) {
        return lookupMemberForBackend(getMembers(), 
                name, backends);
    }

    @Override
    public Type getDeclaringType(Declaration d) {
        if (d.isClassMember()) {
            Class containingAnonClass =
                    (Class) d.getContainer();
            if (containingAnonClass.isAnonymous()) {
                return containingAnonClass.getType();
            }
        }
        return null;
    }

    /**
     * Search in the package, taking into account
     * imports
     */
    @Override
    public Declaration getMemberOrParameter(Unit unit, 
            String name, List<Type> signature, 
            boolean ellipsis) {
        //this implements the rule that imports hide 
        //toplevel members of a package
        //TODO: would it be better to look in the given unit 
        //      first, before checking imports?
        Declaration d = 
                unit.getImportedDeclaration(name, 
                        signature, ellipsis);
        if (d!=null) {
            return d;
        }
        d = getDirectMember(name, signature, ellipsis);
        if (d!=null) {
            return d;
        }
        return unit.getLanguageModuleDeclaration(name);
    }
    
    @Override
    public boolean isInherited(Declaration d) {
        return false;
    }
    
    @Override
    public TypeDeclaration getInheritingDeclaration(Declaration d) {
        return null;
    }
    
    @Override
    public Map<String,DeclarationWithProximity> 
    getMatchingDeclarations(Unit unit, String startingWith, 
            int proximity) {
        Map<String,DeclarationWithProximity> result = 
                getMatchingDirectDeclarations(startingWith, 
                        //package toplevels - just less than 
                        //explicitly imported declarations
                        proximity+1);
        if (unit!=null) {
            result.putAll(unit.getMatchingImportedDeclarations(
                    startingWith, proximity));
        }
        Map<String,DeclarationWithProximity> importables = 
                getModule()
                    .getAvailableDeclarations(
                            startingWith, proximity);
        List<Map.Entry<String,DeclarationWithProximity>> 
        entriesToAdd =
                new ArrayList
                <Map.Entry<String,DeclarationWithProximity>>
                        (importables.size());
        for (Map.Entry<String,DeclarationWithProximity> e: 
                importables.entrySet()) {
            boolean already = false;
            DeclarationWithProximity existing = e.getValue();
            for (DeclarationWithProximity importable: 
                    result.values()) {
                Declaration id = importable.getDeclaration();
                Declaration ed = existing.getDeclaration();
                if (id.equals(ed)) {
                    already = true;
                    break;
                }
            }
            if (!already) {
                entriesToAdd.add(e);
            }
        }
        for (Map.Entry<String,DeclarationWithProximity> e:
                entriesToAdd) {
            result.put(e.getKey(), e.getValue());
        }
        return result;
    }

    public Map<String,DeclarationWithProximity> 
    getMatchingDirectDeclarations(String startingWith, 
            int proximity) {
        Map<String,DeclarationWithProximity> result = 
                new TreeMap<String,DeclarationWithProximity>();
        for (Declaration d: getMembers()) {
            if (isResolvable(d) && 
                    !isOverloadedVersion(d) && 
                    isNameMatching(startingWith, d)) {
                result.put(d.getName(unit), 
                        new DeclarationWithProximity(d, 
                                proximity));
            }
        }
        return result;
    }

    public Map<String,DeclarationWithProximity> 
    getImportableDeclarations(Unit unit, String startingWith, 
            List<Import> imports, int proximity) {
        Map<String,DeclarationWithProximity> result = 
                new TreeMap<String,DeclarationWithProximity>();
        for (Declaration d: getMembers()) {
            if (isResolvable(d) && d.isShared() && 
                    !isOverloadedVersion(d) &&
                    isNameMatching(startingWith, d)) {
                boolean already = false;
                for (Import i: imports) {
                    if (!i.isWildcardImport() && 
                            i.getDeclaration().equals(d)) {
                        already = true;
                        break;
                    }
                }
                if (!already) {
                    result.put(d.getName(), 
                            new DeclarationWithProximity(d, 
                                    proximity));
                }
            }
        }
        return result;
    }
    
    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }

    @Override
    public int hashCode() {
        // use the cached version, profiling says 
        // List.hashCode is expensive
        return getNameAsString().hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Package) {
            Package p = (Package) obj;
            return p.getNameAsString()
                    .equals(getNameAsString());
        }
        else {
            return false;
        }
    }
    
    @Override
    public Unit getUnit() {
        return unit;
    }
    
    public void setUnit(Unit unit) {
        this.unit = unit;
    }
    
    @Override
    public Backends getScopedBackends() {
        return getModule().getNativeBackends();
    }
} 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 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 Setter extends FunctionOrValue implements Scope {

	private Value getter;
	private Parameter parameter;

    public Value getGetter() {
        return getter;
    }

    public void setGetter(Value getter) {
        this.getter = getter;
    }
    
    public Parameter getParameter() {
		return parameter;
	}
    
    public void setParameter(Parameter parameter) {
		this.parameter = parameter;
	}
    
    @Override
    public String getQualifiedNameString() {
        if (getter!=null) {
            return getter.getQualifiedNameString();
        }
        else {
            return super.getQualifiedNameString();
        }
    }
    
    @Override
    public boolean isShared() {
        if (getter!=null) {
            return getter.isShared();
        }
        else {
            return super.isShared();
        }
    }
    
    @Override
    public boolean isVariable() {
        return true;
    }
    
    @Override
    public DeclarationKind getDeclarationKind() {
        return DeclarationKind.SETTER;
    }
    
    @Override
    public boolean isSetter() {
        return true;
    }

    @Override
    public String getQualifier() {
        Value getter = getGetter();
        if(getter == null)
            return null;
        String getterQualifier = getter.getQualifier();
        // use the same qualifier as the getter with a $setter$ prefix
        return getterQualifier == null ? null : "$setter$"+getterQualifier;
    }
    
    @Override
    public Backends getScopedBackends() {
        return super.getScopedBackends();
    }
    
    @Override
    public String toString() {
        return "assign " + toStringName();
    }
} 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 class TypeAlias extends TypeDeclaration {
    
    private List<Declaration> members = new ArrayList<Declaration>(3);
    private List<Annotation> annotations = new ArrayList<Annotation>(4);
    private List<TypeParameter> typeParameters = emptyList();
    
    private boolean anonymous;
    
    @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;
    }
    
    @Override
    public List<Annotation> getAnnotations() {
        return annotations;
    }
    
    @Override
    public List<Declaration> getMembers() {
        return members;
    }
    
    @Override
    public void addMember(Declaration declaration) {
        members.add(declaration);
    }
    
	@Override
	public DeclarationKind getDeclarationKind() {
		return DeclarationKind.TYPE;
	}
	
	@Override
	public boolean isAlias() {
		return true;
	}
	
	@Override
	public boolean isAnonymous() {
	    return anonymous;
	}
	
	public void setAnonymous(boolean anonymous) {
        this.anonymous = anonymous;
    }
	
	@Override
	public boolean isNamed() {
	    return !anonymous;
	}
	
    @Override
    public boolean isParameterized() {
        return !typeParameters.isEmpty();
    }

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

    public void setTypeParameters(List<TypeParameter> typeParameters) {
        this.typeParameters = typeParameters;
    }
    
    @Override
    public boolean isMember() {
        return getContainer() instanceof ClassOrInterface;
    }

    @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
    protected int hashCodeForCache() {
        int ret = 17;
        ret = addHashForModule(ret, this);
        if(isToplevel())
            ret = (37 * ret) + 
                    getQualifiedNameString().hashCode();
        else{
            ret = (37 * ret) + getContainer().hashCode();
            ret = (37 * ret) + Objects.hashCode(getName());
        }
        return ret;
    }

    @Override
    protected boolean equalsForCache(Object o) {
        if(o == null || !(o instanceof ClassOrInterface))
            return false;
        ClassOrInterface b = (ClassOrInterface) o;
        if (!ModelUtil.sameModule(this, b)) {
            return false;
        }
        if (isToplevel()) {
            if (!b.isToplevel()) {
                return false;
            }
            return getQualifiedNameString()
                    .equals(b.getQualifiedNameString());
        }
        else {
            if(b.isToplevel()) {
                return false;
            }
            return getContainer().equals(b.getContainer())
                    && Objects.equals(getName(),b.getName());
        }
    }

    @Override
    public void clearProducedTypeCache() {
        ModelUtil.clearProducedTypeCache(this);
    }
    
    @Override
    public String toString() {
        return "type " + toStringName();
    }
} public abstract class TypeDeclaration extends Declaration 
        implements ImportableScope, Cloneable, Generic {

    private Type extendedType;
    private List<Type> satisfiedTypes = 
            needsSatisfiedTypes() ? 
                    new ArrayList<Type>(3) : 
                    Collections.<Type>emptyList();
    private List<Type> caseTypes = null;
    private Type selfType;
    private List<Type> brokenSupertypes = null; // delayed allocation
    private boolean inconsistentType;
    private boolean dynamic;
	private boolean sealed;
    private List<TypedDeclaration> caseValues;

	/** 
	 * true if the type arguments of this type are not 
	 * available at runtime 
	 */
	public boolean isErasedTypeArguments() {
	    return false;
	}
	
	public boolean isSealed() {
	    return sealed;
    }
	
	public void setSealed(boolean sealed) {
	    this.sealed = sealed;
    }
    
    public boolean isDynamic() {
        return dynamic;
    }
    
    public void setDynamic(boolean dynamic) {
        this.dynamic = dynamic;
    }
    
    public boolean isInconsistentType() {
        return inconsistentType;
    }
    
    protected boolean needsSatisfiedTypes() {
        // NothingType doesn't need any so we save allocation
        return true;
    }

    public void setInconsistentType(boolean inconsistentType) {
        this.inconsistentType = inconsistentType;
    }
    
    public boolean isAbstract() {
        return true;
    }

    @Override
    protected TypeDeclaration clone() {
        try {
            return (TypeDeclaration) super.clone();
        } 
        catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean isSelfType() {
    	return false;
    }
    
    public boolean isFinal() {
    	return false;
    }
    
    public boolean isObjectClass() {
        return false;
    }

    public boolean isValueConstructor() {
        return false;
    }

    @Override
    public List<TypeParameter> getTypeParameters() {
        return emptyList();
    }
    
    @Override
    public void setTypeParameters(List<TypeParameter> params) {
        throw new UnsupportedOperationException();
    }

    /**
     * The class or constructor extended by a class, the 
     * type aliased by a class or interface alias, or the 
     * class Anything for any other type.
     */
    public Type getExtendedType() {
        return extendedType;
    }
    
    public void setExtendedType(Type extendedType) {
        this.extendedType = extendedType;
    }
    
    public List<Type> getSatisfiedTypes() {
        return satisfiedTypes;
    }
    
    public void setSatisfiedTypes(List<Type> satisfiedTypes) {
        this.satisfiedTypes = satisfiedTypes;
    }
    
    public List<Type> getCaseTypes() {
        return caseTypes;
    }
    
    public void setCaseTypes(List<Type> caseTypes) {
        this.caseTypes = caseTypes;
    }
    
    public List<Type> getBrokenSupertypes() {
        return brokenSupertypes == null ? 
                Collections.<Type>emptyList() : 
                brokenSupertypes;
    }
    
    public void addBrokenSupertype(Type type) {
        if (brokenSupertypes == null) {
            brokenSupertypes = 
                    new ArrayList<Type>(1);
        }
        brokenSupertypes.add(type);
    }
    
    @Override
    public Reference appliedReference(Type pt, 
            List<Type> typeArguments) {
        return appliedType(pt, typeArguments);
    }

    @Override
    public final Type getReference() {
        return getType();
    }

    /**
     * Get a produced type 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 nested type.
     *
     * @param qualifyingType the qualifying produced
     *                       type or null if this is 
     *                       not a nested type dec
     * @param typeArguments arguments to the type
     *                      parameters of this 
     *                      declaration
     */
    public Type appliedType(
            Type qualifyingType,
            List<Type> typeArguments) {
    	if (qualifyingType!=null && 
                qualifyingType.isNothing()) {
        	return qualifyingType;
        }
    	else {
            Type pt = new Type();
            pt.setDeclaration(this);
            pt.setQualifyingType(qualifyingType);
            pt.setTypeArguments(getTypeArgumentMap(this, 
                    qualifyingType, typeArguments));
            return pt;
    	}
    }
    
    /**
     * The type of the declaration as seen from within the 
     * body of the declaration itself.
     * <p/>
     * Note that for certain special types which we happen 
     * to know don't have type arguments, we use this as a 
     * convenience method to quickly get a produced type for 
     * use outside the body of the declaration, but this is 
     * not really correct!
     */
    public Type getType() {
        Type type = new Type();
        type.setQualifyingType(getMemberContainerType());
        type.setDeclaration(this);
        type.setTypeArguments(getTypeParametersAsArguments());
        return type;
    }

    private List<Declaration> getInheritableMembers(
            String name, List<TypeDeclaration> visited) {
        if (visited.contains(this)) {
            return Collections.emptyList();
        }
        else {
            visited.add(this);
            List<Declaration> members = 
                    new ArrayList<Declaration>();
            for (Declaration d: getMembers()) {
                if (d.isShared() &&
                        d.getName()!=null && 
                        d.getName().equals(name) &&
                        isResolvable(d)) {
                    members.add(d);
                }
            }
            if (members.isEmpty()) {
                members.addAll(getInheritedMembers(name, 
                        visited));
            }
            return members;
        }
    }
    
    /**
     * Get all members inherited by this type declaration
     * with the given name. Do not include members declared
     * directly by this type. Do not include declarations 
     * refined by a supertype.
     * 
     * @deprecated This does not handle Java's inheritance
     *             model where overloads can be inherited 
     *             from different supertypes ... I think we  
     *             can remove this now and use getRefinedMember()
     */
    public List<Declaration> getInheritedMembers(String name) {
        return getInheritedMembers(name, 
                new ArrayList<TypeDeclaration>());
    }
    
    //identity containment
    private static <T> boolean contains(Iterable<T> iter, T object) {
        for (Object elem: iter) {
            if (elem==object) {
                return true;
            }
        }
        return false;
    }
    
    private List<Declaration> getInheritedMembers(
            String name, List<TypeDeclaration> visited) {
        List<Declaration> members = 
                new ArrayList<Declaration>();
        for (Type st: getSatisfiedTypes()) {
            //if ( !(t instanceof TypeParameter) ) { //don't look for members in a type parameter with a self-referential lower bound
            for (Declaration member: 
                    st.getDeclaration()
                        .getInheritableMembers(name, 
                                visited)) {
                if (!contains(members, member)) {
                    members.add(member);
                }
            }
            //}
        }
        Type et = getExtendedType();
        if (et!=null) {
            for (Declaration member: 
                    et.getDeclaration()
                        .getInheritableMembers(name, 
                                visited)) {
                if (!contains(members, member)) {
                    members.add(member);
                }
            }
        }
        return members;
    }
    
    /**
     * Is the given declaration a direct or inherited
     * member of this type?
     */
    public boolean isMember(Declaration dec) {
        return isMember(dec, 
                new ArrayList<TypeDeclaration>());
    }
    
    private boolean isMember(Declaration dec, 
            List<TypeDeclaration> visited) {
        if (visited.contains(this)) {
            return false;
        }
        visited.add(this);
        for (Declaration member: getMembers()) {
            if (dec.equals(member)) {
                return true;
            }
        }
        for (Type t: getSatisfiedTypes()) {
            if (t.getDeclaration().isMember(dec, visited)) {
                return true;
            }
        }
        Type et = getExtendedType();
        if (et!=null) {
            if (et.getDeclaration()
                    .isMember(dec, visited)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Does the given declaration inherit the given type?
     */
    public abstract boolean inherits(TypeDeclaration dec);
    
    /**
     * Return the least-refined (i.e. the non-actual member)
     * with the given name, by reversing the usual search
     * order and searching supertypes first.
     */
    public Declaration getRefinedMember(String name, 
            List<Type> signature, boolean ellipsis) {
        return getRefinedMember(name, signature, ellipsis,
                false);
    }
    
    public Declaration getRefinedMember(String name, 
            List<Type> signature, boolean ellipsis,
            boolean onlyExactMatches) {
        return getRefinedMember(name, signature, ellipsis,
                onlyExactMatches,
                        new HashSet<TypeDeclaration>());
    }

    protected Declaration getRefinedMember(String name, 
            List<Type> signature, boolean ellipsis, 
            boolean onlyExactMatches,
            Set<TypeDeclaration> visited) {
        if (!visited.add(this)) {
            return null;
        }
        else {
            Declaration result = null;
            Type et = getExtendedType();
            if (et!=null) {
                Declaration ed = 
                        et.getDeclaration()
                            .getRefinedMember(name, 
                                    signature, ellipsis,
                                    onlyExactMatches,
                                    visited);
                if (isBetterRefinement(signature, 
                        ellipsis, result, ed)) {
                    result = ed;
                }
            }
            for (Type st: getSatisfiedTypes()) {
                Declaration sd = 
                        st.getDeclaration()
                            .getRefinedMember(name, 
                                    signature, ellipsis, 
                                    onlyExactMatches,
                                    visited);
                if (isBetterRefinement(signature, 
                        ellipsis, result, sd)) {
                    result = sd;
                }
            }
            Declaration dd = 
                    getDirectMember(name, 
                            signature, ellipsis,
                            onlyExactMatches);
            if (isBetterRefinement(signature, 
                    ellipsis, result, dd)) {
                result = dd;
            }
            return result;
        }
    }

    public boolean isBetterRefinement(
            List<Type> signature, boolean ellipsis, 
            Declaration result, Declaration candidate) {
        if (candidate==null ||
                candidate.isActual() /*&& 
                !candidate.getNameAsString()
                    .equals("ceylon.language::Object")*/ || 
                !candidate.isShared()) {
            return false;
        }
        if (result==null) {
            return signature!=null == 
                    candidate instanceof Functional;
        }
        if (!(result instanceof Functional)) {
            return signature!=null;
        }
        if (!(candidate instanceof Functional)) {
            return signature==null;
        }
        if (signature==null) {
            throw new RuntimeException("missing signature");
        }
        if (candidate.isAbstraction() && 
                !result.isAbstraction()) {
            return false;
        }
        if (!candidate.isAbstraction() && 
                result.isAbstraction()) {
            return true;
        }
        if (hasMatchingSignature(candidate, signature, ellipsis)) {
            return !hasMatchingSignature(result, signature, ellipsis) || 
                    strictlyBetterMatch(candidate, result);
        }
        return false; //asymmetric!!
    }
    
    /**
     * Get the most-refined member with the given name,
     * searching this type first, taking aliases into
     * account, followed by supertypes. We're looking
     * for shared members.
     */
    public Declaration getMember(
            String name, 
            Unit unit, 
            List<Type> signature, 
            boolean variadic) {
        //TODO: does not handle aliased members of supertypes
        Declaration dec = 
                unit.getImportedDeclaration(this, name, 
                        signature, variadic);
        if (dec==null) {
            return getMemberInternal(name, 
                    signature, variadic, false)
                    .getMember();
        }
        else {
            return dec;
        }
    }

    /**
     * Is the most-refined member with the given name,
     * searching this type first, taking aliases into
     * account, followed by supertypes, ambiguous,
     * because we could not construct a principal
     * instantiation for an intersection?
     * TODO: this information should really be encoded 
     *       into the return value of getMember() but
     *       I'm leaving it like this for now to avoid
     *       breaking the backends
     */
    public boolean isMemberAmbiguous(
            String name, 
            Unit unit, 
            List<Type> signature, 
            boolean variadic) {
        //TODO: does not handle aliased members of supertypes
        Declaration dec = 
                unit.getImportedDeclaration(this, name, 
                        signature, variadic);
        if (dec==null) {
            return getMemberInternal(name, 
                    signature, variadic, false)
                    .isAmbiguous();
        }
        else {
            return false;
        }
    }

    /**
     * Get the most-refined member with the given name,
     * searching this type first, followed by supertypes.
     * We're looking for shared members.
     */
    @Override
    public Declaration getMember(
            String name, 
            List<Type> signature, 
            boolean variadic,
            boolean onlyExactMatches) {
        return getMemberInternal(name, 
                signature, variadic, 
                onlyExactMatches)
                .getMember();
    }

    private SupertypeDeclaration getMemberInternal(
            String name,
            List<Type> signature, boolean variadic, 
            boolean onlyExactMatches) {
        if (!onlyExactMatches && signature!=null) {
            //first try for an exact match
            SupertypeDeclaration sd = 
                    getMemberInternal(name, 
                            signature, variadic, 
                            true);
            if (sd.getMember()!=null || sd.isAmbiguous()) {
                return sd;
            }
        }
        
        //first search for the member in the local
        //scope, including non-shared declarations
        Declaration dec = 
                getDirectMember(name, 
                        signature, variadic, 
                        onlyExactMatches);
        if (dec!=null && dec.isShared()) {
            //if it's shared, it's what we're looking 
            //for, return it
            //TODO: should also return it if we're 
            //      calling from local scope!
            return new SupertypeDeclaration(dec, false);
        }
        else {
            //now look for inherited shared declarations
            SupertypeDeclaration sd = 
                    getSupertypeDeclaration(name, 
                            signature, variadic, 
                            onlyExactMatches);
            if (sd.getMember()!=null || sd.isAmbiguous()) {
                return sd;
            }
        }
        //finally return the non-shared member we
        //found earlier, so that the caller can give
        //a nice error message
        return new SupertypeDeclaration(dec, false);
    }
    
    /**
     * Get the parameter or most-refined member with the 
     * given name, searching this type first, followed by 
     * supertypes. Return un-shared members declared by
     * this type.
     */
    @Override
    protected Declaration getMemberOrParameter(
            String name, 
            List<Type> signature, boolean variadic, 
            boolean onlyExactMatches) {
        //first search for the member or parameter 
        //in the local scope, including non-shared 
        //declarations
        Declaration dec = 
                getDirectMember(name, 
                        signature, variadic, 
                        onlyExactMatches);
        if (dec!=null) {
            if (signature!=null && 
                    dec.isAbstraction()) {
                // look for a supertype declaration that 
                // matches the given signature better
                Declaration supertype = 
                        getSupertypeDeclaration(name, 
                                signature, variadic, 
                                onlyExactMatches)
                                .getMember();
                if (supertype!=null && 
                        !supertype.isAbstraction()) {
                    return supertype;
                }
            }
        }
        else {
            // If we couldn't find the declaration in the current
            // scope and the scope is a native implementation we
            // will try again with its header
            if (isNativeImplementation()) {
                Declaration hdr = getNativeHeader(this);
                if (hdr != null) {
                    dec = hdr.getDirectMember(name,
                            signature, variadic, 
                            onlyExactMatches);
                }
            }

            if (dec == null) {
                //now look for inherited shared declarations
                dec = getSupertypeDeclaration(name,
                        signature, variadic, 
                        onlyExactMatches)
                        .getMember();
            }
        }
        return dec;
    }

    /**
     * Is the given declaration inherited from
     * a supertype of this type or an outer
     * type?
     * 
     * @return true if it is
     */
    @Override
    public boolean isInherited(Declaration member) {
        if (member.getContainer().equals(this)) {
            return false;
        }
        else if (isInheritedFromSupertype(member)) {
            return true;
        }
        else if (getContainer()!=null) {
            return getContainer()
                    .isInherited(member);
        }
        else {
            return false;
        }
    }

    /**
     * Get the containing type which inherits the given declaration.
     * 
     * @return null if the declaration is not inherited!!
     */
    @Override
    public TypeDeclaration getInheritingDeclaration(
            Declaration member) {
        Scope container = member.getContainer();
        if (container!=null && container.equals(this)) {
            return null;
        }
        else if (isInheritedFromSupertype(member)) {
            return this;
        }
        else if (getContainer()!=null) {
            return getContainer()
                    .getInheritingDeclaration(member);
        }
        else {
            return null;
        }
    }

    public boolean isInheritedFromSupertype(
            final Declaration member) {
        final List<Type> signature = 
                getSignature(member);
        class Criteria implements Type.Criteria {
            @Override
            public boolean satisfies(TypeDeclaration type) {
                if (type.equals(TypeDeclaration.this)) {
                    return false;
                }
                else {
                    Declaration dm = 
                            type.getDirectMember(
                                    member.getName(), 
                                    signature, false);
                    return dm!=null && dm.equals(member);
                }
            }
            @Override
            public boolean isMemberLookup() {
            	return false;
            }
        };
        return getType()
                .getSupertype(new Criteria())!=null;
    }
    
    static class SupertypeDeclaration {
        private Declaration member;
        private boolean ambiguous;
        SupertypeDeclaration(Declaration declaration, 
                boolean ambiguous) {
            this.member = declaration;
            this.ambiguous = ambiguous;
        }
        private boolean isAmbiguous() {
            return ambiguous;
        }
        private Declaration getMember() {
            return member;
        }
    }
    
    /**
     * Get the supertype which defines the most-refined
     * member with the given name.
     */
    SupertypeDeclaration getSupertypeDeclaration(
            final String name, 
            final List<Type> signature, 
            final boolean variadic,
            final boolean onlyExactMatches) {
        class ExactCriteria implements Type.Criteria {
            @Override
            public boolean satisfies(TypeDeclaration type) {
                // do not look in ourselves
                if (type == TypeDeclaration.this) {
                    return false;
                }
                Declaration dm = 
                        type.getDirectMember(name, 
                                signature, variadic, 
                                onlyExactMatches);
                if (dm!=null && 
                        dm.isShared() && 
                        isResolvable(dm)) {
                    // only accept abstractions if we 
                    // don't have a signature
                    return !dm.isAbstraction() || 
                            signature == null;
                }
                else {
                    return false;
                }
            }
            @Override
            public boolean isMemberLookup() {
            	return true;
            }
        };
        class LooseCriteria implements Type.Criteria {
            @Override
            public boolean satisfies(TypeDeclaration type) {
                // do not look in ourselves
                if (type == TypeDeclaration.this) {
                    return false;
                }
                Declaration dm = 
                        type.getDirectMember(name, 
                                null, false);
                if (dm!=null && 
                        dm.isShared() && 
                        isResolvable(dm)) {
                    // only accept abstractions
                    return dm.isAbstraction();
                }
                else {
                    return false;
                }
            }
            @Override
            public boolean isMemberLookup() {
                return true;
            }
        };
        class SkipFormalCriteria implements Type.Criteria {
            @Override
            public boolean satisfies(TypeDeclaration type) {
                // do not look in ourselves
                if (type == TypeDeclaration.this) {
                    return false;
                }
                Declaration dm = 
                        type.getDirectMember(name, 
                                signature, variadic, 
                                onlyExactMatches);
                if (dm!=null && 
                        dm.isShared() &&
                        isResolvable(dm)) {
                    //ignore formals, to allow for Java's
                    //refinement model
                    return !dm.isFormal() &&
                            (!dm.isAbstraction() || 
                            signature == null);
                }
                else {
                    return false;
                }
            }
            @Override
            public boolean isMemberLookup() {
                return true;
            }
        };
        //this works by finding the most-specialized 
        //supertype that defines the member
        Type type = getType();
        Type st = type.getSupertype(new ExactCriteria());
        if (st == null) {
            //no match
            if (!onlyExactMatches) {
                //try again, ignoring the given signature
                st = type.getSupertype(new LooseCriteria());
            }
        }
        else if (st.isUnknown()) {
            //ambiguous
            if (this instanceof Class) {
                //try again, ignoring Java abstract members
                st = type.getSupertype(new SkipFormalCriteria());
            }
        }
        if (st == null) {
            //no such member
            return new SupertypeDeclaration(null, false);
        }
        else if (st.isUnknown()) {
            //we're dealing with an ambiguous member of an 
            //intersection type
            //TODO: this is pretty fragile - it depends upon
            //      the fact that getSupertype() just happens
            //      to return an UnknownType instead of null
            //      in this case
            return new SupertypeDeclaration(null, true);
        }
        else {
            //we got exactly one uniquely-defined member
            Declaration member = 
                    st.getDeclaration()
                        .getDirectMember(name, 
                                signature, variadic, 
                                onlyExactMatches);
            return new SupertypeDeclaration(member, false);
        }
    }
    
    /**
     * Is this a class or interface alias? 
     */
    public boolean isAlias() {
        return false;
    }

    public void setSelfType(Type selfType) {
        this.selfType = selfType;
    }

    public Type getSelfType() {
        return selfType;
    }
    
    public Map<String,DeclarationWithProximity> 
    getImportableDeclarations(Unit unit, String startingWith, 
            List<Import> imports, int proximity) {
        //TODO: fix copy/paste from below!
        Map<String,DeclarationWithProximity> result = 
                new TreeMap<String,DeclarationWithProximity>();
        for (Declaration dec: getMembers()) {
            if (isResolvable(dec) && 
                    dec.isShared() && 
            		!isOverloadedVersion(dec) &&
                    isNameMatching(startingWith, dec) ) {
                boolean already = false;
                for (Import i: imports) {
                    if (i.getDeclaration().equals(dec)) {
                        already = true;
                        break;
                    }
                }
                if (!already) {
                    result.put(dec.getName(unit), 
                            new DeclarationWithProximity(dec, 
                                    proximity));
                }
            }
        }
        return result;
    }
    
    @Override
    public Map<String,DeclarationWithProximity> 
    getMatchingDeclarations(Unit unit, String startingWith, 
            int proximity) {
        Map<String,DeclarationWithProximity> result = 
                super.getMatchingDeclarations(unit, 
                        startingWith, proximity);
        //Inherited declarations hide outer and imported declarations
        result.putAll(getMatchingMemberDeclarations(unit, 
                null, startingWith, proximity));
        //Local declarations always hide inherited declarations, even if non-shared
        for (Declaration dec: getMembers()) {
            if (isResolvable(dec) && 
                    !isOverloadedVersion(dec) ) {
                if (isNameMatching(startingWith, dec)) {
                    result.put(dec.getName(unit), 
                            new DeclarationWithProximity(dec, 
                                    proximity));
                }
                for(String alias : dec.getAliases()){
                    if(isNameMatching(startingWith, alias)){
                        result.put(alias, 
                                new DeclarationWithProximity(
                                        alias, dec, proximity));
                    }
                }
            }
        }
        return result;
    }

    public Map<String,DeclarationWithProximity> 
    getMatchingMemberDeclarations(Unit unit, Scope scope, 
            String startingWith, int proximity) {
        Map<String,DeclarationWithProximity> result = 
                new TreeMap<String,DeclarationWithProximity>();
        for (Type st: getSatisfiedTypes()) {
            mergeMembers(result, 
                    st.getDeclaration()
                        .getMatchingMemberDeclarations(unit, 
                                scope, startingWith, 
                                proximity+1));
        }
        Type et = 
                getExtendedType();
        if (et!=null) {
            mergeMembers(result, 
                    et.getDeclaration()
                        .getMatchingMemberDeclarations(unit, 
                                scope, startingWith, 
                                proximity+1));
        }
        for (Declaration member: getMembers()) {
            if (isResolvable(member) && 
                    !isOverloadedVersion(member) &&
                (member.isShared() || 
                        ModelUtil.contains(member.getScope(), 
                                scope))) {
                if (isNameMatching(startingWith, member)) {
                    result.put(member.getName(unit), 
                            new DeclarationWithProximity(
                                    member, proximity));
                }
                for (String alias : member.getAliases()) {
                    if (isNameMatching(startingWith, alias)) {
                        result.put(alias, 
                                new DeclarationWithProximity(
                                        alias, member, proximity));
                    }
                }
            }
        }
        //premature optimization so that we don't have to 
        //call d.getName(unit) on *every* member
        result.putAll(unit.getMatchingImportedDeclarations(
                this, startingWith, proximity));
        return result;
    }

    private void mergeMembers(
            Map<String,DeclarationWithProximity> result,
            Map<String,DeclarationWithProximity> etm) {
        for (Map.Entry<String,DeclarationWithProximity> e: 
                etm.entrySet()) {
            String name = e.getKey();
            DeclarationWithProximity current = 
                    e.getValue();
            DeclarationWithProximity existing = 
                    result.get(name);
            if (existing==null || 
                    !existing.getDeclaration()
                        .refines(current.getDeclaration())) {
                result.put(name, current);
            }
        }
    }
    
    /**
     * implement the rule that Foo&Bar==Nothing if 
     * here exists some enumerated type Baz with
     * 
     *    Baz of Foo | Bar 
     * 
     * (the intersection of disjoint types is empty)
     * 
     * @param type a type which might be disjoint from
     *        a list of other given types
     * @param list the list of other types
     * @param unit
     * 
     * @return true of the given type was disjoint from
     *         the given list of types
     */
    public boolean isDisjoint(TypeDeclaration td) {
        if (this instanceof ClassOrInterface &&
            td instanceof ClassOrInterface &&
                equals(td)) {
            return false;
        }
        if (this instanceof TypeParameter &&
                td instanceof TypeParameter &&
                    equals(td)) {
            return false;
        }
        List<Type> sts = getSatisfiedTypes();
        for (int i=0, s=sts.size(); i<s; i++) {
            Type st = sts.get(i);
            if (isDisjoint(td, st)) {
                return true;
            }
        }     
        Type et = getExtendedType();
        if (et!=null) {
            if (isDisjoint(td, et)) {
                return true;
            }
        }
        return false;
    }

    private boolean isDisjoint(TypeDeclaration td, Type st) {
        TypeDeclaration std = st.getDeclaration();
        List<Type> cts = std.getCaseTypes();
        if (cts!=null) {
            for (int i=0, s=cts.size(); i<s; i++) {
                TypeDeclaration ctd = 
                        cts.get(i)
                            .getDeclaration();
                if (ctd.equals(this)) {
                    for (int j=0, l=cts.size(); j<l; j++) {
                        if (i!=j) {
                            TypeDeclaration octd = 
                                    cts.get(j)
                                        .getDeclaration();
                            if (td.inherits(octd)) {
                                return true;
                            }
                        }
                    }
                    break;
                }
            }
        }
        if (std.isDisjoint(td)) {
            return true;
        }
        return false;
    }
    
//    private List<TypeDeclaration> supertypeDeclarations;
    
    public final List<TypeDeclaration> getSupertypeDeclarations() {
//        if (TypeCache.isEnabled()) {
//            if (supertypeDeclarations==null) {
//                supertypeDeclarations = 
//                        getSupertypeDeclarationsInternal();
//            }
//            return supertypeDeclarations;
//        }
//        else {
            return getSupertypeDeclarationsInternal();
//        }
    }

    private List<TypeDeclaration> getSupertypeDeclarationsInternal() {
        List<TypeDeclaration> results = 
                new ArrayList<TypeDeclaration>(
                        getSatisfiedTypes().size()+2);
        results.add(unit.getAnythingDeclaration());
        collectSupertypeDeclarations(results);
//        results = unmodifiableList(results);
        return results;
    }
    
    abstract void collectSupertypeDeclarations(
            List<TypeDeclaration> results);
    
    /**
     * Clears the Type supertype caches for that declaration. Does nothing
     * for Union/Intersection types since they are not cached. Only does something
     * for ClassOrInterface, TypeAlias, TypeParameter.
     */
    public void clearProducedTypeCache() {
        // do nothing, work in subclasses
//        supertypeDeclarations = null;
    }

    public boolean isAnything() {
        return false;
    }
    
    public boolean isObject() {
        return false;
    }
    
    public boolean isNull() {
        return false;
    }
    
    public boolean isNullValue() {
        return false;
    }
    
    public boolean isBasic() {
        return false;
    }

    public boolean isBoolean() {
        return false;
    }

    public boolean isString() {
        return false;
    }

    public boolean isCharacter() {
        return false;
    }

    public boolean isFloat() {
        return false;
    }

    public boolean isInteger() {
        return false;
    }

    public boolean isByte() {
        return false;
    }

    public boolean isEmpty() {
        return false;
    }

    boolean isEmptyValue() {
        return false;
    }
    
    public boolean isEntry() {
        return false;
    }
    
    public boolean isTuple() {
        return false;
    }
    
    public boolean isIterable() {
        return false;
    }
    
    public boolean isSequential() {
        return false;
    }

    public boolean isSequence() {
        return false;
    }
    
    public boolean isRange() {
        return false;
    }

    public List<TypedDeclaration> getCaseValues() {
        return caseValues;
    }

    public void setCaseValues(List<TypedDeclaration> caseValues) {
        this.caseValues = caseValues;
    }
    
    public boolean isSequentialType() {
        return false;
    }

    public boolean isSequenceType() {
        return false;
    }
    
    public boolean isEmptyType() {
        return false;
    }
    
    public boolean isTupleType() {
        return false;
    }

    @Override
    public Backends getScopedBackends() {
        return super.getScopedBackends();
    }
    
    public EnumSet<AnnotationTarget> getAnnotationTarget() {
        return null;
    }
    
} 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 abstract class TypedDeclaration extends Declaration {
    
    private static final int UNCHECKED_NULL = 1<<11;
    private static final int UNBOXED_KNOWN = 1<<12;
    private static final int UNBOXED = 1<<13;
    private static final int TYPE_ERASED = 1<<14;
    private static final int UNTRUSTED_TYPE = 1<<15;
    private static final int DYNAMIC = 1<<16;
    
    private Type type;
    
    private TypedDeclaration originalDeclaration;
    
    public boolean isDynamicallyTyped() {
        return (flags&DYNAMIC)!=0;
    }
    
    public void setDynamicallyTyped(boolean dynamicallyTyped) {
        if (dynamicallyTyped) {
            flags|=DYNAMIC;
        }
        else {
            flags&=(~DYNAMIC);
        }
    }
        
    public TypeDeclaration getTypeDeclaration() {
    	if (type==null) {
    	    return null;
    	}
    	else {
            return type.getDeclaration();
    	}
    }

    public Type getType() {
        return type;
    }

    public void setType(Type t) {
        this.type = t;
    }

    public TypedReference appliedTypedReference(
            Type qualifyingType,
            List<Type> typeArguments) {
        return appliedTypedReference(qualifyingType, 
                typeArguments, false);
    }
    
    /**
     * 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 qualifyingType the qualifying produced
     *                       type or null if this is 
     *                       not a nested type dec
     * @param typeArguments arguments to the type
     *                      parameters of this 
     *                      declaration
     * @param assignment the reference occurs on the
     *                   LHS of an assignment
     */
    public TypedReference appliedTypedReference(
            Type qualifyingType,
            List<Type> typeArguments, 
            boolean assignment) {
        TypedReference ptr = 
                new TypedReference(!assignment, 
                        assignment);
        ptr.setDeclaration(this);
        ptr.setQualifyingType(qualifyingType);
        ptr.setTypeArguments(getTypeArgumentMap(this, 
                qualifyingType, typeArguments));
        return ptr;
    }
    
    /**
     * The type of this declaration, as viewed from within
     * itself.
     */
    public TypedReference getTypedReference() {
        TypedReference ptr =
                new TypedReference(true, false);
        ptr.setQualifyingType(getMemberContainerType());
        ptr.setDeclaration(this);
        ptr.setTypeArguments(getTypeParametersAsArguments());
        return ptr;
    }

    @Override
    public Reference appliedReference(Type pt, 
            List<Type> typeArguments) {
        return appliedTypedReference(pt, typeArguments);
    }
    
    @Override
    public final Reference getReference() {
    	return getTypedReference();
    }

    @Override
    public boolean isMember() {
        return getContainer() instanceof ClassOrInterface;
    }

    public boolean isVariable() {
        return false;
    }
    
    public boolean isLate() {
    	return false;
    }
    
    public TypedDeclaration getOriginalDeclaration() {
        return originalDeclaration;
    }
    
    public void setOriginalDeclaration(
            TypedDeclaration originalDeclaration) {
        this.originalDeclaration = originalDeclaration;
    }

    public Boolean getUnboxed() {
        if ((flags&UNBOXED_KNOWN)==0) {
            return null;
        }
        else {
            return (flags&UNBOXED)!=0;
        }
    }

    public void setUnboxed(Boolean value) { 
        if (value==null) {
            flags&=(~UNBOXED_KNOWN);
        }
        else {
            flags|=UNBOXED_KNOWN;
            if (value) {
                flags|=UNBOXED;
            }
            else {
                flags&=(~UNBOXED);
            }
        }
    }

    public Boolean getTypeErased() { 
        return (flags&TYPE_ERASED)!=0; 
    }

    public void setTypeErased(Boolean typeErased) { 
        if (typeErased) {
           flags|=TYPE_ERASED; 
        }
        else {
            flags&=(~TYPE_ERASED);
        }
    }

    public Boolean getUntrustedType() { 
        return (flags&UNTRUSTED_TYPE)!=0; 
    }

    public void setUntrustedType(Boolean untrustedType) { 
        if (untrustedType) {
            flags|=UNTRUSTED_TYPE; 
         }
         else {
             flags&=(~UNTRUSTED_TYPE);
         }
    }

    public boolean hasUncheckedNullType() {
        return (flags&UNCHECKED_NULL)!=0;
    }
    
    public void setUncheckedNullType(boolean uncheckedNullType) {
        if (uncheckedNullType) {
            flags|=UNCHECKED_NULL; 
         }
         else {
             flags&=(~UNCHECKED_NULL);
         }
    }

    @Override
    protected int hashCodeForCache() {
        int ret = 17;
        Scope container = getContainer();
        if (container instanceof Declaration) {
            Declaration dec = (Declaration) container;
            ret = (37 * ret) + dec.hashCodeForCache();
        }
        else {
            ret = (37 * ret) + container.hashCode();
        }
        String qualifier = getQualifier();
        ret = (37 * ret) + (qualifier == null ? 
                0 : qualifier.hashCode());
        ret = (37 * ret) + Objects.hashCode(getName());
        return ret;
    }

    @Override
    protected boolean equalsForCache(Object o) {
        if (o == null || !(o instanceof TypedDeclaration)) {
            return false;
        }
        TypedDeclaration b = (TypedDeclaration) o;
        Scope container = getContainer();
        if (container instanceof Declaration) {
            Declaration dec = (Declaration) container;
            if (!dec.equalsForCache(b.getContainer())) {
                return false;
            }
        }
        else {
            if (!container.equals(b.getContainer())) {
                return false;
            }
        }
        if (!Objects.equals(getQualifier(), b.getQualifier())) {
            return false;
        }
        return Objects.equals(getName(), b.getName());
    }

    // implemented in Value
    public boolean isSelfCaptured() {
        return false;
    }
} 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;
    }

} public class UnknownType extends TypeDeclaration {

    private ErrorReporter errorReporter;
    
    public static class ErrorReporter {
        private String message;
        
        public ErrorReporter(String message) {
            this.message = message;
        }
        
        public String getMessage() {
            return message;
        }
        
        public void reportError() {}
    }
    
    @Override
    public void addMember(Declaration declaration) {
        throw new UnsupportedOperationException();
    }
    
    public UnknownType(Unit unit) {
        this.unit = unit;
    }
    
    @Override
    public String getName() {
        return "unknown";
    }
    
	@Override
	public DeclarationKind getDeclarationKind() {
		return DeclarationKind.TYPE;
	}
	
    @Override
    public String getQualifiedNameString() {
        return getName();
    }

    @Override
    public String toString() {
        return getName();
    }
    
    @Override
    public boolean isShared() {
        return true;
    }
    
    @Override
    void collectSupertypeDeclarations(
            List<TypeDeclaration> results) {}
    
    @Override
    public boolean inherits(TypeDeclaration dec) {
        return false;
    }
    
    @Override
    public boolean equals(Object object) {
    	return this==object;
    }
    
    @Override
    public int hashCode() {
    	return System.identityHashCode(this);
    }

    public void setErrorReporter(ErrorReporter errorReporter) {
        this.errorReporter = errorReporter;
    }
    
    public ErrorReporter getErrorReporter() {
        return errorReporter;
    }

    public void reportErrors() {
        if (errorReporter != null) {
            errorReporter.reportError();
        }
    }

    @Override
    protected int hashCodeForCache() {
        return hashCode();
    }
    
    @Override
    protected boolean equalsForCache(Object o) {
        return equals(o);
    }
} public class Value extends FunctionOrValue implements Scope {
    
    private static final int VARIABLE = 1<<22;
    private static final int TRANSIENT = 1<<23;
    private static final int LATE = 1<<24;
    private static final int ENUM_VALUE = 1<<25;
    private static final int SPECIFIED_IN_FOR_ELSE = 1<<26;
    private static final int INFERRED = 1<<27;
    private static final int SELF_CAPTURED = 1<<28;
    
    private Setter setter;

    public Setter getSetter() {
        return setter;
    }
    
    public void setSetter(Setter setter) {
        this.setter = setter;
    }
    
    @Override
    public boolean isVariable() {
        return (flags&VARIABLE)!=0 || setter!=null;
    }

    public void setVariable(boolean variable) {
        if (variable) {
            flags|=VARIABLE;
        }
        else {
            flags&=(~VARIABLE);
        }
    }

    @Override
    public boolean isTransient() {
        return (flags&TRANSIENT)!=0;
    }
    
    public void setTransient(boolean trans) {
        if (trans) {
            flags|=TRANSIENT;
        }
        else {
            flags&=(~TRANSIENT);
        }
    }
    
    @Override
    public boolean isLate() {
		return (flags&LATE)!=0;
	}
    
    public void setLate(boolean late) {
        if (late) {
            flags|=LATE;
        }
        else {
            flags&=(~LATE);
        }
	}

    public boolean isEnumValue() {
        return (flags&ENUM_VALUE)!=0;
    }

    public void setEnumValue(boolean enumValue) {
        if (enumValue) {
            flags|=ENUM_VALUE;
        }
        else {
            flags&=(~ENUM_VALUE);
        }
    }

    public boolean isSpecifiedInForElse() {
        return (flags&SPECIFIED_IN_FOR_ELSE)!=0;
    }

    public void setSpecifiedInForElse(boolean assignedInFor) {
        if (assignedInFor) {
            flags|=SPECIFIED_IN_FOR_ELSE;
        }
        else {
            flags&=(~SPECIFIED_IN_FOR_ELSE);
        }
    }

    /**
     * used for object declarations that use their own 
     * value binding in their body
     */
    @Override
    public boolean isSelfCaptured(){
        return (flags&SELF_CAPTURED)!=0;
    }
    
    public void setSelfCaptured(boolean selfCaptured) {
        if (selfCaptured) {
            flags|=SELF_CAPTURED;
        }
        else {
            flags&=(~SELF_CAPTURED);
        }
    }
    
    public boolean isInferred() {
        return (flags&INFERRED)!=0;
    }
    
    public void setInferred(boolean inferred) {
        if (inferred) {
            flags|=INFERRED;
        }
        else {
            flags&=(~INFERRED);
        }
    }

    @Override
    public Backends getScopedBackends() {
        return super.getScopedBackends();
    }
    
    @Override
    public String toString() {
        Type type = getType();
        if (type==null) {
            return "value " + toStringName();
        }
        else {
            return "value " + toStringName() + 
                    " => " + type.asString();
        }
    }

} public class ModuleManager implements BackendSupport {

    public static final String MODULE_FILE = "module.ceylon";
    public static final String PACKAGE_FILE = "package.ceylon";
    protected Modules modules;

    public ModuleManager() {
    }
    
    public Package createPackage(String pkgName, Module module) {
        final Package pkg = new Package();
        List<String> name = pkgName.isEmpty() ? Arrays.asList("") : splitModuleName(pkgName); 
        pkg.setName(name);
        if (module != null) {
            module.getPackages().add(pkg);
            pkg.setModule(module);
        }
        return pkg;
    }

    public void initCoreModules(Modules initialModules) {
        modules = initialModules;
        if ( modules.getLanguageModule() == null ) {
            //build empty package
            final Package emptyPackage = createPackage("", null);

            //build default module (module in which packages belong to when not explicitly under a module
            final List<String> defaultModuleName = Collections.singletonList(Module.DEFAULT_MODULE_NAME);
            final Module defaultModule = createModule(defaultModuleName, "unversioned");
            defaultModule.setDefault(true);
            defaultModule.setAvailable(true);
            bindPackageToModule(emptyPackage, defaultModule);
            modules.getListOfModules().add(defaultModule);
            modules.setDefaultModule(defaultModule);

            //create language module and add it as a dependency of defaultModule
            //since packages outside a module cannot declare dependencies
            final List<String> languageName = Arrays.asList("ceylon", "language");
            Module languageModule = createModule(languageName, Versions.CEYLON_VERSION_NUMBER);
            languageModule.setLanguageModule(languageModule);
            languageModule.setAvailable(false); //not available yet
            modules.setLanguageModule(languageModule);
            modules.getListOfModules().add(languageModule);
            defaultModule.addImport(new ModuleImport(languageModule, false, false));
            defaultModule.setLanguageModule(languageModule);
        }
    }

    public void bindPackageToModule(Package pkg, Module module) {
        //undo nomodule setting if necessary
        if (pkg.getModule() != null) {
            pkg.getModule().getPackages().remove(pkg);
            pkg.setModule(null);
        }
        module.getPackages().add(pkg);
        pkg.setModule(module);
    }

    protected Module createModule(List<String> moduleName, String version) {
		Module module = new Module();
		module.setName(moduleName);
		module.setVersion(version);
		return module;
	}

    /**
     * Get or create a module.
     * version == null is considered equal to any version.
     * Likewise a module with no version will match any version passed
     */
    public Module getOrCreateModule(List<String> moduleName, String version) {
        if (moduleName.size() == 0) {
            return null;
        }
        Module module = null;
        final Set<Module> moduleList = modules.getListOfModules();
        for (Module current : moduleList) {
            final List<String> names = current.getName();
            if (moduleName.equals(names)
                    && compareVersions(current, version, current.getVersion())) {
                module = current;
                break;
            }
        }
        if (module == null) {
            module = createModule(moduleName, version);
            module.setLanguageModule(modules.getLanguageModule());
            moduleList.add(module);
        }
        return module;
    }

    protected boolean compareVersions(Module current, String version, String currentVersion) {
        return currentVersion == null || version == null || currentVersion.equals(version);
    }


    public ModuleImport findImport(Module owner, Module dependency) {
        for (ModuleImport modImprt : owner.getImports()) {
            if (equalsForModules(modImprt.getModule(), dependency, true)) return modImprt;
        }
        return null;
    }

    public boolean equalsForModules(Module left, Module right, boolean exactVersionMatch) {
        if (left == right) return true;
        List<String> leftName = left.getName();
        List<String> rightName = right.getName();
        if (leftName.size() != rightName.size()) return false;
        for(int index = 0 ; index < leftName.size(); index++) {
            if (!leftName.get(index).equals(rightName.get(index))) return false;
        }
        if (exactVersionMatch && !left.getVersion().equals(right.getVersion())) return false;
        return true;
    }

    public Module findModule(Module module, List<Module> listOfModules, boolean exactVersionMatch) {
        for(Module current : listOfModules) {
            if (equalsForModules(module, current, exactVersionMatch)) return current;
        }
        return null;
    }

    public boolean similarForModules(Module left, Module right) {
        if (left == right) return true;
        String leftName = ModuleUtil.toCeylonModuleName(left.getNameAsString());
        String rightName = ModuleUtil.toCeylonModuleName(right.getNameAsString());
        return leftName.equals(rightName);
    }

    /**
     * This treats Maven and Ceylon modules as similar: com:foo and com.foo will match
     */
    public Module findSimilarModule(Module module, List<Module> listOfModules) {
        for(Module current : listOfModules) {
            if (similarForModules(module, current)) return current;
        }
        return null;
    }

    public Module findLoadedModule(String moduleName, String searchedVersion) {
        return findLoadedModule(moduleName, searchedVersion, modules);
    }
    
    /**
     * @Deprecated This looks fishy: why would we have an extra Modules parameter?
     */
    @Deprecated
    public Module findLoadedModule(String moduleName, String searchedVersion, Modules modules) {
        if(moduleName.equals(Module.DEFAULT_MODULE_NAME))
            return modules.getDefaultModule();
        for(Module module : modules.getListOfModules()){
            if(module.getNameAsString().equals(moduleName)
                    && compareVersions(module, searchedVersion, module.getVersion())){
                return module;
            }
        }
        return null;
    }

    public Iterable<String> getSearchedArtifactExtensions() {
        return Arrays.asList("src");
    }

    public static List<String> splitModuleName(String moduleName) {
        return Arrays.asList(moduleName.split("[\\.]"));
    }

    public void prepareForTypeChecking() {
        // to be overridden by subclasses
    }

    public void addImplicitImports() {
    }

    public void modulesVisited() {
        // to be overridden by subclasses
    }

    public void visitedModule(Module module, boolean forCompiledModule) {
        // to be overridden by subclasses
    }

    public Module overridesModule(ArtifactResult artifact,
            Module module, ModuleImport moduleImport) {
        String realName = artifact.name();
        String realVersion = artifact.version();
        if (! realName.equals(module.getNameAsString()) ||
            ! realVersion.equals(module.getVersion())) {
            if (module != module.getLanguageModule()) {
                Module realModule = getOrCreateModule(splitModuleName(realName), realVersion);
                moduleImport.override(new ModuleImport(realModule, moduleImport.isOptional(), moduleImport.isExport()));
                return realModule;
            }
        }
        return null;
    }
    
    public Modules getModules(){
        return modules;
    }

    protected void setModules(Modules modules) {
        this.modules = modules;
    }
    /**
     * Returns the set of native backend names supported by
     * this ModuleManager. Returning an empty set signifies
     * that any backend is acceptable.
     */
    @Override
    public Backends getSupportedBackends() {
        return Backends.ANY;
    }
}
import static com.redhat.ceylon.model.typechecker.model.ModelUtil.intersection;
import static com.redhat.ceylon.model.typechecker.model.ModelUtil.union;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.lang.model.type.TypeKind;
import com.redhat.ceylon.common.Backend;
import com.redhat.ceylon.common.Backends;
import com.redhat.ceylon.common.BooleanUtil;
import com.redhat.ceylon.common.JVMModuleUtil;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.model.cmr.ArtifactResult;
import com.redhat.ceylon.model.cmr.JDKUtils;
import com.redhat.ceylon.model.loader.mirror.AccessibleMirror;
import com.redhat.ceylon.model.loader.mirror.AnnotatedMirror;
import com.redhat.ceylon.model.loader.mirror.AnnotationMirror;
import com.redhat.ceylon.model.loader.mirror.ClassMirror;
import com.redhat.ceylon.model.loader.mirror.FieldMirror;
import com.redhat.ceylon.model.loader.mirror.MethodMirror;
import com.redhat.ceylon.model.loader.mirror.PackageMirror;
import com.redhat.ceylon.model.loader.mirror.TypeMirror;
import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror;
import com.redhat.ceylon.model.loader.mirror.VariableMirror;
import com.redhat.ceylon.model.loader.model.AnnotationProxyClass;
import com.redhat.ceylon.model.loader.model.AnnotationProxyMethod;
import com.redhat.ceylon.model.loader.model.FieldValue;
import com.redhat.ceylon.model.loader.model.JavaBeanValue;
import com.redhat.ceylon.model.loader.model.JavaMethod;
import com.redhat.ceylon.model.loader.model.LazyClass;
import com.redhat.ceylon.model.loader.model.LazyClassAlias;
import com.redhat.ceylon.model.loader.model.LazyContainer;
import com.redhat.ceylon.model.loader.model.LazyElement;
import com.redhat.ceylon.model.loader.model.LazyFunction;
import com.redhat.ceylon.model.loader.model.LazyInterface;
import com.redhat.ceylon.model.loader.model.LazyInterfaceAlias;
import com.redhat.ceylon.model.loader.model.LazyModule;
import com.redhat.ceylon.model.loader.model.LazyPackage;
import com.redhat.ceylon.model.loader.model.LazyTypeAlias;
import com.redhat.ceylon.model.loader.model.LazyValue;
import com.redhat.ceylon.model.loader.model.LocalDeclarationContainer;
import com.redhat.ceylon.model.loader.model.OutputElement;
import com.redhat.ceylon.model.loader.model.SetterWithLocalDeclarations;
import com.redhat.ceylon.model.typechecker.model.Annotated;
import com.redhat.ceylon.model.typechecker.model.Annotation;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Constructor;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.DeclarationCompleter;
import com.redhat.ceylon.model.typechecker.model.Element;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.ModelUtil;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.ModuleImport;
import com.redhat.ceylon.model.typechecker.model.Modules;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.Setter;
import com.redhat.ceylon.model.typechecker.model.SiteVariance;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeAlias;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
import com.redhat.ceylon.model.typechecker.model.UnknownType;
import com.redhat.ceylon.model.typechecker.model.Value;
import com.redhat.ceylon.model.typechecker.util.ModuleManager;
    static final String CEYLON_LANGUAGE_THROWS_ANNOTATION = "ceylon.language.ThrownExceptionAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_AUTHORS_ANNOTATION = "ceylon.language.AuthorsAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_SEE_ANNOTATIONS = "ceylon.language.SeeAnnotation$annotations$";
    static final String CEYLON_LANGUAGE_SEE_ANNOTATION = "ceylon.language.SeeAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_DEPRECATED_ANNOTATION = "ceylon.language.DeprecationAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_SUPPRESS_WARNINGS_ANNOTATION = "ceylon.language.SuppressWarningsAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_LICENSE_ANNOTATION = "ceylon.language.LicenseAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_TAGS_ANNOTATION = "ceylon.language.TagsAnnotation$annotation$";
    static final String CEYLON_LANGUAGE_ALIASES_ANNOTATION = "ceylon.language.AliasesAnnotation$annotation$";

    // important that these are with ::
    private static final String CEYLON_LANGUAGE_CALLABLE_TYPE_NAME = "ceylon.language::Callable";
    private static final String CEYLON_LANGUAGE_TUPLE_TYPE_NAME = "ceylon.language::Tuple";
    private static final String CEYLON_LANGUAGE_SEQUENTIAL_TYPE_NAME = "ceylon.language::Sequential";
    private static final String CEYLON_LANGUAGE_SEQUENCE_TYPE_NAME = "ceylon.language::Sequence";
    private static final String CEYLON_LANGUAGE_EMPTY_TYPE_NAME = "ceylon.language::Empty";
    
    private static final TypeMirror OBJECT_TYPE = simpleCeylonObjectType("java.lang.Object");
    private static final TypeMirror ANNOTATION_TYPE = simpleCeylonObjectType("java.lang.annotation.Annotation");
    private static final TypeMirror CEYLON_OBJECT_TYPE = simpleCeylonObjectType("ceylon.language.Object");
    private static final TypeMirror CEYLON_ANNOTATION_TYPE = simpleCeylonObjectType("ceylon.language.Annotation");
    private static final TypeMirror CEYLON_CONSTRAINED_ANNOTATION_TYPE = simpleCeylonObjectType("ceylon.language.ConstrainedAnnotation");
//    private static final TypeMirror CEYLON_FUNCTION_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.FunctionDeclaration");
    private static final TypeMirror CEYLON_FUNCTION_OR_VALUE_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.FunctionOrValueDeclaration");
    private static final TypeMirror CEYLON_VALUE_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ValueDeclaration");
    private static final TypeMirror CEYLON_ALIAS_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.AliasDeclaration");
    private static final TypeMirror CEYLON_CLASS_OR_INTERFACE_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ClassOrInterfaceDeclaration");
    private static final TypeMirror CEYLON_CLASS_WITH_INIT_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ClassWithInitializerDeclaration");
    private static final TypeMirror CEYLON_CLASS_WITH_CTORS_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ClassWithConstructorsDeclaration");
    private static final TypeMirror CEYLON_CONSTRUCTOR_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ConstructorDeclaration");
    private static final TypeMirror CEYLON_CALLABLE_CONSTRUCTOR_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.CallableConstructorDeclaration");
    private static final TypeMirror CEYLON_VALUE_CONSTRUCTOR_DECLARATION_TYPE = simpleCeylonObjectType("ceylon.language.meta.declaration.ValueConstructorDeclaration");
    private static final TypeMirror CEYLON_ANNOTATED_TYPE = simpleCeylonObjectType("ceylon.language.Annotated");
    private static final TypeMirror CEYLON_BASIC_TYPE = simpleCeylonObjectType("ceylon.language.Basic");
    private static final TypeMirror CEYLON_REIFIED_TYPE_TYPE = simpleCeylonObjectType("com.redhat.ceylon.compiler.java.runtime.model.ReifiedType");
    private static final TypeMirror CEYLON_SERIALIZABLE_TYPE = simpleCeylonObjectType("com.redhat.ceylon.compiler.java.runtime.serialization.Serializable");
    private static final TypeMirror CEYLON_TYPE_DESCRIPTOR_TYPE = simpleCeylonObjectType("com.redhat.ceylon.compiler.java.runtime.model.TypeDescriptor");
    
    private static final TypeMirror THROWABLE_TYPE = simpleCeylonObjectType("java.lang.Throwable");
//    private static final TypeMirror ERROR_TYPE = simpleCeylonObjectType("java.lang.Error");
    private static final TypeMirror EXCEPTION_TYPE = simpleCeylonObjectType("java.lang.Exception");
    private static final TypeMirror CEYLON_THROWABLE_TYPE = simpleCeylonObjectType("java.lang.Throwable");
    private static final TypeMirror CEYLON_EXCEPTION_TYPE = simpleCeylonObjectType("ceylon.language.Exception");
    
    private static final TypeMirror STRING_TYPE = simpleJDKObjectType("java.lang.String");
    private static final TypeMirror CEYLON_STRING_TYPE = simpleCeylonObjectType("ceylon.language.String");
    
    private static final TypeMirror PRIM_BOOLEAN_TYPE = simpleJDKObjectType("boolean");
    private static final TypeMirror CEYLON_BOOLEAN_TYPE = simpleCeylonObjectType("ceylon.language.Boolean");
    
    private static final TypeMirror PRIM_BYTE_TYPE = simpleJDKObjectType("byte");
    private static final TypeMirror CEYLON_BYTE_TYPE = simpleCeylonObjectType("ceylon.language.Byte");
    
    private static final TypeMirror PRIM_SHORT_TYPE = simpleJDKObjectType("short");

    private static final TypeMirror PRIM_INT_TYPE = simpleJDKObjectType("int");
    private static final TypeMirror PRIM_LONG_TYPE = simpleJDKObjectType("long");
    private static final TypeMirror CEYLON_INTEGER_TYPE = simpleCeylonObjectType("ceylon.language.Integer");
    
    private static final TypeMirror PRIM_FLOAT_TYPE = simpleJDKObjectType("float");
    private static final TypeMirror PRIM_DOUBLE_TYPE = simpleJDKObjectType("double");
    private static final TypeMirror CEYLON_FLOAT_TYPE = simpleCeylonObjectType("ceylon.language.Float");

    private static final TypeMirror PRIM_CHAR_TYPE = simpleJDKObjectType("char");
    private static final TypeMirror CEYLON_CHARACTER_TYPE = simpleCeylonObjectType("ceylon.language.Character");
    
    // this one has no "_" postfix because that's how we look it up
    protected static final String JAVA_LANG_BYTE_ARRAY = "java.lang.ByteArray";
    protected static final String JAVA_LANG_SHORT_ARRAY = "java.lang.ShortArray";
    protected static final String JAVA_LANG_INT_ARRAY = "java.lang.IntArray";
    protected static final String JAVA_LANG_LONG_ARRAY = "java.lang.LongArray";
    protected static final String JAVA_LANG_FLOAT_ARRAY = "java.lang.FloatArray";
    protected static final String JAVA_LANG_DOUBLE_ARRAY = "java.lang.DoubleArray";
    protected static final String JAVA_LANG_CHAR_ARRAY = "java.lang.CharArray";
    protected static final String JAVA_LANG_BOOLEAN_ARRAY = "java.lang.BooleanArray";
    protected static final String JAVA_LANG_OBJECT_ARRAY = "java.lang.ObjectArray";

    // this one has the "_" postfix because that's what we translate it to
    private static final String CEYLON_BYTE_ARRAY = "com.redhat.ceylon.compiler.java.language.ByteArray";
    private static final String CEYLON_SHORT_ARRAY = "com.redhat.ceylon.compiler.java.language.ShortArray";
    private static final String CEYLON_INT_ARRAY = "com.redhat.ceylon.compiler.java.language.IntArray";
    private static final String CEYLON_LONG_ARRAY = "com.redhat.ceylon.compiler.java.language.LongArray";
    private static final String CEYLON_FLOAT_ARRAY = "com.redhat.ceylon.compiler.java.language.FloatArray";
    private static final String CEYLON_DOUBLE_ARRAY = "com.redhat.ceylon.compiler.java.language.DoubleArray";
    private static final String CEYLON_CHAR_ARRAY = "com.redhat.ceylon.compiler.java.language.CharArray";
    private static final String CEYLON_BOOLEAN_ARRAY = "com.redhat.ceylon.compiler.java.language.BooleanArray";
    private static final String CEYLON_OBJECT_ARRAY = "com.redhat.ceylon.compiler.java.language.ObjectArray";

    private static final TypeMirror JAVA_BYTE_ARRAY_TYPE = simpleJDKObjectType("java.lang.ByteArray");
    private static final TypeMirror JAVA_SHORT_ARRAY_TYPE = simpleJDKObjectType("java.lang.ShortArray");
    private static final TypeMirror JAVA_INT_ARRAY_TYPE = simpleJDKObjectType("java.lang.IntArray");
    private static final TypeMirror JAVA_LONG_ARRAY_TYPE = simpleJDKObjectType("java.lang.LongArray");
    private static final TypeMirror JAVA_FLOAT_ARRAY_TYPE = simpleJDKObjectType("java.lang.FloatArray");
    private static final TypeMirror JAVA_DOUBLE_ARRAY_TYPE = simpleJDKObjectType("java.lang.DoubleArray");
    private static final TypeMirror JAVA_CHAR_ARRAY_TYPE = simpleJDKObjectType("java.lang.CharArray");
    private static final TypeMirror JAVA_BOOLEAN_ARRAY_TYPE = simpleJDKObjectType("java.lang.BooleanArray");
    private static final TypeMirror JAVA_IO_SERIALIZABLE_TYPE_TYPE = simpleJDKObjectType("java.io.Serializable");
    
    private static TypeMirror simpleJDKObjectType(String name) {
        return new SimpleReflType(name, SimpleReflType.Module.JDK, TypeKind.DECLARED);
    }
    private static TypeMirror simpleCeylonObjectType(String name) {
        return new SimpleReflType(name, SimpleReflType.Module.CEYLON, TypeKind.DECLARED);
    }

    protected Map<String, Declaration> valueDeclarationsByName = new HashMap<String, Declaration>();
    protected Map<String, Declaration> typeDeclarationsByName = new HashMap<String, Declaration>();
    protected Map<Package, Unit> unitsByPackage = new HashMap<Package, Unit>();
    protected TypeParser typeParser;
    /** 
     * The type factory 
     * (<strong>should not be used while completing a declaration</strong>)
     */
    protected Unit typeFactory;
    protected final Set<String> loadedPackages = new HashSet<String>();
    protected final Map<String,LazyPackage> packagesByName = new HashMap<String,LazyPackage>();
    protected boolean packageDescriptorsNeedLoading = false;
    protected boolean isBootstrap;
    protected ModuleManager moduleManager;
    protected Modules modules;
    protected Map<String, ClassMirror> classMirrorCache = new HashMap<String, ClassMirror>();