Skip to content
Open
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
54 changes: 54 additions & 0 deletions Coder.Test/Languages/GoGeneratedSourceCompilesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,60 @@ public void MixedIntegerAndFloatingOperands_PromoteToFloating()
});
}

/// <summary>
/// Tests that a conditional whose condition compares numbers of different widths widens the
/// narrower one, as the same comparison outside a conditional does, so the output compiles and is
/// still what <c>gofmt</c> writes (issue #395).
/// </summary>
[TestMethod]
public void ConditionComparingMixedNumericWidths_Compiles()
{
SourceFile file = new("conditions");
file.Members.Add(Size("size", "int", new VariableReference("limit"), "long"));
file.Members.Add(Size("half", "int", Literal.DecimalValue(0.5), null));
file.Members.Add(Size("single", "float", new VariableReference("limit"), "double"));

string source = new GoGenerator().Generate(file);
Assert.Contains("if int64(n) < limit {", source, StringComparison.Ordinal, source);
Assert.Contains("if float64(n) < 0.5 {", source, StringComparison.Ordinal, source);
Assert.Contains("if float64(n) < limit {", source, StringComparison.Ordinal, source);

if (ToolchainHarness.FindOnPath("version", "go") is null)
{
Assert.Inconclusive("No Go toolchain on the path, so nothing was compiled.");
return;
}

ToolchainHarness.InTemporaryDirectory(directory =>
{
File.WriteAllText(Path.Combine(directory, "go.mod"), Module);
File.WriteAllText(Path.Combine(directory, "conditions.go"), source);
(int exitCode, string output) = ToolchainHarness.Run("go", "build ./...", directory);
Assert.AreEqual(0, exitCode, $"Go rejected the generated source:{Environment.NewLine}{output}{Environment.NewLine}{source}");

(int formatted, string differs) = ToolchainHarness.Run("gofmt", "-l conditions.go", directory);
Assert.AreEqual(0, formatted, $"gofmt did not run:{Environment.NewLine}{differs}");
Assert.AreEqual(string.Empty, differs.Trim(), $"gofmt would rewrite the generated source.{Environment.NewLine}{source}");
});

// string name(<type> n[, <limitType> limit]) => n < limit ? "small" : "large";
static FunctionDeclaration Size(string name, string type, Expression limit, string? limitType)
{
FunctionDeclaration function = new(name) { ReturnType = new TypeReference("string") };
function.Parameters.Add(new Parameter("n", type));
if (limitType is not null)
{
function.Parameters.Add(new Parameter("limit", limitType));
}

function.Body.Add(new ReturnStatement(new ConditionalExpression(
new BinaryExpression(new VariableReference("n"), BinaryOperator.LessThan, limit),
new LiteralExpression<string>("small"),
new LiteralExpression<string>("large"))));
return function;
}
}

/// <summary>
/// Tests that a condition comparing against a struct literal compiles in every position a
/// conditional is lowered to an <c>if</c>, and is still what <c>gofmt</c> writes.
Expand Down
33 changes: 31 additions & 2 deletions Coder/Languages/GoGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1957,9 +1957,16 @@ private void WriteCondition(AstNode condition, CodeBlocker code)
switch (condition)
{
case BinaryExpression binary when !IsBooleanBitwise(binary.Operator, binary.Left, binary.Right):
WriteConditionOperand(binary.Left, code);
// The operands meet at the wider of their types, as GenerateBinary widens them
// everywhere else (#395). A shift's count is not such an operand, and a logical
// operator's operands are booleans.
TypeReference? common = binary.Operator is BinaryOperator.LeftShift or BinaryOperator.RightShift
or BinaryOperator.LogicalAnd or BinaryOperator.LogicalOr
? null
: Promoted(StaticType(binary.Left), StaticType(binary.Right));
WriteWidenedConditionOperand(binary.Left, common, code);
code.Write($" {GetOperatorSpelling(binary.Operator)} ");
WriteConditionOperand(binary.Right, code);
WriteWidenedConditionOperand(binary.Right, common, code);
return;

case BinaryExpression binary when BooleanBitwiseSpelling(binary) is string spelling:
Expand All @@ -1979,6 +1986,28 @@ private void WriteCondition(AstNode condition, CodeBlocker code)
}
}

/// <summary>
/// Writes one operand of a comparison an <c>if</c> tests, converted to the type it meets the other
/// operand at when it is the narrower number.
/// </summary>
/// <param name="operand">The operand to write.</param>
/// <param name="common">The type the two operands meet at, or null where they are not widened.</param>
/// <param name="code">The writer to emit into.</param>
/// <remarks>
/// A struct literal is never a number, so it is never converted, and is left to
/// <see cref="WriteConditionOperand"/>, which parenthesises it.
/// </remarks>
private void WriteWidenedConditionOperand(AstNode operand, TypeReference? common, CodeBlocker code)
{
if (common is not null && !StartsWithCompositeLiteral(operand))
{
GenerateWidenedTo(operand, common, code);
return;
}

WriteConditionOperand(operand, code);
}

/// <summary>
/// Writes one operand of the expression an <c>if</c> tests, parenthesising a struct literal so the
/// clause does not end at its opening brace.
Expand Down
Loading