diff --git a/Coder.Test/Languages/GoGeneratedSourceCompilesTests.cs b/Coder.Test/Languages/GoGeneratedSourceCompilesTests.cs
index 01b3bdd..2489c2c 100644
--- a/Coder.Test/Languages/GoGeneratedSourceCompilesTests.cs
+++ b/Coder.Test/Languages/GoGeneratedSourceCompilesTests.cs
@@ -399,6 +399,60 @@ public void MixedIntegerAndFloatingOperands_PromoteToFloating()
});
}
+ ///
+ /// 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 gofmt writes (issue #395).
+ ///
+ [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( n[, 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("small"),
+ new LiteralExpression("large"))));
+ return function;
+ }
+ }
+
///
/// Tests that a condition comparing against a struct literal compiles in every position a
/// conditional is lowered to an if, and is still what gofmt writes.
diff --git a/Coder/Languages/GoGenerator.cs b/Coder/Languages/GoGenerator.cs
index 48574ee..0d2b6f8 100644
--- a/Coder/Languages/GoGenerator.cs
+++ b/Coder/Languages/GoGenerator.cs
@@ -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:
@@ -1979,6 +1986,28 @@ private void WriteCondition(AstNode condition, CodeBlocker code)
}
}
+ ///
+ /// Writes one operand of a comparison an if tests, converted to the type it meets the other
+ /// operand at when it is the narrower number.
+ ///
+ /// The operand to write.
+ /// The type the two operands meet at, or null where they are not widened.
+ /// The writer to emit into.
+ ///
+ /// A struct literal is never a number, so it is never converted, and is left to
+ /// , which parenthesises it.
+ ///
+ private void WriteWidenedConditionOperand(AstNode operand, TypeReference? common, CodeBlocker code)
+ {
+ if (common is not null && !StartsWithCompositeLiteral(operand))
+ {
+ GenerateWidenedTo(operand, common, code);
+ return;
+ }
+
+ WriteConditionOperand(operand, code);
+ }
+
///
/// Writes one operand of the expression an if tests, parenthesising a struct literal so the
/// clause does not end at its opening brace.