diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs
index 01ef0de..5c72214 100644
--- a/JavaToCSharp.Tests/IntegrationTests.cs
+++ b/JavaToCSharp.Tests/IntegrationTests.cs
@@ -89,6 +89,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath)
[InlineData("Resources/StaticImports.java")]
[InlineData("Resources/LabeledBreakContinue.java")]
[InlineData("Resources/ExceptionGetMessage.java")]
+ [InlineData("Resources/LongLiterals.java")]
public void FullIntegrationTests(string filePath, bool allowWarnings = false)
=> RunFullIntegrationTest(filePath, allowWarnings);
diff --git a/JavaToCSharp.Tests/Resources/LongLiterals.java b/JavaToCSharp.Tests/Resources/LongLiterals.java
new file mode 100644
index 0000000..81841f2
--- /dev/null
+++ b/JavaToCSharp.Tests/Resources/LongLiterals.java
@@ -0,0 +1,21 @@
+/// Expect:
+/// - output: "-1 9223372036854775807 10 2147483648 255 8 1000000\n"
+package example;
+
+public class Program {
+ public static void main(String[] args) {
+ // An all-ones hex long is -1 in Java's two's-complement representation. Without the
+ // L suffix the generated C# literal is a ulong and fails to compile (CS0266).
+ long allOnes = 0xFFFFFFFFFFFFFFFFL;
+ long maxValue = 0x7FFFFFFFFFFFFFFFL;
+ long small = 10L;
+ // Above int.MaxValue, so a bare literal would not be typed as int in C#.
+ long aboveIntMax = 2147483648L;
+ long hex = 0xFFL;
+ long octal = 010L;
+ long separated = 1_000_000L;
+
+ System.out.println(allOnes + " " + maxValue + " " + small + " " + aboveIntMax
+ + " " + hex + " " + octal + " " + separated);
+ }
+}
diff --git a/JavaToCSharp.Tests/VisitLiteralExpressionTests.cs b/JavaToCSharp.Tests/VisitLiteralExpressionTests.cs
index 6785419..a97c6e8 100644
--- a/JavaToCSharp.Tests/VisitLiteralExpressionTests.cs
+++ b/JavaToCSharp.Tests/VisitLiteralExpressionTests.cs
@@ -55,10 +55,42 @@ public void VisitLiteralExpression_Integer(string javaLiteral, int expected)
[InlineData("0B1010L", 10L)]
[InlineData("0x1FL", 31L)]
[InlineData("010L", 8L)]
+ [InlineData("10L", 10L)]
+ [InlineData("2147483648L", 2147483648L)]
+ // Java long literals are two's-complement, so an all-ones hex literal is -1.
+ [InlineData("0xFFFFFFFFFFFFFFFFL", -1L)]
+ [InlineData("0x7FFFFFFFFFFFFFFFL", long.MaxValue)]
+ // A lowercase l suffix is equally valid Java.
+ [InlineData("42l", 42L)]
public void VisitLiteralExpression_Long(string javaLiteral, long expected)
{
var expr = ExpressionVisitor.VisitExpression(new ConversionContext(new JavaConversionOptions()), new LongLiteralExpr(javaLiteral));
Assert.Equal(expected, expr?.GetFirstToken().Value);
}
+
+ ///
+ /// The emitted text must keep the L suffix. C# types a bare numeric literal as int, so
+ /// dropping it makes 0xFFFFFFFFFFFFFFFF a ulong that will not implicitly convert to long
+ /// (CS0266), and pushes any value above int.MaxValue to a different inferred type.
+ ///
+ [Theory]
+ // Above long.MaxValue C# would type the hex literal as ulong (CS0266), so the wrapped
+ // decimal value is emitted instead.
+ [InlineData("0xFFFFFFFFFFFFFFFFL", "-1L")]
+ [InlineData("0x8000000000000000L", "-9223372036854775808L")]
+ [InlineData("2147483648L", "2147483648L")]
+ [InlineData("0x1FL", "0x1FL")]
+ [InlineData("0b10L", "0b10L")]
+ [InlineData("10L", "10L")]
+ [InlineData("42l", "42L")]
+ // Underscores are separators in Java and are dropped from the emitted literal.
+ [InlineData("1_000_000L", "1000000L")]
+ // Java octal has no C# equivalent, so it is rewritten in decimal - still suffixed.
+ [InlineData("010L", "8L")]
+ public void VisitLiteralExpression_Long_PreservesSuffixInText(string javaLiteral, string expectedText)
+ {
+ var expr = ExpressionVisitor.VisitExpression(new ConversionContext(new JavaConversionOptions()), new LongLiteralExpr(javaLiteral));
+ Assert.Equal(expectedText, expr?.GetFirstToken().Text);
+ }
}
diff --git a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs
index 438de6b..ae560d4 100644
--- a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs
+++ b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs
@@ -9,16 +9,30 @@ public class LongLiteralExpressionVisitor : ExpressionVisitor -1), matching Java's two's-complement semantics.
int64Value = Convert.ToInt64(value, 16);
+
+ // C# types a hex literal by its magnitude, so anything above long.MaxValue becomes
+ // ulong and will not implicitly convert to long (CS0266) even with the L suffix.
+ // Emit the wrapped decimal value instead, which is the number Java means.
+ if (int64Value < 0)
+ {
+ value = int64Value.ToString();
+ }
}
else if (value.StartsWith("0b", StringComparison.OrdinalIgnoreCase))
{
@@ -34,6 +48,10 @@ protected override ExpressionSyntax Visit(ConversionContext context, LiteralStri
int64Value = Convert.ToInt64(value);
}
- return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(value, int64Value));
+ // Re-append the L suffix. C# infers int for a bare literal, so without it a value above
+ // int.MaxValue either fails to compile (0xFFFFFFFFFFFFFFFF is ulong) or changes type.
+ return SyntaxFactory.LiteralExpression(
+ SyntaxKind.NumericLiteralExpression,
+ SyntaxFactory.Literal(value + "L", int64Value));
}
}