AbstractAddParameterCodeFixProvider.cs 26.5 KB
Newer Older
C
CyrusNajmabadi 已提交
1 2 3 4
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
C
CyrusNajmabadi 已提交
5
using System.Collections.Immutable;
C
CyrusNajmabadi 已提交
6
using System.Linq;
7
using System.Threading;
C
CyrusNajmabadi 已提交
8
using System.Threading.Tasks;
9
using Microsoft.CodeAnalysis.CodeActions;
C
CyrusNajmabadi 已提交
10
using Microsoft.CodeAnalysis.CodeFixes;
11 12
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
13
using Microsoft.CodeAnalysis.FindSymbols;
14
using Microsoft.CodeAnalysis.Formatting;
15
using Microsoft.CodeAnalysis.LanguageServices;
T
Tomas Matousek 已提交
16
using Microsoft.CodeAnalysis.PooledObjects;
17 18 19
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
C
CyrusNajmabadi 已提交
20 21 22

namespace Microsoft.CodeAnalysis.AddParameter
{
23 24 25 26 27 28
    internal abstract class AbstractAddParameterCodeFixProvider<
        TArgumentSyntax,
        TAttributeArgumentSyntax,
        TArgumentListSyntax,
        TAttributeArgumentListSyntax,
        TInvocationExpressionSyntax,
29
        TObjectCreationExpressionSyntax> : CodeFixProvider
30 31 32 33 34
        where TArgumentSyntax : SyntaxNode
        where TArgumentListSyntax : SyntaxNode
        where TAttributeArgumentListSyntax : SyntaxNode
        where TInvocationExpressionSyntax : SyntaxNode
        where TObjectCreationExpressionSyntax : SyntaxNode
C
CyrusNajmabadi 已提交
35
    {
C
CyrusNajmabadi 已提交
36
        protected abstract ImmutableArray<string> TooManyArgumentsDiagnosticIds { get; }
37
        protected abstract ImmutableArray<string> CannotConvertDiagnosticIds { get; }
C
CyrusNajmabadi 已提交
38

39 40 41 42 43 44
        public override FixAllProvider GetFixAllProvider()
        {
            // Fix All is not supported for this code fix.
            return null;
        }

45 46 47 48 49 50
        protected virtual RegisterFixData<TArgumentSyntax> TryGetLanguageSpecificFixInfo(
            SemanticModel semanticModel,
            SyntaxNode node,
            CancellationToken cancellationToken)
            => null;

51 52 53
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var cancellationToken = context.CancellationToken;
C
CyrusNajmabadi 已提交
54
            var diagnostic = context.Diagnostics.First();
55 56 57 58

            var document = context.Document;
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

C
CyrusNajmabadi 已提交
59
            var initialNode = root.FindNode(diagnostic.Location.SourceSpan);
60 61
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
62 63

            for (var node = initialNode; node != null; node = node.Parent)
64
            {
65 66 67 68 69 70
                var fixData =
                    TryGetInvocationExpressionFixInfo(semanticModel, syntaxFacts, node, cancellationToken) ??
                    TryGetObjectCreationFixInfo(semanticModel, syntaxFacts, node, cancellationToken) ??
                    TryGetLanguageSpecificFixInfo(semanticModel, node, cancellationToken);

                if (fixData != null)
71
                {
72 73 74 75 76 77 78 79 80 81 82
                    var candidates = fixData.MethodCandidates;
                    if (fixData.IsConstructorInitializer)
                    {
                        // The invocation is a :this() or :base() call. In  the 'this' case we need to exclude the 
                        // method with the diagnostic because otherwise we might introduce a call to itself (which is forbidden).
                        if (semanticModel.GetEnclosingSymbol(node.SpanStart, cancellationToken) is IMethodSymbol methodWithDiagnostic)
                        {
                            candidates = candidates.Remove(methodWithDiagnostic);
                        }
                    }

C
CyrusNajmabadi 已提交
83
                    var argumentOpt = TryGetRelevantArgument(initialNode, node, diagnostic);
84 85 86
                    var argumentInsertPositionInMethodCandidates = GetArgumentInsertPositionForMethodCandidates(
                        argumentOpt, semanticModel, syntaxFacts, fixData.Arguments, candidates);
                    RegisterFixForMethodOverloads(context, fixData.Arguments, argumentInsertPositionInMethodCandidates);
87 88 89 90 91
                    return;
                }
            }
        }

92 93 94 95 96
        /// <summary>
        /// If the diagnostic is on a argument, the argument is considered to be the argument to fix.
        /// There are some exceptions to this rule. Returning null indicates that the fixer needs
        /// to find the relevant argument by itself.
        /// </summary>
C
CyrusNajmabadi 已提交
97 98
        private TArgumentSyntax TryGetRelevantArgument(
            SyntaxNode initialNode, SyntaxNode node, Diagnostic diagnostic)
99
        {
C
CyrusNajmabadi 已提交
100 101 102 103 104
            if (this.TooManyArgumentsDiagnosticIds.Contains(diagnostic.Id))
            {
                return null;
            }

105 106 107 108 109
            if (this.CannotConvertDiagnosticIds.Contains(diagnostic.Id))
            {
                return null;
            }

110
            return initialNode.GetAncestorsOrThis<TArgumentSyntax>()
111
                              .LastOrDefault(a => a.AncestorsAndSelf().Contains(node));
112 113
        }

114 115 116 117 118
        private static RegisterFixData<TArgumentSyntax> TryGetInvocationExpressionFixInfo(
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SyntaxNode node,
            CancellationToken cancellationToken)
119
        {
120 121 122 123 124 125 126 127 128 129 130
            if (node is TInvocationExpressionSyntax invocationExpression)
            {
                var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression);
                var candidates = semanticModel.GetMemberGroup(expression, cancellationToken).OfType<IMethodSymbol>().ToImmutableArray();
                var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfInvocationExpression(invocationExpression);

                // In VB a constructor calls other constructor overloads via a Me.New(..) invocation.
                // If the candidates are MethodKind.Constructor than these are the equivalent the a C# ConstructorInitializer.
                var isConstructorInitializer = candidates.All(m => m.MethodKind == MethodKind.Constructor);
                return new RegisterFixData<TArgumentSyntax>(arguments, candidates, isConstructorInitializer);
            }
131

132
            return null;
133 134
        }

