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