diff --git a/Coder.Test/Languages/FloatingRemainderTests.cs b/Coder.Test/Languages/FloatingRemainderTests.cs
index 3a3e264..7675238 100644
--- a/Coder.Test/Languages/FloatingRemainderTests.cs
+++ b/Coder.Test/Languages/FloatingRemainderTests.cs
@@ -59,6 +59,20 @@ public void AnIntegerRemainderIsUnchanged(string language, string remainder, str
StringAssert.Contains(code, compoundRemainder, StringComparison.Ordinal);
}
+ ///
+ /// Tests that Go widens an int or float32 operand of a float64 remainder,
+ /// and of a float64 %=, because math.Mod takes float64 alone (#376).
+ ///
+ [TestMethod]
+ public void Go_MixedOperandsOfADoubleRemainderAreWidened()
+ {
+ string code = new GoGenerator().Generate(FileOfMixedRemainders());
+
+ StringAssert.Contains(code, "math.Mod(float64(x), d)", StringComparison.Ordinal, code);
+ StringAssert.Contains(code, "r = math.Mod(r, float64(i))", StringComparison.Ordinal, code);
+ StringAssert.Contains(code, "math.Mod(float64(i), d)", StringComparison.Ordinal, code);
+ }
+
///
/// Tests that a file taking a floating remainder includes or imports the call's declaration once,
/// and that one taking only integer remainders does not.
@@ -119,7 +133,7 @@ public void RemaindersCompile(string language)
return;
}
- SourceFile[] files = [FileOfRemainders(), FileOfIntegerRemainders(), FileOfAccessorRemainder(), FileOfEntryPointRemainder()];
+ SourceFile[] files = [FileOfRemainders(), FileOfIntegerRemainders(), FileOfMixedRemainders(), FileOfAccessorRemainder(), FileOfEntryPointRemainder()];
foreach (SourceFile file in files)
{
ToolchainHarness.InTemporaryDirectory(directory =>
@@ -239,6 +253,29 @@ private static SourceFile FileOfIntegerRemainders()
return file;
}
+ ///
+ /// Builds double rem(float x, double d, int i) { double r = x % d; r %= i; return r + (i % d); },
+ /// whose remainders each mix a double with a narrower operand (#376).
+ ///
+ private static SourceFile FileOfMixedRemainders()
+ {
+ FunctionDeclaration function = new("rem") { ReturnType = new TypeReference("double") };
+ function.Parameters.Add(new Parameter("x", "float"));
+ function.Parameters.Add(new Parameter("d", "double"));
+ function.Parameters.Add(new Parameter("i", "int"));
+ function.Body.Add(new VariableDeclaration("r", "double", new BinaryExpression(
+ new VariableReference("x"), BinaryOperator.Modulo, new VariableReference("d"))));
+ function.Body.Add(new AssignmentStatement(new VariableReference("r"), new VariableReference("i"), AssignmentOperator.ModuloAssign));
+ function.Body.Add(new ReturnStatement(new BinaryExpression(
+ new VariableReference("r"),
+ BinaryOperator.Add,
+ new BinaryExpression(new VariableReference("i"), BinaryOperator.Modulo, new VariableReference("d")))));
+
+ SourceFile file = new("remainders");
+ file.Members.Add(function);
+ return file;
+ }
+
private static FunctionDeclaration Remainder(string name, string type)
{
FunctionDeclaration function = new(name) { ReturnType = new TypeReference(type) };
diff --git a/Coder/Languages/GoGenerator.cs b/Coder/Languages/GoGenerator.cs
index 48574ee..5e43bf2 100644
--- a/Coder/Languages/GoGenerator.cs
+++ b/Coder/Languages/GoGenerator.cs
@@ -310,10 +310,13 @@ protected override void WriteFloatingRemainder(Expression dividend, Expression d
Ensure.NotNull(code);
if (floatingType == "double")
{
+ // math.Mod takes float64 alone, and Go converts nothing implicitly, so an int or a
+ // float32 operand is widened here as the float32 path below widens both (#376).
+ TypeReference float64 = new("double");
code.Write("math.Mod(");
- GenerateInternal(dividend, code);
+ GenerateWidenedTo(dividend, float64, code);
code.Write(", ");
- GenerateInternal(divisor, code);
+ GenerateWidenedTo(divisor, float64, code);
code.Write(")");
return;
}