Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion Coder.Test/Languages/NumericWideningTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2023-2026 ktsu-dev contributors
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.Coder.Test.Languages;

Expand Down Expand Up @@ -55,7 +55,24 @@ internal static SourceFile Widening()
called.Body.Add(new ReturnStatement(call));
file.Members.Add(called);

// A shift has its left operand's type, so int << long is an int that a long receives (#362).
FunctionDeclaration shiftedLocal = new("shiftedLocal") { ReturnType = new TypeReference("long") };
shiftedLocal.Parameters.Add(new Parameter("b", "int"));
shiftedLocal.Parameters.Add(new Parameter("n", "long"));
shiftedLocal.Body.Add(new VariableDeclaration("s", initialValue: Shift(BinaryOperator.LeftShift)));
shiftedLocal.Body.Add(new ReturnStatement(new VariableReference("s")));
file.Members.Add(shiftedLocal);

FunctionDeclaration shiftedReturn = new("shiftedReturn") { ReturnType = new TypeReference("long") };
shiftedReturn.Parameters.Add(new Parameter("b", "int"));
shiftedReturn.Parameters.Add(new Parameter("n", "long"));
shiftedReturn.Body.Add(new ReturnStatement(Shift(BinaryOperator.RightShift)));
file.Members.Add(shiftedReturn);

return file;

static BinaryExpression Shift(BinaryOperator shift) =>
new(new VariableReference("b"), shift, new VariableReference("n"));
}

/// <summary>Tests where Rust converts.</summary>
Expand All @@ -70,6 +87,9 @@ public void Rust_WidensTheNarrowerSide()
Assert.Contains("h = (b as f64);", source, StringComparison.Ordinal);
Assert.Contains("return (b as i64);", source, StringComparison.Ordinal);
Assert.Contains("mixed((b as i64), b)", source, StringComparison.Ordinal);
Assert.Contains("let mut s = (b << n);", source, StringComparison.Ordinal, source);
Assert.Contains("return (s as i64);", source, StringComparison.Ordinal, source);
Assert.Contains("return ((b >> n) as i64);", source, StringComparison.Ordinal, source);
}

/// <summary>Tests where Go converts.</summary>
Expand All @@ -84,6 +104,45 @@ public void Go_WidensTheNarrowerSide()
Assert.Contains("h = float64(b)", source, StringComparison.Ordinal);
Assert.Contains("return int64(b)", source, StringComparison.Ordinal);
Assert.Contains("mixed(int64(b), b)", source, StringComparison.Ordinal);
Assert.Contains("return int64(s)", source, StringComparison.Ordinal, source);
Assert.Contains("return int64((b >> n))", source, StringComparison.Ordinal, source);
}

/// <summary>
/// Tests that JavaScript keeps <c>(x &lt;&lt; n) &lt;&lt; 31</c> on an <c>int</c> <c>x</c> in 32 bits
/// when <c>n</c> is a <c>long</c>, because the inner shift is an <c>int</c> (#362).
/// </summary>
[TestMethod]
public void JavaScript_AShiftByALongStaysInItsLeftOperandsWidth()
{
FunctionDeclaration chained = new("chained") { ReturnType = new TypeReference("int") };
chained.Parameters.Add(new Parameter("x", "int"));
chained.Parameters.Add(new Parameter("n", "long"));
chained.Body.Add(new ReturnStatement(new BinaryExpression(
new BinaryExpression(new VariableReference("x"), BinaryOperator.LeftShift, new VariableReference("n")),
BinaryOperator.LeftShift,
Literal.Number(31))));
SourceFile file = new("shifts");
file.Members.Add(chained);

string source = new JavaScriptGenerator().Generate(file);
Assert.DoesNotContain("BigInt", source, StringComparison.Ordinal, source);

string? node = ToolchainHarness.FindOnPath("--version", "node");
if (node is null)
{
Assert.Inconclusive("No JavaScript runtime on the path, so nothing was run.");
return;
}

string check = source + Environment.NewLine
+ "if (chained(1, 1) !== 0) { throw new Error(`chained(1, 1): expected 0, got ${chained(1, 1)}`); }" + Environment.NewLine;
ToolchainHarness.InTemporaryDirectory(directory =>
{
File.WriteAllText(Path.Combine(directory, "check.js"), check);
(int exitCode, string output) = ToolchainHarness.Run(node, "check.js", directory);
Assert.AreEqual(0, exitCode, $"{output}{Environment.NewLine}{check}");
});
}

/// <summary>Tests that a language converting by itself is written as before.</summary>
Expand Down
4 changes: 4 additions & 0 deletions Coder/Languages/StandardLanguageGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
/// without this its body has no scope and a typed local or the setter's value is not known to be
/// an integer.
/// </remarks>
protected IDisposable OpenStaticTypeScope(PropertyDeclaration property, bool isSetter, string valueName = "value")

Check warning on line 153 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'value' 5 times.

Check warning on line 153 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'OpenStaticTypeScope' method overloads should be adjacent.

Check warning on line 153 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'value' 5 times.

Check warning on line 153 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'OpenStaticTypeScope' method overloads should be adjacent.
{
Ensure.NotNull(property);
Ensure.NotNull(valueName);
Expand Down Expand Up @@ -277,7 +277,7 @@
return null;
}

return left == "double" || right == "double" ? "double" : "float";

Check warning on line 280 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'float' 7 times.

Check warning on line 280 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'double' 7 times.

Check warning on line 280 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'float' 7 times.

Check warning on line 280 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'double' 7 times.
}

private static string? FloatingTypeName(TypeReference? type) =>
Expand Down Expand Up @@ -530,6 +530,10 @@
or BinaryOperator.LogicalAnd
or BinaryOperator.LogicalOr:
return new TypeReference("bool");
case BinaryExpression { Operator: BinaryOperator.LeftShift or BinaryOperator.RightShift } shift:
// A shift has its left operand's type in every target: the count does not widen the
// value it shifts (#362).
return StaticType(shift.Left);
case BinaryExpression binary:
return Promoted(StaticType(binary.Left), StaticType(binary.Right));
case VariableReference reference:
Expand Down Expand Up @@ -690,7 +694,7 @@
/// matched. A function's own local or parameter of the same name is not told apart here; each
/// language decides what shadowing means for the names this returns.
/// </remarks>
protected HashSet<string> WrittenFileScopeFields()

Check warning on line 697 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 20 to the 15 allowed.
{
HashSet<string> written = new(StringComparer.Ordinal);
if (GenerationRoot is null)
Expand Down Expand Up @@ -1374,7 +1378,7 @@
}

string name = fallback;
for (int suffix = 2; taken.Contains(name); suffix++)

Check warning on line 1381 in Coder/Languages/StandardLanguageGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

This loop's stop condition tests 'name' and 'taken' but the incrementer updates 'suffix'.
{
name = string.Create(CultureInfo.InvariantCulture, $"{fallback}{suffix}");
}
Expand Down
Loading