135 136 137 138 139
        private static RegisterFixData<TArgumentSyntax> TryGetObjectCreationFixInfo(
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SyntaxNode node,
            CancellationToken cancellationToken)
140
        {
141
            if (node is TObjectCreationExpressionSyntax objectCreation)
142 143
            {

144 145 146 147 148 149
                // Not supported if this is "new { ... }" (as there are no parameters at all.
                var typeNode = syntaxFacts.GetObjectCreationType(objectCreation);
                if (typeNode == null)
                {
                    return new RegisterFixData<TArgumentSyntax>();
                }
C
CyrusNajmabadi 已提交
150

151 152 153 154 155 156 157 158 159 160 161
                // If we can't figure out the type being created, or the type isn't in source,
                // then there's nothing we can do.
                if (!(semanticModel.GetSymbolInfo(typeNode, cancellationToken).GetAnySymbol() is INamedTypeSymbol type))
                {
                    return new RegisterFixData<TArgumentSyntax>();
                }

                if (!type.IsNonImplicitAndFromSource())
                {
                    return new RegisterFixData<TArgumentSyntax>();
                }
162

163 164
                var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfObjectCreationExpression(objectCreation);
                var methodCandidates = type.InstanceConstructors;
165

166 167
                return new RegisterFixData<TArgumentSyntax>(arguments, methodCandidates, isConstructorInitializer: false);
            }
168

169
            return null;
170 171
        }

