/* * 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. */ /* * TypeExpr.java * Created: July 17, 2000 * By: Bo Ilic */ package org.openquark.cal.compiler; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; 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; import org.openquark.cal.module.Cal.Core.CAL_Prelude; /** * The internal representation of type expressions. Some examples are (schematically) * a -> b, Int, (Int, a -> Boolean). * <p> * From the point of view of client packages outside the compiler package, TypeExpr is an immutable class. * <p> * Replacing an instantiated type variable by its instantiation results in an equivalent type. * <p> * TypeExpr is not an immutable class, but its state transitions are limited. * The only state transitions allowed are: * <ol> * <li> pruning (replacing instantiated type variables by their instantiations. This preserves * the type equivalence class). * <li> instantiating an uninstantiated type variable to a type constructor or to a type variable * with more stringent type class constraints. This makes the type expression a specialization * of the original type expression. * </ol> * <p> * As a consequence, type expressions whose types do not depend on type variables will always * represent the same type. * <p> * Creation date: (July 17, 2000) * @author Bo Ilic */ abstract public class TypeExpr { /** * Serialization schema for a record indicating an already visited type expression. This * goes with the record tag: EXISTING_TYPE_EXPR */ private static final int alreadyVisitedTypeExprSerializationSchema = 0; /** * Serialization schema for a record indicating constant type expression. i.e. a type expression * defined as a static final value. */ private static final int staticConstantTypeExprSerializationSchema = 0; /** the main effect is to print type variables with addresses rather than using letters */ static final boolean DEBUG_INFO = false; //Here are some useful static constants for some of the most common types. Also, note the //static helper functions in this class which help with creating types. //Warning: do not add parametric types as public constants, since they can be mutated via type variable instantiation. /** the type of the empty record {} */ static final TypeExpr EMPTY_RECORD = new RecordType(RecordVar.NO_FIELDS, Collections.<FieldName, TypeExpr>emptyMap()); /** * The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by * the {@link #load(RecordInputStream, ModuleTypeInfo, Map, Map, CompilerMessageLogger)} method. */ private static final short[] TYPE_EXPR_RECORD_TAGS = new short[]{ ModuleSerializationTags.ALREADY_VISITED_TYPE_EXPR, ModuleSerializationTags.STATIC_CONSTANT_TYPE_EXPR, ModuleSerializationTags.TYPE_CONSTRUCTOR, ModuleSerializationTags.TYPE_VAR, ModuleSerializationTags.TYPE_VAR_WITH_INSTANCE, ModuleSerializationTags.RECORD_TYPE, ModuleSerializationTags.TYPE_APP }; /** * Whether to parenthesize a type expression sometimes depends on the parent node in the type expression * tree. For example, * Int -> Char must be parenthesized in (Int -> Char) -> Boolean because it is the domain of a function type * and it itself is a function. * Either Int Char must be parenthesized in Maybe (Either Int Char) because its parent is a textual type * constructor (Maybe) and it itself is a positive arity textual type constructor. * * This class is a type-safe enum indicating the information passed from the parent node in the type expression * graph that enables this node to decide if it needs parentheses. * * @author Bo Ilic */ final static class ParenthesizationInfo { private final String name; /** indicates that no parentheses are necessary for the root of the type expression. */ static final ParenthesizationInfo NONE = new ParenthesizationInfo("no parentheses required"); /** * indicates that the typeExpr is an argument of a textual form type constructor or type var. * * An example for the (rare) case of a textual type var is the type string for * "Functor.map (Prelude.undefined :: Maybe Int -> Either Char String)" * which is "Cal.Core.Functor.Functor a => a (Cal.Core.Prelude.Maybe Cal.Core.Prelude.Int) -> a (Cal.Core.Prelude.Either Cal.Core.Prelude.Char Cal.Core.Prelude.String)" */ static final ParenthesizationInfo ARG_OF_TEXTUAL_TYPE_CONS_OR_VAR = new ParenthesizationInfo("arg of textual type constructor or type var"); /** indicates that the typeExpr is the domain of a function type constructor (->). */ static final ParenthesizationInfo DOMAIN_OF_FUNCTION = new ParenthesizationInfo("domain of function"); private ParenthesizationInfo(String name) { this.name = name; } /** {@inheritDoc} */ @Override public String toString() { return name; } } /** * @return TypeConsApp if this TypeExpr has a TypeConsApp at its root then the TypeConsApp * is returned, otherwise null. */ public final TypeConsApp rootTypeConsApp() { TypeExpr typeExpr = this.prune(); return (typeExpr instanceof TypeConsApp) ? (TypeConsApp) typeExpr : null; } /** * @return RecordType if this TypeExpr has a TypeApp at its root then the TypeApp * is returned, otherwise null. */ public final TypeApp rootTypeApp() { TypeExpr typeExpr = this.prune(); return (typeExpr instanceof TypeApp) ? (TypeApp) typeExpr : null; } /** * @return TypeVar if this TypeExpr has a TypeVar at its root then the TypeVar * is returned, otherwise null. */ public final TypeVar rootTypeVar() { TypeExpr typeExpr = this.prune(); return (typeExpr instanceof TypeVar) ? (TypeVar) typeExpr : null; } /** * @return RecordType if this TypeExpr has a RecordType at its root then the RecordType * is returned, otherwise null. */ public final RecordType rootRecordType() { TypeExpr typeExpr = this.prune(); return (typeExpr instanceof RecordType) ? (RecordType) typeExpr : null; } /** * Checks if typeExpr2 can be specialized to typeExpr1. There are no side effects. * * Creation date: (5/16/01 3:24:26 PM) * @param typeExpr1 * @param typeExpr2 * @param contextModuleTypeInfo context in which this pattern matching takes place. This is needed to know what class instances are in scope. * @return boolean returns True if typeExpr2 can be specialized to typeExpr1. */ public static boolean canPatternMatch(TypeExpr typeExpr1, TypeExpr typeExpr2, ModuleTypeInfo contextModuleTypeInfo) { //Note: we copy both type expressions simultaneously since there may be referentially //identical type variables in common. //So for example, (a, Int) and (Char, a) can't unify whereas (a, Int) and (Char, a') can. TypeExpr[] copiedTypes = TypeExpr.copyTypeExprs(new TypeExpr[] {typeExpr1, typeExpr2}); TypeExpr typeExprCopy1 = copiedTypes[0]; TypeExpr typeExprCopy2 = copiedTypes[1]; try { TypeExpr.patternMatch(typeExprCopy1, typeExprCopy2, contextModuleTypeInfo); } catch (TypeException te) { return false; } return true; } /** * Please see the comment for patternMatchTypePieces. Returns true if patternMatchTypePieces would return without an * exception. * * Note: there are no side effects on the arguments. * * @param types1 * @param types2 * @param contextModuleTypeInfo * @return boolean */ public static boolean canPatternMatchPieces(TypeExpr[] types1, TypeExpr[] types2, ModuleTypeInfo contextModuleTypeInfo) { try { TypeExpr.patternMatchPieces (types1, types2, contextModuleTypeInfo); } catch (TypeException te) { return false; } return true; } /** * Checks if unification of 2 type expressions would succeed. There are no side effects. * This function properly handles shared type variables between typeExpr1 and typeExpr2. * So for example, (a, Int) and (Char, a) can't unify whereas (a, Int) and (Char, a') can. * * @param typeExpr1 * @param typeExpr2 * @param contextModuleTypeInfo context in which this unification takes place. This is needed to know what class instances are in scope. * @return boolean returns True if unification of the 2 type expressions would succeed. */ public static boolean canUnifyType(TypeExpr typeExpr1, TypeExpr typeExpr2, ModuleTypeInfo contextModuleTypeInfo) { // Copy the type expressions and try to unify them. //Note: we copy both type expressions simultaneously since there may be referentially //identical type variables in common. TypeExpr[] copiedTypes = TypeExpr.copyTypeExprs(new TypeExpr[] {typeExpr1, typeExpr2}); TypeExpr typeExprCopy1 = copiedTypes[0]; TypeExpr typeExprCopy2 = copiedTypes[1]; try { TypeExpr.unifyType(typeExprCopy1, typeExprCopy2, contextModuleTypeInfo); } catch (TypeException te) { return false; } return true; } /** * Please see the comment for unifyTypePieces. Returns true if unifyTypePieces would return without an * exception. * * Note: there are no side effects on the arguments. * * @param types1 * @param types2 * @param contextModuleTypeInfo * @return whether unifyTypePieces would return without an exception. */ public static boolean canUnifyTypePieces(TypeExpr[] types1, TypeExpr[] types2, ModuleTypeInfo contextModuleTypeInfo) { try { TypeExpr.unifyTypePieces (types1, types2, contextModuleTypeInfo); } catch (TypeException te) { return false; } return true; } /** * Calculates the degree of closeness between the 2 types. There are no side effects. * * <P> * The type closeness is a measure of the closeness between the 2 argument types. A value of -1 means that the * types do not unify. A value >= 0 means that the type do unify. The larger the number, the closer * the 2 types are. This value should not be persisted and may change in the future. It is intended for use * by UI clients such as Intellicut in filtering the list of possible candidates to display in the pick list * of gems to those that are a closer match. This number is not scaled. What this means is that you can't * interpret a closeness of 3 as a having a fixed meaning. In other words, if you fix the first argument, * unifyType can be considered as a function from TypeExpr -> Int and the maximum and minimum values attained * by this function of one variable on its domain gives the interpretation of the closeness magnitude for * that particular first argument. * * <P> * The intuition behind the heuristic is that it represents how many "coincidences" * there are between two types. So every time we encounter two things that are the * same without being totally uninstantiated type variables, we add a point. * * <P> * The current heuristic gives 1 point for each time it matches: * <ol> * <li> a type constructor * <li> a record type * <li> a record field name that was present in both "original" types * </ol> * * <P> * to something other than an uninstantiated variable. * * <P> * There are a couple of subtleties here: * <ol> * <li> Matching a constrained type variable to a type that meets the contraint * is just as good as matching the type itself. So for example the type * closeness of "Int" to "Num a => a" is 1, just like the closeness of * "Int" to "Int". * <li> Variables are instantiated as the type closeness check proceeds. For * example, the type closeness of "a -> a" to "Int -> Int" is 2: * <ul> * <li> 1 point for matching "->" type constructor * <li> 0 points for matching first a to first Int, since a is uninstantiated * <li> 1 point for matching second a to second Int, because by that point a * has been instantiated to Int. * </ul> * <li> Field types (in records) are sometimes matched against intermediate types, * but field names are only matched against original types. For example, * when calculating the closeness of "Eq a => a" to "{x :: Int, y :: Char}", * the type closeness is 3: * First we replace "Eq a => a" with the intermediate type "(Eq b, Eq c) => {x :: b, y :: c}" * <ul> * <li> 1 point for matching record types * <li> 1 point for matching type "Eq b => b" to Int * <li> 1 point for matching type "Eq c => c" to Char * <li> 0 points for matching field names (x and y), because the field names * were not present in both original types * </ul> * <ol> * <P> * @param typeExpr1 * @param typeExpr2 * @param contextModuleTypeInfo context in which this closeness measure takes place. This is needed to know what class instances are in scope. * @return int */ public static int getTypeCloseness(TypeExpr typeExpr1, TypeExpr typeExpr2, ModuleTypeInfo contextModuleTypeInfo) { //Copy the type expressions and try to unify them. //Note: we copy both arguments simultaneously since there may be referentially //identical type variables in typeExpr1 and typeExpr2. //So for example, (a, Int) and (Char, a) can't unify whereas (a, Int) and (Char, a') can. TypeExpr[] copiedTypes = TypeExpr.copyTypeExprs(new TypeExpr[] {typeExpr1, typeExpr2}); TypeExpr typeExprCopy1 = copiedTypes[0]; TypeExpr typeExprCopy2 = copiedTypes[1]; try { return typeExprCopy1.unifyType(typeExprCopy2, contextModuleTypeInfo); } catch (TypeException te) { return -1; } } /** * Returns true if searchTypeExpr appears somewhere in this TypeExpr tree. * Note: We are doing object/pointer equality here. * Creation date: (08/08/01 9:28:13 AM) * @return boolean * @param searchTypeExpr */ public abstract boolean containsTypeExpr(TypeExpr searchTypeExpr); /** * Returns true if searchTypeVar appears somewhere in this TypeExpr tree. * Note: We are doing object/pointer equality here. * * For example, "a -> b" contains the uninstantiated type variable a. * We do not allow unifications where a type variable is specialized to a type expression * in which the variable itself occurs since this leads to infinite types. * There are many difficulties in dealing with infinite types and the Hindley-Milner typing * algorithm just disallows them. * * @return boolean * @param searchTypeVar */ final boolean containsUninstantiatedTypeVar(TypeVar searchTypeVar) { if (searchTypeVar.getInstance() != null) { throw new IllegalArgumentException(); } return containsTypeExpr(searchTypeVar); } /** * Returns true if searchRecordVar appears somewhere in this TypeExpr tree. * Note: We are doing object/pointer equality here. * * @return boolean * @param searchRecordVar */ abstract boolean containsRecordVar(RecordVar searchRecordVar); /** * A polymorphic type expression is one that includes an uninstantiated type variable * or record variable. * Examples of polymorphic types are: * a * {r} * r\field1 => {r | field1 :: Double} * Int -> Either a b * Examples of non-polymorphic types are: * Int * {field1 :: Double -> Maybe Char, field2 :: Int} * [(Int, Boolean)] * @return true if this type expression is polymorphic */ public abstract boolean isPolymorphic(); /** * Makes a copy of this type expression. Instantiating the copy won't affect the original * type expression. * Creation date: (7/30/01 10:10:48 AM) * @return TypeExpr */ public final TypeExpr copyTypeExpr() { return CopyEnv.freshType(this, null); } /** * Make copies of the given type expressions, maintaining referential equality in the duplicated expressions. * eg. if the types to copy are [a -> b, c -> a], this will return [a' -> b', c' -> a']. * * @param typesToCopy the type expressions to be copied * @return TypeExpr[] the copies of the type expressions. Referential equality among the types to copy * will be retained in the duplicated types. */ public static TypeExpr[] copyTypeExprs(TypeExpr[] typesToCopy) { return CopyEnv.freshTypePieces(typesToCopy, null); } private static CopyEnv.TypesPair copyTypesPair(TypeExpr[] types1, TypeExpr[] types2) { return CopyEnv.freshTypesPair(types1, types2, null); } /** * Get a reference to this type expression with a number of parameters hidden at the beginning. * For instance, if the type expression is a -> (b -> bool) -> (c -> d) -> e, calling this method with n == 2 * drops a and (b -> bool), returning (c -> d) -> e. * Note that if n == 1, this is equivalent to getResultType(). * * Creation date: (30/07/2001 12:00:01 PM) * @param n the number of parameters to suppress from the beginning of this type expression * @return TypeExpr the resulting type expression */ public final TypeExpr dropFirstNArgs(int n) { TypeExpr typeExpr = this.prune(); for (int i = 0; i < n; i++) { if (!(typeExpr instanceof TypeConsApp && ((TypeConsApp) typeExpr).getName().equals(CAL_Prelude.TypeConstructors.Function))) { throw new IllegalStateException("Programming Error"); } typeExpr = ((TypeConsApp) typeExpr).getArg(1).prune(); } return typeExpr; } /** * Returns the TypeExpr of a subcomponent of the param typeExpr indicated by params componentNumber and dataConstructor. * * Eg: If we called 'getComponentTypeExpr( (Either Double Bool), 0, Left), we get back 'Double'. * This is because the type of the Either type is Either a b, with data constructors Left a and Right b. * * @param typeExpr the type for which the component should be returned. * @param componentNumber the zero-based index of the component to return. * @param dataConstructor the data constructor whose component should be returned. * @return TypeExpr the component type expression. */ public static TypeExpr getComponentTypeExpr(TypeExpr typeExpr, int componentNumber, DataConstructor dataConstructor) { // eg. for Left a, the type is (a -> Either a b), the type pieces are [a, Either a b] TypeExpr[] dataConstructorTypePieces = dataConstructor.getTypeExpr().getTypePieces(); if (dataConstructorTypePieces.length <= componentNumber) { throw new IllegalArgumentException("TypeExpr.getComponentTypeExpr() - invalid component number: " + componentNumber + " for data constructor " + dataConstructor); } // This is the TypeExpr that we want (although it may have parts not instantiated properly yet). TypeExpr targetTypeExpr = dataConstructorTypePieces[componentNumber]; // So, let's do a search, and instantiate where appropriate. TypeExpr[] actualTypeList = typeExpr.rootTypeConsApp().getArgs(); TypeConsApp dataConstructorResultType = dataConstructorTypePieces[dataConstructorTypePieces.length - 1].rootTypeConsApp(); TypeExpr[] searchList = dataConstructorResultType.getArgs(); return instantiateWithCorrespondingTypeExpr(targetTypeExpr, searchList, actualTypeList); } /** * Helper method for getComponentTypeExpr(). * Returns a TypeExpr modeled after targetTypeExpr with the TypeVars instantiated according to searchList and actualTypeList. * (If targetTypeExpr is an uninstantiated TypeVar, it will find it's match in the searchList, then, it will be set to the corresponding * TypeExpr in the actualTypeList) * * Eg: If we called instantiateWithCorrespondingTypeExpr( 'a', ['b', 'a', 'c'], [Double, Int, Bool] ), then "Int" will be returned. * Eg: If we called instantiateWithCorrespondingTypeExpr( Maybe 'a', ['b', 'a'], [Double, Int] ), then "Maybe Int" will be returned. * * Warning: this is a mutator. * * @param targetTypeExpr * @param searchList * @param actualTypeList * @return targetTypeExpr, potentially pruned */ private static TypeExpr instantiateWithCorrespondingTypeExpr(TypeExpr targetTypeExpr, TypeExpr[] searchList, TypeExpr[] actualTypeList) { // After pruning, the targetTypeExpr must either be an uninstantiated TypeVar (which means we have to search thru the list), // or it's a TypeConsApp (which means we'll have to check its arg list [if any]). targetTypeExpr = targetTypeExpr.prune(); if (targetTypeExpr instanceof TypeVar) { TypeVar typeVar = (TypeVar)targetTypeExpr; for (int i = 0, n = searchList.length; i < n; ++i) { if (typeVar.equals(searchList[i])) { return actualTypeList[i]; } } throw new IllegalArgumentException("Error in instantiateWithCorrespondingTypeExpr method: Unable to find matching TypeExpr."); } else if (targetTypeExpr instanceof RecordType) { RecordType recordType = targetTypeExpr.rootRecordType(); Map<FieldName, TypeExpr> hasFieldsMap = recordType.getHasFieldsMap(); for (final Map.Entry<FieldName, TypeExpr> entry : hasFieldsMap.entrySet()) { TypeExpr fieldType = entry.getValue(); instantiateWithCorrespondingTypeExpr(fieldType, searchList, actualTypeList); } return recordType; } else if (targetTypeExpr instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp) targetTypeExpr; for (int i = 0, nArgs = typeConsApp.getNArgs(); i < nArgs; ++i) { typeConsApp.setArg(i, instantiateWithCorrespondingTypeExpr(typeConsApp.getArg(i), searchList, actualTypeList)); } return typeConsApp; } else { throw new IllegalArgumentException("Unhandled TypeExpr class: " + targetTypeExpr.getClass()); } } /** * Returns the set of generic, type-class constrained and uninstantiated polymorphic variables (i.e. either type variables or * record variables) that occur in this TypeExpr. This set has a well-defined ordering determined by the traversal * of the TypeExpr tree. * (As a technical point, record types are traversed first via they polymorphic record variable, then * by field name order of the "has fields". In other words, the ordering returned below is independent of the representation * of the record type as a sequence of extensions). * Note that record variables can have both type-class constraints and lacks constraints. This refers only to the type-class * constraints. * * @param nonGenericVars nonGenericVars list of type variables that are not generic. Can be null to indicate an empty list. * @return Set (PolymorphicVar Set, where each PolymorphicVar is either a TypeVar or RecordVar) */ final Set<PolymorphicVar> getGenericClassConstrainedPolymorphicVars(NonGenericVars nonGenericVars) { //use a LinkedHashSet to ensure iteration order. Set<PolymorphicVar> varSet = new LinkedHashSet<PolymorphicVar>(); getGenericClassConstrainedPolymorphicVars(varSet, nonGenericVars); return varSet; } /** * A helper function that returns the TypeVar with the specified typeClassTypeVarName * @param typeClassTypeVarName * @return TypeVar will not be null (the method fails in an exception if the type variable cannot be found). */ final TypeVar getTypeClassTypeVar(String typeClassTypeVarName) { if (typeClassTypeVarName == null) { throw new NullPointerException("typeClassTypeVarName cannot be null."); } //todoBI can give a more efficient implementation. for (final PolymorphicVar typeVar : getGenericClassConstrainedPolymorphicVars(null)) { if (typeClassTypeVarName.equals(typeVar.getPreferredName())) { return (TypeVar)typeVar; } } throw new IllegalArgumentException("type class type variable '" + typeClassTypeVarName + "' not found"); } /** * Helper function for finding the set of generic, constrained and uninstantiated polymorphic variables (i.e. either type variables or * record variables) that occur in this TypeExpr. This set has a well-defined ordering determined by the traversal of * the TypeExpr tree. * (As a technical point, record types are traversed first via they polymorphic record variable, then * by field name order of the "has fields". In other words, the ordering returned below is independent of the representation * of the record type as a sequence of extensions). * Note that record variables can have both type-class constraints and lacks constraints. This refers only to the type-class * constraints. * * @param varSet Set (PolymorphicVar Set, where each PolymorphicVar is either a TypeVar or RecordVar) * @param nonGenericVars nonGenericVars list of type variables that are not generic. Can be null to indicate an empty list. */ abstract void getGenericClassConstrainedPolymorphicVars(Set<PolymorphicVar> varSet, NonGenericVars nonGenericVars); /** * Returns the set of uninstantiated TypeVars that occur in this TypeExpr. This set has a * well-defined ordering determined by the traversal of the TypeExpr tree. * However, the ordering is *not* invariant with respect to the representation of record types as extensions, * and so in effect the ordering should not be used for any critical purpose. (This is in contrast with * getGenenericClassConstrainedPolymorphicVars. An invariant order could be defined, but it is more expensive, * and we don't need it currently). * @return Set */ final Set<TypeVar> getUninstantiatedTypeVars() { //use a LinkedHashSet to ensure iteration order. Set<TypeVar> varSet = new LinkedHashSet<TypeVar>(); getUninstantiatedTypeVars(varSet); return varSet; } /** * Helper function for finding the set of uninstantiated TypeVars that occur in this * TypeExpr. This set has a well-defined ordering determined by the traversal of the TypeExpr tree. * However, the ordering is *not* invariant with respect to the representation of record types as extensions, * and so in effect the ordering should not be used for any critical purpose. (This is in contrast with * getGenenericClassConstrainedPolymorphicVars. An invariant order could be defined, but it is more expensive, * and we don't need it currently). * * @param varSet Set set of the uninstantiated TypeVars in this TypeExpr. */ abstract void getUninstantiatedTypeVars(Set<TypeVar> varSet); /** * Returns the set of uninstantiated RecordVars that occur in this TypeExpr. Don't depend on this * set being in any particular order. * @return Set */ final Set<RecordVar> getUninstantiatedRecordVars() { Set<RecordVar> varSet = new HashSet<RecordVar>(); getUninstantiatedRecordVars(varSet); return varSet; } /** * Helper function for finding the set of uninstantiated RecordVars that occur in this TypeExpr. * Don't rely on the order of this set. */ abstract void getUninstantiatedRecordVars(Set<RecordVar> varSet); /** * Get the TypeExpr corresponding to a reference to a subtype in another typeExpr * eg. if this typeExpr == (a -> (Either a b) -> c), correspondingSuperType == (d -> (Either d e) -> f), typeToFind == a, * this will return d. * Creation date: (04/04/2002 5:03:00 PM) */ public abstract TypeExpr getCorrespondingTypeExpr(TypeExpr correspondingSuperType, TypeExpr typeToFind); /** * Returns the number of top level fully saturated applications of the Prelude.Function * type constructor in this TypeExpr. Intuitively, this can be considered the arity of a * function having this as its type. * * For example, if this TypeExpr corresponds to (a -> b) -> Boolean -> (c -> d) then it returns 3. * This counts the arrows as shown: (a -> b)-#1#->Bool-#2#->(c-#3#->d). Note that the * -> operator is right associative so that the parentheses around c->d are not * necessary. * * Creation date: (9/13/00 1:55:49 PM) * @return int */ abstract public int getArity(); /** * Similar to getTypePieces, except that it just returns the result type. It is more * efficient to use this method if that is all you need. * Creation date: (4/23/01 4:09:38 PM) * @return TypeExpr */ public final TypeExpr getResultType() { TypeExpr typeExpr = this.prune(); if (typeExpr instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp) typeExpr; if (typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) && typeConsApp.getNArgs() == 2) { TypeExpr rhs = typeConsApp.getArg(1); return rhs.getResultType(); } } else if (typeExpr instanceof TypeApp) { TypeApp typeApp = (TypeApp)typeExpr; //recognize the special forms: //(TypeApp (TypeConsApp Function te1) te2) //and //(TypeApp (TypeApp (TypeConsApp Function) te1) te2) TypeExpr operatorType = typeApp.getOperatorType().prune(); if (operatorType instanceof TypeConsApp) { //recognize the form (TypeApp (TypeConsApp Function e1) (e2)) TypeConsApp typeConsApp = (TypeConsApp)operatorType; if (typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) && typeConsApp.getNArgs() == 1) { return typeApp.getOperandType().getResultType(); } } else if (operatorType instanceof TypeApp) { //recognize the form (TypeApp (TypeApp (TypeConsApp Function) te1) te2) TypeApp nextTypeApp = (TypeApp)operatorType; TypeExpr nextOperatorType = nextTypeApp.getOperatorType().prune(); if (nextOperatorType instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp)nextOperatorType; if (typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) && typeConsApp.getNArgs() == 0) { return typeApp.getOperandType().getResultType(); } } } } return typeExpr; } /** * Returns a TypeExpr that represents the input types of this gem. * If the gem has no inputs then null is returned. * @return TypeExpr the expression that represents the input types */ public final TypeExpr getArgumentType() { TypeExpr pieces[] = getTypePieces(); if (pieces.length == 1) { return null; } else if (pieces.length == 2) { return pieces[0]; } else { TypeExpr arguments = pieces[pieces.length - 2]; for (int i = pieces.length - 3; i >= 0; i--) { arguments = makeFunType(pieces[i], arguments); } return arguments; } } /** * Note that the list type must be fully saturated for this method to return true. i.e. "isListType (Prelude.List)" returns false. * Creation date: (July 25, 2002) * @return boolean true if this type is of the form [a] for any type a. */ public final boolean isListType() { TypeExpr typeExpr = this.prune(); if (typeExpr instanceof TypeConsApp) { //recognize the form (TypeConsApp List te) TypeConsApp typeConsApp = (TypeConsApp)typeExpr; return typeConsApp.getNArgs() == 1 && typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.List); } else if (typeExpr instanceof TypeApp) { //recognize the form (TypeApp (TypeConsApp List) te) TypeExpr operatorType = ((TypeApp)typeExpr).getOperatorType(); if (operatorType instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp)operatorType; return typeConsApp.getNArgs() == 0 && typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.List); } } return false; } /** * Creation date: (July 25, 2002) * @return boolean true if this type is a tuple of dimension 2 or more. e.g. (Int, a), ([Int], [(a,b)], c) */ public final boolean isTupleType() { return getTupleDimension() >= 2; } /** * Note that a non fully saturated application of Prelude.Function is not considered to be a function type e.g. * calling this method on "Prelude.Function Int" returns false. * @return true if this type is of the form a -> b, for some types a and b. For example, a -> b, Int -> [String] -> Boolean, etc.) */ public final boolean isFunctionType() { //we use getArity() in order to correctly handle TypeApp forms return getArity() > 0; } /** * @param typeConsName the name of the type constructor * @return true if this type expression, after pruning, is a list type and its elements, * after pruning, are type constructors with the given name. */ public final boolean isListTypeOf(QualifiedName typeConsName) { //todoBI recognize types using the TypeApp form TypeConsApp typeConsApp = this.rootTypeConsApp(); if (typeConsApp != null && typeConsApp.hasRootTypeConstructor(CAL_Prelude.TypeConstructors.List) && typeConsApp.getNArgs() == 1) { return typeConsApp.getArg(0).hasRootTypeConstructor(typeConsName); } return false; } /** * Returns true if this TypeExpr is a non-parametric type (e.g. such as Int, String or Ordering) and * its name is the same as typeConsName. * * It is faster to use TypeExpr.sameType(TypeExpr) if you have access to the other TypeExpr to compare with. * * @param typeConsName * @return true if this TypeExpr is a non-parametric type with type constructor name as specified. */ public final boolean isNonParametricType(QualifiedName typeConsName) { TypeConsApp typeConsApp = this.rootTypeConsApp(); return typeConsApp != null && typeConsApp.getNArgs() == 0 && typeConsApp.getName().equals(typeConsName); } /** * An enumeration type is a: * -non parameteric type (i.e. the type has 0 arity) * -not a foreign type * -there is at least one data constructor * -all data constructors have 0 arity * -it is not Prelude.Boolean * For example, Prelude.Ordering is an enumeration type. * * @return true if the given type is an enumeration, according to the above definition. */ public static boolean isEnumType(TypeConstructor typeCons) { if (typeCons.getTypeArity() > 0 || typeCons.getForeignTypeInfo() != null || typeCons.getNDataConstructors() == 0 || typeCons.getName().equals(CAL_Prelude.TypeConstructors.Boolean)) { return false; } for (int i = 0, nDataCons = typeCons.getNDataConstructors(); i < nDataCons; ++i) { DataConstructor dc = typeCons.getNthDataConstructor(i); if (dc.getArity() > 0) { return false; } } return true; } /** * For example, it this TypeExpr is "Prelude.Maybe Prelude.Int", and typeConsName is "Prelude.Maybe", * then hasRootTypeConstructor would return true. * * @param typeConsName the name of the type constructor * @return true if this type expression, after pruning, has as its root a type constructor with the given name */ public final boolean hasRootTypeConstructor(QualifiedName typeConsName) { TypeExpr typeExpr = this.prune(); return typeExpr instanceof TypeConsApp && ((TypeConsApp) typeExpr).getName().equals(typeConsName); } /** * Returns the tuple dimension of the current type, or -1 if the type is not a tuple. * A tuple is by definition a record type of the form {#1 :: t1, ..., #n :: tn} where n>=2, * there are no gaps in the ordinal fields and there are no textual fields. * * For example, for * {#1 :: Int, #2 :: {name = "Fred", age = 40}} * we return 2. * * For * Just {#1 :: Int, #2 :: {name = "Fred", age = 40}} * we return -1. * * @return int the number of components of the tuple type (n in the above notation) */ public final int getTupleDimension() { TypeExpr typeExpr = this.prune(); if (typeExpr instanceof RecordType) { RecordType recordType = (RecordType)typeExpr; RecordVar prunedRecordVar = recordType.getPrunedRecordVar(); if (!prunedRecordVar.isNoFields()) { return -1; } //todoBI this can be computed more efficiently. Don't need to create the set to find its //maximal element. SortedSet<FieldName> hasFieldsSet = recordType.getHasFields(); int nHasFields = hasFieldsSet.size(); if (nHasFields <= 1) { return -1; } FieldName lastFieldName = hasFieldsSet.last(); if (lastFieldName instanceof FieldName.Ordinal) { if (((FieldName.Ordinal)lastFieldName).getOrdinal() == nHasFields) { return nHasFields; } } } return -1; } /** * Breaks up a type into an array of types holding the type of each argument followed by the return type where we * are interpreting this TypeExpr to be the TypeExpr of a function (or functional agent). Of course, this interpretation can * always be made! This method is intended for use primarily by a UI client for colouring arguments and return types based * on type equality, * * For example, the type (a->b) -> Double -> b will return [a->b, Double, b]. * Creation date: (11/23/00 12:47:26 PM) * @return TypeExpr[] an array holding the type of each argument followed by the return type */ public final TypeExpr[] getTypePieces() { return getTypePiecesHelper(getArity()); } /** * In certain cases there are multiple options as to how to break up a type into argument types and * return type. Primarily, this is the case if a functional return type is needed. * For example, if the type is Int -> Int -> Boolean. Then getTypePieces() will return: * [Int, Int, Boolean]. However, we may want to consider this as a type expression for a function of 1 * argument returning a function. Then getTypePieces(1) returns: [Int, Int->Boolean]. * * @param arity must be less than or equal to the type arity of this type expression * @return TypeExpr[] type pieces array of length arity + 1. */ public final TypeExpr[] getTypePieces(int arity) { if (arity < 0 || arity > getArity()) { throw new IllegalArgumentException("arity arguments must be non-negative and less than or equal to TypeExpr.getArity()"); } return getTypePiecesHelper(arity); } private final TypeExpr[] getTypePiecesHelper(int arity) { TypeExpr[] typePieces = new TypeExpr[arity + 1]; TypeExpr typeExpr = this.prune(); for (int i = 0; i < arity; ++i) { //will match one of the 3 functional forms for te1 -> te2, or will fail in an IllegalStateException. if (typeExpr instanceof TypeConsApp) { TypeConsApp typeConsApp = (TypeConsApp)typeExpr; if (!typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) || typeConsApp.getNArgs() != 2) { throw new IllegalStateException(); } typePieces[i] = typeConsApp.getArg(0).prune(); typeExpr = typeConsApp.getArg(1).prune(); } else if (typeExpr instanceof TypeApp) { TypeApp typeApp = (TypeApp)typeExpr; TypeExpr operatorType = typeApp.getOperatorType().prune(); if (operatorType instanceof TypeConsApp) { //recognize the form (TypeApp (TypeConsApp Function e1) (e2)) TypeConsApp typeConsApp = (TypeConsApp)operatorType; if (!typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) || typeConsApp.getNArgs() != 1) { throw new IllegalStateException(); } typePieces[i] = typeConsApp.getArg(0).prune(); typeExpr = typeApp.getOperandType().prune(); } else if (operatorType instanceof TypeApp) { TypeApp nextTypeApp = (TypeApp)operatorType; TypeExpr nextOperatorType = nextTypeApp.getOperatorType().prune(); if (nextOperatorType instanceof TypeConsApp) { // recognize the form (TypeApp (TypeApp (TypeConsApp Function) te1) te2) TypeConsApp typeConsApp = (TypeConsApp)nextOperatorType; if (!typeConsApp.getName().equals(CAL_Prelude.TypeConstructors.Function) || typeConsApp.getNArgs() != 0) { throw new IllegalStateException(); } typePieces[i] = nextTypeApp.getOperandType().prune(); typeExpr = typeApp.getOperandType().prune(); } } else { throw new IllegalStateException(); } } else { throw new IllegalStateException(); } } typePieces[arity] = typeExpr; return typePieces; } /** * Add the uninstantiated typevars and record variables in this type expression to the * PolymorphicVarContext. This is useful when toString-ing several related TypeExpr objects, * in such a way that the variables within them do no overlap unless they are indeed the same. * * Creation date: (10/01/01 11:58:50 AM) * * @param polymorphicVarContext object threaded through nested toString invocations * to track the type and record variables within this TypeExpr. * @return PolymorphicVarContext the polymorphicVarContext argument, if it was not null, * or a new PolymorphicVarContext, suitably populated, otherwise. */ final PolymorphicVarContext indexPolymorphicVars(PolymorphicVarContext polymorphicVarContext) { if (polymorphicVarContext == null) { polymorphicVarContext = PolymorphicVarContext.make(); } // indexes uninstantiated typevars as a side effect. toSourceText(new StringBuilder(), polymorphicVarContext, ParenthesizationInfo.NONE, ScopedEntityNamingPolicy.FULLY_QUALIFIED); return polymorphicVarContext; } /** * @return the TypeExpr of the compose operator (#), namely (b->c) -> (a->b) -> (a->c). */ static TypeExpr makeComposeType() { TypeExpr a = TypeExpr.makeParametricType(); TypeExpr b = TypeExpr.makeParametricType(); TypeExpr c = TypeExpr.makeParametricType(); TypeExpr b2c = TypeExpr.makeFunType(b, c); TypeExpr a2b = TypeExpr.makeFunType(a, b); TypeExpr a2c = TypeExpr.makeFunType(a, c); return TypeExpr.makeFunType(b2c, TypeExpr.makeFunType(a2b, a2c)); } /** * Create a type expression for the type of function from domain to codomain. * * Creation date: (1/22/01 1:08:03 PM) * @return TypeExpr the type expression for the function * @param domain the type of the domain. Cannot be null. * @param codomain the type of the codomain. Cannot be null. */ public static TypeExpr makeFunType(TypeExpr domain, TypeExpr codomain) { if (domain == null) { throw new NullPointerException("domain type cannot be null"); } if (codomain == null) { throw new NullPointerException("codomain type cannot be null"); } TypeExpr[] args = new TypeExpr[] {domain, codomain}; return new TypeConsApp(TypeConstructor.FUNCTION, args); } /** * Create a type expression for a list. * * @param elementTypeExpr the type of the element. Cannot be null. * @param contextModuleTypeInfo * @return TypeExpr the type expression for the list with given element type */ public static TypeExpr makeListType(TypeExpr elementTypeExpr, ModuleTypeInfo contextModuleTypeInfo) { if (elementTypeExpr == null) { throw new NullPointerException("elementTypeExpr cannot be null"); } TypeConstructor listTypeCons = contextModuleTypeInfo.getVisibleTypeConstructor(CAL_Prelude.TypeConstructors.List); return new TypeConsApp(listTypeCons, new TypeExpr [] {elementTypeExpr}); } /** * Create a type expression for a data type that is not dependent on any type variables. * For example, "Maybe a" is not such a type because of the "a" while "Boolean" is such a type. * * Creation date: (3/1/01 5:27:09 PM) * @param rootTypeCons must have type arity == 0. * @return TypeExpr the type name of the non parametric type. */ public static TypeExpr makeNonParametricType(TypeConstructor rootTypeCons) { if (rootTypeCons.getTypeArity() != 0) { throw new IllegalArgumentException("TypeExpr.makeNonParametricType can only be used for types with type arity 0. " + rootTypeCons + " has type arity " + rootTypeCons.getTypeArity() + "."); } return new TypeConsApp(rootTypeCons, null); } /** * Create a type expression for the type "a". * * @return TypeExpr */ public static TypeExpr makeParametricType() { return new TypeVar(); } /** * Create a type expression for a tuple data constructor. For example, if n = 3, then the data * constructor Tuple3 or (,,,) has type a -> b -> c -> (a, b, c). * * @return TypeExpr the type expression for the tuple data cons * @param n size of the Tuple type */ public static TypeExpr makeTupleDataConsType(int n) { if (n < 2) { throw new IllegalArgumentException("TypeExpr.makeTupleDataConsType: a tuple type must have at least 2 components."); } List<TypeExpr> componentTypeVarList = new ArrayList<TypeExpr>(n); for (int i = 0; i < n; ++i) { componentTypeVarList.add(new TypeVar()); } TypeExpr typeExpr = makeTupleType(componentTypeVarList); for (int i = n - 1; i >= 0; --i) { typeExpr = makeFunType(componentTypeVarList.get(i), typeExpr); } return typeExpr; } /** * Create a type expression for a tuple each of whose entries is a different type var. * * @param n size of the Tuple type * @return TypeExpr the type expression for the tuple * @throws IllegalArgumentException if n is not greater than 2 */ public static TypeExpr makeTupleType(int n) { if (n < 2) { throw new IllegalArgumentException("the tuple dimension must be greater than or equal to 2."); } List<TypeExpr> componentTypeList = new ArrayList<TypeExpr>(n); for (int i = 0; i < n; ++i) { componentTypeList.add(new TypeVar()); } return makeTupleType(componentTypeList); } /** * Create a type expression for a tuple. The type of the tuple depends on the number of elements * of componentTypeExprList. * * @param componentTypeExprList (TypeExpr) a list of the component type expressions. * @return TypeExpr the type expression for the tuple * @throws IllegalArgumentException if the number of elements in the list is not greater than 2 */ public static TypeExpr makeTupleType(List<TypeExpr> componentTypeExprList) { int nChildren = componentTypeExprList != null ? componentTypeExprList.size() : 0; if (nChildren < 2) { throw new IllegalArgumentException("the tuple dimension must be greater than or equal to 2."); } Map<FieldName, TypeExpr> fieldNameToTypeMap = new HashMap<FieldName, TypeExpr>(); //FieldName -> TypeExpr for (int i = 0; i < nChildren; ++i) { fieldNameToTypeMap.put(FieldName.makeOrdinalField(i + 1), componentTypeExprList.get(i)); } return new RecordType(RecordVar.NO_FIELDS, fieldNameToTypeMap); } /** * Create a type expression for a free record. The result will be a record type containing * fields named as specified and associated to parametric values. * * Ex: makeRecordType([age,height]) creates record (r\age, r\height) == {r | age :: a, height :: b} * Ex: makeRecordType([]) == {r} * * @param fieldNamesSet Set of field names. * @return RecordType the record type */ public static RecordType makeFreeRecordType(Set<FieldName> fieldNamesSet) { // Create field name to field type map Map<FieldName, TypeExpr> extensionFieldsMap = new HashMap<FieldName, TypeExpr>(); for (final FieldName fieldName : fieldNamesSet) { TypeExpr fieldType = TypeExpr.makeParametricType(); extensionFieldsMap.put(fieldName, fieldType); } // Create a new free record with this type return new RecordType(RecordVar.makeRecordVar(null, new HashSet<FieldName>(extensionFieldsMap.keySet()), TypeClass.NO_CLASS_CONSTRAINTS, false), extensionFieldsMap); } /** * Create a new type expression for a record-polymorphic type containing the * specified fields and associated types. * * Ex: makePolymorphicRecordType( [("age"->Double), ("name"->a)] ) * creates the record (r\age,r\name) => {r | age :: Double, name :: a} * Ex: makePolymorphicRecordType( [] ) creates the free record {r} * * Note: The record fields will contain copies of the specified types, but referential equality * will be preserved. * * @param fieldNameToTypeMap Map (FieldName->TypeExpr) of field name to field type expression * @return RecordType the resulting record type */ public static RecordType makePolymorphicRecordType(Map<FieldName, TypeExpr> fieldNameToTypeMap) { // Copy the field type expressions, preserving referential equality TypeExpr[] oldFieldTypes = fieldNameToTypeMap.values().toArray(new TypeExpr[fieldNameToTypeMap.values().size()]); TypeExpr[] newFieldTypes = copyTypeExprs(oldFieldTypes); List<TypeExpr> oldFieldTypesList = Arrays.asList(oldFieldTypes); List<TypeExpr> newFieldTypesList = Arrays.asList(newFieldTypes); // Check the extension field names, and make a copy of the map Map<FieldName, TypeExpr> newExtensionFieldsMap = new HashMap<FieldName, TypeExpr>(); //FieldName -> TypeExpr for (final Map.Entry<FieldName, TypeExpr> entry : fieldNameToTypeMap.entrySet()) { FieldName fieldName = entry.getKey(); TypeExpr fieldType = entry.getValue(); if (fieldType == null) { throw new IllegalArgumentException("The record field \"" + fieldName.getCalSourceForm() + "\" refers to a null type expression"); } newExtensionFieldsMap.put(fieldName, newFieldTypesList.get(oldFieldTypesList.indexOf(fieldType))); } // Create a new free record with these fields return new RecordType(RecordVar.makeRecordVar(null, new HashSet<FieldName>(newExtensionFieldsMap.keySet()), TypeClass.NO_CLASS_CONSTRAINTS, false), newExtensionFieldsMap); } /** * Create a new type expression for a non-record-polymorphic type containing the * specified fields and associated types. * * For example, * makeNonPolymorphicRecordType([("age", Double), ("name", a)] ) == {age :: Double, name :: a} * makeNonPolymorphicRecordType([("age", Double), ("inner", (r\name) => {r | name :: String})]) * == (r\name)=>{age :: Double, inner :: {r | name :: String}} * makeNonPolymorphicRecordType([]) == {} * * Note: The record fields will contain copies of the specified types, but referential equality * will be preserved. * * @param fieldNameToTypeMap Map (FieldName->TypeExpr) of field name to field type expression * @return RecordType the resulting record type */ public static RecordType makeNonPolymorphicRecordType(Map<FieldName, TypeExpr> fieldNameToTypeMap) { // Copy the field type expressions, preserving referential equality TypeExpr[] oldFieldTypes = fieldNameToTypeMap.values().toArray(new TypeExpr[fieldNameToTypeMap.values().size()]); TypeExpr[] newFieldTypes = copyTypeExprs(oldFieldTypes); List<TypeExpr> oldFieldTypesList = Arrays.asList(oldFieldTypes); List<TypeExpr> newFieldTypesList = Arrays.asList(newFieldTypes); // Check the extension field names, and make a copy of the map Map<FieldName, TypeExpr> newExtensionFieldsMap = new HashMap<FieldName, TypeExpr>(); for (final Map.Entry<FieldName, TypeExpr> entry : fieldNameToTypeMap.entrySet()) { FieldName fieldName = entry.getKey(); TypeExpr fieldType = entry.getValue(); if (fieldType == null) { throw new IllegalArgumentException("The record field \"" + fieldName.getCalSourceForm() + "\" refers to a null type expression"); } newExtensionFieldsMap.put(fieldName, newFieldTypesList.get(oldFieldTypesList.indexOf(fieldType))); } // Create a new free record with these fields return new RecordType(RecordVar.NO_FIELDS, newExtensionFieldsMap); } /** * Succeeds without throwing a TypeException if anotherTypeExpr can be specialized to this TypeExpr. * If it can, then anotherTypeExpr is thus specialized. * Creation date: (3/22/01 4:02:47 PM) * @param anotherTypeExpr * @param patternMatchContext */ abstract void patternMatch(TypeExpr anotherTypeExpr, PatternMatchContext patternMatchContext) throws TypeException; /** * Attempt to match inferredTypeExpr (binding the type variables occurring within it) to * declaredTypeExpr. During pattern matching, declaredTypeExpr is not modified at all. * Thus, unification really corresponds to two-way pattern matching. * Note that unlike the unifyType and canUnifyType methods, the order of arguments is significant. * * Creation date: (9/12/00 5:56:28 PM) * @param declaredTypeExpr type expression arising from a declaration e.g. head :: [Int] -> Int * @param inferredTypeExpr type expression arising from type inference e.g. for head it is [a] -> a * @param contextModuleTypeInfo context in which this pattern matching takes place. This is needed to know what class instances are in scope. * @throws TypeException */ static void patternMatch(TypeExpr declaredTypeExpr, TypeExpr inferredTypeExpr, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { patternMatch(declaredTypeExpr, inferredTypeExpr, (NonGenericVars)null, contextModuleTypeInfo); } /** * Attempt to match inferredTypeExpr (binding the type variables occurring within it) to * declaredTypeExpr. During pattern matching, declaredTypeExpr is not modified at all. * Thus, unification really corresponds to two-way pattern matching. * Note that unlike the unifyType and canUnifyType methods, the order of arguments is significant. * <P> * This method is intended for pattern matching for local type declarations, where the list of * non-generic type variables can be non-empty. A non-generic type variable in inferredTypeExpr * cannot be instantiated to a generic type variable in declaredTypeExpr. Note however, that it * is OK to instantiate a non-generic type variable to a type constructor. * <P> * Creation date: (9/12/00 5:56:28 PM) * @param declaredTypeExpr type expression arising from a declaration e.g. head :: [Int] -> Int * @param inferredTypeExpr type expression arising from type inference e.g. for head it is [a] -> a * @param nonGenericVars the list of non-generic type variables when inferredTypeExpr is being defined. * Can be null if there are no non-generic type variables. * @param contextModuleTypeInfo * @throws TypeException */ static void patternMatch(TypeExpr declaredTypeExpr, TypeExpr inferredTypeExpr, NonGenericVars nonGenericVars, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { if (contextModuleTypeInfo == null) { throw new NullPointerException(); } declaredTypeExpr = declaredTypeExpr.prune(); inferredTypeExpr = inferredTypeExpr.prune(); PatternMatchContext patternMatchContext = new PatternMatchContext(declaredTypeExpr, nonGenericVars, contextModuleTypeInfo); declaredTypeExpr.patternMatch(inferredTypeExpr, patternMatchContext); } /** * If types1 = [t1, ..., tn], and types2 = [u1, ..., un], then this function * checks if types2 can be specialized to types1 i.e. u1 can be specialized to t1, * u2 can be specialized to t2, ... * * What this really means: we can assign types to the type variables occurring in types2, * (other than those also occurring in types1) so that u1(with assignments) == t1, * u2(with assignments) == t2, ... * * There are no side-effects on the arguments. * * The TypeExpr elements of the returned array have no instantiated type variables within them. * * This function correctly handles shared type variables i.e. the same type variable appearing * in types1 and in types2. * * The types2 array may be longer than the types1 array. For example: * patternMatchTypePieces ([Int], [a, a -> Char, Maybe a]) = [Int, Int -> Char, Maybe Int]. * Another example: * patternMatchTypePieces ([c -> c], [a -> b, [a -> b]]) = [d -> d, [d -> d]]. * The types1 array may have null values. For example: * patternMatchTypePieces ([null, Int], [a -> Char, a]) = [Int -> Char, Int]. * One can think of types1 as specifying constraints on types2, and there might not be a constraint for each type * expression in the types2 array. The returned value array, which has the same length * as types2, is types2 where the pattern matching constraints have been taken into account. * * @param types1 an array whose length is <= the length of types1 and may contain null values * @param types2 an array whose length is >= the length of types1, and does not contain null values * @param contextModuleTypeInfo * @return TypeExpr[] an array having the same length as types2. * @throws TypeException */ public static TypeExpr[] patternMatchPieces(TypeExpr[] types1, TypeExpr[] types2, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { if (contextModuleTypeInfo == null) { throw new NullPointerException(); } int nTypes1 = types1.length; int nTypes2 = types2.length; if (nTypes1 > nTypes2) { throw new IllegalArgumentException(); } //types2 may not contain null values if (TypeExpr.hasNullType(types2)) { throw new IllegalArgumentException(); } //so at this point nTypes2 >= nTypes1. //copy as a pair since there may be referentially identical type variables in types1 and types2. CopyEnv.TypesPair copyTypesPair = TypeExpr.copyTypesPair(types1, types2); TypeExpr[] copyTypes1 = copyTypesPair.getTypes1(); TypeExpr[] copyTypes2 = copyTypesPair.getTypes2(); for (int i = 0; i < nTypes1; ++i) { if (copyTypes1[i] != null) { TypeExpr.patternMatch(copyTypes1[i], copyTypes2[i], contextModuleTypeInfo); } } for (int i = 0; i < nTypes2; ++i) { copyTypes2[i] = copyTypes2[i].deepPrune(); } return copyTypes2; } /** * Returns a TypeExpr without redundant instantiated type variables at the top of this TypeExpr. * The result of prune is always a non-instantiated type variable, a type constructor or a * record type. * * This function has no side-effects. * * Creation date: (7/17/00 3:59:38 PM) * @return TypeExpr the pruned type expression */ abstract TypeExpr prune(); /** * Similar to prune, except that the arguments (if any) are also pruned. * Also record variables are pruned. * * Another important difference is that this function has side effects- this TypeExpr is * modified to not have instantiation chains (except for possibly at the root of the TypeExpr * tree if it happens to be an instantiated type variable). * * Creation date: (18/07/01 1:15:10 PM) * @return TypeExpr a TypeExpr representing the same Type as this TypeExpr, but without any * instantiated type or record variables in its tree representation. */ abstract TypeExpr deepPrune(); /** * Returns the most compact and efficient representation of a TypeExpr * within the equivalence class of TypeExpr objects that represent the same type. * * Technically, this function removes redundant instantiated type and record variables * as well as simplifies TypeApp nodes that could be written as TypeConsApp. * For example, (TypeApp (TypeApp (TypeConsApp Function []) Int) Char) is replaced * by its equivalent form (TypeConsApp Function [Int, Char]). * * This function has no side-effects. However, the type are record variables * of this TypeExpr and the returned TypeExpr are shared, so modification of one e.g. * via instantiations will also modify the other. * * Creation date: (18/07/01 1:15:10 PM) * @return TypeExpr a TypeExpr representing the same type as this TypeExpr, but using the * simplest representative in the equivalence class. */ abstract TypeExpr normalize(); /** * Checks if 2 types are equal, up to renamings of type variables. For example, the types: * "a -> b -> [a] -> [b]", "b -> a -> [b] -> [a]" and "c -> d -> [c] -> [d]" would all be * considered to be the same type under this method. * * By definition, 2 types t1 and t2 are the same via this method if t1.toString().equals(t2.toString()). * However, this method will be significantly faster in many cases than comparing string representations, * especially in the case when the 2 types are different. */ public abstract boolean sameType(TypeExpr anotherTypeExpr); /** * Returns the address of this object. Primarily intended for debugging purposes. * Creation date: (6/6/01 4:29:53 PM) * @return String */ final String toAddressString() { return "" + super.hashCode(); } /** * Returns a string representation of this TypeExpr that also indicates the address of each * type variable appearing in the TypeExpr. * @return String */ final String toDebugString() { StringBuilder debugTypeString = new StringBuilder(); Set<TypeVar> typeVarsSet = getUninstantiatedTypeVars(); int nVars = typeVarsSet.size(); if (nVars > 0) { debugTypeString.append('<'); int varN = 0; for (final TypeVar typeVar : typeVarsSet) { debugTypeString.append(PolymorphicVarContext.indexToVarName(varN + 1)).append("=").append(typeVar.toAddressString()); if (varN < nVars - 1) { debugTypeString.append(", "); } ++varN; } debugTypeString.append("> "); } debugTypeString.append(toString()); return debugTypeString.toString(); } /** * Returns a string representation of this TypeExpr. Type and record variables are represented * by lower case letters: a, b, c, ..., z, a1, a2, a3,... according to their first occurrence in the * non-context part of this TypeExpr. Type constructor and type class names appearing in the type * are fully qualified. * <p> * The String representation will be the same for equivalent type expressions i.e. ones that represent * the "same" type (where type variables are all considered to be quantified over the type itself). * Some defining characteristics: * <ul> * <li>the resulting String is parseable as a CAL type.</li> * <li>redundant parentheses are not displayed; all parentheses displayed are required for a correct parse</li> * <li>If Prelude.Function or Prelude.List are fully saturated, then they will appear in their symbolic forms e.g. * (Prelude.Int -> Prelude.Char rather than the equivalent Prelude.Function Prelude.Int Prelude.Char * and [Prelude.String] rather than the equivalent Prelude.List Prelude.String).</li> * <li>records will use tuple notation when possible, and otherwise use field-name notation where the fields are * displayed in field-name order.</li> * </ul> * <p> * Creation date: (3/26/01 2:26:03 PM) * @return a canonical String representation of this TypeExpr */ @Override public final String toString() { return toString((PolymorphicVarContext)null, ScopedEntityNamingPolicy.FULLY_QUALIFIED); } /** * @see TypeExpr#toString(boolean, ScopedEntityNamingPolicy) */ public final String toString(ScopedEntityNamingPolicy namingPolicy) { return toString((PolymorphicVarContext)null, namingPolicy); } /** * A version of TypeExpr.toString that provides more options for displaying the String in a more human-readable format. * * @param favorPreferredPolymorphicVarNames if true, then the preferred names of type are record variables will be used * if possible. The definition of 'if possible' is subject to change so this is more of a preference to display prettier * type strings in client code. if false, then type and record variables are represented by lower case letters: * a, b, c, ..., z, a1, a2, a3,... according to their first occurrence in the non-context part of this TypeExpr * @param namingPolicy naming policy influencing how type constructor and type class names occurring within this TypeExpr * are displayed in the returned value. * @return a String representation of this TypeExpr */ public final String toString(boolean favorPreferredPolymorphicVarNames, ScopedEntityNamingPolicy namingPolicy) { PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make(favorPreferredPolymorphicVarNames); if (favorPreferredPolymorphicVarNames) { indexPolymorphicVars(polymorphicVarContext); } return toString(polymorphicVarContext, namingPolicy); } /** * Similar to TypeExpr#toSourceModel(boolean, ScopedEntityNamingPolicy). Type are record variables are scoped over the * whole TypeExpr array argument, and so sharing between the different types in the array is properly shown. * * @see TypeExpr#toString(boolean, ScopedEntityNamingPolicy) * @param types * @param favorPreferredPolymorphicVarNames * @param namingPolicy * @return String[] */ public static final String[] toStringArray( TypeExpr[] types, boolean favorPreferredPolymorphicVarNames, ScopedEntityNamingPolicy namingPolicy) { PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make(favorPreferredPolymorphicVarNames); final int nTypes = types.length; if (favorPreferredPolymorphicVarNames) { for (int i = 0; i < nTypes; ++i) { types[i].indexPolymorphicVars(polymorphicVarContext); } } String[] typeStrings = new String[nTypes]; for (int i = 0; i < nTypes; ++i) { typeStrings[i] = types[i].toString(polymorphicVarContext, namingPolicy); } return typeStrings; } /** * Returns a string representation of this TypeExpr. Type and record variables are represented * by lower case letters: a, b, c, ..., z, a1, a2, a3,... according to their first occurrence in the * polymorphicVarContext argument. * <p> * Creation date: (3/26/01 2:26:03 PM) * @param polymorphicVarContext object threaded through nested toString invocations * to track the type and record variables within this TypeExpr. Can be null. If not null, then any * uninstantiated type and record variables occurring in this TypeExpr that are not already * in the context are added. * @param namingPolicy * @return the resultant string representation */ public final String toString(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy) { // create a new PolymorphicVarContext if necessary if (polymorphicVarContext == null) { polymorphicVarContext = PolymorphicVarContext.make(); } if (namingPolicy == null) { namingPolicy = ScopedEntityNamingPolicy.FULLY_QUALIFIED; } // generate the type string, without including the context (the stuff to the left of =>). StringBuilder sb = new StringBuilder(); toSourceText(sb, polymorphicVarContext, ParenthesizationInfo.NONE, namingPolicy); String typeString = sb.toString(); // now generate the context string String contextString = makeContextString(polymorphicVarContext, namingPolicy); return contextString + typeString; } /** * Returns a source model representation of this TypeExpr. * * @return SourceModel.TypeSignature the resultant source model representation */ public final SourceModel.TypeSignature toSourceModel() { return toSourceModel((PolymorphicVarContext)null, ScopedEntityNamingPolicy.FULLY_QUALIFIED); } /** * Returns a SourceModel representation of this TypeExpr, but with more control about the formatting use * for type and record variables as well as type constructors and type classes occurring within the TypeExpr. * @param favorPreferredPolymorphicVarNames if true, then the preferred names of type are record variables will be used * if possible. The definition of 'if possible' is subject to change so this is more of a preference to display prettier * type strings in client code. if false, then type and record variables are represented by lower case letters: * a, b, c, ..., z, a1, a2, a3,... according to their first occurrence in the non-context part of this TypeExpr * @param namingPolicy naming policy influencing how type constructor and type class names occurring within this TypeExpr * are displayed in the returned value. * @return SourceModel.TypeSignature the resultant source model representation */ public final SourceModel.TypeSignature toSourceModel(boolean favorPreferredPolymorphicVarNames, ScopedEntityNamingPolicy namingPolicy) { PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make(favorPreferredPolymorphicVarNames); if (favorPreferredPolymorphicVarNames) { indexPolymorphicVars(polymorphicVarContext); } return toSourceModel(polymorphicVarContext, namingPolicy); } /** * Similar to TypeExpr#toSourceModel(boolean, ScopedEntityNamingPolicy). Type are record variables are scoped over the * whole TypeExpr array argument, and so sharing between the different types in the array is properly shown. * * @see TypeExpr#toSourceModel(boolean, ScopedEntityNamingPolicy) * @param types * @param favorPreferredPolymorphicVarNames * @param namingPolicy * @return SourceModel.TypeSignature[] */ public static final SourceModel.TypeSignature[] toSourceModelArray( TypeExpr[] types, boolean favorPreferredPolymorphicVarNames, ScopedEntityNamingPolicy namingPolicy) { PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make(favorPreferredPolymorphicVarNames); final int nTypes = types.length; if (favorPreferredPolymorphicVarNames) { for (int i = 0; i < nTypes; ++i) { types[i].indexPolymorphicVars(polymorphicVarContext); } } SourceModel.TypeSignature[] typeSourceModels = new SourceModel.TypeSignature[nTypes]; for (int i = 0; i < nTypes; ++i) { typeSourceModels[i] = types[i].toSourceModel(polymorphicVarContext, namingPolicy); } return typeSourceModels; } /** * Returns a source model representation of this TypeExpr. * @param polymorphicVarContext object threaded through nested toString invocations * to track the type and record variables within this TypeExpr. Can be null. If not null, then any * uninstantiated type and record variables occurring in this TypeExpr that are not already * in the context are added. * @param namingPolicy NamingPolicy to use when generating names * @return SourceModel.TypeSignature the resultant source model representation */ final SourceModel.TypeSignature toSourceModel(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy) { // create a new PolymorphicVarContext if necessary if (polymorphicVarContext == null) { polymorphicVarContext = PolymorphicVarContext.make(); } if (namingPolicy == null) { namingPolicy = ScopedEntityNamingPolicy.FULLY_QUALIFIED; } // generate the source model for the type, without including the context (the stuff to the left of =>). SourceModel.TypeExprDefn typeSourceModel = makeDefinitionSourceModel(polymorphicVarContext, namingPolicy); // now generate the context portion SourceModel.Constraint[] contextSourceModel = makeContextSourceModel(polymorphicVarContext, namingPolicy); return SourceModel.TypeSignature.make(contextSourceModel, typeSourceModel); } /** * A helper function that returns all the constrained type and record variables occurring in * this TypeExpr, ordered by first occurrence. * * TypeVars and RecordVars can technically live in a separate namespace, but for reasons of * simplicity we share the namespace i.e. we could technically allow: * (a\field1, Num a) => {a | } -> {a | field1 :: a} * but actually require * (a\field1, Num b) => {a | } -> {a | field1 :: b} * for reasons of end-user simplicity in reading String representations of type expressions. * * @return (Set (Either TypeVar RecordVar)) */ final Set<PolymorphicVar> getConstrainedPolymorphicVars(){ PolymorphicVarContext localizedVarContext = PolymorphicVarContext.make(); //called for the side effect of populating localizedVarContext toSourceText(new StringBuilder(), localizedVarContext, ParenthesizationInfo.NONE, ScopedEntityNamingPolicy.FULLY_QUALIFIED); return localizedVarContext.getConstrainedPolymorphicVars(); } /** * Build a string representation of the context of a type expression with a given PolymorphicVarContext * that determines how the class constraints (on type variables) and the lacks constraints (on record variables) * will be displayed. * @param polymorphicVarContext object threaded through nested toString invocations * to track the type and record variables within this TypeExpr. * @param namingPolicy * @return the string representation */ private final String makeContextString(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy) { Set<PolymorphicVar> contextPolymorphicVars = this.getConstrainedPolymorphicVars(); List<String> contextList = new ArrayList<String>(); for (final PolymorphicVar polymorphicVar : contextPolymorphicVars) { if (polymorphicVar instanceof TypeVar) { TypeVar typeVar = (TypeVar)polymorphicVar; String typeVarName = polymorphicVarContext.getPolymorphicVarName(typeVar); if (DEBUG_INFO) { typeVarName += typeVar.toAddressString(); } if (typeVar.noClassConstraints()) { //there is a bug in getGenericConstrainedTypeVars... throw new IllegalStateException(); } for (final TypeClass typeClass : typeVar.getTypeClassConstraintSet()) { String typeClassName = namingPolicy.getName(typeClass); contextList.add(typeClassName + " " + typeVarName); } } else if (polymorphicVar instanceof RecordVar) { RecordVar recordVar = (RecordVar)polymorphicVar; String recordVarName = polymorphicVarContext.getPolymorphicVarName(recordVar); //add the type class constraints for (final TypeClass typeClass : recordVar.getTypeClassConstraintSet()) { String typeClassName = namingPolicy.getName(typeClass); contextList.add(typeClassName + " " + recordVarName); } //add the lacks fields constraints Set<FieldName> lacksFieldsSet = recordVar.getLacksFieldsSet(); List<String> lacksConstraints = new ArrayList<String>(); for (final FieldName lacksFieldName : lacksFieldsSet) { lacksConstraints.add(recordVarName + "\\" + lacksFieldName.getCalSourceForm()); } //need a canonical order for the lacks fields. This is alphabetical order. Collections.sort(lacksConstraints); contextList.addAll(lacksConstraints); } else { throw new IllegalStateException(); } } int nContexts = contextList.size(); if (nContexts == 0) { return ""; } StringBuilder result = new StringBuilder(); if (nContexts > 1) { result.append("("); } for (int i = 0; i < nContexts; ++i) { if (i > 0) { result.append(", "); } result.append(contextList.get(i)); } if (nContexts > 1) { result.append(")"); } result.append(" => "); return result.toString(); } /** * Build a source model representation of the context of a type expression * with a given PolymorphicVarContext that determines how the class * constraints (on type variables) and the lacks constraints (on record * variables) will be displayed. * * @param polymorphicVarContext * object threaded through nested makeDefinitionSourceModel * invocations to track the type and record variables within this * TypeExpr. * @param namingPolicy NamingPolicy to use when constructing names * @return the source model representation of the context */ private final SourceModel.Constraint[] makeContextSourceModel(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy) { Set<PolymorphicVar> contextPolymorphicVars = this.getConstrainedPolymorphicVars(); List<SourceModel.Constraint> contextList = new ArrayList<SourceModel.Constraint>(); for (final PolymorphicVar polymorphicVar : contextPolymorphicVars) { if (polymorphicVar instanceof TypeVar) { TypeVar typeVar = (TypeVar)polymorphicVar; String typeVarName = polymorphicVarContext.getPolymorphicVarName(typeVar); if (DEBUG_INFO) { typeVarName += typeVar.toAddressString(); } if (typeVar.noClassConstraints()) { //there is a bug in getGenericConstrainedTypeVars... throw new IllegalStateException(); } for (final TypeClass typeClass : typeVar.getTypeClassConstraintSet()) { final ModuleName typeClassModuleName = namingPolicy.getModuleNameForScopedEntity(typeClass); SourceModel.Name.TypeClass typeClassName = SourceModel.Name.TypeClass.make(typeClassModuleName, typeClass.getName().getUnqualifiedName()); contextList.add(SourceModel.Constraint.TypeClass.make(typeClassName, SourceModel.Name.TypeVar.make(typeVarName))); } } else if (polymorphicVar instanceof RecordVar) { RecordVar recordVar = (RecordVar)polymorphicVar; String recordVarName = polymorphicVarContext.getPolymorphicVarName(recordVar); //add the type class constraints for (final TypeClass typeClass : recordVar.getTypeClassConstraintSet()) { final ModuleName typeClassModuleName = namingPolicy.getModuleNameForScopedEntity(typeClass); SourceModel.Name.TypeClass typeClassName = SourceModel.Name.TypeClass.make(typeClassModuleName, typeClass.getName().getUnqualifiedName()); contextList.add(SourceModel.Constraint.TypeClass.make(typeClassName, SourceModel.Name.TypeVar.make(recordVarName))); } //add the lacks fields constraints Set<FieldName> lacksFieldsSet = recordVar.getLacksFieldsSet(); List<SourceModel.Constraint.Lacks> lacksConstraints = new ArrayList<SourceModel.Constraint.Lacks>(); for (final FieldName lacksFieldName : lacksFieldsSet) { lacksConstraints.add(SourceModel.Constraint.Lacks.make(SourceModel.Name.TypeVar.make(recordVarName), SourceModel.Name.Field.make(lacksFieldName))); } //need a canonical order for the lacks fields. This is alphabetical order. Collections.sort(lacksConstraints, new Comparator<SourceModel.Constraint.Lacks>() { /** {@inheritDoc} */ public int compare(SourceModel.Constraint.Lacks o1, SourceModel.Constraint.Lacks o2) { return o1.toSourceText().compareTo(o2.toSourceText()); } }); contextList.addAll(lacksConstraints); } else { throw new IllegalStateException(); } } return contextList.isEmpty() ? null : contextList.toArray(new SourceModel.Constraint[0]); } /** * Build a textual representation of this type expression without including the context (the stuff to the left of =>). * Creation date: (10/11/00 5:52:57 PM) * @param sb StringBuilder to use to accumulate the resulting source text. * @param polymorphicVarContext object threaded through nested toString invocations * to track the type and record variables within this TypeExpr. * @param parenthesizationInfo info on whether to parenthesize the root node of this type expression from the point of view * of the parent of this node in the TypeExpr tree. * @param namingPolicy how to display type constructors e.g. whether unqualified, fully qualified etc. */ abstract void toSourceText(StringBuilder sb, PolymorphicVarContext polymorphicVarContext, ParenthesizationInfo parenthesizationInfo, ScopedEntityNamingPolicy namingPolicy); /** * Builds a source model representation of this type expression without * including the context (the stuff to the left of =>). * * @param polymorphicVarContext * object threaded through nested makeDefinitionSourceModel * invocations to track the type and record variables within this * TypeExpr. * @param namingPolicy ScopedEntityNamingPolicy to use when creating names * @return the source model representation of the type expression definition * (without the context) */ abstract SourceModel.TypeExprDefn makeDefinitionSourceModel(PolymorphicVarContext polymorphicVarContext, ScopedEntityNamingPolicy namingPolicy); /** * A helper function for unifying this type expression with another type expression. * @param anotherTypeExpr * @param contextModuleTypeInfo * @return int type closeness. See the javadoc for TypeExpr.getTypeCloseness for a detailed description of the type closeness heuristic. */ abstract int unifyType(TypeExpr anotherTypeExpr, ModuleTypeInfo contextModuleTypeInfo) throws TypeException; /** * Unify two type expressions. What this means is to find the most general type to which both * typeExpr1 and typeExpr2 can be specialized to. Unification fails when trying to match 2 different * type constructors (such as Boolean and Int) or when trying to instantiate a type variable to a term * containing that type variable (like a and a->b, where a circular structure would be built.) * As a general point of interest, this process is essentially the same as unification in Prolog. * <p> * Note that this function has side effects and modifies the TypeExpr arguments. If it fails in a TypeException, * the modification is undefined. Otherwise, the modification is the unification. * <p> * Creation date: (7/21/00 1:27:22 PM) * @param typeExpr1 * @param typeExpr2 * @param contextModuleTypeInfo context in which this unification takes place. This is needed to know what class instances are in scope. * @return int a measure of the closeness between the 2 argument types. The larger the number, the closer * the 2 types are. This value should not be persisted and may change in the future. It is intended for use * by UI clients such as Intellicut in filtering the list of possible candidates to display in the pick list * of gems to those that are a closer match. This number is not scaled. What this means is that you can't * interpret a closeness of 3 as a having a fixed meaning. In other words, if you fix the first argument, * unifyType can be considered as a function from TypeExpr -> Int and the maximum and minimum values attained * by this function of one variable on its domain gives the interpretation of the closeness magnitude for * that particular first argument. See the javadoc for TypeExpr.getTypeCloseness for a detailed description of the type closeness heuristic. */ static int unifyType(TypeExpr typeExpr1, TypeExpr typeExpr2, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { if (contextModuleTypeInfo == null) { throw new NullPointerException(); } typeExpr1 = typeExpr1.prune(); typeExpr2 = typeExpr2.prune(); return typeExpr1.unifyType(typeExpr2, contextModuleTypeInfo); } /** * Similar to TypeExpr.unifyType except there are no side effects on the argument type expressions. * The returned TypeExpr has no instantiated type variables within it. * <p> * Note: this function correctly handles the case where typeExpr1 and typeExpr2 have shared type variables. * For example, we can't unify (a, Int) and (Char, a), but we can unify (a, Int) and (Char, a'). * @param typeExpr1 * @param typeExpr2 * @param contextModuleTypeInfo * @return TypeExpr * @throws TypeException */ public static TypeExpr unify(TypeExpr typeExpr1, TypeExpr typeExpr2, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { //Note: we copy both type expressions simultaneously since there may be referentially //identical type variables in common. //So for example, (a, Int) and (Char, a) can't unify whereas (a, Int) and (Char, a') can. TypeExpr[] copiedTypes = TypeExpr.copyTypeExprs(new TypeExpr[] {typeExpr1, typeExpr2}); TypeExpr typeExprCopy1 = copiedTypes[0]; TypeExpr typeExprCopy2 = copiedTypes[1]; TypeExpr.unifyType(typeExprCopy1, typeExprCopy2, contextModuleTypeInfo); return typeExprCopy1.deepPrune(); } /** * If types1 = [t1, ..., tn], and types2 = [u1, ..., un], then this function unifies t1, u1, then t2, u2, * ... and then tn, un, returning a copy of the result. * <p> * There are no side-effects on the arguments. * <p> * The TypeExpr elements of the returned array have no instantiated type variables within them. * <p> * This function correctly handles shared type variables. For example: * [a -> b, a, (a, b, Boolean)] and [Int -> Double, a, (a, c, d)] unifies to [Int -> Double, Int, (Int, Double, Boolean)]. * * The types2 array may be longer than the types1 array. For example: * unifyTypePieces ([a], [Int, a -> Char]) = [Int, Int -> Char]. * The types1 array may have null values. For example: * unifyTypePieces ([null, a], [a -> Char, Int]) = [Int -> Char, Int]. * One can think of types1 as specifying constraints on types2, and there might not be a constraint for each type * expression in the types2 array. The returned value array, which has the same length * as types2, is types2 where the unification constraints from types1 have been taken into account. * * @param types1 an array whose length is <= the length of types2, and may contain null values * @param types2 an array whose length is >= the length of types and does not contain null values * @param contextModuleTypeInfo * @return TypeExpr[] an array having the same length as types2. * @throws TypeException */ public static TypeExpr[] unifyTypePieces(TypeExpr[] types1, TypeExpr[] types2, ModuleTypeInfo contextModuleTypeInfo) throws TypeException { if (contextModuleTypeInfo == null) { throw new NullPointerException(); } int nTypes1 = types1.length; int nTypes2 = types2.length; if (nTypes1 > nTypes2) { throw new IllegalArgumentException(); } //types2 may not contain null values if (TypeExpr.hasNullType(types2)) { throw new IllegalArgumentException(); } //so at this point nTypes2 >= nTypes1. //copy as a pair since there may be referentially identical type variables in types1 and types2. CopyEnv.TypesPair copyTypesPair = TypeExpr.copyTypesPair(types1, types2); TypeExpr[] copyTypes1 = copyTypesPair.getTypes1(); TypeExpr[] copyTypes2 = copyTypesPair.getTypes2(); for (int i = 0; i < nTypes1; ++i) { if (copyTypes1[i] != null) { TypeExpr.unifyType(copyTypes1[i], copyTypes2[i], contextModuleTypeInfo); } } for (int i = 0; i < nTypes2; ++i) { copyTypes2[i] = copyTypes2[i].deepPrune(); } return copyTypes2; } /** * @param types * @return boolean true if any of the elements in the types array are null. */ static boolean hasNullType(TypeExpr[] types) { for (int i = 0, nTypes = types.length; i < nTypes; ++i) { if (types[i] == null) { return true; } } return false; } /** * Method usesForeignType. * @return boolean return true if any of the type constructors in the type was created with a foreign data declaration */ abstract public boolean usesForeignType (); /** * The idea here is that unconstrainedTypes can be specialized to oldSpecializedTypes (such as when the inputs of a gem are specialized * by value panels), and someone has changed one of the oldSpecializedTypes, at index switchIndex to switchedType. Then what is the best * updatedSpecializedTypes that can be computed? In other words, unconstrainedTypes can be specialized to updatedSpecializedTypes, * updatedSpecializedTypes[switchIndex] is equal to switchType, and unconstrainedTypes has all the specializations of oldSpecializedTypes * that are not invalidated by switchType. For example: * * getUpdatedSpecializedTypes((Char, Long), 1, [(Int, Boolean), (Int, Double)], [(a, b), (a, c)]) == [(Char, Boolean), (Char, Long)]. * * @param switchedType * @param switchIndex * @param oldSpecializedTypes * @param unconstrainedTypes * @param currentModuleTypeInfo * @return TypeExpr[] null if the specialization relationships described above between the arguments of this function do not hold */ public static TypeExpr[] getUpdatedSpecializedTypes(TypeExpr switchedType, int switchIndex, TypeExpr[] oldSpecializedTypes, TypeExpr[] unconstrainedTypes, ModuleTypeInfo currentModuleTypeInfo) { TypeExpr unconstrainedSwitchType = unconstrainedTypes[switchIndex]; if (!TypeExpr.canPatternMatchPieces(oldSpecializedTypes, unconstrainedTypes, currentModuleTypeInfo) || !TypeExpr.canPatternMatch(switchedType, unconstrainedSwitchType, currentModuleTypeInfo)) { return null; } // Returns a map going from the typeVars in the unconstrained type that the value was connected with, // to their associated TypeExprs in the newValueNode type. Map<TypeExpr, TypeExpr> affectedTypeVarToNewTypeMap = getTypeVarCorrespondenceMap(unconstrainedSwitchType, switchedType); int nTypes = unconstrainedTypes.length; // Now construct the map from type var to its specialization. // First populate with its former specialization.. Map<TypeExpr, TypeExpr> typeVarToSpecializationMap = new HashMap<TypeExpr, TypeExpr>(); for (int i = 0; i < nTypes; ++i) { // Map type vars in the unconstrained type to copied corresponding type in the old value. Map<TypeExpr, TypeExpr> typeVarToOldSpecializedTypeMap = getTypeVarCorrespondenceMap(unconstrainedTypes[i], oldSpecializedTypes[i]); typeVarToSpecializationMap.putAll(typeVarToOldSpecializedTypeMap); } // Now add new specializations to ensure these take precedence. typeVarToSpecializationMap.putAll(affectedTypeVarToNewTypeMap); // Copy the type specializations, so we can use them in the return values. // First create of the type var specializations, in the same order that they appear in an iterator over the map keys. List<TypeExpr> typeVarSpecializationList = new ArrayList<TypeExpr>(); for (final Map.Entry<TypeExpr, TypeExpr> entry : typeVarToSpecializationMap.entrySet()) { final TypeExpr specialization = entry.getValue(); typeVarSpecializationList.add(specialization); } // Convert to an array, make a copy. TypeExpr[] typeVarSpecializationArray = typeVarSpecializationList.toArray(new TypeExpr[typeVarSpecializationList.size()]); TypeExpr[] typeVarSpecializationArrayCopy = copyTypeExprs(typeVarSpecializationArray); // Create a map from the type var to the copied specialized type. Map<TypeExpr, TypeExpr> typeVarToSpecializationCopyMap = new HashMap<TypeExpr, TypeExpr>(); int index = 0; for (final TypeExpr typeVar : typeVarToSpecializationMap.keySet()) { typeVarToSpecializationCopyMap.put(typeVar, typeVarSpecializationArrayCopy[index]); index++; } // The array of updated specializations - starts out as a copy of the unconstrained types, has its type vars specialized. TypeExpr[] updatedSpecializedTypes = copyTypeExprs(unconstrainedTypes); // Create a map from type vars in the original to type vars in the copy. Map<TypeExpr, TypeExpr> typeVarToTypeVarCopyMap = new HashMap<TypeExpr, TypeExpr>(); for (int i = 0; i < nTypes; i++) { typeVarToTypeVarCopyMap.putAll(getTypeVarCorrespondenceMap(unconstrainedTypes[i], updatedSpecializedTypes[i])); } // Specialize the type variables in the copy to the new types. for (final Map.Entry<TypeExpr, TypeExpr> entry : typeVarToSpecializationCopyMap.entrySet()) { TypeExpr affectedTypeVar = entry.getKey(); TypeExpr affectedTypeVarCopy = typeVarToTypeVarCopyMap.get(affectedTypeVar); TypeExpr specializeTypeCopy = entry.getValue(); try { TypeExpr.patternMatch(specializeTypeCopy, affectedTypeVarCopy, currentModuleTypeInfo); } catch (TypeException e) { return null; } } // Fully-prune.. for (int i = 0; i < nTypes; ++i) { updatedSpecializedTypes[i] = updatedSpecializedTypes[i].deepPrune(); } return updatedSpecializedTypes; } /** * For use in finding associations between type vars in a given type and their corresponding types in a * specialization of that type. * * Note: it is assumed that canPatternMatch(correspondingType, typeToMap). * * Example: * typeToMap: (a, (Double, b)) * correspondingType: ((Foo, c), (Double, [d])) * resulting mappings: a -> (Foo, c) * b -> [d] * * How do you find that 'a' corresponds to 'x'? You use this method. * Once you know that 'a' corresponds to 'x', then you can find all other values that corresponds to 'a' as well, and * manipulate them as necessary. * * @param typeToMap the type expr from which to map type vars. * @param correspondingType the corresponding type expr for which type vars from typeToMap will be mapped. * This will be at least as specialized as the type to map. * @return Map (TypeExpr->TypeExpr) from type var in typeToMap to the corresponding type expr in corresponding type. */ private static Map<TypeExpr, TypeExpr> getTypeVarCorrespondenceMap(TypeExpr typeToMap, TypeExpr correspondingType) { Map<TypeExpr, TypeExpr> returnMap = new HashMap<TypeExpr, TypeExpr>(); TypeExpr.getTypeVarCorrespondenceMapHelper(typeToMap, correspondingType, returnMap); return returnMap; } /** * Helper for getTypeVarCorrespondenceMap * @param typeToMap * @param correspondingType * @param map */ private static void getTypeVarCorrespondenceMapHelper(TypeExpr typeToMap, TypeExpr correspondingType, Map<TypeExpr, TypeExpr> map) { // If it's a type var, then we've completed mapping if (typeToMap instanceof TypeVar) { map.put(typeToMap, correspondingType); } else if (typeToMap instanceof TypeConsApp) { // If it's a data constructor, map its arguments. for (int i = 0, size = typeToMap.rootTypeConsApp().getNArgs(); i < size; i++) { getTypeVarCorrespondenceMapHelper(typeToMap.rootTypeConsApp().getArg(i), correspondingType.rootTypeConsApp().getArg(i).prune(), map); } } else if (typeToMap instanceof RecordType) { Map<FieldName, TypeExpr> hasFieldsMap = ((RecordType)typeToMap).getHasFieldsMap(); RecordType correspondingRecordType = correspondingType.rootRecordType(); Map<FieldName, TypeExpr> correspondingHasFieldsMap = correspondingRecordType.getHasFieldsMap(); for (final Map.Entry<FieldName, TypeExpr> entry : hasFieldsMap.entrySet()) { FieldName fieldName = entry.getKey(); TypeExpr fieldType = entry.getValue(); TypeExpr correspondingFieldType = correspondingHasFieldsMap.get(fieldName); getTypeVarCorrespondenceMapHelper(fieldType, correspondingFieldType, map); } } else { throw new IllegalStateException("Programming error."); } } /** * Write this TypeExpr to the RecordOutputStream. Writing a single type object out is suspicious are you * sure you don't want the write(RecordOutputStream, TypeExpr[]) function? * @param s * @throws IOException */ final void write (RecordOutputStream s) throws IOException { Map<TypeExpr, Short> visitedTypeExpr = new HashMap<TypeExpr, Short>(); Map<RecordVar, Short> visitedRecordVar = new HashMap<RecordVar, Short>(); write (s, visitedTypeExpr, visitedRecordVar); } /** * Write the array of types to the output stream. This will make sure all vars are mapped to the same objects. * @param s * @param types * @throws IOException */ static final void write(RecordOutputStream s, ArrayList<TypeExpr> types) throws IOException{ Map<TypeExpr, Short> visitedTypeExpr = new HashMap<TypeExpr, Short>(); Map<RecordVar, Short> visitedRecordVar = new HashMap<RecordVar, Short>(); for(int i = 0, nTypes = types.size(); i < nTypes; ++i){ types.get(i).write (s, visitedTypeExpr, visitedRecordVar); } } /** * Write this TypeExpr to the RecordOutputStream. * @param s * @param visitedTypeExpr - map of TypeExpr instances that have been serialized. * @param visitedRecordVar - map of RecordVar instances that have been serialized. * @throws IOException */ final void write (RecordOutputStream s, Map<TypeExpr, Short> visitedTypeExpr, Map<RecordVar, Short> visitedRecordVar) throws IOException { if (this == EMPTY_RECORD) { s.startRecord(ModuleSerializationTags.STATIC_CONSTANT_TYPE_EXPR, staticConstantTypeExprSerializationSchema); s.writeUTF("EMPTY_RECORD"); s.endRecord(); } else { Short key = visitedTypeExpr.get(this); if (key != null) { // This instance has already been serialized so we simply reference it by key. s.startRecord(ModuleSerializationTags.ALREADY_VISITED_TYPE_EXPR, alreadyVisitedTypeExprSerializationSchema); s.writeShortCompressed(key.shortValue()); s.endRecord(); } else { // This instance has not been serialized. Add it to the map of serialized TypeExpr // and write it out. key = new Short((short)visitedTypeExpr.size()); visitedTypeExpr.put(this, key); writeActual(s, visitedTypeExpr, visitedRecordVar); } } } /** * Method to actually write the instance of TypeExpr to the RecordOutputStream. * @param s * @param visitedTypeExpr * @param visitedRecordVar * @throws IOException */ abstract void writeActual (RecordOutputStream s, Map<TypeExpr, Short> visitedTypeExpr, Map<RecordVar, Short> visitedRecordVar) throws IOException; /** * Load a TypeExpr from the RecordInputStream. * @param s * @param mti - the containing ModuleTypeInfo * @param msgLogger the logger to which to log deserialization messages. * @return an instance of TypeExpr * @throws IOException */ static final TypeExpr load (RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException { Map<Short, TypeExpr> visitedTypeExpr = new HashMap<Short, TypeExpr>(); Map<Short, RecordVar> visitedRecordVar = new HashMap<Short, RecordVar>(); return TypeExpr.load(s, mti, visitedTypeExpr, visitedRecordVar, msgLogger); } /** * Load an array of TypeExpr from the RecordInputStream. This will ensure that the same variables will * map the the same TypeVar objects. * @param s * @param mti * @param nTypes * @param msgLogger the logger to which to log deserialization messages. * @return An array of TypeExpr as read from the input stream. * @throws IOException */ static final TypeExpr[] load (RecordInputStream s, ModuleTypeInfo mti, int nTypes, CompilerMessageLogger msgLogger) throws IOException { Map<Short, TypeExpr> visitedTypeExpr = new HashMap<Short, TypeExpr>(); Map<Short, RecordVar> visitedRecordVar = new HashMap<Short, RecordVar>(); TypeExpr[] types = new TypeExpr[nTypes]; for(int i = 0; i < nTypes; ++i){ types[i] = TypeExpr.load(s, mti, visitedTypeExpr, visitedRecordVar, msgLogger); } return types; } /** * Load a TypeExpr from the RecordInputStream. * @param s * @param mti * @param visitedTypeExpr - a map of loaded TypeExpr instances. * @param visitedRecordVar - a map of loaded RecordVar instances. * @param msgLogger the logger to which to log deserialization messages. * @return an instance of TypeExpr * @throws IOException */ static final TypeExpr load (RecordInputStream s, ModuleTypeInfo mti, Map<Short, TypeExpr> visitedTypeExpr, Map<Short, RecordVar> visitedRecordVar, CompilerMessageLogger msgLogger) throws IOException { // Determine which specific class this serialized instance is. RecordHeaderInfo rhi = s.findRecord(TYPE_EXPR_RECORD_TAGS); if (rhi == null) { throw new IOException ("Unable to find TypeExpr record header."); } // Based on the record tag load the actual instance. switch (rhi.getRecordTag()) { case ModuleSerializationTags.STATIC_CONSTANT_TYPE_EXPR: { if (rhi.getSchema() > staticConstantTypeExprSerializationSchema) { throw new IOException("Stored schema " + rhi.getSchema() + " is greater than current schema " + staticConstantTypeExprSerializationSchema + " when loading a static constant TypeExpr."); } String constName = s.readUTF(); s.skipRestOfRecord(); if (constName.equals ("EMPTY_RECORD")) { return EMPTY_RECORD; } else { } } case ModuleSerializationTags.ALREADY_VISITED_TYPE_EXPR: { // This is a reference to an existing instance. The instance should be // in the visitedTypeExpr map. if (rhi.getSchema() > alreadyVisitedTypeExprSerializationSchema) { throw new IOException("Stored schema " + rhi.getSchema() + " is greater than current schema " + alreadyVisitedTypeExprSerializationSchema + " loading an already visited TypeExpr."); } // Read the key and do the lookup. short id = s.readShortCompressed(); TypeExpr te = visitedTypeExpr.get(new Short(id)); if (te == null) { throw new IOException ("Unable to resolve previously encountered TypeExpr instance in TypeExpr."); } s.skipRestOfRecord(); return te; } case ModuleSerializationTags.TYPE_CONSTRUCTOR: { return TypeConsApp.load(s, rhi.getSchema(), mti, visitedTypeExpr, visitedRecordVar, msgLogger); } case ModuleSerializationTags.TYPE_APP: { return TypeApp.load(s, rhi.getSchema(), mti, visitedTypeExpr, visitedRecordVar, msgLogger); } case ModuleSerializationTags.TYPE_VAR: case ModuleSerializationTags.TYPE_VAR_WITH_INSTANCE: { return TypeVar.load(s, rhi.getRecordTag(), rhi.getSchema(), mti, visitedTypeExpr, visitedRecordVar, msgLogger); } case ModuleSerializationTags.RECORD_TYPE: { return RecordType.load(s, rhi.getSchema(), mti, visitedTypeExpr, visitedRecordVar, msgLogger); } default: { throw new IOException("Unexpected record tag " + rhi.getRecordTag() + " found when looking for TypeExpr."); } } } }