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; }