From 90b9c6e8da8bafe4390e3d22877d3ce866c35706 Mon Sep 17 00:00:00 2001 From: Marc Sallin Date: Sun, 6 Sep 2026 21:51:47 +0200 Subject: [PATCH] perf: stop allocating a warnings array for a clean compile Every document allocated a string array even when the compiler reported no warnings, which is the common case. Array.Empty covers it without allocating. Array.AsReadOnly already returns the shared empty collection for a zero-length array, so the wrapper it is passed to costs nothing either way. --- src/typstsharp.tests/Tests.cs | 20 ++++++++++++++++++++ src/typstsharp/TypstDocument.cs | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/typstsharp.tests/Tests.cs b/src/typstsharp.tests/Tests.cs index 4a5b967..1bf8aa6 100644 --- a/src/typstsharp.tests/Tests.cs +++ b/src/typstsharp.tests/Tests.cs @@ -910,6 +910,26 @@ public async Task WarningsFromADocumentCannotBeMutatedByCallers() await Assert.That(document.Warnings is string[]).IsFalse(); } + /// + /// The warning-free case had no coverage. This pins the public contract rather than the + /// allocation, so it holds whichever way the empty list is produced: empty, and not a mutable + /// array handed out to callers. + /// + [Test] + public async Task WarningsFromACleanCompileAreEmptyAndStillImmutable() + { + using var compiler = TypstCompiler.FromSource("= No warnings here"); + var document = compiler.CompileToDocument(); + + await Assert.That(document.Warnings.Count).IsEqualTo(0); + await Assert.That(document.Warnings is string[]).IsFalse(); + + document.Dispose(); + + // Warnings are copied out of native memory eagerly, so they outlive the document. + await Assert.That(document.Warnings.Count).IsEqualTo(0); + } + private const string TwoPageSource = """ First page #pagebreak() diff --git a/src/typstsharp/TypstDocument.cs b/src/typstsharp/TypstDocument.cs index 5813764..73326ce 100644 --- a/src/typstsharp/TypstDocument.cs +++ b/src/typstsharp/TypstDocument.cs @@ -53,7 +53,8 @@ internal unsafe TypstDocument(CsBindgen.CompileResult native) } // Warnings are small and are copied eagerly so that they stay usable after disposal. - var warnings = new string[warningCount]; + // A clean compile is the common case, and Array.Empty spares it the only allocation here. + var warnings = warningCount == 0 ? Array.Empty() : new string[warningCount]; for (int i = 0; i < warnings.Length; i++) { var warning = native.warnings[i];