diff --git a/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs b/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs index cfc4b51940..a0c71c7980 100644 --- a/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs +++ b/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs @@ -135,9 +135,25 @@ public ParametrizedCode AssignParameters(Func parameterAssigner, out bool allIsDefault) { - var pp = FindAssignment(parameterAssigner, optional: false, allIsDefault: out allIsDefault); + allIsDefault = true; var codes = new(CodeParameterAssignment parameter, string code)[parameters!.Length]; for (int i = 0; i < parameters.Length; i++) { - codes[i] = (pp[i], pp[i].Code!.ToString(parameterAssigner, out bool allIsDefault_local)); + var assignment = parameterAssigner(parameters[i].Parameter); + if (assignment.Code == null) + { + assignment = parameters[i].DefaultAssignment; + if (assignment.Code == null) + throw new InvalidOperationException($"Assignment of parameter '{parameters[i].Parameter}' was not found."); + } + else + allIsDefault = false; + + codes[i] = (assignment, assignment.Code!.ToString(parameterAssigner, out bool allIsDefault_local)); allIsDefault &= allIsDefault_local; } return codes; } - private CodeParameterAssignment[] FindAssignment(Func parameterAssigner, bool optional, out bool allIsDefault) + private (CodeParameterAssignment[]? assigned, ParametrizedCode?[]? newDefaults) FindAssignment(Func parameterAssigner) { - allIsDefault = true; - var pp = new CodeParameterAssignment[parameters!.Length]; + if (parameters is null) + return (null, null); + + // these are different variables, as we have to preserve the tree-like structure of the ParametrizedCodes, + // when we assign parameters in the default values. + // newDefaults -> we will change the code in the parameters[i].DefaultAssignment + // assigned -> this parameter will be removed and its assignment inlined into stringParts[i] and parameters[i] + CodeParameterAssignment[]? assigned = null; // when null, all are default + ParametrizedCode[]? newDefaults = null; // when null, no defaults were changed for (int i = 0; i < parameters.Length; i++) { - if ((pp[i] = parameterAssigner(parameters[i].Parameter)).Code == null) + var p = parameterAssigner(parameters[i].Parameter); + if (p.Code is not null) + { + assigned ??= new CodeParameterAssignment[parameters.Length]; + assigned[i] = p; + } + else if (parameters[i].DefaultAssignment is { Code: { HasParameters: true } } defaultAssignment) { - if (!optional) + // check if the default assignment contains any of the assigned parameters, and adjust the default if necessary + var newDefault = defaultAssignment.Code.AssignParameters(parameterAssigner); + if (newDefault != defaultAssignment.Code) { - pp[i] = parameters[i].DefaultAssignment; - if (pp[i].Code == null) - throw new InvalidOperationException($"Assignment of parameter '{parameters[i].Parameter}' was not found."); + newDefaults ??= new ParametrizedCode[parameters.Length]; + newDefaults[i] = newDefault; } } - else - allIsDefault = false; } - return pp; + return (assigned, newDefaults); } public IEnumerable EnumerateAllParameters() diff --git a/src/Framework/Framework/Controls/KnockoutHelper.cs b/src/Framework/Framework/Controls/KnockoutHelper.cs index 6dc13aa580..c07a016826 100644 --- a/src/Framework/Framework/Controls/KnockoutHelper.cs +++ b/src/Framework/Framework/Controls/KnockoutHelper.cs @@ -355,8 +355,8 @@ private static JsExpression TransformOptionValueToExpression(DotvvmBindableObjec { case IValueBinding binding: { var adjustedCode = binding.GetParametrizedKnockoutExpression(handler, unwrapped: true).AssignParameters(o => - o == JavascriptTranslator.KnockoutContextParameter ? new ParametrizedCode("c") : - o == JavascriptTranslator.KnockoutViewModelParameter ? new ParametrizedCode("d") : + o == JavascriptTranslator.KnockoutContextParameter ? CodeParameterAssignment.FromIdentifier("c") : + o == JavascriptTranslator.KnockoutViewModelParameter ? CodeParameterAssignment.FromIdentifier("d") : default(CodeParameterAssignment) ); return new JsSymbolicParameter(new CodeSymbolicParameter("tmp symbol", defaultAssignment: adjustedCode)); diff --git a/src/Samples/Common/ViewModels/FeatureSamples/PostBack/PostBackHandlerBindingViewModel.cs b/src/Samples/Common/ViewModels/FeatureSamples/PostBack/PostBackHandlerBindingViewModel.cs new file mode 100644 index 0000000000..d0ad214be0 --- /dev/null +++ b/src/Samples/Common/ViewModels/FeatureSamples/PostBack/PostBackHandlerBindingViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.ViewModel; +using DotVVM.Framework.Hosting; + +namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.PostBack +{ + public class PostBackHandlerBindingViewModel : DotvvmViewModelBase + { + public bool Enabled { get; set; } = false; + + public int Counter { get; set; } = 0; + + public string[] Items { get; set; } = new string[] { "Item 1", "Item 2", "Item 3" }; + } +} + diff --git a/src/Samples/Common/Views/FeatureSamples/PostBack/PostBackHandlerBinding.dothtml b/src/Samples/Common/Views/FeatureSamples/PostBack/PostBackHandlerBinding.dothtml new file mode 100644 index 0000000000..a381148d91 --- /dev/null +++ b/src/Samples/Common/Views/FeatureSamples/PostBack/PostBackHandlerBinding.dothtml @@ -0,0 +1,35 @@ +@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.PostBack.PostBackHandlerBindingViewModel, DotVVM.Samples.Common + + + + + + + + + + +

+ +

+

Counter: {{value: Counter}}

+ +

Click on grid rows to increment the counter.

+ + + + + + + + + + + + + + + + + + diff --git a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs index 7549c68a19..fb434046a7 100644 --- a/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs +++ b/src/Samples/Tests/Abstractions/SamplesRouteUrls.designer.cs @@ -311,6 +311,7 @@ public partial class SamplesRouteUrls public const string FeatureSamples_ParameterBinding_OptionalParameterBinding = "FeatureSamples/ParameterBinding/OptionalParameterBinding"; public const string FeatureSamples_ParameterBinding_ParameterBinding = "FeatureSamples/ParameterBinding/ParameterBinding"; public const string FeatureSamples_PostBack_ConfirmPostBackHandler = "FeatureSamples/PostBack/ConfirmPostBackHandler"; + public const string FeatureSamples_PostBack_PostBackHandlerBinding = "FeatureSamples/PostBack/PostBackHandlerBinding"; public const string FeatureSamples_PostBack_PostBackHandlerCommandTypes = "FeatureSamples/PostBack/PostBackHandlerCommandTypes"; public const string FeatureSamples_PostBack_PostbackUpdate = "FeatureSamples/PostBack/PostbackUpdate"; public const string FeatureSamples_PostBack_PostbackUpdateRepeater = "FeatureSamples/PostBack/PostbackUpdateRepeater"; diff --git a/src/Samples/Tests/Tests/Feature/PostBackTests.cs b/src/Samples/Tests/Tests/Feature/PostBackTests.cs index 86ac1bc458..87e2304874 100644 --- a/src/Samples/Tests/Tests/Feature/PostBackTests.cs +++ b/src/Samples/Tests/Tests/Feature/PostBackTests.cs @@ -87,6 +87,24 @@ public void Feature_PostBack_PostBackHandlers_Localization() }); } + [Fact] + public void Feature_PostBack_PostBackHandlerBinding() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_PostBack_PostBackHandlerBinding); + + var counter = browser.Single(".result"); + AssertUI.TextEquals(counter, "0"); + + browser.ElementAt("td", 0).Click(); + AssertUI.TextEquals(counter, "1"); + + browser.Single("input[type=checkbox]").Click(); + browser.ElementAt("td", 0).Click(); + AssertUI.TextEquals(counter, "1"); + }); + } + private void ValidatePostbackHandlersComplexSection(string sectionSelector, IBrowserWrapper browser) { IElementWrapper section = null; diff --git a/src/Tests/Binding/BindingCompilationTests.cs b/src/Tests/Binding/BindingCompilationTests.cs index fdd0d6d360..1c7f2c6c8c 100755 --- a/src/Tests/Binding/BindingCompilationTests.cs +++ b/src/Tests/Binding/BindingCompilationTests.cs @@ -1493,7 +1493,7 @@ public override string ToString() return SomeString + ": " + MyProperty; } } - class TestViewModel3 : DotvvmViewModelBase + public class TestViewModel3 : DotvvmViewModelBase { public string SomeString { get; set; } } diff --git a/src/Tests/Binding/JavascriptCompilationTests.cs b/src/Tests/Binding/JavascriptCompilationTests.cs index 163022b480..c43ce56e2b 100644 --- a/src/Tests/Binding/JavascriptCompilationTests.cs +++ b/src/Tests/Binding/JavascriptCompilationTests.cs @@ -1434,6 +1434,40 @@ public void JavascriptCompilation_CustomPrimitiveParse() Assert.AreEqual("VehicleNumber()==\"123\"", result); } + [TestMethod] + public void JavascriptCompilation_ParametrizedCode_ParentContexts() + { + // root: TestViewModel + // parent: TestViewModel2 + _index + _collection + // this: TestViewModel3 + _index + var context0 = DataContextStack.Create(typeof(TestViewModel)); + var context1 = DataContextStack.Create(typeof(TestViewModel2), parent: context0, extensionParameters: [ new CurrentCollectionIndexExtensionParameter(), new BindingCollectionInfoExtensionParameter("_collection") ]); + var context2 = DataContextStack.Create(typeof(TestViewModel3), parent: context1, extensionParameters: [ new CurrentCollectionIndexExtensionParameter() ]); + + var result = bindingHelper.ValueBindingToParametrizedCode("_root.IntProp + _parent._index + _parent._collection.Index + _parent.MyProperty + _index + SomeString.Length", context2); + + Assert.AreEqual("$parents[1].IntProp() + $parentContext.$index() + $parentContext.$index() + $parent.MyProperty() + $index() + SomeString().length", result.ToDefaultString()); + + // assign `context` and `vm` variables + var formatted2 = result.ToString(o => + o == JavascriptTranslator.KnockoutContextParameter ? new ParametrizedCode("context", OperatorPrecedence.Max) : + o == JavascriptTranslator.KnockoutViewModelParameter ? new ParametrizedCode("vm", OperatorPrecedence.Max) : + default(CodeParameterAssignment) + ); + Console.WriteLine(formatted2); + + var symbolicParameters = result.Parameters.ToArray(); + Assert.AreEqual(6, symbolicParameters.Length); + Assert.AreEqual(2, XAssert.IsAssignableFrom(symbolicParameters[0].Parameter).ParentIndex); + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[1].Parameter); // JavascriptTranslator.ParentKnockoutContextParameter would also work + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[2].Parameter); // JavascriptTranslator.ParentKnockoutContextParameter would also work + Assert.AreEqual(JavascriptTranslator.ParentKnockoutViewModelParameter, symbolicParameters[3].Parameter); + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[4].Parameter); + Assert.AreEqual(JavascriptTranslator.KnockoutViewModelParameter, symbolicParameters[5].Parameter); + + Assert.AreEqual("context.$parents[1].IntProp() + context.$parentContext.$index() + context.$parentContext.$index() + context.$parent.MyProperty() + context.$index() + vm.SomeString().length", formatted2); + } + public class TestMarkupControl: DotvvmMarkupControl { public string SomeProperty diff --git a/src/Tests/ControlTests/PostbackHandlerTests.cs b/src/Tests/ControlTests/PostbackHandlerTests.cs new file mode 100644 index 0000000000..aa4a5ca76b --- /dev/null +++ b/src/Tests/ControlTests/PostbackHandlerTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Threading.Tasks; +using CheckTestOutput; +using DotVVM.Framework.Compilation; +using DotVVM.Framework.Controls; +using DotVVM.Framework.Tests.Binding; +using DotVVM.Framework.ViewModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DotVVM.Framework.Testing; +using System.Security.Claims; +using System.Collections; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Compilation.ControlTree; +using DotVVM.Framework.Hosting; + +namespace DotVVM.Framework.Tests.ControlTests +{ + [TestClass] + public class PostbackHandlerTests + { + static readonly ControlTestHelper cth = new ControlTestHelper(config: config => { + }); + readonly OutputChecker check = new OutputChecker("testoutputs"); + + + [TestMethod] + public async Task ButtonHandlers() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), """ + + + + 100} /> + + + + + + + + + + + """ + ); + + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + + public class BasicTestViewModel: DotvvmViewModelBase + { + public int Integer { get; set; } = 123; + public bool Boolean { get; set; } = false; + public string String { get; set; } = "some-string"; + + public TestViewModel3 Nested { get; set; } = new TestViewModel3 { SomeString = "a" }; + } + } +} diff --git a/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html b/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html new file mode 100644 index 0000000000..b2a163c90e --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html index d2f57d3194..bc0ae66127 100644 --- a/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html +++ b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html @@ -6,7 +6,7 @@ Two handlers Two handlers, value binding message - + Two handlers, resource binding message Two handlers, default message diff --git a/src/Tests/Runtime/JavascriptCompilation/JsFormatterTests.cs b/src/Tests/Runtime/JavascriptCompilation/JsFormatterTests.cs index 840f1e74e0..b32e12dc3c 100644 --- a/src/Tests/Runtime/JavascriptCompilation/JsFormatterTests.cs +++ b/src/Tests/Runtime/JavascriptCompilation/JsFormatterTests.cs @@ -1,9 +1,7 @@ using DotVVM.Framework.Compilation.Javascript.Ast; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using System.Collections.Generic; using System.Text; -using DotVVM.Framework.Compilation.Javascript; using static DotVVM.Framework.Tests.Runtime.JavascriptCompilation.JsParensInsertionTests; namespace DotVVM.Framework.Tests.Runtime.JavascriptCompilation @@ -47,17 +45,6 @@ public void JsFormatter_Indexer() AssertFormatting("a[b]", new JsIdentifierExpression("a").Indexer(new JsIdentifierExpression("b"))); } - [TestMethod] - public void JsFormatter_SymbolicParameter_Global() - { - var symbol = new CodeSymbolicParameter(); - Assert.AreEqual("a+global", - new JsBinaryExpression(new JsMemberAccessExpression(new JsSymbolicParameter(symbol), "a"), BinaryOperatorType.Plus, - new JsSymbolicParameter(symbol)) - .FormatParametrizedScript().ToString(o => o == symbol ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("global"), isGlobalContext: true) : - throw new Exception())); - } - [TestMethod] public void JsFormatter_FunctionExpression() { diff --git a/src/Tests/Runtime/JavascriptCompilation/JsParametrizedCodeTests.cs b/src/Tests/Runtime/JavascriptCompilation/JsParametrizedCodeTests.cs new file mode 100644 index 0000000000..89df492ea9 --- /dev/null +++ b/src/Tests/Runtime/JavascriptCompilation/JsParametrizedCodeTests.cs @@ -0,0 +1,127 @@ +using DotVVM.Framework.Compilation.Javascript.Ast; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using DotVVM.Framework.Compilation.Javascript; + +namespace DotVVM.Framework.Tests.Runtime.JavascriptCompilation +{ + [TestClass] + public class JsParametrizedCodeTests + { + static CodeSymbolicParameter symbolA = new CodeSymbolicParameter("A"); + static CodeSymbolicParameter symbolB = new CodeSymbolicParameter("B"); + static CodeSymbolicParameter symbolC = new CodeSymbolicParameter("C", defaultAssignment: new JsIdentifier("defaultValue").FormatParametrizedScript()); + static CodeSymbolicParameter symbolD = new CodeSymbolicParameter("D", defaultAssignment: new JsBinaryExpression(new JsSymbolicParameter(symbolA), BinaryOperatorType.Times, new JsSymbolicParameter(symbolC)).FormatParametrizedScript()); + static CodeSymbolicParameter symbolE = new CodeSymbolicParameter("E", defaultAssignment: new JsBinaryExpression(new JsSymbolicParameter(symbolA), BinaryOperatorType.Plus, new JsSymbolicParameter(symbolD)).FormatParametrizedScript()); + + [TestMethod] + public void SymbolicParameters_Global() + { + // full would be "global.a+global + var pcode = new JsBinaryExpression(new JsMemberAccessExpression(new JsSymbolicParameter(symbolA), "a"), BinaryOperatorType.Plus, + new JsSymbolicParameter(symbolA)) + .FormatParametrizedScript(); + Assert.AreEqual("a+global", + pcode.ToString(o => o == symbolA ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("global"), isGlobalContext: true) : + throw new Exception())); + Assert.AreEqual("a+global", + pcode.AssignParameters(o => o == symbolA ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("global"), isGlobalContext: true) : + throw new Exception()) + .ToString(o => default)); + } + + [TestMethod] + public void SymbolicParameters_Global_ThroughDefault() + { + var globalDefaultSymbol = new CodeSymbolicParameter("global", defaultAssignment: symbolA.ToParametrizedCode()); + // may be "global.a+global" or "a+global" - doesn't matter if the optimization works in this case, but it shouldn't be broken + var pcode = new JsBinaryExpression(new JsMemberAccessExpression(new JsSymbolicParameter(globalDefaultSymbol), "a"), BinaryOperatorType.Plus, + new JsSymbolicParameter(globalDefaultSymbol)) + .FormatParametrizedScript(); + Assert.AreEqual("global.a+global", + pcode.ToString(o => o == symbolA ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("global"), isGlobalContext: true) : + default)); + Assert.AreEqual("global.a+global", + pcode.AssignParameters(o => o == symbolA ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("global"), isGlobalContext: true) : + default) + .ToString(o => default)); + } + + [TestMethod] + public void SymbolicParameters_Throws_Unassigned() + { + var pcode = new JsBinaryExpression(new JsMemberAccessExpression(new JsSymbolicParameter(symbolA), "a"), BinaryOperatorType.Plus, new JsSymbolicParameter(symbolA)).FormatParametrizedScript(); + + var pcode2 = pcode.AssignParameters(o => default); // fine, not all have to be assigned + var pcodeResolved = pcode.AssignParameters(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default); + + Assert.AreEqual("y.a+y", pcode.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("y") : default)); + Assert.AreEqual("z.a+z", pcode2.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("z") : default)); + Assert.AreEqual("x.a+x", pcodeResolved.ToString(o => default)); + Assert.AreEqual("x.a+x", pcodeResolved.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("AREADY_ASSIGNED_BEFORE") : default)); + + var ex = Assert.ThrowsException(() => pcode.ToString(o => default)); + XAssert.Contains("Assignment of parameter '{A|", ex.Message); + + ex = Assert.ThrowsException(() => pcode2.ToString(o => default)); + XAssert.Contains("Assignment of parameter '{A|", ex.Message); + } + + [TestMethod] + public void SymbolicParameters_Partial_DefaultChain() + { + var expr = new JsBinaryExpression(symbolA.ToExpression(), BinaryOperatorType.Plus, symbolE.ToExpression()); + var pcode = expr.FormatParametrizedScript(); + + Assert.AreEqual("x+(x+x*defaultValue)", pcode.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default)); + Assert.AreEqual("y+(y+y*defaultValue)", pcode.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("y") : default)); + + // substitute symbolD for identifier, order doesn't matter + Assert.AreEqual("x+(x+D)", + pcode.AssignParameters(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default) + .ToString(o => o == symbolD ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("D")) : default)); + Assert.AreEqual("x+(x+D)", + pcode.AssignParameters(o => o == symbolD ? CodeParameterAssignment.FromExpression(new JsIdentifierExpression("D")) : default) + .ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default)); + + // substitute symbolD for symbolA, order now matters + Assert.AreEqual("x+(x+x)", + pcode.AssignParameters(o => o == symbolD ? CodeParameterAssignment.FromExpression(new JsSymbolicParameter(symbolA)) : default) + .ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default)); + Assert.AreEqual("x+(x+y)", + pcode.AssignParameters(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default) + .AssignParameters(o => o == symbolD ? CodeParameterAssignment.FromExpression(new JsSymbolicParameter(symbolA)) : default) + .ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("y") : default)); + } + + [TestMethod] + public void SymbolicParameters_AddDefault() + { + var expr = new JsBinaryExpression(symbolA.ToExpression(), BinaryOperatorType.Plus, symbolE.ToExpression()); + var pcode = expr.FormatParametrizedScript(); + + // add default for symbolA + var assigned = pcode.AssignParameters(o => o == symbolA ? symbolA.ToExpression(CodeParameterAssignment.FromIdentifier("a")).FormatParametrizedScript() : default); + + Assert.AreEqual("a+(a+a*defaultValue)", assigned.ToString(o => default)); + Assert.AreEqual("a+(a+a*defaultValue)", assigned.ToDefaultString()); + + Assert.AreEqual("x+(x+x*defaultValue)", assigned.ToString(o => o == symbolA ? CodeParameterAssignment.FromIdentifier("x") : default)); + Assert.AreEqual("a+x", assigned.ToString(o => o == symbolE ? CodeParameterAssignment.FromIdentifier("x") : default)); + } + +#if DotNetCore + [TestMethod] + public void SymbolicParameters_NoAssignmentNoAllocation() + { + var expr = new JsBinaryExpression(symbolA.ToExpression(), BinaryOperatorType.Plus, symbolE.ToExpression()); + var pcode = expr.FormatParametrizedScript(); + Func noAssignment = o => default; + + var b = GC.GetAllocatedBytesForCurrentThread(); + pcode.AssignParameters(noAssignment); + Assert.AreEqual(0, GC.GetAllocatedBytesForCurrentThread() - b); + } +#endif + } +}