Skip to content

Commit 67185b0

Browse files
Merge pull request #1161 from icsharpcode/issue/1160
Consider the underlying type if the conversion is a nullable value conversion - fixes #1160
2 parents abea701 + b9f5a55 commit 67185b0

File tree

3 files changed

+52
-12
lines changed

3 files changed

+52
-12
lines changed

CodeConverter/CSharp/TypeConversionAnalyzer.cs

+20-12
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public TypeConversionKind AnalyzeConversion(VBSyntax.ExpressionSyntax vbNode, bo
178178
var csConvertedType = GetCSType(vbConvertedType);
179179

180180
if (csType != null && csConvertedType != null &&
181-
TryAnalyzeCsConversion(vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) {
181+
TryAnalyzeCsConversion(vbCompilation, vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) {
182182
return analyzeConversion;
183183
}
184184

@@ -273,20 +273,28 @@ private ITypeSymbol GetCSType(ITypeSymbol vbType, VBSyntax.ExpressionSyntax vbNo
273273
return csType;
274274
}
275275

276-
private bool TryAnalyzeCsConversion(VBSyntax.ExpressionSyntax vbNode, ITypeSymbol csType,
276+
private bool TryAnalyzeCsConversion(VBasic.VisualBasicCompilation vbCompilation, VBSyntax.ExpressionSyntax vbNode, ITypeSymbol csType,
277277
ITypeSymbol csConvertedType, Conversion vbConversion, ITypeSymbol vbConvertedType, ITypeSymbol vbType, bool isConst, bool sourceForced,
278278
out TypeConversionKind typeConversionKind)
279279
{
280280
var csConversion = _csCompilation.ClassifyConversion(csType, csConvertedType);
281-
vbType.IsNullable(out var underlyingType);
282-
vbConvertedType.IsNullable(out var underlyingConvertedType);
283-
var nullableVbType = underlyingType ?? vbType;
284-
var nullableVbConvertedType = underlyingConvertedType ?? vbConvertedType;
281+
282+
vbType.IsNullable(out var underlyingVbType);
283+
vbConvertedType.IsNullable(out var underlyingVbConvertedType);
284+
underlyingVbType ??= vbType;
285+
underlyingVbConvertedType ??= vbConvertedType;
286+
var vbUnderlyingConversion = vbCompilation.ClassifyConversion(underlyingVbType, underlyingVbConvertedType);
287+
288+
csType.IsNullable(out var underlyingCsType);
289+
csConvertedType.IsNullable(out var underlyingCsConvertedType);
290+
underlyingCsType ??= csType;
291+
underlyingCsConvertedType ??= csConvertedType;
292+
var csUnderlyingConversion = _csCompilation.ClassifyConversion(underlyingCsType, underlyingCsConvertedType);
285293

286294
bool isConvertToString =
287295
(vbConversion.IsString || vbConversion.IsReference && vbConversion.IsNarrowing) && vbConvertedType.SpecialType == SpecialType.System_String;
288296
bool isConvertFractionalToInt =
289-
!csConversion.IsImplicit && nullableVbType.IsFractionalNumericType() && nullableVbConvertedType.IsIntegralOrEnumType();
297+
!csConversion.IsImplicit && underlyingVbType.IsFractionalNumericType() && underlyingVbConvertedType.IsIntegralOrEnumType();
290298

291299
if (!csConversion.Exists || csConversion.IsUnboxing) {
292300
if (ConvertStringToCharLiteral(vbNode, vbConvertedType, out _)) {
@@ -300,27 +308,27 @@ private bool TryAnalyzeCsConversion(VBSyntax.ExpressionSyntax vbNode, ITypeSymbo
300308
return true;
301309
}
302310
if (isConvertToString || vbConversion.IsNarrowing) {
303-
typeConversionKind = nullableVbConvertedType.IsEnumType() && !csConversion.Exists
311+
typeConversionKind = underlyingVbConvertedType.IsEnumType() && !csConversion.Exists
304312
? TypeConversionKind.EnumConversionThenCast
305313
: TypeConversionKind.Conversion;
306314
return true;
307315
}
308316
} else if (vbConversion.IsNarrowing && vbConversion.IsNullableValueType && isConvertFractionalToInt) {
309317
typeConversionKind = TypeConversionKind.FractionalNumberRoundThenCast;
310318
return true;
311-
} else if (vbConversion.IsNumeric && (csConversion.IsNumeric || nullableVbConvertedType.IsEnumType()) && isConvertFractionalToInt) {
319+
} else if (vbConversion.IsNumeric && (csConversion.IsNumeric || underlyingVbConvertedType.IsEnumType()) && isConvertFractionalToInt) {
312320
typeConversionKind = TypeConversionKind.FractionalNumberRoundThenCast;
313321
return true;
314322
} else if (csConversion is {IsExplicit: true, IsEnumeration: true} or {IsBoxing: true, IsImplicit: false}) {
315323
typeConversionKind = TypeConversionKind.NonDestructiveCast;
316324
return true;
317-
} else if (vbConversion.IsNumeric && csConversion.IsNumeric) {
325+
} else if (vbUnderlyingConversion.IsNumeric && csUnderlyingConversion.IsNumeric) {
318326
// For widening, implicit, a cast is really only needed to help resolve the overload for the operator/method used.
319327
// e.g. When VB "&" changes to C# "+", there are lots more overloads available that implicit casts could match.
320328
// e.g. sbyte * ulong uses the decimal * operator in VB. In C# it's ambiguous - see ExpressionTests.vb "TestMul".
321329
typeConversionKind =
322-
isConst && IsImplicitConstantConversion(vbNode) || csConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, vbConvertedType) ? TypeConversionKind.Identity :
323-
csConversion.IsImplicit || vbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast
330+
isConst && IsImplicitConstantConversion(vbNode) || csUnderlyingConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, underlyingVbConvertedType) ? TypeConversionKind.Identity :
331+
csUnderlyingConversion.IsImplicit || underlyingVbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast
324332
: TypeConversionKind.Conversion;
325333
return true;
326334
} else if (isConvertToString && vbType.SpecialType == SpecialType.System_Object) {

Tests/CSharp/ExpressionTests/BinaryExpressionTests.cs

+24
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,30 @@ private void TestMethod()
122122
}");
123123
}
124124

125+
[Fact]
126+
public async Task NullableDoubleArithmeticAsync()
127+
{
128+
await TestConversionVisualBasicToCSharpAsync(@"Class TestClass
129+
Private Sub TestMethod()
130+
Dim TotalRead As Long = 1
131+
Dim ContentLength As Long? = 2 '(It is supposed that TotalRead < ContentLength)
132+
Dim percentage1 As Integer = Convert.ToInt32((TotalRead / ContentLength) * 100.0)
133+
Dim percentage2 As Integer = Convert.ToInt32(TotalRead / ContentLength * 100.0)
134+
End Sub
135+
End Class", @"using System;
136+
137+
internal partial class TestClass
138+
{
139+
private void TestMethod()
140+
{
141+
long TotalRead = 1L;
142+
long? ContentLength = 2; // (It is supposed that TotalRead < ContentLength)
143+
int percentage1 = Convert.ToInt32(TotalRead / (double?)ContentLength * 100.0d);
144+
int percentage2 = Convert.ToInt32(TotalRead / (double?)ContentLength * 100.0d);
145+
}
146+
}");
147+
}
148+
125149
[Fact]
126150
public async Task ImplicitConversionsAsync()
127151
{

Tests/TestData/SelfVerifyingTests/VBToCS/ArithmeticTests.vb

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ Public Class ArithmeticTests
1010
Assert.Equal(x, 3.5)
1111
End Sub
1212

13+
<Fact>
14+
Public Sub TestNullableFloatingPointDivision()
15+
Dim TotalRead As Long = 1
16+
Dim ContentLength As Long? = 2 '(It is supposed that TotalRead < ContentLength)
17+
Dim percentage As Integer = Convert.ToInt32((TotalRead / ContentLength) * 100.0)
18+
Assert.Equal(50, percentage)
19+
End Sub
20+
1321
<Fact>
1422
Public Sub TestIntegerDivisionOfIntegers()
1523
Dim x = 7 \ 2

0 commit comments

Comments
 (0)