172
        private static ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> GetArgumentInsertPositionForMethodCandidates(
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
            TArgumentSyntax argumentOpt,
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            ImmutableArray<IMethodSymbol> methodCandidates)
        {
            var comparer = syntaxFacts.StringComparer;
            var methodsAndArgumentToAdd = ArrayBuilder<ArgumentInsertPositionData<TArgumentSyntax>>.GetInstance();

            foreach (var method in methodCandidates.OrderBy(m => m.Parameters.Length))
            {
                if (method.IsNonImplicitAndFromSource())
                {
                    var isNamedArgument = !string.IsNullOrWhiteSpace(syntaxFacts.GetNameForArgument(argumentOpt));

                    if (isNamedArgument || NonParamsParameterCount(method) < arguments.Count)
                    {
                        var argumentToAdd = DetermineFirstArgumentToAdd(
M
Martin Strecker 已提交
191 192
                            semanticModel, syntaxFacts, comparer, method,
                            arguments, argumentOpt);
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

                        if (argumentToAdd != null)
                        {
                            if (argumentOpt != null && argumentToAdd != argumentOpt)
                            {
                                // We were trying to fix a specific argument, but the argument we want
                                // to fix is something different.  That means there was an error earlier
                                // than this argument.  Which means we're looking at a non-viable 
                                // constructor or method.  Skip this one.
                                continue;
                            }

                            methodsAndArgumentToAdd.Add(new ArgumentInsertPositionData<TArgumentSyntax>(
                                method, argumentToAdd, arguments.IndexOf(argumentToAdd)));
                        }
                    }
                }
            }

            return methodsAndArgumentToAdd.ToImmutableAndFree();
        }

215
        private static int NonParamsParameterCount(IMethodSymbol method)
216 217
            => method.IsParams() ? method.Parameters.Length - 1 : method.Parameters.Length;

M
Martin Strecker 已提交
218 219 220 221
        private void RegisterFixForMethodOverloads(
            CodeFixContext context,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> methodsAndArgumentsToAdd)
222
        {
223
            var codeFixData = PrepareCreationOfCodeActions(context.Document, arguments, methodsAndArgumentsToAdd);
M
Martin Strecker 已提交
224 225 226 227

            // To keep the list of offered fixes short we create one menu entry per overload only
            // as long as there are two or less overloads present. If there are more overloads we
            // create two menu entries. One entry for non-cascading fixes and one with cascading fixes.
228 229 230 231 232
            var fixes = codeFixData.Length <= 2
                ? NestByOverload()
                : NestByCascading();

            context.RegisterFixes(fixes, context.Diagnostics);
233
            return;
234 235 236

            ImmutableArray<CodeAction> NestByOverload()
            {
237
                var builder = ArrayBuilder<CodeAction>.GetInstance(codeFixData.Length);
238 239
                foreach (var data in codeFixData)
                {
M
Martin Strecker 已提交
240
                    // We create the mandatory data.CreateChangedSolutionNonCascading fix first.
241 242 243 244 245 246
                    var title = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true);
                    CodeAction codeAction = new MyCodeAction(
                        title: title,
                        data.CreateChangedSolutionNonCascading);
                    if (data.CreateChangedSolutionCascading != null)
                    {
M
Martin Strecker 已提交
247 248
                        // We have two fixes to offer. We nest the two fixes in an inlinable CodeAction 
                        // so the IDE is free to either show both at once or to create a sub-menu.
249
                        var titleForNesting = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true);
250
                        var titleCascading = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations, data.Method,
251
                                                             includeParameters: true);
252 253 254 255 256 257 258 259 260 261
                        codeAction = new CodeAction.CodeActionWithNestedActions(
                            title: titleForNesting,
                            ImmutableArray.Create(
                                codeAction,
                                new MyCodeAction(
                                    title: titleCascading,
                                    data.CreateChangedSolutionCascading)),
                            isInlinable: true);
                    }

M
Martin Strecker 已提交
262
                    // codeAction is now either a single fix or two fixes wrapped in a CodeActionWithNestedActions
263 264 265
                    builder.Add(codeAction);
                }

266
                return builder.ToImmutableAndFree();
267 268 269 270
            }

            ImmutableArray<CodeAction> NestByCascading()
            {
271
                var builder = ArrayBuilder<CodeAction>.GetInstance(2);
272

273
                var nonCascadingActions = ImmutableArray.CreateRange<CodeFixData, CodeAction>(codeFixData, data =>
274
                {
275
                    var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true);
276
                    return new MyCodeAction(title: title, data.CreateChangedSolutionNonCascading);
277
                });
278 279 280 281

                var cascading = codeFixData.Where(data => data.CreateChangedSolutionCascading != null);
                var cascadingActions = ImmutableArray.CreateRange<CodeAction>(cascading.Select(data =>
                {
282
                    var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true);
283 284 285
                    return new MyCodeAction(title: title, data.CreateChangedSolutionCascading);
                }));

