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
using Microsoft.CodeAnalysis.Formatting;
12
using Microsoft.CodeAnalysis.LanguageServices;
T
Tomas Matousek 已提交
13
using Microsoft.CodeAnalysis.PooledObjects;
14 15
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
C
CyrusNajmabadi 已提交
16 17 18

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

35 36 37 38 39 40
        public override FixAllProvider GetFixAllProvider()
        {
            // Fix All is not supported for this code fix.
            return null;
        }

41 42 43 44 45 46
        protected virtual RegisterFixData<TArgumentSyntax> TryGetLanguageSpecificFixInfo(
            SemanticModel semanticModel,
            SyntaxNode node,
            CancellationToken cancellationToken)
            => null;

47 48 49
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var cancellationToken = context.CancellationToken;
C
CyrusNajmabadi 已提交
50
            var diagnostic = context.Diagnostics.First();
51 52 53 54

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

C
CyrusNajmabadi 已提交
55
            var initialNode = root.FindNode(diagnostic.Location.SourceSpan);
56 57
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
58 59

            for (var node = initialNode; node != null; node = node.Parent)
60
            {
61 62 63 64 65 66
                var fixData =
                    TryGetInvocationExpressionFixInfo(semanticModel, syntaxFacts, node, cancellationToken) ??
                    TryGetObjectCreationFixInfo(semanticModel, syntaxFacts, node, cancellationToken) ??
                    TryGetLanguageSpecificFixInfo(semanticModel, node, cancellationToken);

                if (fixData != null)
67
                {
68 69 70 71 72 73 74 75 76 77 78
                    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 已提交
79
                    var argumentOpt = TryGetRelevantArgument(initialNode, node, diagnostic);
80 81 82
                    var argumentInsertPositionInMethodCandidates = GetArgumentInsertPositionForMethodCandidates(
                        argumentOpt, semanticModel, syntaxFacts, fixData.Arguments, candidates);
                    RegisterFixForMethodOverloads(context, fixData.Arguments, argumentInsertPositionInMethodCandidates);
83 84 85 86 87
                    return;
                }
            }
        }

88 89 90 91 92
        /// <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 已提交
93 94
        private TArgumentSyntax TryGetRelevantArgument(
            SyntaxNode initialNode, SyntaxNode node, Diagnostic diagnostic)
95
        {
96
            if (TooManyArgumentsDiagnosticIds.Contains(diagnostic.Id))
C
CyrusNajmabadi 已提交
97 98 99 100
            {
                return null;
            }

101
            if (CannotConvertDiagnosticIds.Contains(diagnostic.Id))
102 103 104 105
            {
                return null;
            }

106
            return initialNode.GetAncestorsOrThis<TArgumentSyntax>()
107
                              .LastOrDefault(a => a.AncestorsAndSelf().Contains(node));
108 109
        }

110 111 112 113 114
        private static RegisterFixData<TArgumentSyntax> TryGetInvocationExpressionFixInfo(
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SyntaxNode node,
            CancellationToken cancellationToken)
115
        {
116 117 118 119 120 121 122 123 124 125 126
            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);
            }
127

128
            return null;
129 130
        }

131 132 133 134 135
        private static RegisterFixData<TArgumentSyntax> TryGetObjectCreationFixInfo(
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SyntaxNode node,
            CancellationToken cancellationToken)
136
        {
137
            if (node is TObjectCreationExpressionSyntax objectCreation)
138 139
            {

140 141 142 143 144 145
                // 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 已提交
146

147 148 149 150 151 152 153 154 155 156 157
                // 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>();
                }
158

159 160
                var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfObjectCreationExpression(objectCreation);
                var methodCandidates = type.InstanceConstructors;
161

162 163
                return new RegisterFixData<TArgumentSyntax>(arguments, methodCandidates, isConstructorInitializer: false);
            }
164

165
            return null;
166 167
        }

168
        private static ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> GetArgumentInsertPositionForMethodCandidates(
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
            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 已提交
187 188
                            semanticModel, syntaxFacts, comparer, method,
                            arguments, argumentOpt);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

                        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();
        }

211
        private static int NonParamsParameterCount(IMethodSymbol method)
212 213
            => method.IsParams() ? method.Parameters.Length - 1 : method.Parameters.Length;

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

            // 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.
224 225 226 227 228
            var fixes = codeFixData.Length <= 2
                ? NestByOverload()
                : NestByCascading();

            context.RegisterFixes(fixes, context.Diagnostics);
229
            return;
