From e83b23e9baa35367142d92bbe6a6c94c7b3fca5c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 04:28:22 +0000 Subject: [PATCH] Widen a Go double remainder's int and float32 operands [patch] math.Mod takes float64 alone and Go converts nothing implicitly, so the double branch of WriteFloatingRemainder now writes each operand through GenerateWidenedTo, as the float32 branch already converts both. A mixed remainder and %= are added to the Go spelling and compile tests. Fixes #376 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW4VpqLXsf6UDQnX46h --- .../Languages/FloatingRemainderTests.cs | 39 ++++++++++++++++++- Coder/Languages/GoGenerator.cs | 7 +++- 2 files changed, 43 insertions(+), 3 deletions(-) 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; }