diff --git a/Coder.Test/Languages/NonFiniteDoubleLiteralTests.cs b/Coder.Test/Languages/NonFiniteDoubleLiteralTests.cs
index 83957ef..3838132 100644
--- a/Coder.Test/Languages/NonFiniteDoubleLiteralTests.cs
+++ b/Coder.Test/Languages/NonFiniteDoubleLiteralTests.cs
@@ -154,6 +154,46 @@ public void GeneratedGo_Compiles()
directory => File.WriteAllText(Path.Combine(directory, "go.mod"), "module limits\n\ngo 1.21\n"));
}
+ ///
+ /// Tests that a constant field or local holding NaN or an infinity is a Go var, because
+ /// math.NaN() and math.Inf are calls and a Go const cannot hold one.
+ ///
+ [TestMethod]
+ public void GoConstantNonFiniteValue_IsAVar()
+ {
+ string code = new GoGenerator().Generate(FileWithNonFiniteConstants());
+
+ StringAssert.Contains(code, "var Unset float64 = math.NaN()", StringComparison.Ordinal);
+ StringAssert.Contains(code, "var ceiling float64 = math.Inf(1)", StringComparison.Ordinal);
+ Assert.DoesNotContain("const Unset", code, StringComparison.Ordinal, code);
+ Assert.DoesNotContain("const ceiling", code, StringComparison.Ordinal, code);
+ }
+
+ ///
+ /// Tests that a Go file declaring a constant NaN field and a constant infinite local compiles.
+ ///
+ [TestMethod]
+ public void GeneratedGoConstants_Compile()
+ {
+ 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 limits\n\ngo 1.21\n");
+
+ string code = new GoGenerator().Generate(FileWithNonFiniteConstants());
+ File.WriteAllText(Path.Combine(directory, "limits.go"), code);
+
+ (int exitCode, string output) = ToolchainHarness.Run("go", "build ./...", directory);
+
+ Assert.AreEqual(0, exitCode, $"go rejected the generated source:{Environment.NewLine}{code}{Environment.NewLine}{output}");
+ });
+ }
+
///
/// Tests that a Rust file returning NaN and both infinities compiles.
///
@@ -198,6 +238,27 @@ private static SourceFile FileReturning(string name, params double[] values)
return file;
}
+ ///
+ /// Builds a file with a constant NaN field and a function holding a constant infinite local.
+ ///
+ private static SourceFile FileWithNonFiniteConstants()
+ {
+ SourceFile file = new("limits");
+ file.Members.Add(new FieldDeclaration("Unset", new TypeReference("double"))
+ {
+ IsConstant = true,
+ IsStatic = true,
+ InitialValue = new LiteralExpression(double.NaN),
+ });
+
+ FunctionDeclaration function = new("ceiling") { ReturnType = new TypeReference("double") };
+ function.Body.Add(new VariableDeclaration("ceiling", "double", new LiteralExpression(double.PositiveInfinity)) { IsConstant = true });
+ function.Body.Add(new ReturnStatement(new VariableReference("ceiling")));
+ file.Members.Add(function);
+
+ return file;
+ }
+
private static int Occurrences(string text, string value) =>
text.Split(value, StringSplitOptions.None).Length - 1;
diff --git a/Coder/Languages/GoGenerator.cs b/Coder/Languages/GoGenerator.cs
index b19bbfa..1c200d1 100644
--- a/Coder/Languages/GoGenerator.cs
+++ b/Coder/Languages/GoGenerator.cs
@@ -1370,7 +1370,24 @@ private void WriteStorageKeyword(string? name, bool wanted, AstNode? value, Code
/// Whether the declaration says its value never changes.
/// What it starts at.
/// True when Go will hold it as a constant.
- private static bool IsConstant(bool wanted, AstNode? value) => wanted && IsCompileTimeValue(value);
+ ///
+ /// NaN and the infinities are the one kind of number that is not: Go has no constant spelling for
+ /// them, and math.NaN() and math.Inf(1) are calls, which a const cannot hold.
+ ///
+ private static bool IsConstant(bool wanted, AstNode? value) =>
+ wanted && IsCompileTimeValue(value) && !IsNonFinite(value);
+
+ ///
+ /// Reports whether a value is a NaN or infinite floating-point literal.
+ ///
+ /// The value to test.
+ /// True when it is.
+ private static bool IsNonFinite(AstNode? value) => value switch
+ {
+ LiteralExpression literal => !double.IsFinite(literal.Value),
+ LiteralExpression literal => !float.IsFinite(literal.Value),
+ _ => false,
+ };
///
/// Writes what a declaration starts at, giving a bare list the declaration's own type.