From 39984202c0201e3502cd08830ad544c6793ce492 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 4 Oct 2025 18:14:59 +0100 Subject: [PATCH 1/6] Add expression scripting tests Includes regression tests to track backwards compatibility in preparation for the update to modern Dynamic LINQ libraries. --- Bonsai.Scripting.Expressions.sln | 6 ++ .../AssemblyInfo.cs | 1 + .../Bonsai.Scripting.Expressions.Tests.csproj | 21 +++++ .../ExpressionScriptingTests.cs | 80 +++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/Bonsai.Scripting.Expressions.Tests/AssemblyInfo.cs create mode 100644 src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj create mode 100644 src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs diff --git a/Bonsai.Scripting.Expressions.sln b/Bonsai.Scripting.Expressions.sln index 56bb096..e157fec 100644 --- a/Bonsai.Scripting.Expressions.sln +++ b/Bonsai.Scripting.Expressions.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.Scripting.Expression EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.Scripting.Expressions.Design", "src\Bonsai.Scripting.Expressions.Design\Bonsai.Scripting.Expressions.Design.csproj", "{A6ADFFAA-CA25-545C-AC3F-4BC3D819B8CB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.Scripting.Expressions.Tests", "src\Bonsai.Scripting.Expressions.Tests\Bonsai.Scripting.Expressions.Tests.csproj", "{CE4BD71F-2CCD-4ED5-8F4E-EAC123EEF518}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{DEE5DD87-39C1-BF34-B639-A387DCCF972B}" ProjectSection(SolutionItems) = preProject build\Common.csproj.props = build\Common.csproj.props @@ -31,6 +33,10 @@ Global {A6ADFFAA-CA25-545C-AC3F-4BC3D819B8CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6ADFFAA-CA25-545C-AC3F-4BC3D819B8CB}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6ADFFAA-CA25-545C-AC3F-4BC3D819B8CB}.Release|Any CPU.Build.0 = Release|Any CPU + {CE4BD71F-2CCD-4ED5-8F4E-EAC123EEF518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE4BD71F-2CCD-4ED5-8F4E-EAC123EEF518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE4BD71F-2CCD-4ED5-8F4E-EAC123EEF518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE4BD71F-2CCD-4ED5-8F4E-EAC123EEF518}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Bonsai.Scripting.Expressions.Tests/AssemblyInfo.cs b/src/Bonsai.Scripting.Expressions.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..aaf278c --- /dev/null +++ b/src/Bonsai.Scripting.Expressions.Tests/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj b/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj new file mode 100644 index 0000000..fda7ace --- /dev/null +++ b/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj @@ -0,0 +1,21 @@ + + + + net472 + + + + + + + + + + + + + + + + + diff --git a/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs b/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs new file mode 100644 index 0000000..696087f --- /dev/null +++ b/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs @@ -0,0 +1,80 @@ +using Bonsai.Expressions; +using System; +using System.Collections; +using System.Reactive.Linq; +using System.Threading.Tasks; + +namespace Bonsai.Scripting.Expressions.Tests +{ + [TestClass] + public sealed class ExpressionScriptingTests + { + async Task AssertExpressionTransform(string expression, TSource value, TResult expected) + { + var workflowBuilder = new WorkflowBuilder(); + var source = workflowBuilder.Workflow.Add(new CombinatorBuilder { Combinator = new Return(value) }); + var transform = workflowBuilder.Workflow.Add(new ExpressionTransform { Expression = expression }); + var output = workflowBuilder.Workflow.Add(new WorkflowOutputBuilder()); + workflowBuilder.Workflow.AddEdge(source, transform, new()); + workflowBuilder.Workflow.AddEdge(transform, output, new()); + var result = await workflowBuilder.Workflow.BuildObservable(); + if (expected is ICollection expectedCollection && result is ICollection resultCollection) + CollectionAssert.AreEqual(expectedCollection, resultCollection); + else + Assert.AreEqual(expected, result); + } + + [DataTestMethod] + [DataRow("it", 42, 42)] + [DataRow("it * 2", 21, 42)] + [DataRow("Single(it)", 42, 42f)] + [DataRow("Math.PI", 42, Math.PI)] + [DataRow("Convert.ToInt16(it)", 42, (short)42)] + [DataRow("new(it as Data).Data", 42, 42)] + public Task TestExpressionTransform(string expression, TSource value, TResult expected) + { + return AssertExpressionTransform(expression, value, expected); + } + + [TestMethod] + public Task TestNullExpression() => AssertExpressionTransform("null", 0, (object)null); + + [TestMethod] + public Task TestNullString() => AssertExpressionTransform("string(null)", 0, (string)null); + + [TestMethod] + public Task TestObjectExpression() => AssertExpressionTransform("object(it)", 42, (object)42); + + [DataTestMethod] + [DataRow("single(it)", 42, 42f)] + [DataRow("int64?(it).hasvalue", 42, true)] + [DataRow("math.pi", 42, Math.PI)] + [DataRow("boolean.truestring", 42, "True")] + [DataRow("convert.toint16(it)", 42, (short)42)] + [DataRow("datetime.minvalue.second", 42, 0)] + [DataRow("datetimeoffset.minvalue.second", 42, 0)] + [DataRow("guid.empty.tobytearray()[0]", 42, 0)] + [DataRow("timespan.tickspermillisecond", 0, TimeSpan.TicksPerMillisecond)] + [DataRow("it > 0 ? convert.toint16(it) : int16.minvalue", 42, (short)42)] + public Task TestCasingCompatibility(string expression, TSource value, TResult expected) + { + return AssertExpressionTransform(expression, value, expected); + } + + [DataTestMethod] + [DataRow("")] + [DataRow("string(it)")] + public Task TestInvalidExpression(string expression) + { + return Assert.ThrowsExactlyAsync(() => + AssertExpressionTransform(expression, 42, (object)null)); + } + + class Return(TValue value) : Source + { + public TValue Value { get; } = value; + + public override IObservable Generate() => Observable.Return(Value); + } + } +} From 21f6ccb862e9e7c223c307582543e8afafdc4f7c Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 16 Sep 2025 11:58:02 +0100 Subject: [PATCH 2/6] Update backend to System.Linq.Dynamic.Core System.Linq.Dynamic has been deprecated and also does not target modern .NET so we decided to update to the modern Dynamic LINQ package, which includes improved language syntax and is easier to extend with code analysis and auto-completion features. --- .../Bonsai.Scripting.Expressions.csproj | 2 +- .../ExpressionCondition.cs | 4 +- .../ExpressionSink.cs | 4 +- .../ExpressionTransform.cs | 4 +- .../ParsingConfigHelper.cs | 40 +++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs diff --git a/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj b/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj index bae39d8..488e7b6 100644 --- a/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj +++ b/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs b/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs index b876282..5e396b1 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Reactive.Linq; using System.Reflection; @@ -52,7 +53,8 @@ public override Expression Build(IEnumerable arguments) { var source = arguments.First(); var sourceType = source.Type.GetGenericArguments()[0]; - var predicate = System.Linq.Dynamic.DynamicExpression.ParseLambda(sourceType, typeof(bool), Expression); + var config = ParsingConfigHelper.CreateParsingConfig(sourceType); + var predicate = DynamicExpressionParser.ParseLambda(config, sourceType, typeof(bool), Expression); return System.Linq.Expressions.Expression.Call(whereMethod.MakeGenericMethod(sourceType), source, predicate); } diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSink.cs b/src/Bonsai.Scripting.Expressions/ExpressionSink.cs index 133f97f..434deeb 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionSink.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionSink.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Reactive.Linq; using System.Reflection; @@ -59,7 +60,8 @@ public override Expression Build(IEnumerable arguments) var sourceType = source.Type.GetGenericArguments()[0]; var actionType = System.Linq.Expressions.Expression.GetActionType(sourceType); var itParameter = new[] { System.Linq.Expressions.Expression.Parameter(sourceType, string.Empty) }; - var onNext = System.Linq.Dynamic.DynamicExpression.ParseLambda(actionType, itParameter, null, Expression); + var config = ParsingConfigHelper.CreateParsingConfig(sourceType); + var onNext = DynamicExpressionParser.ParseLambda(actionType, config, itParameter, null, Expression); return System.Linq.Expressions.Expression.Call(doMethod.MakeGenericMethod(sourceType), source, onNext); } else return source; diff --git a/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs b/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs index b839455..d93c7c0 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Reactive.Linq; using System.Reflection; @@ -53,7 +54,8 @@ public override Expression Build(IEnumerable arguments) { var source = arguments.First(); var sourceType = source.Type.GetGenericArguments()[0]; - var selector = System.Linq.Dynamic.DynamicExpression.ParseLambda(sourceType, null, Expression); + var config = ParsingConfigHelper.CreateParsingConfig(sourceType); + var selector = DynamicExpressionParser.ParseLambda(config, sourceType, null, Expression); return System.Linq.Expressions.Expression.Call(selectMethod.MakeGenericMethod(sourceType, selector.ReturnType), source, selector); } diff --git a/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs b/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs new file mode 100644 index 0000000..4a1fcf1 --- /dev/null +++ b/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Linq.Dynamic.Core.CustomTypeProviders; + +namespace Bonsai.Scripting.Expressions +{ + internal static class ParsingConfigHelper + { + public static ParsingConfig CreateParsingConfig(params Type[] additionalTypes) + { + var config = new ParsingConfig(); + config.CustomTypeProvider = CreateCustomTypeProvider(config, additionalTypes); + return config; + } + + static IDynamicLinqCustomTypeProvider CreateCustomTypeProvider(ParsingConfig config, params Type[] additionalTypes) + { + return new DefaultDynamicLinqCustomTypeProvider( + config, + additionalTypes.SelectMany(EnumerateTypeHierarchy).ToList()); + } + + static IEnumerable EnumerateTypeHierarchy(Type type) + { + var interfaces = type.GetInterfaces(); + for (int i = 0; i < interfaces.Length; i++) + { + yield return interfaces[i]; + } + + while (type is not null) + { + yield return type; + type = type.BaseType; + } + } + } +} From 0218e7a2329b528e4535ad420ade3ccc74d8fbe8 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 4 Oct 2025 18:11:38 +0100 Subject: [PATCH 3/6] Add fallback support for legacy keywords Earlier versions of the scripting package supported case-invariant predefined type names. To avoid breaking changes we introduce here an exception fallback path which scans the expression for these known type identifiers and replaces them with the corresponding built-in type keyword. --- .../CompatibilityAnalyzer.cs | 69 +++++++++++++++++++ .../DynamicExpressionHelper.cs | 35 ++++++++++ .../ExpressionCondition.cs | 2 +- .../ExpressionSink.cs | 2 +- .../ExpressionTransform.cs | 2 +- 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 src/Bonsai.Scripting.Expressions/CompatibilityAnalyzer.cs create mode 100644 src/Bonsai.Scripting.Expressions/DynamicExpressionHelper.cs diff --git a/src/Bonsai.Scripting.Expressions/CompatibilityAnalyzer.cs b/src/Bonsai.Scripting.Expressions/CompatibilityAnalyzer.cs new file mode 100644 index 0000000..c7b08e6 --- /dev/null +++ b/src/Bonsai.Scripting.Expressions/CompatibilityAnalyzer.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core; +using System.Linq.Dynamic.Core.Tokenizer; +using System.Text; + +namespace Bonsai.Scripting.Expressions +{ + internal static class CompatibilityAnalyzer + { + internal static readonly Dictionary LegacyKeywords = new() + { + { "boolean", "bool" }, + { "datetime", "DateTime" }, + { "datetimeoffset", "DateTimeOffset" }, + { "guid", "Guid" }, + { "int16", "short" }, + { "int32", "int" }, + { "int64", "long" }, + { "single", "float" }, + { "timespan", "TimeSpan" }, + { "uint32", "uint" }, + { "uint64", "ulong" }, + { "uint16", "ushort" }, + { "math", "Math" }, + { "convert", "Convert" } + }; + + public static bool ReplaceLegacyKeywords(ParsingConfig? parsingConfig, string text, out string result) + { + result = text; + if (string.IsNullOrEmpty(text)) + return false; + + + List<(Token, string)> replacements = null; + var previousTokenId = TokenId.Unknown; + var textParser = new TextParser(parsingConfig, text); + while (textParser.CurrentToken.Id != TokenId.End) + { + if (textParser.CurrentToken.Id == TokenId.Identifier && + previousTokenId != TokenId.Dot && + LegacyKeywords.TryGetValue(textParser.CurrentToken.Text, out var keyword)) + { + replacements ??= new(); + replacements.Add((textParser.CurrentToken, keyword)); + } + + previousTokenId = textParser.CurrentToken.Id; + textParser.NextToken(); + } + + if (replacements?.Count > 0) + { + var sb = new StringBuilder(text); + for (int i = 0; i < replacements.Count; i++) + { + var (token, keyword) = replacements[i]; + sb.Remove(token.Pos, token.Text.Length); + sb.Insert(token.Pos, keyword); + } + + result = sb.ToString(); + return true; + } + + return false; + } + } +} diff --git a/src/Bonsai.Scripting.Expressions/DynamicExpressionHelper.cs b/src/Bonsai.Scripting.Expressions/DynamicExpressionHelper.cs new file mode 100644 index 0000000..a5cdaa4 --- /dev/null +++ b/src/Bonsai.Scripting.Expressions/DynamicExpressionHelper.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq.Dynamic.Core; +using System.Linq.Dynamic.Core.Exceptions; +using System.Linq.Expressions; + +namespace Bonsai.Scripting.Expressions +{ + internal class DynamicExpressionHelper + { + public static LambdaExpression ParseLambda(Type delegateType, ParsingConfig? parsingConfig, ParameterExpression[] parameters, Type? resultType, string expression, params object?[] values) + { + return ParseLambda(delegateType, parsingConfig, true, parameters, resultType, expression, values); + } + + public static LambdaExpression ParseLambda(ParsingConfig? parsingConfig, Type itType, Type? resultType, string expression, params object?[] values) + { + return ParseLambda(null, parsingConfig, true, new[] { Expression.Parameter(itType, "it") }, resultType, expression, values); + } + + public static LambdaExpression ParseLambda(Type? delegateType, ParsingConfig? parsingConfig, bool createParameterCtor, ParameterExpression[] parameters, Type? resultType, string expression, params object?[] values) + { + try + { + return DynamicExpressionParser.ParseLambda(delegateType, parsingConfig, createParameterCtor, parameters, resultType, expression, values); + } + catch (ParseException) + { + if (!CompatibilityAnalyzer.ReplaceLegacyKeywords(parsingConfig, expression, out expression)) + throw; + + return DynamicExpressionParser.ParseLambda(delegateType, parsingConfig, createParameterCtor, parameters, resultType, expression, values); + } + } + } +} diff --git a/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs b/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs index 5e396b1..9195b40 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionCondition.cs @@ -54,7 +54,7 @@ public override Expression Build(IEnumerable arguments) var source = arguments.First(); var sourceType = source.Type.GetGenericArguments()[0]; var config = ParsingConfigHelper.CreateParsingConfig(sourceType); - var predicate = DynamicExpressionParser.ParseLambda(config, sourceType, typeof(bool), Expression); + var predicate = DynamicExpressionHelper.ParseLambda(config, sourceType, typeof(bool), Expression); return System.Linq.Expressions.Expression.Call(whereMethod.MakeGenericMethod(sourceType), source, predicate); } diff --git a/src/Bonsai.Scripting.Expressions/ExpressionSink.cs b/src/Bonsai.Scripting.Expressions/ExpressionSink.cs index 434deeb..559765d 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionSink.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionSink.cs @@ -61,7 +61,7 @@ public override Expression Build(IEnumerable arguments) var actionType = System.Linq.Expressions.Expression.GetActionType(sourceType); var itParameter = new[] { System.Linq.Expressions.Expression.Parameter(sourceType, string.Empty) }; var config = ParsingConfigHelper.CreateParsingConfig(sourceType); - var onNext = DynamicExpressionParser.ParseLambda(actionType, config, itParameter, null, Expression); + var onNext = DynamicExpressionHelper.ParseLambda(actionType, config, itParameter, null, Expression); return System.Linq.Expressions.Expression.Call(doMethod.MakeGenericMethod(sourceType), source, onNext); } else return source; diff --git a/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs b/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs index d93c7c0..00ab754 100644 --- a/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs +++ b/src/Bonsai.Scripting.Expressions/ExpressionTransform.cs @@ -55,7 +55,7 @@ public override Expression Build(IEnumerable arguments) var source = arguments.First(); var sourceType = source.Type.GetGenericArguments()[0]; var config = ParsingConfigHelper.CreateParsingConfig(sourceType); - var selector = DynamicExpressionParser.ParseLambda(config, sourceType, null, Expression); + var selector = DynamicExpressionHelper.ParseLambda(config, sourceType, null, Expression); return System.Linq.Expressions.Expression.Call(selectMethod.MakeGenericMethod(sourceType, selector.ReturnType), source, selector); } From 0de08c79eef80ce30959ce02f4df6b90c3f0c6f5 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 4 Oct 2025 18:15:58 +0100 Subject: [PATCH 4/6] Add tests for modern dynamic LINQ syntax --- .../ExpressionScriptingTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs b/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs index 696087f..f5c9c98 100644 --- a/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs +++ b/src/Bonsai.Scripting.Expressions.Tests/ExpressionScriptingTests.cs @@ -31,6 +31,13 @@ async Task AssertExpressionTransform(string expression, TSourc [DataRow("Math.PI", 42, Math.PI)] [DataRow("Convert.ToInt16(it)", 42, (short)42)] [DataRow("new(it as Data).Data", 42, 42)] + // modern Dynamic LINQ parser + [DataRow("float(it)", 42, 42f)] + [DataRow("long?(it).HasValue", 42, true)] + [DataRow("bool.TrueString", 42, "True")] + [DataRow("new[] { it }", 42, new[] { 42 })] + [DataRow("new[] { it }.Select(x => x * 2).ToArray()", 21, new[] { 42 })] + [DataRow("np(string(null).Length) ?? it", 42, 42)] public Task TestExpressionTransform(string expression, TSource value, TResult expected) { return AssertExpressionTransform(expression, value, expected); From 33d5b6c76d95263c6477ad0a68d317ff6e37fd37 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 3 Oct 2025 13:57:06 +0100 Subject: [PATCH 5/6] Avoid scanning all assemblies in AppDomain --- .../ParsingConfigHelper.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs b/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs index 4a1fcf1..0929d2e 100644 --- a/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs +++ b/src/Bonsai.Scripting.Expressions/ParsingConfigHelper.cs @@ -17,7 +17,7 @@ public static ParsingConfig CreateParsingConfig(params Type[] additionalTypes) static IDynamicLinqCustomTypeProvider CreateCustomTypeProvider(ParsingConfig config, params Type[] additionalTypes) { - return new DefaultDynamicLinqCustomTypeProvider( + return new SimpleDynamicLinqCustomTypeProvider( config, additionalTypes.SelectMany(EnumerateTypeHierarchy).ToList()); } @@ -36,5 +36,21 @@ static IEnumerable EnumerateTypeHierarchy(Type type) type = type.BaseType; } } + + class SimpleDynamicLinqCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider + { + readonly HashSet customTypes; + + public SimpleDynamicLinqCustomTypeProvider(ParsingConfig config, IList additionalTypes) + : base(config, additionalTypes, cacheCustomTypes: false) + { + customTypes = new(AdditionalTypes); + } + + public override HashSet GetCustomTypes() + { + return customTypes; + } + } } } From 2b28d4c249fa1dee0591cea715407e04815d2d39 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 4 Oct 2025 18:41:11 +0100 Subject: [PATCH 6/6] Add .NET core target frameworks --- .../Bonsai.Scripting.Expressions.Design.csproj | 2 +- .../Bonsai.Scripting.Expressions.Tests.csproj | 2 +- .../Bonsai.Scripting.Expressions.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj b/src/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj index 9984b85..06238b0 100644 --- a/src/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj +++ b/src/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj @@ -3,7 +3,7 @@ This package provides editors for expression scripting in the Bonsai programming language. $(PackageTags) Design true - net472 + net472;net8.0-windows diff --git a/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj b/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj index fda7ace..0e1a08c 100644 --- a/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj +++ b/src/Bonsai.Scripting.Expressions.Tests/Bonsai.Scripting.Expressions.Tests.csproj @@ -1,7 +1,7 @@  - net472 + net472;net8.0 diff --git a/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj b/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj index 488e7b6..8f09185 100644 --- a/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj +++ b/src/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj @@ -1,7 +1,7 @@  This package provides operators implementing expression scripting infrastructure. - net472 + net472;net8.0