230 231 232

            ImmutableArray<CodeAction> NestByOverload()
            {
233
                using var builderDisposer = ArrayBuilder<CodeAction>.GetInstance(codeFixData.Length, out var builder);
234 235
                foreach (var data in codeFixData)
                {
M
Martin Strecker 已提交
236
                    // We create the mandatory data.CreateChangedSolutionNonCascading fix first.
237 238 239 240 241 242
                    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 已提交
243 244
                        // 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.
245
                        var titleForNesting = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true);
246
                        var titleCascading = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations, data.Method,
247
                                                             includeParameters: true);
248 249 250 251 252 253 254 255 256 257
                        codeAction = new CodeAction.CodeActionWithNestedActions(
                            title: titleForNesting,
                            ImmutableArray.Create(
                                codeAction,
                                new MyCodeAction(
                                    title: titleCascading,
                                    data.CreateChangedSolutionCascading)),
                            isInlinable: true);
                    }

M
Martin Strecker 已提交
258
                    // codeAction is now either a single fix or two fixes wrapped in a CodeActionWithNestedActions
259 260 261
                    builder.Add(codeAction);
                }

262
                return builder.ToImmutable();
263 264 265 266
            }

            ImmutableArray<CodeAction> NestByCascading()
            {
267
                using var builderDisposer = ArrayBuilder<CodeAction>.GetInstance(capacity: 2, out var builder);
268

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

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

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

285 286
                // 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.
287 288 289 290
                builder.Add(new CodeAction.CodeActionWithNestedActions(nestedNonCascadingTitle, nonCascadingActions, isInlinable: false));

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

297
                return builder.ToImmutable();
298
            }
299 300
        }

301
        private ImmutableArray<CodeFixData> PrepareCreationOfCodeActions(
302 303
            Document document,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
304
            ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> methodsAndArgumentsToAdd)
305
        {
306
            using var builderDisposer = ArrayBuilder<CodeFixData>.GetInstance(methodsAndArgumentsToAdd.Length, out var builder);
307

308 309 310
            // 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 已提交
311
            foreach (var argumentInsertPositionData in methodsAndArgumentsToAdd.OrderByDescending(t => t.ArgumentInsertionIndex))
312
            {
M
Martin Strecker 已提交
313 314
                var methodToUpdate = argumentInsertPositionData.MethodToUpdate;
                var argumentToInsert = argumentInsertPositionData.ArgumentToInsert;
315

316
                var cascadingFix = AddParameterService.Instance.HasCascadingDeclarations(methodToUpdate)
317 318 319 320 321 322 323
                    ? 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 已提交
324

325
                builder.Add(codeFixData);
326
            }
327

328
            return builder.ToImmutable();
329 330
        }

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

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

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

            // 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);

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

M
Martin Strecker 已提交
376
        private static async Task<(ITypeSymbol, RefKind)> GetArgumentTypeAndRefKindAsync(Document invocationDocument, TArgumentSyntax argument, CancellationToken cancellationToken)
377 378 379 380 381
        {
            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 已提交
382 383
            var refKind = syntaxFacts.GetRefKindOfArgument(argument);
            return (argumentType, refKind);
384
        }
385

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

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

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

414
        private static TArgumentSyntax DetermineFirstArgumentToAdd(
415 416 417 418
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            StringComparer comparer,
            IMethodSymbol method,
419 420
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            TArgumentSyntax argumentOpt)
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
        {
            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)
                    {
445 446 447 448 449 450
                        if (method.Parameters.LastOrDefault()?.IsParams == true)
                        {
                            // Last parameter is a params.  We can't place any parameters past it.
                            return null;
                        }

451 452 453
                        return argument;
                    }

C
CyrusNajmabadi 已提交
454 455
                    // 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.
456 457 458 459 460 461 462 463
                    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 已提交
464

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

476 477
                    var parameter = method.Parameters[i];

C
CyrusNajmabadi 已提交
478
                    if (!TypeInfoMatchesType(argumentTypeInfo, parameter.Type, isNullLiteral, isDefaultLiteral))
479
                    {
C
CyrusNajmabadi 已提交
480
                        if (TypeInfoMatchesWithParamsExpansion(argumentTypeInfo, parameter, isNullLiteral, isDefaultLiteral))
481
                        {
C
CyrusNajmabadi 已提交
482 483 484 485
                            // 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;
486 487
                        }

488 489 490 491 492 493 494
                        return argument;
                    }
                }
            }

            return null;
        }
C
CyrusNajmabadi 已提交
495

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

            return false;
        }

511
        private static bool TypeInfoMatchesType(
C
CyrusNajmabadi 已提交
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
            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();
            }

530 531
            // 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).
532 533 534 535 536
            if (type.Kind == SymbolKind.TypeParameter)
            {
                return true;
            }

C
CyrusNajmabadi 已提交
537 538
            return false;
        }
539

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