| File: Completion\Providers\AbstractContextVariableArgumentProvider.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Completion; /// <summary> /// This <see cref="ArgumentProvider"/> attempts to locate a matching value in the context of a method invocation. /// </summary> internal abstract class AbstractContextVariableArgumentProvider : ArgumentProvider { protected abstract string ThisOrMeKeyword { get; } protected abstract bool IsInstanceContext(SyntaxTree syntaxTree, SyntaxToken targetToken, SemanticModel semanticModel, CancellationToken cancellationToken); public override async Task ProvideArgumentAsync(ArgumentContext context) { if (context.PreviousValue is not null) { // This argument provider does not attempt to replace arguments already in code. return; } var requireExactType = context.Parameter.Type.IsSpecialType() || context.Parameter.RefKind != RefKind.None; var symbols = context.SemanticModel.LookupSymbols(context.Position); // First try to find a local variable ISymbol? bestSymbol = null; string? bestSymbolName = null; CommonConversion bestConversion = default; foreach (var symbol in symbols) { ISymbol candidate; if (symbol.IsKind(SymbolKind.Parameter, out IParameterSymbol? parameter)) candidate = parameter; else if (symbol.IsKind(SymbolKind.Local, out ILocalSymbol? local)) candidate = local; else continue; CheckCandidate(candidate); } if (bestSymbol is not null) { context.DefaultValue = bestSymbolName; return; } // Next try fields and properties of the current type foreach (var symbol in symbols) { ISymbol candidate; if (symbol.IsKind(SymbolKind.Field, out IFieldSymbol? field)) candidate = field; else if (symbol.IsKind(SymbolKind.Property, out IPropertySymbol? property)) candidate = property; else continue; // Require a name match for primitive types if (candidate.GetSymbolType().IsSpecialType() && !string.Equals(candidate.Name, context.Parameter.Name, StringComparison.OrdinalIgnoreCase)) { continue; } CheckCandidate(candidate); } if (bestSymbol is not null) { context.DefaultValue = bestSymbolName; return; } // Finally, if the invocation occurs in an instance context, check the current type ('this' or 'Me') var tree = context.SemanticModel.SyntaxTree; var targetToken = await tree.GetTouchingTokenAsync(context.Position, context.CancellationToken).ConfigureAwait(false); if (IsInstanceContext(tree, targetToken, context.SemanticModel, context.CancellationToken)) { var enclosingSymbol = context.SemanticModel.GetEnclosingSymbol(targetToken.SpanStart, context.CancellationToken); while (enclosingSymbol is IMethodSymbol { MethodKind: MethodKind.LocalFunction or MethodKind.AnonymousFunction }) { // It is allowed to reference the instance (`this`) within a local function or anonymous function, // as long as the containing method allows it enclosingSymbol = enclosingSymbol.ContainingSymbol; } if (enclosingSymbol is IMethodSymbol { ContainingType: { } containingType }) { CheckCandidate(containingType, ThisOrMeKeyword); } } if (bestSymbol is not null) { context.DefaultValue = bestSymbolName; return; } // Local functions void CheckCandidate(ISymbol candidate, string? overridingName = null) { if (candidate.GetSymbolType() is not { } symbolType) { return; } if (requireExactType && !SymbolEqualityComparer.Default.Equals(context.Parameter.Type, symbolType)) { return; } var conversion = context.SemanticModel.Compilation.ClassifyCommonConversion(symbolType, context.Parameter.Type); if (!conversion.IsImplicit) { return; } if (bestSymbol is not null && !IsNewConversionSameOrBetter(conversion)) { if (!IsNewConversionSameOrBetter(conversion)) return; if (!IsNewNameSameOrBetter(candidate)) return; } bestSymbol = candidate; bestSymbolName = overridingName ?? bestSymbol.Name; bestConversion = conversion; } bool IsNewConversionSameOrBetter(CommonConversion conversion) { if (bestConversion.IsIdentity && !conversion.IsIdentity) return false; if (bestConversion.IsImplicit && !conversion.IsImplicit) return false; return true; } bool IsNewNameSameOrBetter(ISymbol symbol) { if (string.Equals(bestSymbol.Name, context.Parameter.Name)) return string.Equals(symbol.Name, context.Parameter.Name); if (string.Equals(bestSymbol.Name, context.Parameter.Name, StringComparison.OrdinalIgnoreCase)) return string.Equals(symbol.Name, context.Parameter.Name, StringComparison.OrdinalIgnoreCase); return true; } } }