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
12 changes: 12 additions & 0 deletions Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,24 @@ private static ClassDeclaration Boxed()
boxed.Documentation.Add("Holds one of whatever it was given.");
boxed.TypeParameters.Add(TypeParameter.Parse("T : Clone"));
boxed.Members.Add(new FieldDeclaration("held", "T"));
boxed.Members.Add(new FieldDeclaration("CAPACITY", "int")
{
IsStatic = true,
IsConstant = true,
InitialValue = new LiteralExpression<int>(8),
});

FunctionDeclaration copy = new("copy") { ReturnType = "T", IsReadOnly = true };
copy.Body.Add(new ReturnStatement(
new CallExpression(new VariableReference("self.held"), "clone")));
boxed.Members.Add(copy);

// Named through the type, which only compiles if the constant is an associated item of it
// rather than a field of each instance.
FunctionDeclaration capacity = new("capacity") { ReturnType = "int", IsReadOnly = true };
capacity.Body.Add(new ReturnStatement(new VariableReference("Self::CAPACITY")));
boxed.Members.Add(capacity);

return boxed;
}

Expand Down
45 changes: 45 additions & 0 deletions Coder.Test/Languages/RustGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,51 @@
StringComparison.Ordinal);
}

/// <summary>
/// Tests that a constant field of a type is an associated constant in the inherent impl block,
/// keeping its value, rather than a field every instance holds.
/// </summary>
[TestMethod]
public void ConstantField_IsAnAssociatedConstant()
{
ClassDeclaration cfg = new("Cfg") { Kind = TypeDeclarationKind.Struct };
cfg.Members.Add(new FieldDeclaration("width", "int"));
cfg.Members.Add(new FieldDeclaration("Limit", "int")
{
IsStatic = true,
IsConstant = true,
InitialValue = new LiteralExpression<int>(8),
});

string code = Generator.Generate(cfg);
string structBody = code[..code.IndexOf("impl", StringComparison.Ordinal)];

StringAssert.Contains(structBody, "width: i32,", StringComparison.Ordinal);

Check warning on line 83 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=AaDhLjIHobAzMXtwL5t9&open=AaDhLjIHobAzMXtwL5t9&pullRequest=94
Assert.DoesNotContain("Limit", structBody, StringComparison.Ordinal, code);
StringAssert.Contains(code, "impl Cfg {", StringComparison.Ordinal);

Check warning on line 85 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=AaDhLjIHobAzMXtwL5t-&open=AaDhLjIHobAzMXtwL5t-&pullRequest=94
StringAssert.Contains(code, "const Limit: i32 = 8;", StringComparison.Ordinal);

Check warning on line 86 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=AaDhLjIHobAzMXtwL5t_&open=AaDhLjIHobAzMXtwL5t_&pullRequest=94
}

/// <summary>
/// Tests that a mutable static field of a type, which Rust has no associated form for, is
/// written down as a note rather than becoming a per-instance field.
/// </summary>
[TestMethod]
public void MutableStaticField_IsNotedRatherThanAStructField()
{
ClassDeclaration counter = new("Counter") { Kind = TypeDeclarationKind.Struct };
counter.Members.Add(new FieldDeclaration("Count", "int")
{
IsStatic = true,
InitialValue = new LiteralExpression<int>(0),
});

string code = Generator.Generate(counter);

Assert.DoesNotContain("Count: i32,", code, StringComparison.Ordinal, code);
StringAssert.Contains(code, "// Count: Rust has no associated statics", StringComparison.Ordinal);

Check warning on line 106 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=AaDhLjIHobAzMXtwL5uA&open=AaDhLjIHobAzMXtwL5uA&pullRequest=94
}

/// <summary>
/// Tests that a parameter the body assigns to is declared <c>mut</c>, and only that one, since
/// Rust binds a parameter immutably.
Expand Down
64 changes: 61 additions & 3 deletions Coder/Languages/RustGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,21 @@ .. classDecl.Members.OfType<FunctionDeclaration>().Where(member => member.Kind i
.. functions.Where(member => member.Kind is FunctionKind.Method or FunctionKind.Constructor),
];

if (inherent.Count > 0)
List<FieldDeclaration> associated = [.. classDecl.Members.OfType<FieldDeclaration>().Where(IsAssociatedField)];

if (inherent.Count > 0 || associated.Count > 0)
{
code.NewLine();
code.Write($"impl{implBounds} {applied} ");

using Scope block = new(code);
bool first = true;

foreach (FieldDeclaration field in associated)
{
WriteAssociatedField(field, code);
}

bool first = associated.Count == 0;
foreach (FunctionDeclaration function in inherent)
{
if (!first)
Expand Down Expand Up @@ -553,7 +561,7 @@ private void GenerateStruct(ClassDeclaration classDecl, string name, CodeBlocker
WriteStructMember(field.Name, field.Type, VisibilityOf(field), code);
break;

case FieldDeclaration field:
case FieldDeclaration field when !IsAssociatedField(field):
GenerateDocumentation(field, code);
WriteStructMember(field.Name, field.Type, field.Visibility, code);
break;
Expand All @@ -566,6 +574,56 @@ private void GenerateStruct(ClassDeclaration classDecl, string name, CodeBlocker
insideType--;
}

/// <summary>
/// Reports whether a field belongs to its type rather than to each instance of it.
/// </summary>
/// <param name="field">The field to test.</param>
/// <returns>True when the field is static or constant.</returns>
/// <remarks>
/// A constant is static whether or not the declaration says so. Either way it has no place in
/// the struct: a struct's fields are what every instance holds and every constructor supplies.
/// </remarks>
private static bool IsAssociatedField(FieldDeclaration field) => field.IsStatic || field.IsConstant;

/// <summary>
/// Writes a field that belongs to its type into the type's inherent impl block.
/// </summary>
/// <param name="field">The field to write.</param>
/// <param name="code">The writer to emit into.</param>
/// <remarks>
/// A constant is an associated <c>const</c>, named through the type as <c>Cfg::Limit</c>, which
/// is what a C++ <c>static constexpr</c> member is. A mutable static has no such form: Rust has
/// associated constants but no associated statics, so it is written down rather than dropped.
/// </remarks>
private void WriteAssociatedField(FieldDeclaration field, CodeBlocker code)
{
GenerateDocumentation(field, code);

if (!field.IsConstant)
{
WriteInexpressible(code, $"{field.Name}: Rust has no associated statics, so a mutable static belongs to no type");
return;
}

if (field.InitialValue is null)
{
WriteInexpressible(code, $"{field.Name}: Rust gives a const no value of its own");
return;
}

TypeReference type = field.Type ?? new TypeReference(UnknownTypeName);

code.Write($"{SpellVisibilityKeyword(field.Visibility)}const {field.Name}: {SpellStorageType(type)} = ");

if (type.IsArray)
{
code.Write("&");
}

GenerateInternal(field.InitialValue, code);
EndStatement(code);
}

/// <summary>
/// Writes one field of a struct.
/// </summary>
Expand Down
Loading