Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Coder.Test/Languages/RustGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,47 @@
Assert.AreEqual("[ 1 ]", Generate(array));
}

/// <summary>
/// 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.
/// </summary>
[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);

Check warning on line 853 in Coder.Test/Languages/RustGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'StringAssert.Contains'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaE5bSy_o-xUf5AQJsmv&open=AaE5bSy_o-xUf5AQJsmv&pullRequest=229
StringAssert.Contains(generated, "// step defaults to 1: Rust has no field initialisers, and no constructor sets it", StringComparison.Ordinal);

Check warning on line 854 in Coder.Test/Languages/RustGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'StringAssert.Contains'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaE5bSy_o-xUf5AQJsmw&open=AaE5bSy_o-xUf5AQJsmw&pullRequest=229
}

/// <summary>
/// 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.
/// </summary>
[TestMethod]
public void FieldInitialValue_WithAConstructor_IsNotANote()
=> Assert.IsFalse(Generator.Generate(Counter(withConstructor: true)).Contains("defaults to", StringComparison.Ordinal));

/// <summary>
/// Builds the editor's sample type: two fields with declared starting values and one method.
/// </summary>
/// <param name="withConstructor">Whether the type also declares a constructor.</param>
/// <returns>The declaration.</returns>
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;
}

/// <summary>
/// Writes one expression on its own, for the assertions that are about the expression rather than
/// about the declaration holding it.
Expand Down
42 changes: 39 additions & 3 deletions Coder/Languages/RustGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@
/// The associated constants of <c>f64</c>, which need nothing imported.
/// </remarks>
protected override string SpellNonFiniteDouble(double value) =>
double.IsNaN(value) ? "f64::NAN" : value > 0 ? "f64::INFINITY" : "f64::NEG_INFINITY";

Check warning on line 344 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 344 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 344 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

/// <inheritdoc/>
/// <remarks>
Expand All @@ -349,7 +349,7 @@
/// <c>f32</c> implicitly.
/// </remarks>
protected override string SpellNonFiniteSingle(float value) =>
float.IsNaN(value) ? "f32::NAN" : value > 0 ? "f32::INFINITY" : "f32::NEG_INFINITY";

Check warning on line 352 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 352 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 352 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

/// <inheritdoc/>
/// <remarks>
Expand Down Expand Up @@ -616,6 +616,7 @@

case FieldDeclaration field when !IsAssociatedField(field):
WritePreamble(field, code, PreambleSite.Field);
WriteUnconstructedDefault(classDecl, field, code);
WriteStructMember(field.Name, field.Type, field.Visibility, code);
break;

Expand All @@ -627,6 +628,41 @@
insideType--;
}

/// <summary>
/// Says what a field starts at when the type has no constructor to start it there.
/// </summary>
/// <param name="classDecl">The type the field belongs to.</param>
/// <param name="field">The field being written.</param>
/// <param name="code">The writer to emit into.</param>
/// <remarks>
/// 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.
/// </remarks>
private void WriteUnconstructedDefault(ClassDeclaration classDecl, FieldDeclaration field, CodeBlocker code)
{
if (field.InitialValue is not Expression initialValue
|| classDecl.Members.OfType<FunctionDeclaration>().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");
}

/// <summary>
/// Writes an expression to a string, for the places a comment has to quote one.
/// </summary>
/// <param name="expression">The expression to write.</param>
/// <returns>Its Rust source.</returns>
private string GenerateExpression(Expression expression)
{
using CodeBlocker inline = CodeBlocker.Create(IndentString);
GenerateInternal(expression, inline);
return inline.ToString().TrimEnd('\r', '\n');
}

/// <summary>
/// Reports whether a field belongs to its type rather than to each instance of it.
/// </summary>
Expand Down Expand Up @@ -691,9 +727,9 @@
/// <param name="visibility">What the declaration said about who may see it.</param>
/// <param name="code">The writer to emit into.</param>
/// <remarks>
/// 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, <see cref="WriteUnconstructedDefault"/> says so instead.
/// </remarks>
private static void WriteStructMember(string? name, TypeReference? type, Visibility visibility, CodeBlocker code) =>
code.WriteLine($"{SpellVisibilityKeyword(visibility)}{name}: {SpellType(type ?? new TypeReference(UnknownTypeName))},");
Expand Down Expand Up @@ -808,7 +844,7 @@
new(SelfName, new TypeReference(typeName)),
.. funcDecl.Parameters.Select(parameter => new KeyValuePair<string, TypeReference?>(OperandName(parameter), parameter.Type)),
]);
string self = op.ReturnsBool ? "&self" : reassigned.Contains(SelfName) ? $"mut {SelfName}" : SelfName;

Check warning on line 847 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 847 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.
code.Write($"fn {op.Method}({self}");

foreach (Parameter parameter in funcDecl.Parameters)
Expand Down Expand Up @@ -1531,7 +1567,7 @@

bool inferred = varDecl.IsTypeInferred || varDecl.Type is null;
TypeReference? annotation = !inferred ? varDecl.Type
: varDecl.InitialValue is null ? new TypeReference(UnknownTypeName)

Check warning on line 1570 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.

Check warning on line 1570 in Coder/Languages/RustGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Extract this nested ternary operation into an independent statement.
: InferredLiteralWidth(varDecl.InitialValue);

if (annotation is not null)
Expand Down
Loading