From 06f82c486348e356c6f1b7a75ba570ded0b16d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Tue, 17 Oct 2023 21:38:32 +0200 Subject: [PATCH 1/3] js translations: support Linq methods on ImmutableArray ImmutableArray is a struct so it has custom Linq method overloads to avoid allocations. These are preferred by the compiler over IEnumerable, so our Linq support was broken. Also added .AsEnumerable and few other conversion methods to help users work around similar issues. --- .../JavascriptTranslatableMethodCollection.cs | 62 ++++++++++++++----- .../Binding/JavascriptCompilationTests.cs | 41 +++++++++--- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs index ad843b4789..21dabf13e4 100644 --- a/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs +++ b/src/Framework/Framework/Compilation/Javascript/JavascriptTranslatableMethodCollection.cs @@ -175,6 +175,7 @@ public void AddDefaultMethodTranslators() AddMethodTranslator(() => default(ICollection)!.Count, lengthMethod); AddMethodTranslator(() => default(ICollection)!.Count, lengthMethod); AddMethodTranslator(() => default(IReadOnlyCollection)!.Count, lengthMethod); + AddMethodTranslator(() => default(ImmutableArray)!.Length, lengthMethod); AddMethodTranslator(() => "".Length, lengthMethod); AddMethodTranslator(() => Enums.GetNames(), new EnumGetNamesMethodTranslator()); var identityTranslator = new GenericMethodCompiler(a => a[1]); @@ -538,9 +539,15 @@ bool IsDelegateReturnTypeEnum(Type type) string GetDelegateReturnTypeHash(Type type) => type.GetGenericArguments().Last().GetTypeHash(); - AddMethodTranslator(() => Enumerable.All(Enumerable.Empty(), _ => false), new GenericMethodCompiler(args => args[1].Member("every").Invoke(args[2]))); - AddMethodTranslator(() => Enumerable.Any(Enumerable.Empty()), new GenericMethodCompiler(args => args[1].Member("some").Invoke(returnTrueFunc.Clone()))); - AddMethodTranslator(() => Enumerable.Any(Enumerable.Empty(), _ => false), new GenericMethodCompiler(args => args[1].Member("some").Invoke(args[2]))); + var all = new GenericMethodCompiler(args => args[1].Member("every").Invoke(args[2])); + AddMethodTranslator(() => Enumerable.All(Enumerable.Empty(), _ => false), all); + AddMethodTranslator(() => ImmutableArrayExtensions.All(default(ImmutableArray), _ => false), all); + var any = new GenericMethodCompiler(args => args[1].Member("length").Binary(BinaryOperatorType.Greater, new JsLiteral(0))); + AddMethodTranslator(() => Enumerable.Any(Enumerable.Empty()), any); + AddMethodTranslator(() => ImmutableArrayExtensions.Any(default(ImmutableArray)), any); + var anyPred = new GenericMethodCompiler(args => args[1].Member("some").Invoke(args[2])); + AddMethodTranslator(() => Enumerable.Any(Enumerable.Empty(), _ => false), anyPred); + AddMethodTranslator(() => ImmutableArrayExtensions.Any(default(ImmutableArray), _ => false), anyPred); AddMethodTranslator(() => Enumerable.Concat(Enumerable.Empty(), Enumerable.Empty()), new GenericMethodCompiler(args => args[1].Member("concat").Invoke(args[2]))); AddMethodTranslator(() => Enumerable.Count(Enumerable.Empty()), new GenericMethodCompiler(args => args[1].Member("length"))); AddMethodTranslator(() => Enumerable.Empty().Distinct(), new GenericMethodCompiler(args => new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("distinct").Invoke(args[1]), @@ -548,17 +555,27 @@ string GetDelegateReturnTypeHash(Type type) AddMethodTranslator(() => Enumerable.Empty().ElementAt(0), new GenericMethodCompiler((args, method) => BuildIndexer(args[1], args[2], method))); + AddMethodTranslator(() => Enumerable.Empty().ElementAtOrDefault(0), + new GenericMethodCompiler((args, method) => BuildIndexer(args[1], args[2], method))); + AddMethodTranslator(() => ImmutableArrayExtensions.ElementAt(default(ImmutableArray), 0), + new GenericMethodCompiler((args, method) => BuildIndexer(args[1], args[2], method))); + AddMethodTranslator(() => ImmutableArrayExtensions.ElementAtOrDefault(default(ImmutableArray), 0), + new GenericMethodCompiler((args, method) => BuildIndexer(args[1], args[2], method))); - AddMethodTranslator(() => Enumerable.Empty().FirstOrDefault(), new GenericMethodCompiler((args, m) => - args[1].Indexer(0) - .WithAnnotation(new VMPropertyInfoAnnotation(m.ReturnType)).WithAnnotation(MayBeNullAnnotation.Instance))); - AddMethodTranslator(() => Enumerable.Empty().FirstOrDefault(_ => true), new GenericMethodCompiler(args => - new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("firstOrDefault").Invoke(args[1], args[2]).WithAnnotation(MayBeNullAnnotation.Instance))); - - AddMethodTranslator(() => Enumerable.Empty().LastOrDefault(), new GenericMethodCompiler(args => - new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("lastOrDefault").Invoke(args[1], returnTrueFunc.Clone()).WithAnnotation(MayBeNullAnnotation.Instance))); - AddMethodTranslator(() => Enumerable.Empty().LastOrDefault(_ => false), new GenericMethodCompiler(args => - new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("lastOrDefault").Invoke(args[1], args[2]).WithAnnotation(MayBeNullAnnotation.Instance))); + AddMethodTranslator(() => Enumerable.Empty().FirstOrDefault(), new GenericMethodCompiler((args, m) => BuildIndexer(args[1], new JsLiteral(0), m))); + AddMethodTranslator(() => ImmutableArrayExtensions.FirstOrDefault(default(ImmutableArray)), new GenericMethodCompiler((args, m) => BuildIndexer(args[1], new JsLiteral(0), m))); + var firstOrDefaultPred = new GenericMethodCompiler(args => + new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("firstOrDefault").Invoke(args[1], args[2]).WithAnnotation(MayBeNullAnnotation.Instance)); + AddMethodTranslator(() => Enumerable.Empty().FirstOrDefault(_ => true), firstOrDefaultPred); + AddMethodTranslator(() => ImmutableArrayExtensions.FirstOrDefault(default(ImmutableArray), _ => true), firstOrDefaultPred); + + var lastOrDefault = new GenericMethodCompiler(args => args[1].Member("at").Invoke(new JsLiteral(-1)).WithAnnotation(MayBeNullAnnotation.Instance)); + AddMethodTranslator(() => Enumerable.Empty().LastOrDefault(), lastOrDefault); + AddMethodTranslator(() => ImmutableArrayExtensions.LastOrDefault(default(ImmutableArray)), lastOrDefault); + var lastOrDefaultPred = new GenericMethodCompiler(args => + new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("lastOrDefault").Invoke(args[1], args[2]).WithAnnotation(MayBeNullAnnotation.Instance)); + AddMethodTranslator(() => Enumerable.Empty().LastOrDefault(_ => false), lastOrDefaultPred); + AddMethodTranslator(() => ImmutableArrayExtensions.LastOrDefault(default(ImmutableArray), _ => false), lastOrDefaultPred); AddMethodTranslator(() => Enumerable.Empty().OrderBy(_ => Generic.Enum.Something), new GenericMethodCompiler((jArgs, dArgs) => new JsIdentifierExpression("dotvvm").Member("translations").Member("array").Member("orderBy") .Invoke(jArgs[1], jArgs[2], new JsLiteral((IsDelegateReturnTypeEnum(dArgs.Last().Type)) ? GetDelegateReturnTypeHash(dArgs.Last().Type) : null)), @@ -567,17 +584,30 @@ string GetDelegateReturnTypeHash(Type type) .Invoke(jArgs[1], jArgs[2], new JsLiteral((IsDelegateReturnTypeEnum(dArgs.Last().Type)) ? GetDelegateReturnTypeHash(dArgs.Last().Type) : null)), check: (method, _, arguments) => EnsureIsComparableInJavascript(method, arguments.Last().Type.GetGenericArguments().Last()))); - AddMethodTranslator(() => Enumerable.Empty().Select(_ => Generic.Enum.Something), - translator: new GenericMethodCompiler(args => args[1].Member("map").Invoke(args[2]))); + var select = new GenericMethodCompiler(args => args[1].Member("map").Invoke(args[2])); + AddMethodTranslator(() => Enumerable.Empty().Select(_ => Generic.Enum.Something), select); + AddMethodTranslator(() => ImmutableArrayExtensions.Select(default(ImmutableArray), _ => Generic.Enum.Something), select); AddMethodTranslator(() => Enumerable.Empty().Skip(0), new GenericMethodCompiler(args => args[1].Member("slice").Invoke(args[2]))); AddMethodTranslator(() => Enumerable.Empty().Take(0), new GenericMethodCompiler(args => args[1].Member("slice").Invoke(new JsLiteral(0), args[2]))); + + var where = new GenericMethodCompiler(args => args[1].Member("filter").Invoke(args[2])); + AddMethodTranslator(() => Enumerable.Empty().Where(_ => true), where); + AddMethodTranslator(() => ImmutableArrayExtensions.Where(default(ImmutableArray), _ => true), where); + AddMethodTranslator(() => Enumerable.Empty().ToArray(), new GenericMethodCompiler(args => args[1])); AddMethodTranslator(() => Enumerable.Empty().ToList(), new GenericMethodCompiler(args => args[1])); + AddMethodTranslator(() => Enumerable.Empty().ToHashSet(), new GenericMethodCompiler(args => args[1])); + AddMethodTranslator(() => Enumerable.AsEnumerable(Enumerable.Empty()), new GenericMethodCompiler(args => args[1])); + + AddMethodTranslator(() => ImmutableArray.ToImmutableArray(Enumerable.Empty()), new GenericMethodCompiler(args => args[1])); + AddMethodTranslator(() => ImmutableList.ToImmutableList(Enumerable.Empty()), new GenericMethodCompiler(args => args[1])); + AddMethodTranslator(() => ImmutableArrayExtensions.ToArray(ImmutableArray.Empty), new GenericMethodCompiler(args => args[1])); - AddMethodTranslator(() => Enumerable.Empty().Where(_ => true), new GenericMethodCompiler(args => args[1].Member("filter").Invoke(args[2]))); + AddMethodTranslator(() => Enumerable.Empty(), new GenericMethodCompiler(args => new JsArrayExpression())); + AddMethodTranslator(() => Array.Empty(), new GenericMethodCompiler(args => new JsArrayExpression())); AddDefaultNumericEnumerableTranslations(); } diff --git a/src/Tests/Binding/JavascriptCompilationTests.cs b/src/Tests/Binding/JavascriptCompilationTests.cs index f86d57076a..0afcb96536 100644 --- a/src/Tests/Binding/JavascriptCompilationTests.cs +++ b/src/Tests/Binding/JavascriptCompilationTests.cs @@ -562,9 +562,10 @@ public void JsTranslator_DictionaryRemove() [TestMethod] [DataRow("Enumerable.Where(LongArray, (long item) => item % 2 == 0)", DisplayName = "Regular call of Enumerable.Where")] [DataRow("LongArray.Where((long item) => item % 2 == 0)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().Where((long item) => item % 2 == 0)", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableWhere(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray().filter((item)=>ko.unwrap(item)%2==0)", result); } @@ -579,18 +580,21 @@ public void JsTranslator_NestedEnumerableMethods() [TestMethod] [DataRow("Enumerable.Select(LongArray, (long item) => -item)", DisplayName = "Regular call of Enumerable.Select")] [DataRow("LongArray.Select((long item) => -item)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().Select((long item) => -item)", DisplayName = "Immutable array - extension method")] + [DataRow("LongArray.ToImmutableList().Select((long item) => -item)", DisplayName = "Immutable list - extension method")] public void JsTranslator_EnumerableSelect(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray().map((item)=>-ko.unwrap(item))", result); } [TestMethod] [DataRow("Enumerable.Concat(LongArray, LongArray)", DisplayName = "Regular call of Enumerable.Concat")] [DataRow("LongArray.Concat(LongArray)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().Concat(LongArray.ToImmutableArray())", DisplayName = "Immutable arrays")] public void JsTranslator_EnumerableConcat(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray().concat(LongArray())", result); } @@ -636,18 +640,20 @@ public void JsTranslator_ListAddRange() [TestMethod] [DataRow("Enumerable.All(LongArray, (long item) => item > 0)", DisplayName = "Regular call of Enumerable.All")] [DataRow("LongArray.All((long item) => item > 0)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().All((long item) => item > 0)", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableAll(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray().every((item)=>ko.unwrap(item)>0)", result); } [TestMethod] [DataRow("Enumerable.Any(LongArray, (long item) => item > 0)", DisplayName = "Regular call of Enumerable.Any")] [DataRow("LongArray.Any((long item) => item > 0)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().Any((long item) => item > 0)", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableAny(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray().some((item)=>ko.unwrap(item)>0)", result); } @@ -665,21 +671,35 @@ public void JsTranslator_ICollectionClear() Assert.AreEqual("dotvvm.translations.array.clear(TestViewModel2().Collection)", result); } + [TestMethod] + [DataRow("Enumerable.Empty()")] + [DataRow("Array.Empty()")] + [DataRow("ImmutableArray.Empty")] + [DataRow("ImmutableList.Empty")] + public void JsTranslator_EnumerableEmpty(string binding) + { + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); + Assert.AreEqual("[]", result); + } + + [TestMethod] [DataRow("Enumerable.FirstOrDefault(LongArray)", DisplayName = "Regular call of Enumerable.FirstOrDefault")] [DataRow("LongArray.FirstOrDefault()", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().FirstOrDefault()", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableFirstOrDefault(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("LongArray()[0]", result); } [TestMethod] [DataRow("Enumerable.FirstOrDefault(LongArray, (long item) => item > 0)", DisplayName = "Regular call of Enumerable.FirstOrDefault")] [DataRow("LongArray.FirstOrDefault((long item) => item > 0)", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().FirstOrDefault((long item) => item > 0)", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableFirstOrDefaultParametrized(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("dotvvm.translations.array.firstOrDefault(LongArray(),(item)=>ko.unwrap(item)>0)", result); } @@ -718,10 +738,11 @@ public void JsTranslator_ICollectionContains() [TestMethod] [DataRow("Enumerable.LastOrDefault(LongArray)", DisplayName = "Regular call of Enumerable.LastOrDefault")] [DataRow("LongArray.LastOrDefault()", DisplayName = "Syntax sugar - extension method")] + [DataRow("LongArray.ToImmutableArray().LastOrDefault()", DisplayName = "Immutable array - extension method")] public void JsTranslator_EnumerableLastOrDefault(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); - Assert.AreEqual("dotvvm.translations.array.lastOrDefault(LongArray(),()=>true)", result); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); + Assert.AreEqual("LongArray().at(-1)", result); } [TestMethod] @@ -729,7 +750,7 @@ public void JsTranslator_EnumerableLastOrDefault(string binding) [DataRow("LongArray.LastOrDefault((long item) => item > 0)", DisplayName = "Syntax sugar - extension method")] public void JsTranslator_EnumerableLastOrDefaultParametrized(string binding) { - var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq") }, new[] { typeof(TestViewModel) }); + var result = CompileBinding(binding, new[] { new NamespaceImport("System.Linq"), new NamespaceImport("System.Collections.Immutable") }, new[] { typeof(TestViewModel) }); Assert.AreEqual("dotvvm.translations.array.lastOrDefault(LongArray(),(item)=>ko.unwrap(item)>0)", result); } From d231809f15cf894af1e3abfc9f6d39584e0d5ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Tue, 17 Oct 2023 21:41:12 +0200 Subject: [PATCH 2/3] CompilationPage: add title to truncated table columns --- .../Diagnostics/CompilationPage.dothtml | 26 ++++++++++++++----- .../Diagnostics/CompilationPageViewModel.cs | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Framework/Framework/Diagnostics/CompilationPage.dothtml b/src/Framework/Framework/Diagnostics/CompilationPage.dothtml index 6f545a8557..9f2e195383 100644 --- a/src/Framework/Framework/Diagnostics/CompilationPage.dothtml +++ b/src/Framework/Framework/Diagnostics/CompilationPage.dothtml @@ -1,4 +1,4 @@ -@viewModel DotVVM.Framework.Diagnostics.CompilationPageViewModel +@viewModel DotVVM.Framework.Diagnostics.CompilationPageViewModel @@ -52,15 +52,19 @@ - {{value: Url}} + {{value: Url == "" ? "" : Url}} - + {{value: Url}} + HeaderText="Virtual Path"> + + + + - + + + + + - + + + + + + CssClass="fit status"/> diff --git a/src/Framework/Framework/Diagnostics/CompilationPageViewModel.cs b/src/Framework/Framework/Diagnostics/CompilationPageViewModel.cs index cfe6c388ec..c6007f36fc 100644 --- a/src/Framework/Framework/Diagnostics/CompilationPageViewModel.cs +++ b/src/Framework/Framework/Diagnostics/CompilationPageViewModel.cs @@ -15,6 +15,7 @@ public class CompilationPageViewModel : DotvvmViewModelBase public ImmutableArray MasterPages => viewCompilationService.GetMasterPages(); public ImmutableArray Controls => viewCompilationService.GetControls(); public int ActiveTab { get; set; } = 0; + public string PathBase => Context.TranslateVirtualPath("~/"); public CompilationPageViewModel(IDotvvmViewCompilationService viewCompilationService) { From b5ca60582a6772655c225e14e663a3752a69670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Tue, 17 Oct 2023 21:41:46 +0200 Subject: [PATCH 3/3] CompilationPage: add Errors tab --- .../Diagnostics/CompilationPage.dothtml | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/Framework/Framework/Diagnostics/CompilationPage.dothtml b/src/Framework/Framework/Diagnostics/CompilationPage.dothtml index 9f2e195383..6270e1b1a7 100644 --- a/src/Framework/Framework/Diagnostics/CompilationPage.dothtml +++ b/src/Framework/Framework/Diagnostics/CompilationPage.dothtml @@ -1,4 +1,4 @@ -@viewModel DotVVM.Framework.Diagnostics.CompilationPageViewModel +@viewModel DotVVM.Framework.Diagnostics.CompilationPageViewModel @@ -34,10 +34,14 @@ Text="Master pages" class="nav" Class-active="{value: ActiveTab == 2}" /> +
-
+

Routes

@@ -80,7 +84,7 @@
-
+

Controls

@@ -111,7 +115,7 @@
-
+

Master pages

@@ -139,6 +143,67 @@
+
+

Errors

+ +

m.Status == 'None') || Controls.AsEnumerable().Any(m => m.Status == 'None') || Routes.AsEnumerable().Any(m => m.Status == 'None')} + style="color: var(--error-dark-color)"> + Some files have not been compiled yet. Please press the "Compile all" button to make the list of errors complete. +

+ + + + + + + + + r.Status == 'CompilationFailed')} WrapperTagName=tbody > + + + + + + + + + r.Status == 'CompilationFailed')} WrapperTagName=tbody > + + + + + + + + + r.Status == 'CompilationFailed')} WrapperTagName=tbody > + + + + + + + + + + +
TypeNameFile pathStatusActions
Route {RouteName}"}> + + {{value: RouteName}} + + + {{value: RouteName}} + + {{value: VirtualPath}}{{value: Status}} + +
Control + {{value: $"{TagPrefix}:{TagName}"}} + {{value: VirtualPath}}{{value: Status}} + +
Master page{{value: VirtualPath}}{{value: Status}} + +
+