Proper calculation of the type of a list of expressions - turns out it was broken

This commit is contained in:
Pieter Vander Vennet 2021-03-15 20:33:19 +01:00
parent 38a50a6713
commit 6739dd8b25
15 changed files with 511 additions and 276 deletions

View file

@ -60,8 +60,9 @@ namespace AspectedRouting.Test.Snippets
tags
}
);
// First the more general ones!
Assert.Equal(
"if (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\nelseif (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\nend\n",
"if (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\n \nend\nif (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\n \nend\n",
code);
}
@ -82,27 +83,9 @@ namespace AspectedRouting.Test.Snippets
});
var expected =
"local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
"local v\nv = tags.oneway\n\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
Assert.Equal(expected, code);
}
[Fact]
public void MultiplySnippet_SimpleMultiply_GeneratesLua()
{
var gen = new MultiplySnippet();
var f = new Apply(
Funcs.Multiply,
null
);
var code = gen.Convert(new LuaSkeleton(new Context(), true),
"result", new List<IExpression>()
);
var expected =
"local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
Assert.Equal(expected, code);
}
}
}

View file

@ -1,12 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Xunit;
using Type = AspectedRouting.Language.Typ.Type;
namespace AspectedRouting.Test
{
public class TypingTests
{
[Fact]
public void SpecializeToCommonTypes_X2PDouble_Y2Double_Gives_X2Double()
{
// Regression test:
// [ x : (x -> pdouble), y : (y -> double)] is wrongly types as (x -> pdouble), hence killing y in the subsequent typing
var exprs = new List<IExpression> {
new Constant(new List<Type> {
new Curry(Typs.Tags, Typs.Double),
new Curry(new Var("b"), new Curry(Typs.Tags, Typs.Double))
}, "x"),
new Constant(
new List<Type> {
Typs.PDouble,
new Curry(new Var("b"), Typs.PDouble)
}
, "y")
};
exprs.SpecializeToCommonTypes(out var specializedTypes, out var specializedExpressions);
Assert.All(specializedTypes, Assert.NotNull);
Assert.All(specializedExpressions, Assert.NotNull);
Assert.Single(specializedTypes);
Assert.Equal(new Curry(Typs.Tags, Typs.Double), specializedTypes.First());
}
[Fact]
public void WidestCommonGround_A2PdoubleAndT2Double_T2Double()
{
var v = Typs.WidestCommonType(new Curry(new Var("a"), Typs.PDouble),
new Curry(Typs.Tags, Typs.Double)
);
Assert.NotNull(v);
var (x, subsTable) = v.Value;
Assert.NotNull(x);
Assert.Equal(
new Curry(Typs.Tags, Typs.Double), x
);
}
[Fact]
public void WidestCommonGround_StringAndString_String()
{
var v = Typs.WidestCommonType(Typs.String, Typs.String);
Assert.NotNull(v);
var (x, subsTable) = v.Value;
Assert.NotNull(x);
Assert.Equal(
Typs.String, x
);
}
[Fact]
public void SpecializeToCommonTypes_ValueAndFuncType_ShouldFail()
{
// Regression test:
// [ x : (x -> pdouble), y : (y -> double)] is wrongly types as (x -> pdouble), hence killing y in the subsequent typing
var exprs = new List<IExpression> {
new Constant(
new Curry(Typs.Tags, Typs.Double),
"x"),
new Constant(
Typs.PDouble, "y")
};
Assert.Throws(new ArgumentException().GetType(),
() => exprs.SpecializeToCommonTypes(out _, out _));
}
[Fact]
public void JoinApply_Id()

View file

@ -1,7 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3112587d_002D1c06_002D4ad3_002Da845_002D73105d4b723a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3112587d_002D1c06_002D4ad3_002Da845_002D73105d4b723a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.Snippets.SnippetTests&lt;/TestId&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.TypingTests.SpecializeToCommonTypes_X2PDouble_Y2Double_Gives_X2Double&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5e89d9f5_002D24ed_002D4ea2_002Da7ea_002Dcc7faea6d057/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="JoinApply_Id" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
@ -13,5 +14,8 @@
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&amp;lt;AspectedRouting.Test&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d2e3d58f_002Debff_002D4fb5_002D8d18_002Deafe85f4773d/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="SpecializeToCommonTypes_ValueAndFuncType_ShouldFail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&amp;lt;AspectedRouting.Test&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String>
</wpf:ResourceDictionary>

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using Type = AspectedRouting.Language.Typ.Type;
@ -31,6 +32,16 @@ namespace AspectedRouting.IO.LuaSkeleton
return this;
}
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var passed = this.Types.Where(allowedTypes);
if (passed.Any()) {
return new LuaLiteral(passed, this.Lua);
}
return null;
}
public IExpression Optimize()
{
return this;
@ -40,5 +51,6 @@ namespace AspectedRouting.IO.LuaSkeleton
{
throw new NotImplementedException();
}
}
}

