Skip to content
Merged
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
1 change: 1 addition & 0 deletions JavaToCSharp.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
21 changes: 21 additions & 0 deletions JavaToCSharp.Tests/Resources/LongLiterals.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
32 changes: 32 additions & 0 deletions JavaToCSharp.Tests/VisitLiteralExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// 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.
/// </summary>
[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);
}
}

28 changes: 23 additions & 5 deletions JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,30 @@ public class LongLiteralExpressionVisitor : ExpressionVisitor<LiteralStringValue
protected override ExpressionSyntax Visit(ConversionContext context, LiteralStringValueExpr expr)
{
string value = expr is LongLiteralExpr longLiteralExpr ? longLiteralExpr.getValue() : expr.toString();
value = value.Trim('\"')
.Replace("L", string.Empty)
.Replace("l", string.Empty)
.Replace("_", string.Empty);
value = value.Trim('\"').Replace("_", string.Empty);

// Java marks a long literal with a trailing L/l. Strip it as a suffix only: a blanket
// Replace would also corrupt digits in a value we echo back into the generated source.
if (value.EndsWith('L') || value.EndsWith('l'))
{
value = value[..^1];
}

long int64Value;

if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// Convert.ToInt64 accepts the 0x prefix and wraps values above long.MaxValue
// (e.g. 0xFFFFFFFFFFFFFFFF -> -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))
{
Expand All @@ -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));
}
}
Loading