package org.simantics.scl.compiler.types; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.simantics.scl.compiler.common.exceptions.InternalCompilerError; import org.simantics.scl.compiler.environment.Environments; import org.simantics.scl.compiler.errors.Locations; import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException; import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl; import org.simantics.scl.compiler.internal.types.HashConsing; import org.simantics.scl.compiler.internal.types.TypeElaborationContext; import org.simantics.scl.compiler.internal.types.effects.EffectIdMap; import org.simantics.scl.compiler.types.exceptions.KindUnificationException; import org.simantics.scl.compiler.types.exceptions.MatchException; import org.simantics.scl.compiler.types.exceptions.Problem; import org.simantics.scl.compiler.types.exceptions.SCLTypeParseException; import org.simantics.scl.compiler.types.exceptions.UnificationException; import org.simantics.scl.compiler.types.kinds.Kind; import org.simantics.scl.compiler.types.kinds.Kinds; import org.simantics.scl.compiler.types.util.ITypeEnvironment; import org.simantics.scl.compiler.types.util.MultiApply; import org.simantics.scl.compiler.types.util.MultiFunction; import org.simantics.scl.compiler.types.util.TMultiApply; import org.simantics.scl.compiler.types.util.TypeUnparsingContext; import org.simantics.scl.compiler.types.util.Typed; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; /** * An utility class for creating and manipulating types. * * @author Hannu Niemistö */ public class Types { private static final HashConsing conCache = new HashConsing() { protected boolean equals(TCon a, TCon b) { return a.name.equals(b.name) && a.module.equals(b.module); } protected int hashCode(TCon obj) { return obj.module.hashCode()*31 + obj.name.hashCode(); } }; public static final String BUILTIN = "Builtin"; public static final TCon BOOLEAN = con(BUILTIN, "Boolean"); public static final TCon BYTE = con(BUILTIN, "Byte"); public static final TCon CHARACTER = con(BUILTIN, "Character"); public static final TCon SHORT = con(BUILTIN, "Short"); public static final TCon INTEGER = con(BUILTIN, "Integer"); public static final TCon LONG = con(BUILTIN, "Long"); public static final TCon FLOAT = con(BUILTIN, "Float"); public static final TCon DOUBLE = con(BUILTIN, "Double"); public static final TCon STRING = con(BUILTIN, "String"); public static final TCon ARROW = con(BUILTIN, "->"); public static final TCon LIST = con(BUILTIN, "[]"); public static final TCon VECTOR = con(BUILTIN, "Vector"); public static final TCon MVECTOR = con(BUILTIN, "MVector"); public static final TCon MAYBE = con(BUILTIN, "Maybe"); public static final TCon ARRAY = con(BUILTIN, "Array"); public static final TCon UNIT = con(BUILTIN, "()"); public static final TCon PUNIT = con(BUILTIN, "@"); public static final TCon TYPE_PROXY = con(BUILTIN, "TypeProxy"); public static final TCon TYPEABLE = con(BUILTIN, "Typeable"); public static final TCon SERIALIZABLE = con(BUILTIN, "Serializable"); public static final TCon VEC_COMP = con(BUILTIN, "VecComp"); public static final TCon CLASS = con(BUILTIN, "Class"); public static final TCon BINDING = con(BUILTIN, "Binding"); public static final TCon TYPE = con(BUILTIN, "Type"); public static final TCon DYNAMIC = con("Prelude", "Dynamic"); public static final TCon VARIANT = con(BUILTIN, "Variant"); public static final TCon ADDITIVE = con("Prelude", "Additive"); public static final TCon MONAD = con("Prelude", "Monad"); public static final TCon MONAD_E = con("Prelude", "MonadE"); public static final TCon INTEGRAL = con("Prelude", "Integral"); public static final TCon RING = con("Prelude", "Ring"); public static final TCon ORDERED_RING = con("Prelude", "OrderedRing"); public static final TCon REAL = con("Prelude", "Real"); public static final TCon SHOW = con("Prelude", "Show"); public static final TCon ORD = con("Prelude", "Ord"); public static final TCon IO = con("Serialization", "IO"); public static final Type REF = con("Prelude", "Ref"); public static final TCon RANDOM = Types.con("Random", "Random"); public static final TCon READ_GRAPH = Types.con("Simantics/DB", "ReadGraph"); public static final TCon WRITE_GRAPH = Types.con("Simantics/DB", "WriteGraph"); public static final Type RESOURCE = Types.con("Simantics/DB", "Resource"); public static final TUnion NO_EFFECTS = new TUnion(); public static final TCon PROC = con(BUILTIN, "Proc"); public static final TCon EXCEPTION = con(BUILTIN, "Exception"); public static final TCon BRANCH_POINT = con(BUILTIN, "BranchPoint"); public static final TCon CHRContext = con(BUILTIN, "CHRContext"); public static final Type BOOLEAN_ARRAY = vector(BOOLEAN); public static final Type BYTE_ARRAY = vector(BYTE); public static final Type CHARACTER_ARRAY = vector(CHARACTER); public static final Type SHORT_ARRAY = vector(SHORT); public static final Type INTEGER_ARRAY = vector(INTEGER); public static final Type LONG_ARRAY = vector(LONG); public static final Type FLOAT_ARRAY = vector(FLOAT); public static final Type DOUBLE_ARRAY = vector(DOUBLE); private volatile static TCon[] tupleCache = new TCon[] { UNIT, null }; private static final ITypeEnvironment DUMMY_TYPE_ENVIRONMENT = new ITypeEnvironment() { @Override public TCon resolve(String namespace, String name) { if(namespace == null) return con(BUILTIN, name); else return con(namespace, name); } }; public static boolean isPrimitive(Type type) { return type == BOOLEAN || type == BYTE || type == CHARACTER || type == SHORT || type == INTEGER || type == LONG || type == FLOAT || type == DOUBLE || type == STRING; } public static boolean isNumeric(Type type) { return type == BYTE || type == SHORT || type == INTEGER || type == LONG || type == FLOAT || type == DOUBLE; } public static TApply apply(Type function, Type parameter) { return new TApply(function, parameter); } public static Type apply(Type function, Type ... parameters) { for(Type parameter : parameters) function = apply(function, parameter); return function; } /** * Get the concrete type pointed to by a chain of type meta-variables. */ public static Type canonical(Type type) { if(type instanceof TMetaVar) { TMetaVar metaVar = (TMetaVar)type; type = metaVar.ref; if(type == null) return metaVar; else return metaVar.ref = canonical(type); } return type; } public static Type closure(Type type, ArrayList vars) { for(int i=vars.size()-1;i>=0;--i) type = forAll(vars.get(i), type); return type; } public static Type closure(Type type, TVar[] vars) { for(int i=vars.length-1;i>=0;--i) type = forAll(vars[i], type); return type; } public static Type closure(Type type) { return closure(type, freeVars(type)); } public static TCon con(String module, String name) { return conCache.canonical(new TCon(module, name)); } public static Type[] concat(Type[] a, Type[] b) { if(a.length == 0) return b; if(b.length == 0) return a; Type[] result = new Type[a.length + b.length]; for(int i=0;i mVars = new ArrayList(0); int idA = idMap.toId(a, mVars); int idB = idMap.toId(b, mVars); return (idA&idB) == idA; } public static boolean equalsEffect(Type a, Type b) { EffectIdMap idMap = new EffectIdMap(); ArrayList mVars = new ArrayList(0); int idA = idMap.toId(a, mVars); int idB = idMap.toId(b, mVars); return idA == idB; } public static boolean equals(TForAll a, TForAll b) { Kind aKind = a.var.getKind(); if(!Kinds.equalsCanonical(aKind, b.var.getKind())) return false; TVar newVar = var(aKind); return equals(a.type.replace(a.var, newVar), b.type.replace(b.var, newVar)); } public static boolean equals(TPred a, TPred b) { if(a.typeClass != b.typeClass || a.parameters.length != b.parameters.length) return false; Type[] aParameters = a.parameters; Type[] bParameters = b.parameters; for(int i=0;i ca = a.getClass(); Class cb = b.getClass(); if(ca != cb) return false; if(ca == TApply.class) return equals((TApply)a, (TApply)b); else if(ca == TFun.class) return equals((TFun)a, (TFun)b); else if(ca == TForAll.class) return equals((TForAll)a, (TForAll)b); else if(ca == TPred.class) return equals((TPred)a, (TPred)b); else if(ca == TUnion.class) return equals((TUnion)a, (TUnion)b); else // ca == TCon.class // || (ca == TMetaVar.class && a.ref == null && b.ref == null) // || ca = TVar.class return false; // Equals only if a == b, that was already tested } public static boolean subsumes(Type a, Type b) { a = canonical(a); b = canonical(b); if(a == b) return true; Class ca = a.getClass(); Class cb = b.getClass(); if(ca != cb) return false; if(ca == TApply.class) return equals((TApply)a, (TApply)b); else if(ca == TFun.class) return subsumes((TFun)a, (TFun)b); else if(ca == TForAll.class) { TForAll aForAll = (TForAll)a; TForAll bForAll = (TForAll)b; TVar newVar = var(aForAll.var.getKind()); return subsumes(aForAll.type.replace(aForAll.var, newVar), bForAll.type.replace(bForAll.var, newVar)); } else if(ca == TPred.class) return equals((TPred)a, (TPred)b); else if(ca == TUnion.class) return equals((TUnion)a, (TUnion)b); else // ca == TCon.class // || (ca == TMetaVar.class && a.ref == null && b.ref == null) // || ca = TVar.class return false; // Equals only if a == b, that was already tested } public static TForAll forAll(TVar parameter, Type type) { return new TForAll(parameter, type); } public static Type forAll(TVar[] parameters, Type type) { for(int i=parameters.length-1;i>=0;--i) type = forAll(parameters[i], type); return type; } public static ArrayList freeVars(Type type) { ArrayList vars = new ArrayList(2); type.collectFreeVars(vars); return vars; } public static ArrayList freeVars(Type[] types) { ArrayList vars = new ArrayList(2); for(Type type : types) type.collectFreeVars(vars); return vars; } public static TVar[] freeVarsArray(Type type) { ArrayList vars = freeVars(type); return vars.toArray(new TVar[vars.size()]); } public static TVar[] freeVarsArray(Type[] types) { ArrayList vars = freeVars(types); return vars.toArray(new TVar[vars.size()]); } public static TPred pred(TCon typeClass, Type ... parameters) { return new TPred(typeClass, parameters); } public static Type function(Type ... types) { Type result = types[types.length-1]; for(int i=types.length-2;i>=0;--i) result = function(types[i], result); return result; } public static Type function(Type from, Type to) { return new TFun(from, Types.NO_EFFECTS, to); } public static Type function(Type[] from, Type to) { for(int i=from.length-1;i>=0;--i) to = function(from[i], to); return to; } public static TFun functionE(Type from, Type effect, Type to) { return new TFun(from, effect, to); } public static Type functionE(Type[] from, Type effect, Type to) { for(int i=from.length-1;i>=0;--i) { to = functionE(from[i], effect, to); effect = Types.NO_EFFECTS; } return to; } public static Type removeForAll(Type type, ArrayList vars) { while(true) { if(type instanceof TForAll) { TForAll forAll = (TForAll)type; type = forAll.type; vars.add(forAll.var); } else if(type instanceof TMetaVar) { TMetaVar var = (TMetaVar)type; if(var.ref != null) type = var.ref; else return type; } else return type; } } public static Type removeForAll(Type type) { while(true) { if(type instanceof TForAll) { TForAll forAll = (TForAll)type; type = forAll.type; } else if(type instanceof TMetaVar) { TMetaVar var = (TMetaVar)type; if(var.ref != null) type = var.ref; else return type; } else return type; } } public static Type instantiate(TForAll forAll, ArrayList vars) { TMetaVar metaVar = metaVar(forAll.var.getKind()); vars.add(metaVar); return instantiate(forAll.type.replace(forAll.var, metaVar), vars); } public static Type instantiate(Type type, ArrayList vars) { if(type == null) throw new NullPointerException(); type = canonical(type); if(type instanceof TForAll) return instantiate((TForAll)type, vars); else return type; } public static Type list(Type parameter) { return apply(LIST, parameter); } public static Type vector(Type parameter) { return apply(VECTOR, parameter); } public static Type mvector(Type parameter) { return apply(MVECTOR, parameter); } public static MultiFunction matchFunction(Type type, int arity) throws MatchException { if (type instanceof TForAll) return matchFunction(((TForAll)type).type, arity); type = canonical(type); /*while(type instanceof TForAll) type = canonical(((TForAll)type).type);*/ Type[] parameterTypes = new Type[arity]; Type effect = Types.NO_EFFECTS; for(int i=0;i=i;--j) { Type pType = Types.metaVar(Kinds.STAR); parameterTypes[j] = pType; template = Types.functionE(pType, j==arity-1 ? effect : Types.NO_EFFECTS, template); } try { metaVar.setRef(template); } catch (UnificationException e) { // Should never happen throw new MatchException(); } break; }*/ /*else if(type instanceof TApply) { TApply apply1 = (TApply)type; Type function1 = canonical(apply1.function); if(function1 instanceof TApply) { TApply apply2 = (TApply)function1; Type function2 = canonical(apply2.function); if(function2 == ARROW) { result[i] = apply2.parameter; type = canonical(apply1.parameter); } else throw new MatchException(); } else throw new MatchException(); }*/ else throw new MatchException(); } return new MultiFunction(parameterTypes, effect, type); } public static boolean isApply(Type func, int arity, Type type) { while(arity-- > 0) { type = canonical(type); if(!(type instanceof TApply)) return false; type = ((TApply)type).function; } return equals(func, type); } public static Type matchApply(TCon func, Type type) throws MatchException { type = canonical(type); if(type instanceof TApply) { TApply apply = (TApply)type; Type f = canonical(apply.function); if(f.equals(func)) return canonical(apply.parameter); } throw new MatchException(); } public static MultiApply matchApply(Type type) { ArrayList parameters = new ArrayList(); type = canonical(type); while(type instanceof TApply) { TApply apply = (TApply)type; parameters.add(Types.canonical(apply.parameter)); type = canonical(apply.function); } Type[] parametersArray; if(parameters.isEmpty()) parametersArray = Type.EMPTY_ARRAY; else { parametersArray = new Type[parameters.size()]; for(int i=0,j=parametersArray.length-1;i parameterTypes = new ArrayList(); Type effect = Types.NO_EFFECTS; while(true) { if(type instanceof TFun) { TFun fun = (TFun)type; parameterTypes.add(fun.domain); type = canonical(fun.range); if(canonical(fun.effect) != Types.NO_EFFECTS) { effect = fun.effect; break; } } /*else if(type instanceof TApply) { TApply apply1 = (TApply)type; Type function1 = canonical(apply1.function); if(function1 instanceof TApply) { TApply apply2 = (TApply)function1; Type function2 = canonical(apply2.function); if(function2 == ARROW) { types.add(apply2.parameter); type = apply1.parameter; } else { types.add(type); break; } } else { types.add(type); break; } }*/ else { break; } } return new MultiFunction( parameterTypes.toArray(new Type[parameterTypes.size()]), effect, type); } /** * This function always success, but may return a multi function * with arity smaller than given parameter */ public static MultiFunction unifyFunction2(Type type, int arity) { type = canonical(type); Type[] parameterTypes = new Type[arity]; Type effect = Types.NO_EFFECTS; int i; for(i=0;i=0;--i) { requiredType = functionE(parameterTypes[i], effect, requiredType); effect = Types.NO_EFFECTS; } unify(type, requiredType); return result; } private static Type getRangeIfFunction(Type type) { type = canonical(type); if(type instanceof TFun) { return ((TFun)type).range; } /*else if(type instanceof TApply) { TApply apply1 = (TApply)type; Type f = canonical(apply1.function); if(f instanceof TApply) { if( canonical(((TApply)f).function) == Types.ARROW ) { return apply1.parameter; } else return null; } else return null; }*/ else return null; } public static int getArity(Type type) { int arity = 0; while(true) { type = getRangeIfFunction(type); if(type == null) break; ++arity; } return arity; } public static int getMaxArity(Type type) { type = Skeletons.canonicalSkeleton(type); int arity = 0; while(true) { if(type instanceof TFun) { ++arity; type = Skeletons.canonicalSkeleton(((TFun) type).getCanonicalRange()); } else if(type instanceof TMetaVar) { return Integer.MAX_VALUE; } else break; } return arity; } public static TMetaVar metaVar(Kind kind) { return new TMetaVar(kind); } public static Type constrained(TPred constraint, Type type) { return new TFun(constraint, Types.NO_EFFECTS, type); } public static Type constrained(TPred[] constraints, Type type) { for(int i=constraints.length-1;i>=0;--i) type = constrained(constraints[i], type); return type; } public static TMultiApply toMultiApply(Type type) { ArrayList parameters = new ArrayList(); type = canonical(type); while(type instanceof TApply) { TApply apply = (TApply)type; parameters.add(apply.parameter); type = canonical(apply.function); } Collections.reverse(parameters); return new TMultiApply(type, parameters); } public static Type tuple(Type ... parameters) { if(parameters.length == 1) return parameters[0]; else return apply(tupleConstructor(parameters.length), parameters); } public static TCon tupleConstructor(int arity) { if(arity < 0 || arity == 1) throw new IllegalArgumentException("The arity of a tuple cannot be " + arity + "."); TCon[] oldTupleCache = tupleCache; if(oldTupleCache.length <= arity) { int oldLength = oldTupleCache.length; int newLength = oldLength*2; while(newLength <= arity) newLength *= 2; TCon[] newTupleCache = Arrays.copyOf(oldTupleCache, newLength); for(int i=oldLength;i ca = a.getClass(); Class cb = b.getClass(); if(ca != cb) { throw new UnificationException(a, b); } if(ca == TApply.class) unify((TApply)a, (TApply)b); else if(ca == TFun.class) unify((TFun)a, (TFun)b); else if(ca == TForAll.class) unify((TForAll)a, (TForAll)b); else if(ca == TPred.class) unify((TPred)a, (TPred)b); else if(ca == TUnion.class) unify((TUnion)a, (TUnion)b); else // ca == TCon.class || ca = TVar.class throw new UnificationException(a, b); } public static TVar var(Kind kind) { return new TVar(kind); } public static TVar[] vars(TVar[] otherVars) { TVar[] vars = new TVar[otherVars.length]; for(int i=0;i substitution) { b = canonical(b); Class ca = a.getClass(); if(ca == TVar.class) { TVar ta = (TVar)a; Type t = substitution.get(ta); if(t == null) { substitution.put(ta, b); // no occurs check needed return true; } else return match(t, b, substitution); } if(a == b) return true; Class cb = b.getClass(); if(ca != cb || ca == TCon.class) return false; if(ca == TApply.class) return match((TApply)a, (TApply)b, substitution); else if(ca == TFun.class) return match((TFun)a, (TFun)b, substitution); else if(ca == TPred.class) return match((TPred)a, (TPred)b, substitution); else { throw new UnsupportedOperationException("match(" + a + ", " + b +") not supported"); // TForAll not supported } } public static boolean match(TApply a, TApply b, THashMap substitution) { return match(a.function, b.function, substitution) && match(a.parameter, b.parameter, substitution); } public static boolean match(TPred a, TPred b, THashMap substitution) { if(a.typeClass != b.typeClass || a.parameters.length != b.parameters.length) return false; for(int i=0;i substitution) { return match(a.domain, b.domain, substitution) && match(a.effect, b.effect, substitution) && match(a.range, b.range, substitution); } public static Type removePred(Type type, ArrayList preds) { while(type instanceof TFun) { TFun pred = (TFun)type; if(!(pred.domain instanceof TPred)) break; preds.add((TPred)pred.domain); type = canonical(pred.range); } return type; } public static Type[] getTypes(List vars) { Type[] result = new Type[vars.size()]; for(int i=0;i Type[] replace(Type[] types, THashMap map) { if(types.length == 0) return Type.EMPTY_ARRAY; Type[] result = new Type[types.length]; for(int i=0;i effects) { if(effects.size() == 0) return NO_EFFECTS; else if(effects.size() == 1) return effects.get(0); else return new TUnion(effects.toArray(new Type[effects.size()])); } public static void canonize(Type[] types) { for(int i=0;i effects = new ArrayList(union.effects.length); for(Type c : union.effects) { c = simplifyFinalEffect(c); if(c instanceof TUnion) for(Type c2 : ((TUnion)c).effects) effects.add(c2); else effects.add(c); } return union(effects); } return effect; } public static Type simplifyType(Type effect) { effect = canonical(effect); if(effect instanceof TUnion) { TUnion union = (TUnion)effect; if(union.effects.length == 0) return Types.NO_EFFECTS; THashSet effects = new THashSet(union.effects.length); for(Type c : union.effects) { c = simplifyFinalEffect(c); if(c instanceof TUnion) for(Type c2 : ((TUnion)c).effects) effects.add(c2); else effects.add(c); } return union(effects.toArray(new Type[effects.size()])); } return effect; } /** * Use {@link Environments#getType(org.simantics.scl.compiler.environment.Environment, String)} instead. */ @Deprecated public static Type parseType(ITypeEnvironment environment, String text) throws SCLTypeParseException { return parseType(new TypeElaborationContext(environment), text); } /** * This method uses DUMMY_TYPE_ENVIRONMENT that almost does anything useful. Use * {@link Environments#getType(org.simantics.scl.compiler.environment.Environment, String)} instead. */ @Deprecated public static Type parseType(String text) throws SCLTypeParseException { return parseType(new TypeElaborationContext(DUMMY_TYPE_ENVIRONMENT), text); } /** * Use {@link Environments#getType(org.simantics.scl.compiler.environment.Environment, String)} instead. */ @Deprecated private static Type parseType(TypeElaborationContext context, String text) throws SCLTypeParseException { SCLParserImpl parser = new SCLParserImpl(new StringReader(text)); try { org.simantics.scl.compiler.internal.parsing.types.TypeAst ast = (org.simantics.scl.compiler.internal.parsing.types.TypeAst)parser.parseType(); return ast.toType(context); } catch (SCLSyntaxErrorException e) { throw new SCLTypeParseException(new Problem( Locations.beginOf(e.location), Locations.endOf(e.location), e.getMessage())); } } public static Type instantiateAndStrip(Type type) { while(true) { if(type instanceof TForAll) { TForAll forAll = (TForAll)type; type = forAll.type.replace(forAll.var, metaVar(forAll.var.getKind())); } else if(type instanceof TFun) { TFun fun = (TFun)type; if(fun.domain instanceof TPred || fun.domain == Types.PUNIT) type = fun.range; else return type; } else if(type instanceof TMetaVar) { TMetaVar metaVar = (TMetaVar)type; if(metaVar.ref == null) return type; else type = metaVar.ref; } else return type; } } }