View file

@ -248,6 +248,7 @@ namespace AspectedRouting.IO.jsonParser
var exprs = e.EnumerateArray().Select(json =>
Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)))
.ToList();
var list = new Constant(exprs);
return Funcs.Either(Funcs.Id, Funcs.ListDot, list);
}

View file

@ -12,35 +12,49 @@ namespace AspectedRouting.IO.jsonParser
{
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
{
if (!e.TryGetProperty(property, out var prop))
{
if (!e.TryGetProperty(property, out var prop)) {
throw new ArgumentException("Not a valid profile: the declaration expression for '" + property +
"' is missing");
}
try
{
try {
var expr = ParseExpression(prop, c);
if (!expr.Types.Any())
{
if (!expr.Types.Any()) {
throw new Exception($"Could not parse field {property}, no valid typing for expression found");
}
expr = expr.Optimize();
expr = Funcs.Either(Funcs.Id, Funcs.Const, expr);
var specialized = expr.Specialize(new Curry(Typs.Tags, new Var("a")));
if (specialized == null)
{
if (specialized == null) {
throw new ArgumentException("The expression for " + property +
" hasn't the right type of 'Tags -> a'; it has types " +
string.Join(",", expr.Types) + "\n" + expr.TypeBreakdown());
}
return specialized.Optimize();
var pruned = specialized.PruneTypes(type => {
if (!(type is Curry c)) {
return false;
}
catch (Exception exc)
{
if (!Equals(c.ArgType, Typs.Tags)) {
return false;
}
if (c.ResultType is Curry) {
return false;
}
return true;
});
if (pruned.SpecializeToSmallestType().Types.Count() != 1) {
throw new ArgumentException("The expression for " + property +
" hasn't the right type of 'Tags -> a'; it has multiple types " +
string.Join(",", pruned.Types) + "\n" + pruned.TypeBreakdown());
}
return pruned.Optimize();
}
catch (Exception exc) {
throw new Exception("While parsing the property " + property, exc);
}
}
@ -48,24 +62,20 @@ namespace AspectedRouting.IO.jsonParser
private static Dictionary<string, IExpression> ParseParameters(this JsonElement e)
{
var ps = new Dictionary<string, IExpression>();
foreach (var obj in e.EnumerateObject())
{
foreach (var obj in e.EnumerateObject()) {
var nm = obj.Name.TrimStart('#');
if (nm == "")
{
if (nm == "") {
// This is a comment - not a parameter!
continue;
}
switch (obj.Value.ValueKind)
{
switch (obj.Value.ValueKind) {
case JsonValueKind.String:
var v = obj.Value.ToString();
if (v.Equals("yes") || v.Equals("no"))
{
if (v.Equals("yes") || v.Equals("no")) {
ps[nm] = new Constant(Typs.Bool, v);
}
else
{
else {
ps[nm] = new Constant(v);
}
@ -96,8 +106,7 @@ namespace AspectedRouting.IO.jsonParser
private static string Get(this JsonElement json, string key)
{
if (json.TryGetProperty(key, out var p))
{
if (json.TryGetProperty(key, out var p)) {
return p.GetString();
}
@ -106,8 +115,7 @@ namespace AspectedRouting.IO.jsonParser
private static string TryGet(this JsonElement json, string key)
{
if (json.TryGetProperty(key, out var p))
{
if (json.TryGetProperty(key, out var p)) {
return p.GetString();
}

View file

@ -114,6 +114,15 @@ namespace AspectedRouting.Language.Expression
return Specialize(allowedTypes);
}
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var passed = this.FunctionApplications.Where(kv => allowedTypes.Invoke(kv.Key));
if (!passed.Any()) {
return null;
}
return new Apply("pruned", new Dictionary<Type, (IExpression A, IExpression F)>(passed));
}
public IExpression Optimize()
{
if (Types.Count() == 0) {

View file

@ -43,6 +43,16 @@ namespace AspectedRouting.Language.Expression
Name, Description, Author, Unit, Filepath);
}
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
{
var e = ExpressionImplementation.PruneTypes(allowedTypes);
if (e == null) {
return null;
}
return new AspectMetadata(e, Name, Description, Author, Unit,
Filepath, ProfileInternal);
}
public IExpression Optimize()
{
return new AspectMetadata(ExpressionImplementation.Optimize(),

View file

@ -34,17 +34,21 @@ namespace AspectedRouting.Language.Expression
public abstract object Evaluate(Context c, params IExpression[] arguments);
public abstract IExpression Specialize(IEnumerable<Type> allowedTypes);
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var passedTypes = this.Types.Where(allowedTypes);
if (!passedTypes.Any()) {
return null;
}
return new FunctionCall(this.Name, passedTypes);
}
public virtual IExpression Optimize()
{
return this;
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public virtual void Visit(Func<IExpression, bool> f)
{
f(this);

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
@ -51,6 +52,16 @@ namespace AspectedRouting.Language.Expression
return new FunctionCall(_name, unified);
}
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var passedTypes = this.Types.Where(allowedTypes);
if (!passedTypes.Any()) {
return null;
}
return new FunctionCall(this._name, passedTypes);
}
public IExpression Optimize()
{
return this;

View file

@ -53,6 +53,20 @@ namespace AspectedRouting.Language.Expression
LastChange = lastChange;
DefaultParameters = defaultParameters;
Behaviours = behaviours;
CheckTypes(Access, "access");
CheckTypes(Oneway, "oneway");
CheckTypes(Speed, "speed");
}
private static void CheckTypes(IExpression e, string name)
{
if (e.Types.Count() == 1) {
return;
}
throw new Exception("Insufficient specialization: " + name + " has multiple types left, namely " + e.Types.Pretty());
}
public List<IExpression> AllExpressions(Context ctx)

View file

@ -8,18 +8,15 @@ namespace AspectedRouting.Language.Functions
{
public class Constant : IExpression
{
public IEnumerable<Type> Types { get; }
private readonly object _o;
public Constant(IEnumerable<Type> types, object o)
{
Types = types.ToList();
if (o is IEnumerable<IExpression> enumerable)
{
if (o is IEnumerable<IExpression> enumerable) {
o = enumerable.ToList();
if (enumerable.Any(x => x == null))
{
if (enumerable.Any(x => x == null)) {
throw new NullReferenceException("Some subexpression is null");
}
}
@ -30,11 +27,9 @@ namespace AspectedRouting.Language.Functions
public Constant(Type t, object o)
{
Types = new List<Type> {t};
if (o is IEnumerable<IExpression> enumerable)
{
if (o is IEnumerable<IExpression> enumerable) {
o = enumerable.ToList();
if (enumerable.Any(x => x == null))
{
if (enumerable.Any(x => x == null)) {
throw new NullReferenceException("Some subexpression is null");
}
}
@ -47,29 +42,33 @@ namespace AspectedRouting.Language.Functions
var tps = exprs
.SpecializeToCommonTypes(out var specializedVersions)
.Select(t => new ListType(t));
if(specializedVersions.Any(x => x == null))
{
throw new Exception("Specializing to common types failed for "+string.Join("," ,exprs.Select(e => e.ToString())));
if (specializedVersions.Any(x => x == null)) {
throw new Exception("Specializing to common types failed for " +
string.Join(",", exprs.Select(e => e.ToString())));
}
Types = tps.ToList();
_o = specializedVersions;
}
public Constant(List<IExpression> exprs)
{
try
public Constant(IReadOnlyCollection<IExpression> exprs)
{
try {
Types = exprs
.SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t)).ToList();
if (specializedVersions.Any(x => x == null))
{
.SpecializeToCommonTypes(out var specializedVersions)
.Select(t => new ListType(t))
.ToList();
if (specializedVersions.Any(x => x == null)) {
throw new NullReferenceException("Some subexpression is null");
}
_o = specializedVersions.ToList();
}
catch (Exception e)
{
throw new Exception($"While creating a list with members " +
catch (Exception e) {
throw new Exception("While creating a list with members " +
string.Join(", ", exprs.Select(x => x.Optimize())) +
$" {e.Message}", e);
}
@ -83,12 +82,10 @@ namespace AspectedRouting.Language.Functions
public Constant(double d)
{
if (d >= 0)
{
if (d >= 0) {
Types = new[] {Typs.Double, Typs.PDouble};
}
else
{
else {
Types = new[] {Typs.Double};
}
@ -97,12 +94,10 @@ namespace AspectedRouting.Language.Functions
public Constant(int i)
{
if (i >= 0)
{
if (i >= 0) {
Types = new[] {Typs.Double, Typs.Nat, Typs.Nat, Typs.PDouble};
}
else
{
else {
Types = new[] {Typs.Double, Typs.Int};
}
@ -115,23 +110,34 @@ namespace AspectedRouting.Language.Functions
_o = tags;
}
public IEnumerable<Type> Types { get; }
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
{
var passedTypes = Types.Where(allowedTypes);
if (!passedTypes.Any()) {
return null;
}
return new Constant(passedTypes, _o);
}
public object Evaluate(Context c, params IExpression[] args)
{
if (_o is IExpression e)
{
if (_o is IExpression e) {
return e.Evaluate(c).Pretty();
}
if (Types.Count() > 1) return _o;
if (Types.Count() > 1) {
return _o;
}
var t = Types.First();
switch (t)
{
switch (t) {
case DoubleType _:
case PDoubleType _:
if (_o is int i)
{
if (_o is int i) {
return
(double) i; // I know, it seems absurd having to write this as it is nearly the same as the return beneath, but it _is_ needed
}
@ -145,48 +151,35 @@ namespace AspectedRouting.Language.Functions
return _o;
}
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
{
addTo.Add(this);
}
public IExpression Specialize(IEnumerable<Type> allowedTypesEnumerable)
{
var allowedTypes = allowedTypesEnumerable.ToList();
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
if (unified == null) {
return null;
}
var newO = _o;
if (_o is IExpression e)
{
if (_o is IExpression e) {
newO = e.Specialize(allowedTypes);
}
if (_o is IEnumerable<IExpression> es)
{
if (_o is IEnumerable<IExpression> es) {
var innerTypes = new List<Type>();
foreach (var allowedType in allowedTypes)
{
if (allowedType is ListType listType)
{
foreach (var allowedType in allowedTypes) {
if (allowedType is ListType listType) {
innerTypes.Add(listType.InnerType);
}
}
var specializedExpressions = new List<IExpression>();
foreach (var expr in es)
{
if (expr == null)
{
foreach (var expr in es) {
if (expr == null) {
throw new NullReferenceException("Subexpression is null");
}
var specialized = expr.Specialize(innerTypes);
if (specialized == null)
{
if (specialized == null) {
// If a subexpression can not be specialized, this list cannot be specialized
return null;
}
@ -202,26 +195,22 @@ namespace AspectedRouting.Language.Functions
public IExpression Optimize()
{
if (_o is IEnumerable<IExpression> exprs)
{
if (_o is IEnumerable<IExpression> exprs) {
// This is a list
var optExprs = new List<IExpression>();
foreach (var expression in exprs)
{
foreach (var expression in exprs) {
var exprOpt = expression.Optimize();
if (exprOpt == null || exprOpt.Types.Count() == 0)
{
if (exprOpt == null || exprOpt.Types.Count() == 0) {
throw new ArgumentException("Non-optimizable expression:" + expression);
}
optExprs.Add(exprOpt);
}
return new Constant(optExprs.ToList());
return new Constant(optExprs);
}
if (_o is IExpression expr)
{
if (_o is IExpression expr) {
// This is a list
return new Constant(expr.Types, expr.Optimize());
}
@ -229,22 +218,14 @@ namespace AspectedRouting.Language.Functions
return this;
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public void Visit(Func<IExpression, bool> f)
{
if (_o is IExpression e)
{
if (_o is IExpression e) {
e.Visit(f);
}
if (_o is IEnumerable<IExpression> es)
{
foreach (var x in es)
{
if (_o is IEnumerable<IExpression> es) {
foreach (var x in es) {
x.Visit(f);
}
}
@ -252,6 +233,16 @@ namespace AspectedRouting.Language.Functions
f(this);
}
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
{
addTo.Add(this);
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public override string ToString()
{
return _o.Pretty();
@ -262,23 +253,19 @@ namespace AspectedRouting.Language.Functions
{
public static string Pretty(this object o, Context context = null)
{
switch (o)
{
switch (o) {
case Dictionary<string, string> d:
var txt = "";
foreach (var (k, v) in d)
{
foreach (var (k, v) in d) {
txt += $"{k}={v};";
}
return $"{{{txt}}}";
case Dictionary<string, List<string>> d:
var t = "";
foreach (var (k, v) in d)
{
foreach (var (k, v) in d) {
var values = v.Pretty();
if (!v.Any())
{
if (!v.Any()) {
values = "*";
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
@ -45,6 +46,16 @@ namespace AspectedRouting.Language.Functions
return new Parameter(unified, ParamName);
}
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
{
var passedTypes = this.Types.Where(allowedTypes);
if (!passedTypes.Any()) {
return null;
}
return new Parameter(passedTypes, this.ParamName);
}
public IExpression Optimize()
{
return this;

View file

@ -25,6 +25,8 @@ namespace AspectedRouting.Language
/// </summary>
IExpression Specialize(IEnumerable<Type> allowedTypes);
IExpression PruneTypes(System.Func<Type, bool> allowedTypes);
/// <summary>
/// Optimize a single expression, eventually recursively (e.g. a list can optimize all the contents)
/// </summary>
@ -33,7 +35,6 @@ namespace AspectedRouting.Language
/// <summary>
/// Optimize with the given argument, e.g. listdot can become a list of applied arguments.
///
/// By default, this should return 'this.Apply(argument)'
/// </summary>
/// <param name="argument"></param>
@ -46,20 +47,22 @@ namespace AspectedRouting.Language
{
public static object Run(this IExpression e, Context c, Dictionary<string, string> tags)
{
try
{
return e.Apply(new[] {new Constant(tags)}).Evaluate(c);
try {
var result = e.Apply(new Constant(tags)).Evaluate(c);
while (result is IExpression ex) {
result = ex.Apply(new Constant(tags)).Evaluate(c);
}
catch (Exception err)
{
return result;
}
catch (Exception err) {
throw new Exception($"While evaluating the expression {e} with arguments a list of tags", err);
}
}
public static IExpression Specialize(this IExpression e, Type t)
{
if (t == null)
{
if (t == null) {
throw new NullReferenceException("Cannot specialize to null");
}
@ -70,19 +73,16 @@ namespace AspectedRouting.Language
{
var newTypes = new HashSet<Type>();
foreach (var oldType in e.Types)
{
foreach (var oldType in e.Types) {
var newType = oldType.Substitute(substitutions);
if (newType == null)
{
if (newType == null) {
continue;
}
newTypes.Add(newType);
}
if (!newTypes.Any())
{
if (!newTypes.Any()) {
return null;
}
@ -96,10 +96,9 @@ namespace AspectedRouting.Language
return types;
}
/// <summary>
/// Runs over all expresions, determines a common ground by unifications
/// THen specializes every expression onto this common ground
/// Runs over all expressions, determines a common ground by unifications
/// Then specializes every expression onto this common ground
/// </summary>
/// <returns>The common ground of types</returns>
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
@ -107,41 +106,31 @@ namespace AspectedRouting.Language
out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
{
specializedTypes = null;
var exprsForTypes = exprs.SortWidestToSmallest();
exprsForTypes.Reverse();
var allExpressions = new HashSet<IExpression>();
specializedExpressions = allExpressions;
foreach (var f in exprsForTypes)
{
if (specializedTypes == null)
{
specializedTypes = f.Types.ToHashSet();
continue;
foreach (var expr in exprs) {
if (specializedTypes == null) {
specializedTypes = expr.Types;
}
else {
var newlySpecialized = Typs.WidestCommonTypes(specializedTypes, expr.Types);
if (!newlySpecialized.Any()) {
throw new ArgumentException("Could not find a common ground for types "+specializedTypes.Pretty()+ " and "+expr.Types.Pretty());
}
var specialized = f.Types.RenameVars(specializedTypes).SpecializeTo(specializedTypes, false);
// ReSharper disable once JoinNullCheckWithUsage
if (specialized == null)
{
throw new ArgumentException("Could not unify\n "
+ "<previous items>: " + string.Join(", ", specializedTypes) +
"\nwith\n "
+ f.Optimize() + ": " + string.Join(", ", f.Types));
specializedTypes = newlySpecialized;
}
specializedTypes = specialized;
}
var tps = specializedTypes;
var optExprs = new List<IExpression>();
foreach (var expr in exprs)
{
var exprOpt = expr.Specialize(tps);
optExprs.Add(exprOpt);
foreach (var expr in exprs) {
var e = expr.Specialize(specializedTypes);
allExpressions.Add(e);
}
return specializedExpressions = optExprs;
return specializedExpressions = allExpressions;
}
}
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Functions;
namespace AspectedRouting.Language.Typ
{
@ -72,7 +71,13 @@ namespace AspectedRouting.Language.Typ
return SelectSmallestUnion(subbed, t1);
}
private static Type SelectSmallestUnion(this Type wider, Type smaller, bool reverse = false)
/// <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:
@ -80,12 +85,12 @@ namespace AspectedRouting.Language.Typ
case ListType l when smaller is ListType lsmaller:
return new ListType(
l.InnerType.SelectSmallestUnion(
l.InnerType.SelectSmallestUnion(lsmaller.InnerType, reverse)));
l.InnerType.SelectSmallestUnion(lsmaller.InnerType)));
case Curry cWider when smaller is Curry cSmaller:
var arg =
cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType, !reverse);
cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType);
var result =
cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType, reverse);
cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType);
return new Curry(arg, result);
default:
if (wider.IsSuperSet(smaller) && !smaller.IsSuperSet(wider)) {
@ -96,88 +101,6 @@ namespace AspectedRouting.Language.Typ
}
}
public static List<IExpression> SortWidestToSmallest(this IEnumerable<IExpression> expressions)
{
var all = expressions.ToHashSet();
var sorted = new List<IExpression>();
while (all.Any()) {
var widest = SelectWidestType(all);
if (widest == null) {
throw new ArgumentException("Can not sort widest to smallest");
}
all.Remove(widest);
sorted.Add(widest);
}
return sorted;
}
public static IExpression SelectSmallestType(IEnumerable<IExpression> expressions)
{
IExpression smallest = null;
foreach (var current in expressions) {
if (smallest == null) {
smallest = current;
continue;
}
if (smallest.Types.AllAreSuperset(current.Types)) {
smallest = current;
}
}
return smallest;
}
public static IExpression SelectWidestType(IEnumerable<IExpression> expressions)
{
IExpression widest = null;
foreach (var current in expressions) {
if (widest == null) {
widest = current;
continue;
}
if (current.Types.AllAreSuperset(widest.Types)) {
widest = current;
}
}
return widest;
}
public static bool AllAreSuperset(this IEnumerable<Type> shouldBeSuper, IEnumerable<Type> shouldBeSmaller)
{
foreach (var super in shouldBeSuper) {
foreach (var smaller in shouldBeSmaller) {
if (!super.IsSuperSet(smaller)) {
return false;
}
}
}
return true;
}
/// <summary>
/// Tries to unify t0 with all t1's.
/// Every match is returned
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
/// <returns></returns>
public static IEnumerable<Type> UnifyAny(this Type t0, IEnumerable<Type> t1)
{
var result = t1.Select(t => t0.Unify(t)).Where(unification => unification != null).ToHashSet();
if (!result.Any()) {
return null;
}
return result;
}
/// <summary>
/// Tries to unify t0 with all t1's.
/// Every match is returned, but only if every unification could be performed
@ -325,6 +248,7 @@ namespace AspectedRouting.Language.Typ
// This means that $a is substituted by e.g. ($a -> $x), implying an infinite and contradictory type
return null;
}
substitutionsOn0[key] = newVal;
appliedTransitivity = true;
}
@ -426,5 +350,197 @@ namespace AspectedRouting.Language.Typ
args.Add(t);
return args;
}
/// <summary>
/// If given two sets of types, select all the WidestCommonType-Combinations possible
/// e.g.:
/// { Double, Tags -> PDouble} x {PDouble, a -> Double} will result in:
/// { Double, Tags -> Double}
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
/// <returns></returns>
public static IEnumerable<Type> WidestCommonTypes(IEnumerable<Type> t0, IEnumerable<Type> t1)
{
var widest = new HashSet<Type>();
foreach (var type0 in t0) {
foreach (var type1 in t1) {
var t = WidestCommonType(type0, type1);
if (t != null) {
var (type, subsTable) = t.Value;
if (subsTable != null) {
type = type.Substitute(subsTable);
}
widest.Add(type);
}
}
}
return widest;
}
/// <summary>
/// Tries to find the widest type between the two given types, without assuming which one is wider.
/// This is used to find the Union of types, e.g. in a list of expressions
/// e.g.:
/// WidestCommonType(Double, PDouble) == Double
/// WidestCommonType(a -> Double, Double) == null
/// WidestCommonType(a -> Double, Tags -> PDouble) => Tags -> Double
/// WidestCommonType(Double -> a, PDouble -> b) => PDouble -> a
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
/// <returns></returns>
public static (Type, Dictionary<string, Type>? substitutionTable)? WidestCommonType(Type t0, Type t1)
{
// First things first: we try to unify
if (t0 is Curry c0 && t1 is Curry c1) {
var arg = SmallestCommonType(c0.ArgType, c1.ArgType);
var result = WidestCommonType(c0.ResultType, c1.ResultType);
if (arg == null) {
return null;
}
var (argT, subs0) = arg.Value;
if (result == null) {
return null;
}
var (resultT, subs1) = result.Value;
return (new Curry(argT, resultT), MergeDicts(subs0, subs1));
}
if (t0 is Var v) {
if (t1 is Var vx) {
if (v.Name == vx.Name) {
return (t0, null);
}
}
return (t1, new Dictionary<string, Type> {{v.Name, t1}});
}
if (t1 is Var v1) {
return (t0, new Dictionary<string, Type> {{v1.Name, t1}});
}
if (t0 == t1 || t0.Equals(t1)) {
return (t0, null);
}
var t0IsSuperT1 = t0.IsSuperSet(t1);
var t1IsSuperT0 = t1.IsSuperSet(t0);
if (t0IsSuperT1 && !t1IsSuperT0) {
return (t0, null);
}
if (t1IsSuperT0 && !t0IsSuperT1) {
return (t1, null);
}
return null;
}
private static Dictionary<string, Type> MergeDicts(Dictionary<string, Type> subs0,
Dictionary<string, Type> subs1)
{
if (subs0 == null && subs1 == null) {
return null;
}
var subsTable = new Dictionary<string, Type>();
void AddSubs(Dictionary<string, Type> dict)
{
if (dict == null) {
return;
}
foreach (var kv in dict) {
if (subsTable.TryGetValue(kv.Key, out var t)) {
// We have seen this variable-type-name before... We should check if it matches
if (t.Equals(kv.Value)) {
// Ok! No problem!
// It is already added anyway, so we continue
continue;
}
// Bruh, we have a problem!!!
throw new Exception(t + " != " + kv.Value);
}
if (kv.Value is Var v) {
if (v.Name == kv.Key) {
// Well, this is a useless substitution...
continue;
}
}
subsTable[kv.Key] = kv.Value;
}
}
AddSubs(subs0);
AddSubs(subs1);
if (!subsTable.Any()) {
return null;
}
return subsTable;
}
/// <summary>
/// Tries to find the smallest type between the two given types, without assuming which one is wider.
/// This is used to find the Union of types, e.g. in a list of expressions
/// e.g.:
/// WidestCommonType(Double, PDouble) == PDouble
/// WidestCommonType(a -> Double, Double) == null
/// WidestCommonType(a -> Double, Tags -> PDouble) => Tags -> PDouble
/// WidestCommonType(Double -> a, PDouble -> b) => Double -> a
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
/// <returns></returns>
public static (Type, Dictionary<string, Type> substitutionTable)? SmallestCommonType(Type t0, Type t1)
{
if (t0 is Curry c0 && t1 is Curry c1) {
var arg = WidestCommonType(c0.ArgType, c1.ArgType);
var result = SmallestCommonType(c0.ResultType, c1.ResultType);
if (arg == null) {
return null;
}
var (argT, subs0) = arg.Value;
if (result == null) {
return null;
}
var (resultT, subs1) = result.Value;
return (new Curry(argT, resultT), MergeDicts(subs0, subs1));
}
if (t0 is Var v) {
return (t1, new Dictionary<string, Type> {{v.Name, t1}});
}
if (t1 is Var v1) {
return (t0, new Dictionary<string, Type> {{v1.Name, t1}});
}
if (t0 == t1 || t0.Equals(t1)) {
return (t0, null);
}
var t0IsSuperT1 = t0.IsSuperSet(t1);
var t1IsSuperT0 = t1.IsSuperSet(t0);
if (t0IsSuperT1 && !t1IsSuperT0) {
return (t0, null);
}
if (t1IsSuperT0 && !t0IsSuperT1) {
return (t1, null);
}
return null;
}
}
}