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(); }