/// <summary> /// Findes the smallest union type between the two types, with the assumption that 'wider' is a supertype of 'smaller' /// </summary> /// <param name="wider"></param> /// <param name="smaller"></param> /// <returns></returns> private static Type SelectSmallestUnion(this Type wider, Type smaller) { switch (wider) { case Var a: return(a); case ListType l when smaller is ListType lsmaller: return(new ListType( l.InnerType.SelectSmallestUnion( l.InnerType.SelectSmallestUnion(lsmaller.InnerType)))); case Curry cWider when smaller is Curry cSmaller: var arg = cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType); var result = cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType); return(new Curry(arg, result)); default: if (wider.IsSuperSet(smaller) && !smaller.IsSuperSet(wider)) { return(smaller); } return(wider); } }
public static IExpression SpecializeToSmallestType(this IExpression e) { if (e.Types.Count() == 1) { return(e); } Type smallest = null; foreach (var t in e.Types) { if (smallest == null) { smallest = t; continue; } var smallestIsSuperset = smallest.IsSuperSet(t); if (!t.IsSuperSet(smallest) && !smallestIsSuperset) { // Neither one is smaller then the other, we can not compare them return(e); } if (smallestIsSuperset) { smallest = t; } } return(e.Specialize(new[] { smallest })); }
/// <summary> /// The unification table is built when the type of an argument is introspected to see if it fits in the excpect type /// t0 here is the **expected** (wider) type, whereas t1 is the **actual** argument type. /// In other words, if we expect a `double`, a `pdouble` fits in there too. /// If we expect a function capable of handling pdoubles and giving strings, a function capable of handling doubles and /// giving bools will work just as well /// </summary> /// <param name="t0"></param> /// <param name="t1"></param> /// <returns></returns> public static Dictionary <string, Type> UnificationTable(this Type t0, Type t1, bool reverseSupersetRelation = false) { var substitutionsOn0 = new Dictionary <string, Type>(); bool AddSubs(string key, Type valueToAdd) { if (substitutionsOn0.TryGetValue(key, out var oldSubs)) { return(oldSubs.Equals(valueToAdd)); } substitutionsOn0[key] = valueToAdd; return(true); } bool AddAllSubs(Dictionary <string, Type> table) { if (table == null) { return(false); } foreach (var(key, tp) in table) { if (!AddSubs(key, tp)) { return(false); } } return(true); } switch (t0) { case Var a: if (!AddSubs(a.Name, t1)) { return(null); } break; case ListType l0 when t1 is ListType l1: { var table = l0.InnerType.UnificationTable(l1.InnerType, reverseSupersetRelation); if (!AddAllSubs(table)) { return(null); } break; } case Curry curry0 when t1 is Curry curry1: { // contravariance for arguments: reversed var tableA = curry0.ArgType.UnificationTable(curry1.ArgType, !reverseSupersetRelation); var tableB = curry0.ResultType.UnificationTable(curry1.ResultType, reverseSupersetRelation); if (!(AddAllSubs(tableA) && AddAllSubs(tableB))) { return(null); } break; } default: if (t1 is Var v) { AddSubs(v.Name, t0); break; } if (!reverseSupersetRelation && !t0.IsSuperSet(t1)) { return(null); } if (reverseSupersetRelation && !t1.IsSuperSet(t0)) { return(null); } break; } // We have the unification table at this point // However, the unifications are transitive and this transitivity should be encoded // E.g. if the unification table is // { $a --> $x; $x --> string} it should be rewritten to {$a --> string; $x --> string} // This can happen e.g. when ($a -> string) is unified with ($x -> $x) // We do not have to worry about overlapping names, as they should be cleaned before calling this method bool appliedTransitivity; do { appliedTransitivity = false; var keys = substitutionsOn0.Keys.ToList(); foreach (var key in keys) { var val = substitutionsOn0[key]; var usedVars = val.UsedVariables(); var isContained = keys.Any(usedVars.Contains); if (!isContained) { continue; } var newVal = val.Substitute(substitutionsOn0); if (newVal.Equals(val)) { continue; } if (newVal.UsedVariables().Contains(key) && !newVal.Equals(new Var(key))) { // The substitution contains itself; and it is bigger then itself // This means that $a is substituted by e.g. ($a -> $x), implying an infinite and contradictory type return(null); } substitutionsOn0[key] = newVal; appliedTransitivity = true; } } while (appliedTransitivity); return(substitutionsOn0); }