1
+ using Microsoft . VisualBasic ;
2
+
3
+ namespace ICSharpCode . CodeConverter . CSharp ;
4
+
5
+ internal class ArgumentConverter
6
+ {
7
+ public CommonConversions CommonConversions { get ; }
8
+ private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison ;
9
+ private readonly ITypeContext _typeContext ;
10
+ private readonly SemanticModel _semanticModel ;
11
+ private CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get ; }
12
+
13
+ public ArgumentConverter ( VisualBasicEqualityComparison visualBasicEqualityComparison , ITypeContext typeContext , SemanticModel semanticModel , CommonConversions commonConversions )
14
+ {
15
+ CommonConversions = commonConversions ;
16
+ _visualBasicEqualityComparison = visualBasicEqualityComparison ;
17
+ _typeContext = typeContext ;
18
+ _semanticModel = semanticModel ;
19
+ TriviaConvertingExpressionVisitor = commonConversions . TriviaConvertingExpressionVisitor ;
20
+ }
21
+
22
+ public async Task < CSharpSyntaxNode > ConvertSimpleArgumentAsync ( VBSyntax . SimpleArgumentSyntax node )
23
+ {
24
+ var argList = ( VBasic . Syntax . ArgumentListSyntax ) node . Parent ;
25
+ var invocation = argList . Parent ;
26
+ if ( invocation is VBasic . Syntax . ArrayCreationExpressionSyntax )
27
+ return await node . Expression . AcceptAsync < CSharpSyntaxNode > ( TriviaConvertingExpressionVisitor ) ;
28
+ var symbol = GetInvocationSymbol ( invocation ) ;
29
+ SyntaxToken token = default ( SyntaxToken ) ;
30
+ var convertedArgExpression = ( await node . Expression . AcceptAsync < CSSyntax . ExpressionSyntax > ( TriviaConvertingExpressionVisitor ) ) . SkipIntoParens ( ) ;
31
+ var typeConversionAnalyzer = CommonConversions . TypeConversionAnalyzer ;
32
+ var baseSymbol = symbol ? . OriginalDefinition . GetBaseSymbol ( ) ;
33
+ var possibleParameters = ( CommonConversions . GetCsOriginalSymbolOrNull ( baseSymbol ) ?? symbol ) ? . GetParameters ( ) ;
34
+ if ( possibleParameters . HasValue ) {
35
+ var refType = _semanticModel . GetRefConversionType ( node , argList , possibleParameters . Value , out var argName , out var refKind ) ;
36
+ token = CommonConversions . GetRefToken ( refKind ) ;
37
+ if ( refType != SemanticModelExtensions . RefConversion . Inline ) {
38
+ convertedArgExpression = HoistByRefDeclaration ( node , convertedArgExpression , refType , argName , refKind ) ;
39
+ } else {
40
+ convertedArgExpression = typeConversionAnalyzer . AddExplicitConversion ( node . Expression , convertedArgExpression , defaultToCast : refKind != RefKind . None ) ;
41
+ }
42
+ } else {
43
+ convertedArgExpression = typeConversionAnalyzer . AddExplicitConversion ( node . Expression , convertedArgExpression ) ;
44
+ }
45
+
46
+ var nameColon = node . IsNamed ? CS . SyntaxFactory . NameColon ( await node . NameColonEquals . Name . AcceptAsync < CSSyntax . IdentifierNameSyntax > ( TriviaConvertingExpressionVisitor ) ) : null ;
47
+ return CS . SyntaxFactory . Argument ( nameColon , token , convertedArgExpression ) ;
48
+ }
49
+
50
+ public async Task < IEnumerable < CSSyntax . ArgumentSyntax > > ConvertArgumentsAsync ( VBasic . Syntax . ArgumentListSyntax node )
51
+ {
52
+ ISymbol invocationSymbol = GetInvocationSymbol ( node . Parent ) ;
53
+ var forceNamedParameters = false ;
54
+ var invocationHasOverloads = invocationSymbol . HasOverloads ( ) ;
55
+
56
+ var processedParameters = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
57
+ var argumentSyntaxs = ( await node . Arguments . SelectAsync ( ConvertArg ) ) . Where ( a => a != null ) ;
58
+ return Enumerable . Concat ( argumentSyntaxs , GetAdditionalRequiredArgs ( node . Arguments , processedParameters , invocationSymbol , invocationHasOverloads ) ) ;
59
+
60
+ async Task < CSSyntax . ArgumentSyntax > ConvertArg ( VBSyntax . ArgumentSyntax arg , int argIndex )
61
+ {
62
+ var argName = arg is VBSyntax . SimpleArgumentSyntax { IsNamed : true } namedArg ? namedArg . NameColonEquals . Name . Identifier . Text : null ;
63
+ var parameterSymbol = invocationSymbol ? . GetParameters ( ) . GetArgument ( argName , argIndex ) ;
64
+ var convertedArg = await ConvertArgForParameter ( arg , parameterSymbol ) ;
65
+
66
+ if ( convertedArg is not null && parameterSymbol is not null ) {
67
+ processedParameters . Add ( parameterSymbol . Name ) ;
68
+ }
69
+
70
+ return convertedArg ;
71
+ }
72
+
73
+ async Task < CSSyntax . ArgumentSyntax > ConvertArgForParameter ( VBSyntax . ArgumentSyntax arg , IParameterSymbol parameterSymbol )
74
+ {
75
+ if ( arg . IsOmitted ) {
76
+ if ( invocationSymbol != null && ! invocationHasOverloads ) {
77
+ forceNamedParameters = true ;
78
+ return null ; //Prefer to skip omitted and use named parameters when the symbol has only one overload
79
+ }
80
+ return ConvertOmittedArgument ( parameterSymbol ) ;
81
+ }
82
+
83
+ var argSyntax = await arg . AcceptAsync < CSSyntax . ArgumentSyntax > ( TriviaConvertingExpressionVisitor ) ;
84
+ if ( forceNamedParameters && ! arg . IsNamed && parameterSymbol != null ) {
85
+ return argSyntax . WithNameColon ( CS . SyntaxFactory . NameColon ( CS . SyntaxFactory . IdentifierName ( CommonConversions . CsEscapedIdentifier ( parameterSymbol . Name ) ) ) ) ;
86
+ }
87
+
88
+ return argSyntax ;
89
+ }
90
+
91
+ CSSyntax . ArgumentSyntax ConvertOmittedArgument ( IParameterSymbol parameter )
92
+ {
93
+ if ( parameter == null ) {
94
+ return CS . SyntaxFactory . Argument ( CS . SyntaxFactory . LiteralExpression ( CS . SyntaxKind . DefaultLiteralExpression ) ) ;
95
+ }
96
+
97
+ var csRefKind = CommonConversions . GetCsRefKind ( parameter ) ;
98
+ return csRefKind != RefKind . None
99
+ ? CreateOptionalRefArg ( parameter , csRefKind )
100
+ : CS . SyntaxFactory . Argument ( CommonConversions . Literal ( parameter . ExplicitDefaultValue ) ) ;
101
+ }
102
+ }
103
+
104
+ public async Task < CSSyntax . AttributeArgumentSyntax > ToAttributeArgumentAsync ( VBasic . Syntax . ArgumentSyntax arg )
105
+ {
106
+ if ( ! ( arg is VBasic . Syntax . SimpleArgumentSyntax ) )
107
+ throw new NotSupportedException ( ) ;
108
+ var a = ( VBasic . Syntax . SimpleArgumentSyntax ) arg ;
109
+ var attr = CS . SyntaxFactory . AttributeArgument ( await a . Expression . AcceptAsync < CSSyntax . ExpressionSyntax > ( TriviaConvertingExpressionVisitor ) ) ;
110
+ if ( a . IsNamed ) {
111
+ attr = attr . WithNameEquals ( CS . SyntaxFactory . NameEquals ( await a . NameColonEquals . Name . AcceptAsync < CSSyntax . IdentifierNameSyntax > ( TriviaConvertingExpressionVisitor ) ) ) ;
112
+ }
113
+ return attr ;
114
+ }
115
+
116
+ public async Task < CSSyntax . ArgumentListSyntax > ConvertArgumentListOrEmptyAsync ( SyntaxNode node , VBSyntax . ArgumentListSyntax argumentList )
117
+ {
118
+ return await argumentList . AcceptAsync < CSSyntax . ArgumentListSyntax > ( TriviaConvertingExpressionVisitor ) ?? CreateArgList ( _semanticModel . GetSymbolInfo ( node ) . Symbol ) ;
119
+ }
120
+
121
+
122
+ private CSSyntax . ExpressionSyntax HoistByRefDeclaration ( VBSyntax . SimpleArgumentSyntax node , CSSyntax . ExpressionSyntax refLValue , SemanticModelExtensions . RefConversion refType , string argName , RefKind refKind )
123
+ {
124
+ string prefix = $ "arg{ argName } ";
125
+ var expressionTypeInfo = _semanticModel . GetTypeInfo ( node . Expression ) ;
126
+ bool useVar = expressionTypeInfo . Type ? . Equals ( expressionTypeInfo . ConvertedType , SymbolEqualityComparer . IncludeNullability ) == true && ! CommonConversions . ShouldPreferExplicitType ( node . Expression , expressionTypeInfo . ConvertedType , out var _ ) ;
127
+ var typeSyntax = CommonConversions . GetTypeSyntax ( expressionTypeInfo . ConvertedType , useVar ) ;
128
+
129
+ if ( refLValue is CSSyntax . ElementAccessExpressionSyntax eae ) {
130
+ //Hoist out the container so we can assign back to the same one after (like VB does)
131
+ var tmpContainer = _typeContext . PerScopeState . Hoist ( new AdditionalDeclaration ( "tmp" , eae . Expression , ValidSyntaxFactory . VarType ) ) ;
132
+ refLValue = eae . WithExpression ( tmpContainer . IdentifierName ) ;
133
+ }
134
+
135
+ var withCast = CommonConversions . TypeConversionAnalyzer . AddExplicitConversion ( node . Expression , refLValue , defaultToCast : refKind != RefKind . None ) ;
136
+
137
+ var local = _typeContext . PerScopeState . Hoist ( new AdditionalDeclaration ( prefix , withCast , typeSyntax ) ) ;
138
+
139
+ if ( refType == SemanticModelExtensions . RefConversion . PreAndPostAssignment ) {
140
+ var convertedLocalIdentifier = CommonConversions . TypeConversionAnalyzer . AddExplicitConversion ( node . Expression , local . IdentifierName , forceSourceType : expressionTypeInfo . ConvertedType , forceTargetType : expressionTypeInfo . Type ) ;
141
+ _typeContext . PerScopeState . Hoist ( new AdditionalAssignment ( refLValue , convertedLocalIdentifier ) ) ;
142
+ }
143
+
144
+ return local . IdentifierName ;
145
+ }
146
+
147
+ private ISymbol GetInvocationSymbol ( SyntaxNode invocation )
148
+ {
149
+ var symbol = invocation . TypeSwitch (
150
+ ( VBSyntax . InvocationExpressionSyntax e ) => _semanticModel . GetSymbolInfo ( e ) . ExtractBestMatch < ISymbol > ( ) ,
151
+ ( VBSyntax . ObjectCreationExpressionSyntax e ) => _semanticModel . GetSymbolInfo ( e ) . ExtractBestMatch < ISymbol > ( ) ,
152
+ ( VBSyntax . RaiseEventStatementSyntax e ) => _semanticModel . GetSymbolInfo ( e . Name ) . ExtractBestMatch < ISymbol > ( ) ,
153
+ ( VBSyntax . MidExpressionSyntax _ ) => CommonConversions . KnownTypes . VbCompilerStringType ? . GetMembers ( "MidStmtStr" ) . FirstOrDefault ( ) ,
154
+ _ => throw new NotSupportedException ( ) ) ;
155
+ return symbol ;
156
+ }
157
+
158
+ private IEnumerable < CSSyntax . ArgumentSyntax > GetAdditionalRequiredArgs (
159
+ IEnumerable < VBSyntax . ArgumentSyntax > arguments ,
160
+ ISymbol invocationSymbol )
161
+ {
162
+ var invocationHasOverloads = invocationSymbol . HasOverloads ( ) ;
163
+ return GetAdditionalRequiredArgs ( arguments , processedParametersNames : null , invocationSymbol , invocationHasOverloads ) ;
164
+ }
165
+
166
+ private IEnumerable < CSSyntax . ArgumentSyntax > GetAdditionalRequiredArgs (
167
+ IEnumerable < VBSyntax . ArgumentSyntax > arguments ,
168
+ ICollection < string > processedParametersNames ,
169
+ ISymbol invocationSymbol ,
170
+ bool invocationHasOverloads )
171
+ {
172
+ if ( invocationSymbol is null ) {
173
+ yield break ;
174
+ }
175
+
176
+ var invocationHasOmittedArgs = arguments . Any ( t => t . IsOmitted ) ;
177
+ var expandOptionalArgs = invocationHasOmittedArgs && invocationHasOverloads ;
178
+ var missingArgs = invocationSymbol . GetParameters ( ) . Where ( t => processedParametersNames is null || ! processedParametersNames . Contains ( t . Name ) ) ;
179
+ var requiresCompareMethod = _visualBasicEqualityComparison . OptionCompareTextCaseInsensitive && RequiresStringCompareMethodToBeAppended ( invocationSymbol ) ;
180
+
181
+ foreach ( var parameterSymbol in missingArgs ) {
182
+ var extraArg = CreateExtraArgOrNull ( parameterSymbol , requiresCompareMethod , expandOptionalArgs ) ;
183
+ if ( extraArg != null ) {
184
+ yield return extraArg ;
185
+ }
186
+ }
187
+ }
188
+
189
+
190
+ private static bool RequiresStringCompareMethodToBeAppended ( ISymbol symbol ) =>
191
+ symbol ? . ContainingType . Name == nameof ( Strings ) &&
192
+ symbol . ContainingType . ContainingNamespace . Name == nameof ( Microsoft . VisualBasic ) &&
193
+ symbol . ContainingType . ContainingNamespace . ContainingNamespace . Name == nameof ( Microsoft ) &&
194
+ symbol . Name is "InStr" or "InStrRev" or "Replace" or "Split" or "StrComp" ;
195
+
196
+ private CSSyntax . ArgumentSyntax CreateExtraArgOrNull ( IParameterSymbol p , bool requiresCompareMethod , bool expandOptionalArgs )
197
+ {
198
+ var csRefKind = CommonConversions . GetCsRefKind ( p ) ;
199
+ if ( csRefKind != RefKind . None ) {
200
+ return CreateOptionalRefArg ( p , csRefKind ) ;
201
+ }
202
+
203
+ if ( requiresCompareMethod && p . Type . GetFullMetadataName ( ) == "Microsoft.VisualBasic.CompareMethod" ) {
204
+ return ( CSSyntax . ArgumentSyntax ) CommonConversions . CsSyntaxGenerator . Argument ( p . Name , RefKind . None , _visualBasicEqualityComparison . CompareMethodExpression ) ;
205
+ }
206
+
207
+ if ( expandOptionalArgs && p . HasExplicitDefaultValue ) {
208
+ return ( CSSyntax . ArgumentSyntax ) CommonConversions . CsSyntaxGenerator . Argument ( p . Name , RefKind . None , CommonConversions . Literal ( p . ExplicitDefaultValue ) ) ;
209
+ }
210
+
211
+ return null ;
212
+ }
213
+
214
+ private CSSyntax . ArgumentSyntax CreateOptionalRefArg ( IParameterSymbol p , RefKind refKind )
215
+ {
216
+ string prefix = $ "arg{ p . Name } ";
217
+ var type = CommonConversions . GetTypeSyntax ( p . Type ) ;
218
+ CSSyntax . ExpressionSyntax initializer ;
219
+ if ( p . HasExplicitDefaultValue ) {
220
+ initializer = CommonConversions . Literal ( p . ExplicitDefaultValue ) ;
221
+ } else if ( HasOptionalAttribute ( p ) ) {
222
+ if ( TryGetDefaultParameterValueAttributeValue ( p , out var defaultValue ) ) {
223
+ initializer = CommonConversions . Literal ( defaultValue ) ;
224
+ } else {
225
+ initializer = CS . SyntaxFactory . DefaultExpression ( type ) ;
226
+ }
227
+ } else {
228
+ //invalid VB.NET code
229
+ return null ;
230
+ }
231
+ var local = _typeContext . PerScopeState . Hoist ( new AdditionalDeclaration ( prefix , initializer , type ) ) ;
232
+ return ( CSSyntax . ArgumentSyntax ) CommonConversions . CsSyntaxGenerator . Argument ( p . Name , refKind , local . IdentifierName ) ;
233
+
234
+ bool HasOptionalAttribute ( IParameterSymbol p )
235
+ {
236
+ var optionalAttribute = CommonConversions . KnownTypes . OptionalAttribute ;
237
+ if ( optionalAttribute == null ) {
238
+ return false ;
239
+ }
240
+
241
+ return p . GetAttributes ( ) . Any ( a => SymbolEqualityComparer . IncludeNullability . Equals ( a . AttributeClass , optionalAttribute ) ) ;
242
+ }
243
+
244
+ bool TryGetDefaultParameterValueAttributeValue ( IParameterSymbol p , out object defaultValue )
245
+ {
246
+ defaultValue = null ;
247
+
248
+ var defaultParameterValueAttribute = CommonConversions . KnownTypes . DefaultParameterValueAttribute ;
249
+ if ( defaultParameterValueAttribute == null ) {
250
+ return false ;
251
+ }
252
+
253
+ var attributeData = p . GetAttributes ( ) . FirstOrDefault ( a => SymbolEqualityComparer . IncludeNullability . Equals ( a . AttributeClass , defaultParameterValueAttribute ) ) ;
254
+ if ( attributeData == null ) {
255
+ return false ;
256
+ }
257
+
258
+ if ( attributeData . ConstructorArguments . Length == 0 ) {
259
+ return false ;
260
+ }
261
+
262
+ defaultValue = attributeData . ConstructorArguments . First ( ) . Value ;
263
+ return true ;
264
+ }
265
+ }
266
+
267
+ public CSSyntax . ArgumentListSyntax CreateArgList ( ISymbol invocationSymbol )
268
+ {
269
+ return CS . SyntaxFactory . ArgumentList ( CS . SyntaxFactory . SeparatedList (
270
+ GetAdditionalRequiredArgs ( Array . Empty < VBSyntax . ArgumentSyntax > ( ) , invocationSymbol ) )
271
+ ) ;
272
+ }
273
+ }
0 commit comments