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];