From f4cc5c950e0fec773c573620dbe0b34e15166a7e Mon Sep 17 00:00:00 2001 From: gaschd Date: Mon, 11 Nov 2024 23:06:48 +0100 Subject: [PATCH 1/5] Adding tests for #1159 related to inferred generic types with cast integer values --- Tests/CSharp/TypeCastTests.cs | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Tests/CSharp/TypeCastTests.cs b/Tests/CSharp/TypeCastTests.cs index 19b304e5..bfbe4dca 100644 --- a/Tests/CSharp/TypeCastTests.cs +++ b/Tests/CSharp/TypeCastTests.cs @@ -1559,6 +1559,47 @@ private static T GenericFunctionWithCastThatExistsInCsharp() where T : TestGe } [Fact] + public async Task TestInferringImplicitGenericTypesAsync() + { + await TestConversionVisualBasicToCSharpAsync(@" +Imports System +Imports System.Linq + +Public Class TestClass + Public Sub GenerateFromConstants + Dim floatArr = Enumerable.Repeat(1.0F, 5).ToArray() + Dim doubleArr = Enumerable.Repeat(2.0, 5).ToArray() + Dim decimalArr = Enumerable.Repeat(3.0D, 5).ToArray() + Dim boolArr = Enumerable.Repeat(true, 5).ToArray() + Dim intArr = Enumerable.Repeat(1, 5).ToArray() + Dim uintArr = Enumerable.Repeat(1ui, 5).ToArray() + Dim longArr = Enumerable.Repeat(1l, 5).ToArray() + Dim ulongArr = Enumerable.Repeat(1ul, 5).ToArray() + Dim charArr = Enumerable.Repeat(""a""c, 5).ToArray() + Dim strArr = Enumerable.Repeat(""a"", 5).ToArray() + Dim objArr = Enumerable.Repeat(new object(), 5).ToArray() + End Sub + + Public Sub GenerateFromCasts + Dim floatArr = Enumerable.Repeat(CSng(1), 5).ToArray() + Dim doubleArr = Enumerable.Repeat(CDbl(2), 5).ToArray() + Dim decimalArr = Enumerable.Repeat(CDec(3), 5).ToArray() + Dim boolArr = Enumerable.Repeat(CBool(1), 5).ToArray() + Dim intArr = Enumerable.Repeat(CInt(1.0), 5).ToArray() + Dim uintArr = Enumerable.Repeat(CUInt(1.0), 5).ToArray() + Dim longArr = Enumerable.Repeat(CLng(1.0), 5).ToArray() + Dim ulongArr = Enumerable.Repeat(CULng(1.0), 5).ToArray() + Dim charArr = Enumerable.Repeat(CChar(""a""), 5).ToArray() + Dim strArr = Enumerable.Repeat(CStr(""a""c), 5).ToArray() + Dim objArr1 = Enumerable.Repeat(CObj(""a""), 5).ToArray() + Dim objArr2 = Enumerable.Repeat(CType(""a"", object), 5).ToArray() + End Sub +End Class +", @""); + } + + + [Fact] public async Task TestCTypeStringToEnumAsync() { await TestConversionVisualBasicToCSharpAsync( From 2b2e9ea590569c9925664d343f4ab8a2e661b6cd Mon Sep 17 00:00:00 2001 From: GrahamTheCoder Date: Sun, 5 Jan 2025 17:11:37 +0000 Subject: [PATCH 2/5] Set expectation --- Tests/CSharp/TypeCastTests.cs | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Tests/CSharp/TypeCastTests.cs b/Tests/CSharp/TypeCastTests.cs index bfbe4dca..cace3a47 100644 --- a/Tests/CSharp/TypeCastTests.cs +++ b/Tests/CSharp/TypeCastTests.cs @@ -1595,7 +1595,44 @@ Public Sub GenerateFromCasts Dim objArr2 = Enumerable.Repeat(CType(""a"", object), 5).ToArray() End Sub End Class -", @""); +", @" +using System; +using System.Linq; +using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic + +public partial class TestClass +{ + public void GenerateFromConstants() + { + float[] floatArr = Enumerable.Repeat(1.0f, 5).ToArray(); + double[] doubleArr = Enumerable.Repeat(2.0d, 5).ToArray(); + decimal[] decimalArr = Enumerable.Repeat(3.0m, 5).ToArray(); + bool[] boolArr = Enumerable.Repeat(true, 5).ToArray(); + int[] intArr = Enumerable.Repeat(1, 5).ToArray(); + uint[] uintArr = Enumerable.Repeat(1U, 5).ToArray(); + long[] longArr = Enumerable.Repeat(1L, 5).ToArray(); + ulong[] ulongArr = Enumerable.Repeat(1UL, 5).ToArray(); + char[] charArr = Enumerable.Repeat('a', 5).ToArray(); + string[] strArr = Enumerable.Repeat(""a"", 5).ToArray(); + object[] objArr = Enumerable.Repeat(new object(), 5).ToArray(); + } + + public void GenerateFromCasts() + { + float[] floatArr = Enumerable.Repeat(1f, 5).ToArray(); + double[] doubleArr = Enumerable.Repeat(2d, 5).ToArray(); + decimal[] decimalArr = Enumerable.Repeat(3m, 5).ToArray(); + bool[] boolArr = Enumerable.Repeat(Conversions.ToBoolean(1), 5).ToArray(); + int[] intArr = Enumerable.Repeat((int)Math.Round(1.0d), 5).ToArray(); + uint[] uintArr = Enumerable.Repeat((uint)Math.Round(1.0d), 5).ToArray(); + long[] longArr = Enumerable.Repeat((long)Math.Round(1.0d), 5).ToArray(); + ulong[] ulongArr = Enumerable.Repeat((ulong)Math.Round(1.0d), 5).ToArray(); + char[] charArr = Enumerable.Repeat('a', 5).ToArray(); + string[] strArr = Enumerable.Repeat(""a"", 5).ToArray(); + object[] objArr1 = Enumerable.Repeat((object)""a"", 5).ToArray(); + object[] objArr2 = Enumerable.Repeat((object)""a"", 5).ToArray(); + } +}"); } From 2b3f31615590d2a5974addb0eef1002e4020f528 Mon Sep 17 00:00:00 2001 From: GrahamTheCoder Date: Sun, 5 Jan 2025 17:11:55 +0000 Subject: [PATCH 3/5] Specially handle using the literal suffix to change the type --- .../CSharp/TypeConversionAnalyzer.cs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index 385fd102..3da81d0e 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -178,7 +178,7 @@ public TypeConversionKind AnalyzeConversion(VBSyntax.ExpressionSyntax vbNode, bo var csConvertedType = GetCSType(vbConvertedType); if (csType != null && csConvertedType != null && - TryAnalyzeCsConversion(vbCompilation, vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) { + TryAnalyzeCsConversion(vbNode, csType, csConvertedType, vbConversion, vbConvertedType, vbType, isConst, forceSourceType != null, out TypeConversionKind analyzeConversion)) { return analyzeConversion; } @@ -273,28 +273,20 @@ private ITypeSymbol GetCSType(ITypeSymbol vbType, VBSyntax.ExpressionSyntax vbNo return csType; } - private bool TryAnalyzeCsConversion(VBasic.VisualBasicCompilation vbCompilation, VBSyntax.ExpressionSyntax vbNode, ITypeSymbol csType, + private bool TryAnalyzeCsConversion(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 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); + vbType.IsNullable(out var underlyingType); + vbConvertedType.IsNullable(out var underlyingConvertedType); + var nullableVbType = underlyingType ?? vbType; + var nullableVbConvertedType = underlyingConvertedType ?? vbConvertedType; bool isConvertToString = (vbConversion.IsString || vbConversion.IsReference && vbConversion.IsNarrowing) && vbConvertedType.SpecialType == SpecialType.System_String; bool isConvertFractionalToInt = - !csConversion.IsImplicit && underlyingVbType.IsFractionalNumericType() && underlyingVbConvertedType.IsIntegralOrEnumType(); + !csConversion.IsImplicit && nullableVbType.IsFractionalNumericType() && nullableVbConvertedType.IsIntegralOrEnumType(); if (!csConversion.Exists || csConversion.IsUnboxing) { if (ConvertStringToCharLiteral(vbNode, vbConvertedType, out _)) { @@ -308,7 +300,7 @@ private bool TryAnalyzeCsConversion(VBasic.VisualBasicCompilation vbCompilation, return true; } if (isConvertToString || vbConversion.IsNarrowing) { - typeConversionKind = underlyingVbConvertedType.IsEnumType() && !csConversion.Exists + typeConversionKind = nullableVbConvertedType.IsEnumType() && !csConversion.Exists ? TypeConversionKind.EnumConversionThenCast : TypeConversionKind.Conversion; return true; @@ -316,19 +308,19 @@ private bool TryAnalyzeCsConversion(VBasic.VisualBasicCompilation vbCompilation, } else if (vbConversion.IsNarrowing && vbConversion.IsNullableValueType && isConvertFractionalToInt) { typeConversionKind = TypeConversionKind.FractionalNumberRoundThenCast; return true; - } else if (vbConversion.IsNumeric && (csConversion.IsNumeric || underlyingVbConvertedType.IsEnumType()) && isConvertFractionalToInt) { + } else if (vbConversion.IsNumeric && (csConversion.IsNumeric || nullableVbConvertedType.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 (vbUnderlyingConversion.IsNumeric && csUnderlyingConversion.IsNumeric) { + } else if (vbConversion.IsNumeric && csConversion.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) || csUnderlyingConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, underlyingVbConvertedType) ? TypeConversionKind.Identity : - csUnderlyingConversion.IsImplicit || underlyingVbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast + isConst && IsImplicitConstantConversion(vbNode) || csConversion.IsIdentity || !sourceForced && IsExactTypeNumericLiteral(vbNode, vbConvertedType) ? TypeConversionKind.Identity : + csConversion.IsImplicit || vbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast : TypeConversionKind.Conversion; return true; } else if (isConvertToString && vbType.SpecialType == SpecialType.System_Object) { From 6704752d677c4b4fb0e5d3dbc78c56d0f17bfc87 Mon Sep 17 00:00:00 2001 From: GrahamTheCoder Date: Sun, 5 Jan 2025 17:14:31 +0000 Subject: [PATCH 4/5] Specially handle using the literal suffix to change the type - fixes #1161 --- .../CSharp/TypeConversionAnalyzer.cs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index 3da81d0e..c727d8d0 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -98,6 +98,11 @@ private ExpressionSyntax AddTypeConversion(VBSyntax.ExpressionSyntax vbNode, Exp var enumUnderlyingType = ((INamedTypeSymbol) vbType).EnumUnderlyingType; csNode = AddTypeConversion(vbNode, csNode, TypeConversionKind.NonDestructiveCast, addParenthesisIfNeeded, vbType, enumUnderlyingType); return AddTypeConversion(vbNode, csNode, TypeConversionKind.Conversion, addParenthesisIfNeeded, enumUnderlyingType, vbConvertedType); + case TypeConversionKind.LiteralSuffix: + if (vbNode is VBSyntax.LiteralExpressionSyntax { Token: { Value: { } val, Text: { } text } } && LiteralConversions.GetLiteralExpression(val, text, vbConvertedType) is { } csLiteral) { + return csLiteral; + } + return csNode; case TypeConversionKind.Unknown: case TypeConversionKind.Identity: return addParenthesisIfNeeded ? vbNode.ParenthesizeIfPrecedenceCouldChange(csNode) : csNode; @@ -178,7 +183,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; } @@ -273,20 +278,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 _)) { @@ -300,7 +313,7 @@ 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; @@ -308,19 +321,19 @@ private bool TryAnalyzeCsConversion(VBSyntax.ExpressionSyntax vbNode, ITypeSymbo } 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.LiteralSuffix : + csUnderlyingConversion.IsImplicit || underlyingVbType.IsNumericType() ? TypeConversionKind.NonDestructiveCast : TypeConversionKind.Conversion; return true; } else if (isConvertToString && vbType.SpecialType == SpecialType.System_Object) { @@ -494,7 +507,8 @@ public enum TypeConversionKind NullableBool, StringToCharArray, DelegateConstructor, - FractionalNumberRoundThenCast + FractionalNumberRoundThenCast, + LiteralSuffix } public static bool ConvertStringToCharLiteral(VBSyntax.ExpressionSyntax node, From c5180ef889be8fdf5214ad287e93964f5058846f Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 19 Jan 2025 23:43:42 +0000 Subject: [PATCH 5/5] Respect prior conversion details --- CodeConverter/CSharp/TypeConversionAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index c727d8d0..b43f7296 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -99,7 +99,7 @@ private ExpressionSyntax AddTypeConversion(VBSyntax.ExpressionSyntax vbNode, Exp csNode = AddTypeConversion(vbNode, csNode, TypeConversionKind.NonDestructiveCast, addParenthesisIfNeeded, vbType, enumUnderlyingType); return AddTypeConversion(vbNode, csNode, TypeConversionKind.Conversion, addParenthesisIfNeeded, enumUnderlyingType, vbConvertedType); case TypeConversionKind.LiteralSuffix: - if (vbNode is VBSyntax.LiteralExpressionSyntax { Token: { Value: { } val, Text: { } text } } && LiteralConversions.GetLiteralExpression(val, text, vbConvertedType) is { } csLiteral) { + if (csNode is CSSyntax.LiteralExpressionSyntax && vbNode is VBSyntax.LiteralExpressionSyntax { Token: { Value: { } val, Text: { } text } } && LiteralConversions.GetLiteralExpression(val, text, vbConvertedType) is { } csLiteral) { return csLiteral; } return csNode;