From b7a2034bcfbc33e5621a83c6ecf9784d1e112e09 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 02:27:22 +0000 Subject: [PATCH] fix: double the braces in a Rust compile-time assertion message [patch] assert! reads its message as a format string, so a message such as "size {N} fits" was parsed as a placeholder naming N and the output failed with E0425, and "brace {} here" failed with a missing argument. The Rust generator now doubles { and } after escaping the message, so the text reaches the compiler's error unchanged. Fixes #166 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X8BPy6trRZiUxiwJcbUGzR --- .../Languages/RustGeneratedSourceCompilesTests.cs | 5 +++++ Coder.Test/Languages/RustGeneratorTests.cs | 12 ++++++++++++ Coder/Languages/RustGenerator.cs | 9 ++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs b/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs index ddc0fef..f9ee681 100644 --- a/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs +++ b/Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs @@ -303,6 +303,11 @@ private static SourceFile Exemplar() Condition = "std::mem::size_of::() == 4", Message = "an i32 is four bytes", }); + geometry.Members.Add(new CompileTimeAssertion + { + Condition = "std::mem::size_of::() == 1", + Message = "size {N} fits, brace {} here", + }); file.Members.Add(geometry); return file; diff --git a/Coder.Test/Languages/RustGeneratorTests.cs b/Coder.Test/Languages/RustGeneratorTests.cs index c7d2dc6..2acf980 100644 --- a/Coder.Test/Languages/RustGeneratorTests.cs +++ b/Coder.Test/Languages/RustGeneratorTests.cs @@ -692,6 +692,18 @@ public void CompileTimeAssertion_IsAnEvaluatedConstant() StringAssert.Contains(code, "\"a Point is two i32s\");", StringComparison.Ordinal); } + /// + /// Tests that braces in an assertion's message are doubled, because assert! reads its + /// message as a format string and a single brace would open a placeholder. + /// + [TestMethod] + public void CompileTimeAssertion_DoublesBracesInTheMessage() + { + CompileTimeAssertion assertion = new("true", "size {N} fits, brace {} here"); + + StringAssert.Contains(Generator.Generate(assertion), "\"size {{N}} fits, brace {{}} here\");", StringComparison.Ordinal); + } + /// /// Tests that an alias is a type alias. /// diff --git a/Coder/Languages/RustGenerator.cs b/Coder/Languages/RustGenerator.cs index b1fb54d..131cf7a 100644 --- a/Coder/Languages/RustGenerator.cs +++ b/Coder/Languages/RustGenerator.cs @@ -1383,6 +1383,10 @@ protected override void GenerateUsingAlias(UsingAlias usingAlias, CodeBlocker co /// const _: () = assert!(…), which is Rust's own compile-time assertion and needs no /// macro crate: a constant nobody names still has to be evaluated for the program to build, and /// a failed assert! in that position is a compile error naming the message. + /// + /// The message is assert!'s format string rather than a plain literal, so its braces are + /// doubled: a single {N} would be read as a placeholder naming a value that does not exist. + /// /// protected override void GenerateCompileTimeAssertion(CompileTimeAssertion assertion, CodeBlocker code) { @@ -1397,7 +1401,10 @@ protected override void GenerateCompileTimeAssertion(CompileTimeAssertion assert { code.WriteLine(","); code.Indent(); - code.Write($"\"{EscapeString(assertion.Message)}\""); + string message = EscapeString(assertion.Message) + .Replace("{", "{{", StringComparison.Ordinal) + .Replace("}", "}}", StringComparison.Ordinal); + code.Write($"\"{message}\""); code.Outdent(); }