diff --git a/Coder.Test/Languages/NumericWideningTests.cs b/Coder.Test/Languages/NumericWideningTests.cs
index 69c895b..7156da3 100644
--- a/Coder.Test/Languages/NumericWideningTests.cs
+++ b/Coder.Test/Languages/NumericWideningTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2023-2026 ktsu-dev contributors
+// Copyright (c) 2023-2026 ktsu-dev contributors
namespace ktsu.Coder.Test.Languages;
@@ -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"));
}
/// Tests where Rust converts.
@@ -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);
}
/// Tests where Go converts.
@@ -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);
+ }
+
+ ///
+ /// Tests that JavaScript keeps (x << n) << 31 on an int x in 32 bits
+ /// when n is a long, because the inner shift is an int (#362).
+ ///
+ [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}");
+ });
}
/// Tests that a language converting by itself is written as before.
diff --git a/Coder/Languages/StandardLanguageGenerator.cs b/Coder/Languages/StandardLanguageGenerator.cs
index f1baaf9..17fac0a 100644
--- a/Coder/Languages/StandardLanguageGenerator.cs
+++ b/Coder/Languages/StandardLanguageGenerator.cs
@@ -530,6 +530,10 @@ or BinaryOperator.GreaterThanOrEqual
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: