Skip to content

Commit cde4d4d

Browse files
Evaluate simple compile time conversions within const declarations
1 parent bc05a24 commit cde4d4d

File tree

5 files changed

+48
-30
lines changed

5 files changed

+48
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1818
* Convert optional ref parameters - fixes #91
1919
* Always convert Call statement to method call [#445](https://github.com/icsharpcode/CodeConverter/issues/445)
2020
* Avoid compilation errors when converting const Dates [#213](https://github.com/icsharpcode/CodeConverter/issues/213)
21+
* Evaluate simple compile time conversions within const declarations
2122

2223
### C# -> VB
2324

CodeConverter/CSharp/OperationExtensions.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.CodeAnalysis;
1+
using ICSharpCode.CodeConverter.Util.FromRoslyn;
2+
using Microsoft.CodeAnalysis;
23
using Microsoft.CodeAnalysis.Operations;
34

45
namespace ICSharpCode.CodeConverter.CSharp
@@ -15,21 +16,12 @@ public static IOperation GetParentIgnoringConversions(this IOperation operation)
1516
return parent;
1617
}
1718

18-
public static IOperation GetIgnoringParentheses(this IOperation operation)
19-
{
20-
while (operation is IParenthesizedOperation) {
21-
operation = operation?.Parent;
22-
}
23-
24-
return operation;
25-
}
26-
27-
public static IOperation GetNonConversionOperation(this IOperation operation)
19+
public static IOperation SkipParens(this IOperation operation, bool skipImplicitNumericConvert = false)
2820
{
2921
while (true) {
3022
switch (operation)
3123
{
32-
case IConversionOperation co:
24+
case IConversionOperation co when skipImplicitNumericConvert && co.IsImplicit && co.Conversion.IsNumeric && !co.Conversion.IsUserDefined && co.Operand.Type.IsNumericType() && co.Type.IsNumericType():
3325
operation = co.Operand;
3426
break;
3527
case IParenthesizedOperation po:

CodeConverter/CSharp/TypeConversionAnalyzer.cs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
2424
using TypeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax;
2525
using Microsoft.CodeAnalysis.CSharp.Syntax;
26+
using System.Linq.Expressions;
2627

2728
namespace ICSharpCode.CodeConverter.CSharp
2829
{
@@ -73,14 +74,19 @@ public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic
7374
csNode = addParenthesisIfNeeded && (conversionKind == TypeConversionKind.DestructiveCast || conversionKind == TypeConversionKind.NonDestructiveCast)
7475
? VbSyntaxNodeExtensions.ParenthesizeIfPrecedenceCouldChange(vbNode, csNode)
7576
: csNode;
76-
return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, forceTargetType: forceTargetType);
77+
return AddExplicitConversion(vbNode, csNode, conversionKind, addParenthesisIfNeeded, isConst, forceTargetType: forceTargetType);
7778
}
7879

79-
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, ITypeSymbol forceTargetType = null)
80+
public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool isConst = false, ITypeSymbol forceTargetType = null)
8081
{
8182
var typeInfo = ModelExtensions.GetTypeInfo(_semanticModel, vbNode);
8283
var vbType = typeInfo.Type;
8384
var vbConvertedType = forceTargetType ?? typeInfo.ConvertedType;
85+
86+
if (isConst && GetConstantOrNull(vbNode, vbConvertedType, csNode) is ExpressionSyntax constLiteral) {
87+
return constLiteral;
88+
}
89+
8490
switch (conversionKind)
8591
{
8692
case TypeConversionKind.Unknown:
@@ -90,9 +96,7 @@ public ExpressionSyntax AddExplicitConversion(Microsoft.CodeAnalysis.VisualBasic
9096
case TypeConversionKind.NonDestructiveCast:
9197
return CreateCast(csNode, vbConvertedType);
9298
case TypeConversionKind.Conversion:
93-
return AddExplicitConvertTo(vbNode, csNode, vbType, vbConvertedType);
94-
case TypeConversionKind.ConstConversion:
95-
return ConstantFold(vbNode, vbConvertedType);
99+
return AddExplicitConvertTo(vbNode, csNode, vbType, vbConvertedType);;
96100
case TypeConversionKind.NullableBool:
97101
return SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, csNode,
98102
LiteralConversions.GetLiteralExpression(true));
@@ -189,7 +193,7 @@ private bool TryAnalyzeCsConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.Ex
189193
return true;
190194
}
191195
if (isConvertToString || vbConversion.IsNarrowing) {
192-
typeConversionKind = isConst ? TypeConversionKind.ConstConversion : TypeConversionKind.Conversion;
196+
typeConversionKind = TypeConversionKind.Conversion;
193197
return true;
194198
}
195199
} else if (vbConversion.IsWidening && vbConversion.IsNumeric && csConversion.IsImplicit &&
@@ -207,21 +211,21 @@ private bool TryAnalyzeCsConversion(Microsoft.CodeAnalysis.VisualBasic.Syntax.Ex
207211
vbCompilation.ClassifyConversion(vbConvertedType,
208212
vbCompilation.GetTypeByMetadataName("System.Int32"));
209213
if (arithmeticConversion.IsWidening && !arithmeticConversion.IsIdentity) {
210-
typeConversionKind = isConst ? TypeConversionKind.ConstConversion : TypeConversionKind.Conversion;
214+
typeConversionKind = TypeConversionKind.Conversion;
211215
return true;
212216
}
213217
} else if (csConversion.IsExplicit && csConversion.IsNumeric && vbConversion.IsNarrowing && isConst) {
214218
typeConversionKind = IsImplicitConstantConversion(vbNode) ? TypeConversionKind.Identity : TypeConversionKind.NonDestructiveCast;
215219
return true;
216220
} else if (csConversion.IsExplicit && vbConversion.IsNumeric && vbType.TypeKind != TypeKind.Enum) {
217221
typeConversionKind = IsImplicitConstantConversion(vbNode) ? TypeConversionKind.Identity :
218-
isConst ? TypeConversionKind.ConstConversion : TypeConversionKind.Conversion;
222+
TypeConversionKind.Conversion;
219223
return true;
220224
} else if (csConversion.IsExplicit && vbConversion.IsIdentity && csConversion.IsNumeric && vbType.TypeKind != TypeKind.Enum) {
221-
typeConversionKind = isConst ? TypeConversionKind.ConstConversion : TypeConversionKind.Conversion;
225+
typeConversionKind = TypeConversionKind.Conversion;
222226
return true;
223227
} else if (isConvertToString && vbType.SpecialType == SpecialType.System_Object) {
224-
typeConversionKind = isConst ? TypeConversionKind.ConstConversion : TypeConversionKind.Conversion;
228+
typeConversionKind = TypeConversionKind.Conversion;
225229
return true;
226230
} else if (csConversion.IsNullable && csConvertedType.SpecialType == SpecialType.System_Boolean) {
227231
typeConversionKind = TypeConversionKind.NullableBool;
@@ -260,16 +264,36 @@ private static TypeConversionKind AnalyzeVbConversion(bool alwaysExplicit, IType
260264
return TypeConversionKind.Unknown;
261265
}
262266

263-
private ExpressionSyntax ConstantFold(VBSyntax.ExpressionSyntax vbNode, ITypeSymbol type)
267+
private ExpressionSyntax GetConstantOrNull(VBSyntax.ExpressionSyntax vbNode, ITypeSymbol type, ExpressionSyntax csNode)
264268
{
265-
var vbOperation = _semanticModel.GetOperation(vbNode);
269+
var vbOperation = _semanticModel.GetOperation(vbNode).SkipParens(true);
270+
271+
// Guideline tradeoff: Usually would aim for erring on the side of correct runtime behaviour. But making lots of simple constants unreadable for the sake of an edge case that will turn into an easily fixed compile error seems overkill.
272+
// See https://github.com/icsharpcode/CodeConverter/blob/master/.github/CONTRIBUTING.md#deciding-what-the-output-should-be
273+
if (Equals(vbOperation.Type, type) && IsProbablyConstExpression(vbOperation)) return csNode;
266274

267275
if (TryCompileTimeEvaluate(vbOperation, out var result) && ConversionsTypeFullNames.TryGetValue(type.GetFullMetadataName(), out var method)) {
268276
result = method.Invoke(null, new[] { result });
269-
return LiteralConversions.GetLiteralExpression(result);
277+
return LiteralConversions.GetLiteralExpression(result, convertedType: type);
278+
}
279+
280+
return null;
281+
}
282+
283+
/// <remarks>Deal with cases like "2*PI" without inlining the const</remarks>
284+
private bool IsProbablyConstExpression(IOperation op)
285+
{
286+
op = op.SkipParens(true);
287+
288+
if (op is IFieldReferenceOperation fro && fro.Field.IsConst || op is ILocalReferenceOperation lro && lro.Local.IsConst || op is ILiteralOperation) {
289+
return true;
270290
}
271291

272-
throw new NotImplementedException("Cannot generate constant C# expression");
292+
if (op is IBinaryOperation bo && IsProbablyConstExpression(bo.LeftOperand) && IsProbablyConstExpression(bo.RightOperand)) {
293+
return true;
294+
}
295+
296+
return false;
273297
}
274298

275299
private bool TryCompileTimeEvaluate(IOperation vbOperation, out object result)
@@ -427,7 +451,6 @@ public enum TypeConversionKind
427451
DestructiveCast,
428452
NonDestructiveCast,
429453
Conversion,
430-
ConstConversion,
431454
NullableBool,
432455
StringToCharArray
433456
}

Tests/CSharp/ExpressionTests/ExpressionTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ await TestConversionVisualBasicToCSharp(
15041504
Const k As UInteger = 1
15051505
Const l As UShort = 1
15061506
Const m As ULong = 1
1507+
Const Nl As String = ChrW(13) + ChrW(10)
15071508
15081509
Sub Main()
15091510
Const x As SByte = 4
@@ -1512,7 +1513,7 @@ End Sub
15121513
internal static partial class Module1
15131514
{
15141515
private const bool a = true;
1515-
private const char b = (char)1;
1516+
private const char b = '\u0001';
15161517
private const float c = 1;
15171518
private const double d = 1;
15181519
private const decimal e = 1;
@@ -1524,6 +1525,7 @@ internal static partial class Module1
15241525
private const uint k = 1;
15251526
private const ushort l = 1;
15261527
private const ulong m = 1;
1528+
private const string Nl = ""\r\n"";
15271529
15281530
public static void Main()
15291531
{

Tests/CSharp/StatementTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,7 +1073,7 @@ await TestConversionVisualBasicToCSharp(
10731073
Private Sub TestMethod()
10741074
the_beginning:
10751075
Dim value As Integer = 1
1076-
Const myPIe As Double = System.Math.PI
1076+
Const myPIe As Double = 2 * System.Math.PI
10771077
Dim text = ""This is my text!""
10781078
GoTo the_beginning
10791079
End Sub
@@ -1086,7 +1086,7 @@ private void TestMethod()
10861086
the_beginning:
10871087
;
10881088
int value = 1;
1089-
const double myPIe = Math.PI;
1089+
const double myPIe = 2 * Math.PI;
10901090
string text = ""This is my text!"";
10911091
goto the_beginning;
10921092
}

0 commit comments

Comments
 (0)