From 238cc044c6b868049b0dc58129e6ba426a5fb8bd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 07:44:59 +0000 Subject: [PATCH] Write down a Rust field's starting value when no constructor sets it [patch] A field with an InitialValue in a type with no constructor was written as `name: type,` with nothing to say its declared value had gone. Go and C already write a "defaults to" note for the same case; Rust now does too. A type with a constructor needs no note, because the constructor builds a field it does not set from its declared value. Fixes #202 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LJsBos8vaJcor3uRqngfwE --- Coder.Test/Languages/RustGeneratorTests.cs | 41 +++++++++++++++++++++ Coder/Languages/RustGenerator.cs | 42 ++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Coder.Test/Languages/RustGeneratorTests.cs b/Coder.Test/Languages/RustGeneratorTests.cs index bd2c0f1..ad2d6b8 100644 --- a/Coder.Test/Languages/RustGeneratorTests.cs +++ b/Coder.Test/Languages/RustGeneratorTests.cs @@ -841,6 +841,47 @@ public void Construction_IsAStructLiteralACallOrAnArray() Assert.AreEqual("[ 1 ]", Generate(array)); } + /// + /// Tests that a field's declared starting value is written down when the type has no constructor + /// to apply it, rather than dropped without a word, as Go and C already do. + /// + [TestMethod] + public void FieldInitialValue_WithoutAConstructor_IsANote() + { + string generated = Generator.Generate(Counter(withConstructor: false)); + + StringAssert.Contains(generated, "// count defaults to 0: Rust has no field initialisers, and no constructor sets it", StringComparison.Ordinal); + StringAssert.Contains(generated, "// step defaults to 1: Rust has no field initialisers, and no constructor sets it", StringComparison.Ordinal); + } + + /// + /// Tests that a type with a constructor carries no such note, since the constructor builds a + /// field it does not set from its declared value. + /// + [TestMethod] + public void FieldInitialValue_WithAConstructor_IsNotANote() + => Assert.IsFalse(Generator.Generate(Counter(withConstructor: true)).Contains("defaults to", StringComparison.Ordinal)); + + /// + /// Builds the editor's sample type: two fields with declared starting values and one method. + /// + /// Whether the type also declares a constructor. + /// The declaration. + private static ClassDeclaration Counter(bool withConstructor) + { + ClassDeclaration counter = new("Counter"); + counter.Members.Add(new FieldDeclaration("count", new TypeReference("int")) { InitialValue = Literal.Number(0) }); + counter.Members.Add(new FieldDeclaration("step", new TypeReference("int")) { InitialValue = Literal.Number(1) }); + counter.Members.Add(new FunctionDeclaration("increment") { Visibility = Visibility.Public }); + + if (withConstructor) + { + counter.Members.Add(new FunctionDeclaration("Counter") { Kind = FunctionKind.Constructor, Visibility = Visibility.Public }); + } + + return counter; + } + /// /// Writes one expression on its own, for the assertions that are about the expression rather than /// about the declaration holding it. diff --git a/Coder/Languages/RustGenerator.cs b/Coder/Languages/RustGenerator.cs index 9a63018..2b415fa 100644 --- a/Coder/Languages/RustGenerator.cs +++ b/Coder/Languages/RustGenerator.cs @@ -616,6 +616,7 @@ private void GenerateStruct(ClassDeclaration classDecl, string name, CodeBlocker case FieldDeclaration field when !IsAssociatedField(field): WritePreamble(field, code, PreambleSite.Field); + WriteUnconstructedDefault(classDecl, field, code); WriteStructMember(field.Name, field.Type, field.Visibility, code); break; @@ -627,6 +628,41 @@ private void GenerateStruct(ClassDeclaration classDecl, string name, CodeBlocker insideType--; } + /// + /// Says what a field starts at when the type has no constructor to start it there. + /// + /// The type the field belongs to. + /// The field being written. + /// The writer to emit into. + /// + /// A constructor builds a field it does not set from its declared value, so a type with one + /// needs no note. A type without one has nothing to carry the value, and leaving it out would + /// look like the declaration never asked for one — Go and C say the same thing for the same + /// reason. + /// + private void WriteUnconstructedDefault(ClassDeclaration classDecl, FieldDeclaration field, CodeBlocker code) + { + if (field.InitialValue is not Expression initialValue + || classDecl.Members.OfType().Any(function => function.Kind == FunctionKind.Constructor)) + { + return; + } + + WriteInexpressible(code, $"{field.Name} defaults to {GenerateExpression(initialValue)}: Rust has no field initialisers, and no constructor sets it"); + } + + /// + /// Writes an expression to a string, for the places a comment has to quote one. + /// + /// The expression to write. + /// Its Rust source. + private string GenerateExpression(Expression expression) + { + using CodeBlocker inline = CodeBlocker.Create(IndentString); + GenerateInternal(expression, inline); + return inline.ToString().TrimEnd('\r', '\n'); + } + /// /// Reports whether a field belongs to its type rather than to each instance of it. /// @@ -691,9 +727,9 @@ private void WriteAssociatedField(FieldDeclaration field, CodeBlocker code) /// What the declaration said about who may see it. /// The writer to emit into. /// - /// A field's initial value is not written, and unlike C that is not a gap to apologise for: a - /// Rust struct is built by naming every field at once, so what a field starts at is the - /// constructor's business rather than the declaration's. + /// A field's initial value is not written here: a Rust struct is built by naming every field at + /// once, so what a field starts at is the constructor's business rather than the declaration's. + /// Where there is no constructor, says so instead. /// private static void WriteStructMember(string? name, TypeReference? type, Visibility visibility, CodeBlocker code) => code.WriteLine($"{SpellVisibilityKeyword(visibility)}{name}: {SpellType(type ?? new TypeReference(UnknownTypeName))},");