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)); } }