Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider the underlying type if the conversion is a nullable value conversion - fixes #1160 #1161

Merged
merged 1 commit into from
Jan 5, 2025
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
32 changes: 20 additions & 12 deletions CodeConverter/CSharp/TypeConversionAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public TypeConversionKind AnalyzeConversion(VBSyntax.ExpressionSyntax vbNode, bo
var csConvertedType = GetCSType(vbConvertedType);

if (csType != null && csConvertedType != null &&
TryAnalyzeCsConversion(vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) {
TryAnalyzeCsConversion(vbCompilation, vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) {
return analyzeConversion;
}

Expand Down Expand Up @@ -273,20 +273,28 @@ private ITypeSymbol GetCSType(ITypeSymbol vbType, VBSyntax.ExpressionSyntax vbNo
return csType;
}

private bool TryAnalyzeCsConversion(VBSyntax.ExpressionSyntax vbNode, ITypeSymbol csType,
private bool TryAnalyzeCsConversion(VBasic.VisualBasicCompilation vbCompilation, VBSyntax.ExpressionSyntax vbNode, ITypeSymbol csType,
ITypeSymbol csConvertedType, Conversion vbConversion, ITypeSymbol vbConvertedType, ITypeSymbol vbType, bool isConst, bool sourceForced,
out TypeConversionKind typeConversionKind)
{
var csConversion = _csCompilation.ClassifyConversion(csType, csConvertedType);
vbType.IsNullable(out var underlyingType);
vbConvertedType.IsNullable(out var underlyingConvertedType);
var nullableVbType = underlyingType ?? vbType;
var nullableVbConvertedType = underlyingConvertedType ?? vbConvertedType;

vbType.IsNullable(out var underlyingVbType);
vbConvertedType.IsNullable(out var underlyingVbConvertedType);
underlyingVbType ??= vbType;
underlyingVbConvertedType ??= vbConvertedType;
var vbUnderlyingConversion = vbCompilation.ClassifyConversion(underlyingVbType, underlyingVbConvertedType);

csType.IsNullable(out var underlyingCsType);
csConvertedType.IsNullable(out var underlyingCsConvertedType);
underlyingCsType ??= csType;
underlyingCsConvertedType ??= csConvertedType;
var csUnderlyingConversion = _csCompilation.ClassifyConversion(underlyingCsType, underlyingCsConvertedType);

bool isConvertToString =
(vbConversion.IsString || vbConversion.IsReference && vbConversion.IsNarrowing) && vbConvertedType.SpecialType == SpecialType.System_String;
bool isConvertFractionalToInt =
!csConversion.IsImplicit && nullableVbType.IsFractionalNumericType() && nullableVbConvertedType.IsIntegralOrEnumType();
!csConversion.IsImplicit && underlyingVbType.IsFractionalNumericType() && underlyingVbConvertedType.IsIntegralOrEnumType();

if (!csConversion.Exists || csConversion.IsUnboxing) {
if (ConvertStringToCharLiteral(vbNode, vbConvertedType, out _)) {
Expand All @@ -300,27 +308,27 @@ private bool TryAnalyzeCsConversion(VBSyntax.ExpressionSyntax vbNode, ITypeSymbo
return true;
}
if (isConvertToString || vbConversion.IsNarrowing) {
typeConversionKind = nullableVbConvertedType.IsEnumType() && !csConversion.Exists
typeConversionKind = underlyingVbConvertedType.IsEnumType() && !csConversion.Exists
? TypeConversionKind.EnumConversionThenCast
: TypeConversionKind.Conversion;
return true;
}
} else if (vbConversion.IsNarrowing && vbConversion.IsNullableValueType && isConvertFractionalToInt) {
typeConversionKind = TypeConversionKind.FractionalNumberRoundThenCast;
return true;
} else if (vbConversion.IsNumeric && (csConversion.IsNumeric || nullableVbConvertedType.IsEnumType()) && isConvertFractionalToInt) {
} else if (vbConversion.IsNumeric && (csConversion.IsNumeric || underlyingVbConvertedType.IsEnumType()) && isConvertFractionalToInt) {
typeConversionKind = TypeConversionKind.FractionalNumberRoundThenCast;
return true;
} else if (csConversion is {IsExplicit: true, IsEnumeration: true} or {IsBoxing: true, IsImplicit: false}) {
typeConversionKind = TypeConversionKind.NonDestructiveCast;
return true;
} else if (vbConversion.IsNumeric && csConversion.IsNumeric) {
} else if (vbUnderlyingConversion.IsNumeric && csUnderlyingConversion.IsNumeric) {
// For widening, implicit, a cast is really only needed to help resolve the overload for the operator/method used.
// e.g. When VB "&" changes to C# "+", there are lots more overloads available that implicit casts could match.
// e.g. sbyte * ulong uses the decimal * operator in VB. In C# it's ambiguous - see ExpressionTests.vb "TestMul".
typeConversionKind =
isConst && IsImplicitConstantConversion(vbNode) || csConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, vbConvertedType) ? TypeConversionKind.Identity :
csConversion.IsImplicit || vbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast
isConst && IsImplicitConstantConversion(vbNode) || csUnderlyingConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, underlyingVbConvertedType) ? TypeConversionKind.Identity :
csUnderlyingConversion.IsImplicit || underlyingVbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast
: TypeConversionKind.Conversion;
return true;
} else if (isConvertToString && vbType.SpecialType == SpecialType.System_Object) {
Expand Down
24 changes: 24 additions & 0 deletions Tests/CSharp/ExpressionTests/BinaryExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ private void TestMethod()
}");
}

[Fact]
public async Task NullableDoubleArithmeticAsync()
{
await TestConversionVisualBasicToCSharpAsync(@"Class TestClass
Private Sub TestMethod()
Dim TotalRead As Long = 1
Dim ContentLength As Long? = 2 '(It is supposed that TotalRead < ContentLength)
Dim percentage1 As Integer = Convert.ToInt32((TotalRead / ContentLength) * 100.0)
Dim percentage2 As Integer = Convert.ToInt32(TotalRead / ContentLength * 100.0)
End Sub
End Class", @"using System;

internal partial class TestClass
{
private void TestMethod()
{
long TotalRead = 1L;
long? ContentLength = 2; // (It is supposed that TotalRead < ContentLength)
int percentage1 = Convert.ToInt32(TotalRead / (double?)ContentLength * 100.0d);
int percentage2 = Convert.ToInt32(TotalRead / (double?)ContentLength * 100.0d);
}
}");
}

[Fact]
public async Task ImplicitConversionsAsync()
{
Expand Down
8 changes: 8 additions & 0 deletions Tests/TestData/SelfVerifyingTests/VBToCS/ArithmeticTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Public Class ArithmeticTests
Assert.Equal(x, 3.5)
End Sub

<Fact>
Public Sub TestNullableFloatingPointDivision()
Dim TotalRead As Long = 1
Dim ContentLength As Long? = 2 '(It is supposed that TotalRead < ContentLength)
Dim percentage As Integer = Convert.ToInt32((TotalRead / ContentLength) * 100.0)
Assert.Equal(50, percentage)
End Sub

<Fact>
Public Sub TestIntegerDivisionOfIntegers()
Dim x = 7 \ 2
Expand Down
Loading