M
Martin Strecker 已提交
286
                var aMethod = codeFixData.First().Method; // We need to term the MethodGroup and need an arbitrary IMethodSymbol to do so.
287
                var nestedNonCascadingTitle = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, aMethod, includeParameters: false);
288

289 290
                // Create a sub-menu entry with all the non-cascading CodeActions.
                // We make sure the IDE does not inline. Otherwise the context menu gets flooded with our fixes.
291 292 293 294
                builder.Add(new CodeAction.CodeActionWithNestedActions(nestedNonCascadingTitle, nonCascadingActions, isInlinable: false));

                if (cascadingActions.Length > 0)
                {
M
Martin Strecker 已提交
295
                    // if there are cascading CodeActions create a second sub-menu.
296
                    var nestedCascadingTitle = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations,
297
                                                               aMethod, includeParameters: false);
298 299 300
                    builder.Add(new CodeAction.CodeActionWithNestedActions(nestedCascadingTitle, cascadingActions, isInlinable: false));
                }

301
                return builder.ToImmutableAndFree();
302
            }
303 304
        }

305
        private ImmutableArray<CodeFixData> PrepareCreationOfCodeActions(
306 307
            Document document,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
308
            ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> methodsAndArgumentsToAdd)
309
        {
310
            var builder = ArrayBuilder<CodeFixData>.GetInstance(methodsAndArgumentsToAdd.Length);
311

312 313 314
            // Order by the furthest argument index to the nearest argument index.  The ones with
            // larger argument indexes mean that we matched more earlier arguments (and thus are
            // likely to be the correct match).
M
Martin Strecker 已提交
315
            foreach (var argumentInsertPositionData in methodsAndArgumentsToAdd.OrderByDescending(t => t.ArgumentInsertionIndex))
316
            {
M
Martin Strecker 已提交
317 318
                var methodToUpdate = argumentInsertPositionData.MethodToUpdate;
                var argumentToInsert = argumentInsertPositionData.ArgumentToInsert;
319

320
                var cascadingFix = AddParameterService.Instance.HasCascadingDeclarations(methodToUpdate)
321 322 323 324 325 326 327
                    ? new Func<CancellationToken, Task<Solution>>(c => FixAsync(document, methodToUpdate, argumentToInsert, arguments, fixAllReferences: true, c))
                    : null;

                var codeFixData = new CodeFixData(
                    methodToUpdate,
                    c => FixAsync(document, methodToUpdate, argumentToInsert, arguments, fixAllReferences: false, c),
                    cascadingFix);
M
Martin Strecker 已提交
328

329
                builder.Add(codeFixData);
330
            }
331

332
            return builder.ToImmutableAndFree();
333 334
        }

335
        private static string GetCodeFixTitle(string resourceString, IMethodSymbol methodToUpdate, bool includeParameters)
336
        {
337 338
            var methodDisplay = methodToUpdate.ToDisplayString(new SymbolDisplayFormat(
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
M
Martin Strecker 已提交
339
                extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod,
340 341 342 343 344
                parameterOptions: SymbolDisplayParameterOptions.None,
                memberOptions: methodToUpdate.IsConstructor()
                    ? SymbolDisplayMemberOptions.None
                    : SymbolDisplayMemberOptions.IncludeContainingType));

345 346 347 348
            var parameters = methodToUpdate.Parameters.Select(p => p.ToDisplayString(SimpleFormat));
            var signature = includeParameters
                ? $"{methodDisplay}({string.Join(", ", parameters)})"
                : methodDisplay;
M
Martin Strecker 已提交
349
            var title = string.Format(resourceString, signature);
350 351 352
            return title;
        }

