/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * TypeClass.java * Created: March 22, 2001 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.openquark.cal.internal.serialization.ModuleSerializationTags; import org.openquark.cal.internal.serialization.RecordInputStream; import org.openquark.cal.internal.serialization.RecordOutputStream; import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo; /** * Models a type class in CAL. * <p> * Some examples of type classes in CAL are: * Prelude.Eq, Prelude.Ord, Prelude.Num, Prelude.Enum, Prelude.Inputable, * Prelude.Outputable, Prelude.Appendable and Debug.Show. * * @author Bo Ilic */ public final class TypeClass extends ScopedEntity { private static final int serializationSchema = 0; /** * The immediate superclasses of this type class. The ordering is significant in determining * the definitions of the dictionary switching functions and of the dictionary functions. */ private final List<TypeClass> parentTypeClassList; /** * The list of class methods that are supported by this class. Note that methods of superclasses * are not listed here, even though they are implicitly available. The ordering is significant * in determining the definitions of the dictionary functions. */ private final List<ClassMethod> classMethodsList; /** * The kind of the type class. One way to think of this is as the kind of the type class variable. * For example, the kind of the Functor class is * -> * and the kind of the Eq class is *. * There will be no kind variables in kindExpr. */ private KindExpr kindExpr; /** * The name of the type class type variable. Can be used to give better error messages. For example, for * class Functor f where ..., this will be "f". */ private String typeClassTypeVarName; /** * This comparator is used when working with sorted sets of type classes, where the sort * order is determined by the qualified name of the type class. * For example, in the case of the type class constraints on a TypeVar, we need a well-defined * order for adding dictionary arguments to the definition of a function whose type depends on * constrained type variables. */ static private final Comparator<TypeClass> COMPARATOR = new Comparator<TypeClass>() { /** {@inheritDoc}*/ public int compare(TypeClass object1, TypeClass object2) { return object1.getName().getQualifiedName().compareTo(object2.getName().getQualifiedName()); } }; /** an unmodifiable empty set of type classes, sorted by qualified name. */ static final SortedSet<TypeClass> NO_CLASS_CONSTRAINTS = Collections.unmodifiableSortedSet(TypeClass.makeNewClassConstraintSet()); /** * TypeClass constructor. * * @param name the name of the type class * @param scope the scope of the type class * @param typeClassTypeVarName name of the type class type variable parameter. For example, for "class Functor f where..." this is "f". */ TypeClass(QualifiedName name, Scope scope, KindExpr kindExpr, String typeClassTypeVarName) { super (name, scope, null); if (kindExpr == null || typeClassTypeVarName == null) { throw new NullPointerException(); } this.kindExpr = kindExpr; this.typeClassTypeVarName = typeClassTypeVarName; parentTypeClassList = new ArrayList<TypeClass>(); classMethodsList = new ArrayList<ClassMethod>(); } /** Zero argument constructor for serialization. */ TypeClass() { parentTypeClassList = new ArrayList<TypeClass>(); classMethodsList = new ArrayList<ClassMethod>(); } /** * @return The kind of the type class. One way to think of this is as the kind of the type class variable. * For example, the kind of the Functor class is * -> * and the kind of the Eq class is *. * There will be no kind variables in kindExpr. */ KindExpr getKindExpr() { return kindExpr; } /** * Call this method when kind checking for this TypeConstructor is complete and * prior to kind checking later dependency groups. * The effect is to ground the remaining kind variables by * after kind inference * has determined the most general kind of this TypeConstructor. This is because CAL does * not support polymorphic kinds. */ void finishedKindChecking() { kindExpr = kindExpr.bindKindVariablesToConstant(); } /** * Add the class methods in the order that they are declared within the type class. Do not add duplicates! * @param classMethod */ void addClassMethod(ClassMethod classMethod) { if (classMethod == null) { throw new NullPointerException("The argument 'classMethod' cannot be null."); } classMethodsList.add(classMethod); } /** * Add the parent classes in the order that they are mentioned in the context of this type class declaration. * Do not add duplicates. However, redundant constraints are OK- these will be handled later e.g. (Eq a, Ord a) => Num a, * the constraint Eq a is redundant. * @param parentTypeClass */ void addParentClass(TypeClass parentTypeClass) { if (parentTypeClass == null) { throw new NullPointerException("The argument 'parentTypeClass' cannot be null."); } parentTypeClassList.add(parentTypeClass); } /** * Returns the ordered list of all ancestors of this TypeClass. The list does not include * the class itself. It is ordered in the ordering required for writing dictionary functions. * Creation date: (4/4/01 3:11:56 PM) * @return List */ List<TypeClass> calculateAncestorClassList() { List<TypeClass> ancestorClassList = new ArrayList<TypeClass>(); calculateAncestorClassList(ancestorClassList); return ancestorClassList; } /** * Helper function for the public calculateAncestorClassList. The reason for this style of * implementation is to limit reallocations of List objects. * * Creation date: (4/6/01 8:59:36 AM) * @param ancestorClassList list of TypeClass objects */ private void calculateAncestorClassList(List<TypeClass> ancestorClassList) { for (int i = 0, nParents = getNParentClasses(); i < nParents; ++i) { TypeClass parentTypeClass = getNthParentClass(i); parentTypeClass.calculateAncestorClassList(ancestorClassList); ancestorClassList.add(parentTypeClass); } } /** * Provides a deterministic ordering to the ancestors of a type class. * @param ancestorTypeClass * @return int the index of the ancestorTypeClass in the list returned by calculateAncestorClassList, or -1 if this is not an ancestor class. */ int getAncestorClassIndex(TypeClass ancestorTypeClass) { //todoBI this could use a more efficient implementation. However, type class inheritance hierarchies are typically quite small, //so we won't bother for now! List<TypeClass> ancestorClassList = calculateAncestorClassList(); for (int i = 0, nAncestors = ancestorClassList.size(); i < nAncestors; ++i) { TypeClass ancestor = ancestorClassList.get(i); if (ancestorTypeClass == ancestor) { return i; } } return -1; } /** * @param methodN zero-based index. * @return ClassMethod the ClassMethod for the given index. */ public ClassMethod getNthClassMethod(int methodN) { return classMethodsList.get(methodN); } /** * Method getClassMethod. * @param classMethodName the unqualified name of the classMethod, assumed to belong to the same module as this type class. * @return ClassMethod the ClassMethod or null if there is not one with the given classMethodName. */ public ClassMethod getClassMethod(String classMethodName) { for (int i = 0, nClassMethods = getNClassMethods(); i < nClassMethods; ++i) { ClassMethod classMethod = getNthClassMethod(i); if (classMethod.getName().getUnqualifiedName().equals(classMethodName)) { return classMethod; } } return null; } /** * Finds the index of the class method in the list of class methods for this type class. * The class method can then be retreived with getNthClassMethod. * @param classMethodName name of the class method, assumed to belong to the same module as the type class itself. * @return the index of the class method, or -1 if not a class method for this type class. */ int getClassMethodIndex(String classMethodName) { for (int i = 0, nClassMethods = getNClassMethods(); i < nClassMethods; ++i) { ClassMethod classMethod = getNthClassMethod(i); if (classMethod.getName().getUnqualifiedName().equals(classMethodName)) { return i; } } return -1; } /** * @return int the number of class methods defined in this TypeClass (does not include superclass methods) */ public int getNClassMethods() { return classMethodsList.size(); } /** * @return int the number of direct parent classes of this class. */ public int getNParentClasses() { return parentTypeClassList.size(); } /** * @param n * @return TypeClass the nth direct parent class of this class. */ public TypeClass getNthParentClass(int n) { return parentTypeClassList.get(n); } /** * Returns true if this TypeClass is the same or a subclass of anotherTypeClass. * * Creation date: (3/28/01 1:43:23 PM) * @return boolean * @param anotherTypeClass */ boolean isSpecializationOf(TypeClass anotherTypeClass) { if (this == anotherTypeClass) { return true; } for (int i = 0, nParents = getNParentClasses(); i < nParents; ++i) { if (getNthParentClass(i).isSpecializationOf(anotherTypeClass)) { return true; } } return false; } /** * For internal compiler use only. * * @return true if the class has no superclasses, and only 1 class method. For example, * Prelude.Outputable has only the single class method output. */ public boolean internal_isSingleMethodRootClass() { return getNClassMethods() == 1 && getNParentClasses() == 0; } /** * @return The name of the type class type variable. Can be used to give better error messages. For example, for * class Functor f where ..., this will be "f". */ String getTypeClassTypeVarName() { return typeClassTypeVarName; } /** * Returns an approximation to actual CAL source for a class declaration. Currently, it is * only needed for debugging purposes. * * @return String */ @Override public String toString() { StringBuilder result = new StringBuilder(getScope().toString() + " class ("); int nParents = parentTypeClassList.size(); if (nParents > 0) { for (int i = 0; i < nParents; ++i) { if (i > 0) { result.append(", "); } result.append(parentTypeClassList.get(i).getName()).append(' ').append(typeClassTypeVarName); } } result.append(") => ").append(getName()); result.append(' ').append(typeClassTypeVarName).append(" :: ").append(kindExpr).append(" where\n"); for (int i = 0, nMethods = getNClassMethods(); i < nMethods; ++i) { result.append(" ").append(getNthClassMethod(i)).append('\n'); } result.append('\n'); return result.toString(); } /** * @return creates a new empty SortedSet for holding type classes sorted by qualified name. * This set can then be modified by the caller. */ static SortedSet<TypeClass> makeNewClassConstraintSet() { return new TreeSet<TypeClass>(TypeClass.COMPARATOR); } /** * Removes all those type classes that have child classes in the set from the * set. For example, if typeClassConstraintSet = {Eq, Ord, Num, Outputable} then * after the call, it would be {Num, Outputable}. * @param typeClassConstraintSet (TypeClass SortedSet) */ static void removeSuperclassConstraints(SortedSet<TypeClass> typeClassConstraintSet) { //can't have redundant constraints if there are only 0 or 1 classes in the //constraint set! if (typeClassConstraintSet.size() <= 1) { return; } Set<TypeClass> superclassSet = new HashSet<TypeClass>(); for (final TypeClass typeClass : typeClassConstraintSet) { superclassSet.addAll(typeClass.calculateAncestorClassList()); } typeClassConstraintSet.removeAll(superclassSet); } /** * Write this TypeClass instance to the RecordOutputStream. * @param s * @throws IOException */ @Override final void write (RecordOutputStream s) throws IOException { s.startRecord(ModuleSerializationTags.TYPE_CLASS, serializationSchema); super.writeContent(s); // parent type classes s.writeShortCompressed(parentTypeClassList.size()); for (final TypeClass parent : parentTypeClassList) { s.writeQualifiedName(parent.getName()); } // class methods s.writeShortCompressed(classMethodsList.size()); for (final ClassMethod cm : classMethodsList) { cm.write(s); } // kindExpr field kindExpr.write(s); // typeClassTypeVarName field s.writeUTF(typeClassTypeVarName); s.endRecord(); } /** * Because type classes can have mutually recursive references we need to * do an initial load of the TypeClass objects loading just the members in * the base class. This is enough to build up the map of TypeClass instances * for the module and allow resolution of references to other type classes. * The TypeClass specific members are loaded by loadFinal(). * The read position will be before the TypeClass record header. * @param s * @param mti * @param msgLogger the logger to which to log deserialization messages. * @return an instance of TypeClass. * @throws IOException */ static final TypeClass loadInit(RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { TypeClass tc = new TypeClass(); try { tc.readInit(s, mti, msgLogger); } catch (IOException e) { // Add context to error message. QualifiedName qn = tc.getName(); throw new IOException ("Error loading TypeClass " + (qn == null ? "" : qn.getQualifiedName()) + ": " + e.getLocalizedMessage()); } return tc; } /** * Load the TypeClass specific members of this TypeClass instance. * The read position will be before the TypeClass record header. * @param s * @param mti * @param msgLogger the logger to which to log deserialization messages. * @throws IOException */ final void loadFinal(RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { try { readFinal(s, mti, msgLogger); } catch (IOException e) { // Add context to error message. QualifiedName qn = getName(); throw new IOException ("Error loading TypeClass " + (qn == null ? "" : qn.getQualifiedName()) + ": " + e.getLocalizedMessage()); } } /** * Because type classes can have mutually recursive references we need to * do an initial load of the TypeClass objects loading just the members in * the base class. This is enough to build up the map of TypeClass instances * for the module and allow resolution of references to other type classes. * The TypeClass specific members are loaded by readFinal(). * The read position will be before the TypeClass record header. * @param s * @param mti * @param msgLogger the logger to which to log deserialization messages. * @throws IOException */ private void readInit (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { // Look for Record header. RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.TYPE_CLASS); if (rhi == null) { throw new IOException("Unable to find TypeClass record header."); } DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "TypeClass", msgLogger); super.readContent(s, mti, msgLogger); // Now that we've read/set the name of this TypeClass we can add it to the ModuleTypeInfo. mti.addTypeClass(this); s.skipRestOfRecord(); } /** * Load the TypeClass specific members of this TypeClass instance. * The read position will be before the TypeClass record header. * @param s * @param mti * @param msgLogger the logger to which to log deserialization messages. * @throws IOException */ private void readFinal (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { // Look for Record header. RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.TYPE_CLASS); if(rhi == null) { throw new IOException("Unable to find TypeClass record header."); } DeserializationHelper.checkSerializationSchema(rhi.getSchema(), serializationSchema, mti.getModuleName(), "TypeClass", msgLogger); // Now get the record header for the parent members and skip the entire record. RecordHeaderInfo parentHeader = s.findRecord(ModuleSerializationTags.SCOPED_ENTITY); if(parentHeader == null) { throw new IOException("Unable to find ScopedEntityImpl record header while loading TypeClass " + getName() + "."); } s.skipRestOfRecord(); int nParentTypeClasses = s.readShortCompressed(); for (int i = 0; i < nParentTypeClasses; ++i) { QualifiedName qn = s.readQualifiedName(); TypeClass parent = mti.getReachableTypeClass(qn); // This could be a type from a non-direct dependee module. if (parent == null) { throw new IOException("Unable to resolve TypeClass " + qn + " while loading TypeClass " + getName()); } addParentClass(parent); } int nClassMethods = s.readShortCompressed(); for (int i = 0; i < nClassMethods; ++i) { ClassMethod cm = ClassMethod.load(s, mti, msgLogger); addClassMethod(cm); } kindExpr = KindExpr.load(s, mti.getModuleName(), msgLogger); typeClassTypeVarName = s.readUTF(); s.skipRestOfRecord(); } }