2323using SyntaxKind = Microsoft . CodeAnalysis . CSharp . SyntaxKind ;
2424using TypeSyntax = Microsoft . CodeAnalysis . CSharp . Syntax . TypeSyntax ;
2525using Microsoft . CodeAnalysis . CSharp . Syntax ;
26+ using System . Linq . Expressions ;
2627
2728namespace 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 }
0 commit comments