353
        private async Task<Solution> FixAsync(
H
Heejae Chang 已提交
354
            Document invocationDocument,
355 356 357
            IMethodSymbol method,
            TArgumentSyntax argument,
            SeparatedSyntaxList<TArgumentSyntax> argumentList,
358
            bool fixAllReferences,
359 360
            CancellationToken cancellationToken)
        {
M
Martin Strecker 已提交
361
            var (argumentType, refKind) = await GetArgumentTypeAndRefKindAsync(invocationDocument, argument, cancellationToken).ConfigureAwait(false);
362 363 364 365 366 367

            // The argumentNameSuggestion is the base for the parameter name.
            // For each method declaration the name is made unique to avoid name collisions.
            var (argumentNameSuggestion, isNamedArgument) = await GetNameSuggestionForArgumentAsync(
                invocationDocument, argument, cancellationToken).ConfigureAwait(false);

368 369 370
            var newParameterIndex = isNamedArgument ? (int?)null : argumentList.IndexOf(argument);
            return await AddParameterService.Instance
                .AddParameterAsync(invocationDocument, method, argumentType, refKind, argumentNameSuggestion, newParameterIndex, fixAllReferences, cancellationToken).ConfigureAwait(false);
371 372
        }

M
Martin Strecker 已提交
373
        private static async Task<(ITypeSymbol, RefKind)> GetArgumentTypeAndRefKindAsync(Document invocationDocument, TArgumentSyntax argument, CancellationToken cancellationToken)
374 375 376 377 378
        {
            var syntaxFacts = invocationDocument.GetLanguageService<ISyntaxFactsService>();
            var semanticModel = await invocationDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var argumentExpression = syntaxFacts.GetExpressionOfArgument(argument);
            var argumentType = semanticModel.GetTypeInfo(argumentExpression).Type ?? semanticModel.Compilation.ObjectType;
M
Martin Strecker 已提交
379 380
            var refKind = syntaxFacts.GetRefKindOfArgument(argument);
            return (argumentType, refKind);
381
        }
382

383 384
        private async Task<(string argumentNameSuggestion, bool isNamed)> GetNameSuggestionForArgumentAsync(
            Document invocationDocument, TArgumentSyntax argument, CancellationToken cancellationToken)
385
        {
386
            var syntaxFacts = invocationDocument.GetLanguageService<ISyntaxFactsService>();
387

388
            var argumentName = syntaxFacts.GetNameForArgument(argument);
389 390
            if (!string.IsNullOrWhiteSpace(argumentName))
            {
391
                return (argumentNameSuggestion: argumentName, isNamed: true);
392 393 394
            }
            else
            {
395
                var semanticModel = await invocationDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
396 397 398
                var expression = syntaxFacts.GetExpressionOfArgument(argument);
                var semanticFacts = invocationDocument.GetLanguageService<ISemanticFactsService>();
                argumentName = semanticFacts.GenerateNameForExpression(
399
                    semanticModel, expression, capitalize: false, cancellationToken: cancellationToken);
400 401 402
                return (argumentNameSuggestion: argumentName, isNamed: false);
            }
        }
