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
47 changes: 47 additions & 0 deletions Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,53 @@ public void StringLiteralsReceivedAsOwnedStrings_Compile()
});
}

/// <summary>
/// Tests that bitwise complement and unary plus compile: Rust has neither <c>~</c> nor a unary
/// <c>+</c>, so both have to be spelled its way.
/// </summary>
[TestMethod]
public void UnaryOperatorsRustSpellsDifferently_Compile()
{
if (ToolchainHarness.FindOnPath("--version", "rustc") is null)
{
Assert.Inconclusive("No Rust compiler on the path, so nothing was compiled.");
return;
}

FunctionDeclaration complement = new("complement") { ReturnType = "int" };
complement.Parameters.Add(new Parameter("value", "int"));
complement.Body.Add(new ReturnStatement(new UnaryExpression(UnaryOperator.BitwiseNot, new VariableReference("value"))));

FunctionDeclaration identity = new("identity") { ReturnType = "int" };
identity.Parameters.Add(new Parameter("value", "int"));
identity.Body.Add(new ReturnStatement(new UnaryExpression(UnaryOperator.Plus, new VariableReference("value"))));

FunctionDeclaration minusOne = new("minus_one") { ReturnType = "int" };
minusOne.Body.Add(new ReturnStatement(new UnaryExpression(UnaryOperator.Plus, Literal.Number(-1))));

SourceFile file = new("unary");
file.Members.Add(complement);
file.Members.Add(identity);
file.Members.Add(minusOne);

ToolchainHarness.InTemporaryDirectory(directory =>
{
string source = new RustGenerator().Generate(file);
string allowed = "#![allow(unused_parens, dead_code)]";

File.WriteAllText(
Path.Combine(directory, "unary.rs"),
$"{allowed}{Environment.NewLine}{source}");

(int exitCode, string output) = ToolchainHarness.Run(
"rustc",
"--crate-type lib --edition 2021 -o unary.rlib unary.rs",
directory);

Assert.AreEqual(0, exitCode, $"rustc rejected the generated source:{Environment.NewLine}{source}{Environment.NewLine}{output}");
});
}

/// <summary>
/// Tests that the arguments an entry point is handed are the ones the user typed, without the
/// program's own path that <c>std::env::args</c> puts first — which is what they are in every
Expand Down
23 changes: 23 additions & 0 deletions Coder.Test/Languages/RustGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ public void Identity_IsRust()
Assert.AreEqual("rs", Generator.FileExtension);
}

/// <summary>
/// Tests that the unary operators Rust spells differently are written its way: <c>!</c> for bitwise
/// complement, since Rust has no <c>~</c>, and the bare operand for unary plus, since Rust has no
/// unary <c>+</c>.
/// </summary>
/// <param name="op">The operator under test.</param>
/// <param name="expected">The expected generated source.</param>
[TestMethod]
[DataRow(UnaryOperator.BitwiseNot, "(!x)")]
[DataRow(UnaryOperator.Plus, "(x)")]
[DataRow(UnaryOperator.LogicalNot, "(!x)")]
[DataRow(UnaryOperator.Negate, "(-x)")]
public void UnaryOperators_AreSpelledAsRustWritesThem(UnaryOperator op, string expected)
=> Assert.AreEqual(expected, Generate(new UnaryExpression(op, new VariableReference("x"))).Trim());

/// <summary>
/// Tests that unary plus on a negative literal is written as the literal alone, not the <c>+-1</c>
/// rustc rejects.
/// </summary>
[TestMethod]
public void UnaryPlusOnANegativeLiteral_IsTheLiteral()
=> Assert.AreEqual("(-1)", Generate(new UnaryExpression(UnaryOperator.Plus, Literal.Number(-1))).Trim());

/// <summary>
/// Tests that a function with nothing to answer writes no return type, since Rust names the unit
/// only when it has to and nobody writes it.
Expand Down
4 changes: 3 additions & 1 deletion Coder.Test/Languages/UnaryOperandSignTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ktsu.Coder.Test.Languages;

using System.Linq;
using ktsu.Coder.Ast;
using ktsu.Coder.Languages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -74,7 +75,8 @@ public void AnOperandThatCannotFuseIsWrittenBare(UnaryOperator op, int value, st
{
UnaryExpression unary = new(op, Literal.Number(value));

foreach (ILanguageGenerator generator in Generators)
// Rust has no unary plus, so it writes the operand alone; RustGeneratorTests covers it.
foreach (ILanguageGenerator generator in Generators.Where(g => op != UnaryOperator.Plus || g is not RustGenerator))
{
Assert.AreEqual(expected, generator.Generate(unary).Trim(), generator.GetType().Name);
}
Expand Down
17 changes: 17 additions & 0 deletions Coder/Languages/RustGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,23 @@ private sealed record OperatorTrait(string Name, string Method, bool ReturnsBool

/// <inheritdoc/>
protected override string? SpellAnnotation(Annotation annotation) => $"#[{annotation}]";

/// <summary>
/// Maps a unary operator to its Rust spelling.
/// </summary>
/// <param name="op">The operator to map.</param>
/// <returns>The operator's source spelling.</returns>
/// <remarks>
/// Rust has neither <c>~</c> nor a unary <c>+</c>. Its <c>!</c> is bitwise complement on an
/// integer as well as logical negation on a <c>bool</c>, and unary plus, being the identity, is
/// written as the operand alone. The emitter still parenthesises it, so precedence is unchanged.
/// </remarks>
protected override string GetUnaryOperatorSpelling(UnaryOperator op) => op switch
{
UnaryOperator.BitwiseNot => "!",
UnaryOperator.Plus => string.Empty,
_ => GetUnaryOperator(op)
};
/// <inheritdoc/>
/// <remarks>
/// <c>use</c>, which is what Rust writes where the others write an include or an import. A path
Expand Down
Loading