403 404 405 406 407 408 409 410

        private static readonly SymbolDisplayFormat SimpleFormat =
                    new SymbolDisplayFormat(
                        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
                        genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
                        parameterOptions: SymbolDisplayParameterOptions.IncludeParamsRefOut | SymbolDisplayParameterOptions.IncludeType,
                        miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

411
        private static TArgumentSyntax DetermineFirstArgumentToAdd(
412 413 414 415
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            StringComparer comparer,
            IMethodSymbol method,
416 417
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            TArgumentSyntax argumentOpt)
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
        {
            var methodParameterNames = new HashSet<string>(comparer);
            methodParameterNames.AddRange(method.Parameters.Select(p => p.Name));

            for (int i = 0, n = arguments.Count; i < n; i++)
            {
                var argument = arguments[i];
                var argumentName = syntaxFacts.GetNameForArgument(argument);

                if (!string.IsNullOrWhiteSpace(argumentName))
                {
                    // If the user provided an argument-name and we don't have any parameters that
                    // match, then this is the argument we want to add a parameter for.
                    if (!methodParameterNames.Contains(argumentName))
                    {
                        return argument;
                    }
                }
                else
                {
                    // Positional argument.  If the position is beyond what the method supports,
                    // then this definitely is an argument we could add.
                    if (i >= method.Parameters.Length)
                    {
442 443 444 445 446 447
                        if (method.Parameters.LastOrDefault()?.IsParams == true)
                        {
                            // Last parameter is a params.  We can't place any parameters past it.
                            return null;
                        }

448 449 450
                        return argument;
                    }

C
CyrusNajmabadi 已提交
451 452
                    // Now check the type of the argument versus the type of the parameter.  If they
                    // don't match, then this is the argument we should make the parameter for.
453 454 455 456 457 458 459 460
                    var expressionOfArgument = syntaxFacts.GetExpressionOfArgument(argument);
                    if (expressionOfArgument is null)
                    {
                        return null;
                    }
                    var argumentTypeInfo = semanticModel.GetTypeInfo(expressionOfArgument);
                    var isNullLiteral = syntaxFacts.IsNullLiteralExpression(expressionOfArgument);
                    var isDefaultLiteral = syntaxFacts.IsDefaultLiteralExpression(expressionOfArgument);
C
CyrusNajmabadi 已提交
461

C
CyrusNajmabadi 已提交
462 463 464
                    if (argumentTypeInfo.Type == null && argumentTypeInfo.ConvertedType == null)
                    {
                        // Didn't know the type of the argument.  We shouldn't assume it doesn't
465 466
                        // match a parameter.  However, if the user wrote 'null' and it didn't
                        // match anything, then this is the problem argument.
C
CyrusNajmabadi 已提交
467
                        if (!isNullLiteral && !isDefaultLiteral)
468 469 470
                        {
                            continue;
                        }
C
CyrusNajmabadi 已提交
471 472
                    }

473 474
                    var parameter = method.Parameters[i];

C
CyrusNajmabadi 已提交
475
                    if (!TypeInfoMatchesType(argumentTypeInfo, parameter.Type, isNullLiteral, isDefaultLiteral))
476
                    {
C
CyrusNajmabadi 已提交
477
                        if (TypeInfoMatchesWithParamsExpansion(argumentTypeInfo, parameter, isNullLiteral, isDefaultLiteral))
478
                        {
C
CyrusNajmabadi 已提交
479 480 481 482
                            // The argument matched if we expanded out the params-parameter.
                            // As the params-parameter has to be last, there's nothing else to 
                            // do here.
                            return null;
483 484
                        }

485 486 487 488 489 490 491
                        return argument;
                    }
                }
            }

            return null;
        }
C
CyrusNajmabadi 已提交
492

493
        private static bool TypeInfoMatchesWithParamsExpansion(
494
            TypeInfo argumentTypeInfo, IParameterSymbol parameter,
C
CyrusNajmabadi 已提交
495
            bool isNullLiteral, bool isDefaultLiteral)
C
CyrusNajmabadi 已提交
496 497 498
        {
            if (parameter.IsParams && parameter.Type is IArrayTypeSymbol arrayType)
            {
C
CyrusNajmabadi 已提交
499
                if (TypeInfoMatchesType(argumentTypeInfo, arrayType.ElementType, isNullLiteral, isDefaultLiteral))
C
CyrusNajmabadi 已提交
500 501 502 503 504 505 506 507
                {
                    return true;
                }
            }

            return false;
        }

508
        private static bool TypeInfoMatchesType(
C
CyrusNajmabadi 已提交
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
            TypeInfo argumentTypeInfo, ITypeSymbol type,
            bool isNullLiteral, bool isDefaultLiteral)
        {
            if (type.Equals(argumentTypeInfo.Type) || type.Equals(argumentTypeInfo.ConvertedType))
            {
                return true;
            }

            if (isDefaultLiteral)
            {
                return true;
            }

            if (isNullLiteral)
            {
                return type.IsReferenceType || type.IsNullable();
            }

527 528
            // Overload resolution couldn't resolve the actual type of the type parameter. We assume
            // that the type parameter can be the argument's type (ignoring any type parameter constraints).
529 530 531 532 533
            if (type.Kind == SymbolKind.TypeParameter)
            {
                return true;
            }

C
CyrusNajmabadi 已提交
534 535
            return false;
        }
536

537
        private class MyCodeAction : CodeAction.SolutionChangeAction
C
CyrusNajmabadi 已提交
538
        {
539
            public MyCodeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution)
540
                : base(title, createChangedSolution)
541 542
            {
            }
C
CyrusNajmabadi 已提交
543 544
        }
    }
T
Tomas Matousek 已提交
545
}