File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\SymbolKey\SymbolKey.NamedTypeSymbolKey.cs
Web Access
Project: src\src\RoslynAnalyzers\Tools\Metrics.Legacy\Metrics.Legacy.csproj (Metrics.Legacy)
// 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.
 
namespace Microsoft.CodeAnalysis;
 
internal partial struct SymbolKey
{
    private sealed class NamedTypeSymbolKey : AbstractSymbolKey<INamedTypeSymbol>
    {
        public static readonly NamedTypeSymbolKey Instance = new();
 
        public sealed override void Create(INamedTypeSymbol symbol, SymbolKeyWriter visitor)
        {
            visitor.WriteSymbolKey(symbol.ContainingSymbol);
            visitor.WriteString(symbol.Name);
            visitor.WriteInteger(symbol.Arity);
#if !ROSLYN_4_12_OR_LOWER
            // Include the metadata name for extensions.  We need this to uniquely find it when resolving.
            visitor.WriteString(symbol.IsExtension ? symbol.MetadataName : null);
#endif
            // Include the file path for 'file-local' types.  We need this to uniquely find it when resolving.
            visitor.WriteString(symbol.IsFileLocal ? symbol.DeclaringSyntaxReferences[0].SyntaxTree.FilePath : null);
            visitor.WriteBoolean(symbol.IsUnboundGenericType);
            visitor.WriteBoolean(symbol.IsNativeIntegerType);
            visitor.WriteBoolean(symbol.SpecialType == SpecialType.System_IntPtr);
 
            visitor.WriteSymbolKeyArray(
                symbol.Equals(symbol.ConstructedFrom) || symbol.IsUnboundGenericType ? [] : symbol.TypeArguments);
        }
 
        protected sealed override SymbolKeyResolution Resolve(
            SymbolKeyReader reader, INamedTypeSymbol? contextualSymbol, out string? failureReason)
        {
            var containingSymbolResolution = reader.ReadSymbolKey(contextualSymbol?.ContainingSymbol, out var containingSymbolFailureReason);
            var name = reader.ReadRequiredString();
            var arity = reader.ReadInteger();
#if !ROSLYN_4_12_OR_LOWER
            var extensionMetadataName = reader.ReadString();
#else
            string? extensionMetadataName = null;
#endif
            var filePath = reader.ReadString();
            var isUnboundGenericType = reader.ReadBoolean();
            var isNativeIntegerType = reader.ReadBoolean();
            var signed = reader.ReadBoolean();
 
            using var typeArguments = reader.ReadSymbolKeyArray<INamedTypeSymbol, ITypeSymbol>(
                contextualSymbol,
                getContextualSymbol: static (contextualType, i) => SafeGet(contextualType.TypeArguments, i),
                out var typeArgumentsFailureReason);
 
            // If we started with nint/nuint go back to that specific type if the language allows for it.
            if (isNativeIntegerType && reader.Compilation.Language == LanguageNames.CSharp)
            {
                failureReason = null;
                return new SymbolKeyResolution(reader.Compilation.CreateNativeIntegerTypeSymbol(signed));
            }
 
            if (typeArgumentsFailureReason != null)
            {
                Contract.ThrowIfFalse(typeArguments.IsDefault);
 
                failureReason = $"({nameof(NamedTypeSymbolKey)} {nameof(typeArguments)} failed -> {typeArgumentsFailureReason})";
                return default;
            }
 
            Contract.ThrowIfTrue(typeArguments.IsDefault);
 
            var typeArgumentsArray = typeArguments.Count == 0 ? [] : typeArguments.Builder.ToArray();
 
            var normalResolution = ResolveNormalNamedType(
                containingSymbolResolution, containingSymbolFailureReason,
                name, extensionMetadataName, arity, filePath,
                isUnboundGenericType, typeArgumentsArray,
                out failureReason);
 
            if (normalResolution.SymbolCount > 0)
                return normalResolution;
 
            return ResolveContextualErrorType(
                reader, contextualSymbol,
                containingSymbolResolution,
                name, arity, isUnboundGenericType, typeArgumentsArray,
                ref failureReason);
        }
 
        private static SymbolKeyResolution ResolveContextualErrorType(
            SymbolKeyReader reader,
            INamedTypeSymbol? contextualType,
            SymbolKeyResolution containingSymbolResolution,
            string name,
            int arity,
            bool isUnboundGenericType,
            ITypeSymbol[] typeArgumentsArray,
            ref string? failureReason)
        {
            // we weren't able to bind the container of this named type to something legitimate.  In normal cases,
            // that would be the end of resolution.  However, if we are binding in a scenario where we have a
            // contextual type that is an error type, we can see if our symbol key is a viable match for that error
            // type.  
            //
            // For example, consider if our symbol key references System.String, but we're resolving against a
            // compilation that is missing a reference to System.String, but has a method `Goo(string s)` which
            // references it.  This `string s` in `Goo` will be an error type symbol for `System.String` and we *do*
            // want to allow this to match.
            //
            // This is fundamentally inverted from normal resolution.  Normal resolution walks top down from the
            // root of the compilation to find the match.  However, error symbols cannot be found in that fashion.
            // Instead, we have to structurally match this error symbol against our own symbol key to see if it is
            // valid.
            if (contextualType is not IErrorTypeSymbol errorType)
                return default;
 
            // Check name/arity. If not hte same, this symbol key isn't referring to the same contextual type
            // that we're currently looking at.
            if (errorType.Name != name || errorType.Arity != arity)
                return default;
 
            // If we didn't successfully resolve the containing namespace, then use the contextual type's containing
            // namespace.  Note: we only do this for namespaces and not containing types.  For containing types we
            // use recursion to resolve that container properly.  If that failed, then we don't want to continue.
            if (containingSymbolResolution.SymbolCount == 0 && errorType.ContainingSymbol is INamespaceSymbol containingNamespace)
                containingSymbolResolution = new SymbolKeyResolution(containingNamespace);
 
            using var result = PooledArrayBuilder<INamedTypeSymbol>.GetInstance();
            foreach (var container in containingSymbolResolution.OfType<INamespaceOrTypeSymbol>())
            {
                result.AddIfNotNull(Construct(
                    reader.Compilation.CreateErrorTypeSymbol(container, name, arity),
                    isUnboundGenericType,
                    typeArgumentsArray));
            }
 
            return CreateResolution(result, $"({nameof(NamedTypeSymbolKey)} failed contextual error resolution)", out failureReason);
        }
 
        private static SymbolKeyResolution ResolveNormalNamedType(
            SymbolKeyResolution containingSymbolResolution,
            string? containingSymbolFailureReason,
            string name,
            string? extensionMetadataName,
            int arity,
            string? filePath,
            bool isUnboundGenericType,
            ITypeSymbol[] typeArguments,
            out string? failureReason)
        {
            if (containingSymbolFailureReason != null)
            {
                failureReason = $"({nameof(NamedTypeSymbolKey)} {nameof(containingSymbolFailureReason)} failed -> {containingSymbolFailureReason})";
                return default;
            }
 
            using var result = PooledArrayBuilder<INamedTypeSymbol>.GetInstance();
            foreach (var nsOrType in containingSymbolResolution.OfType<INamespaceOrTypeSymbol>())
                Resolve(nsOrType, result);
 
            return CreateResolution(result, $"({nameof(NamedTypeSymbolKey)} failed)", out failureReason);
 
            void Resolve(
                INamespaceOrTypeSymbol container,
                PooledArrayBuilder<INamedTypeSymbol> result)
            {
                if (extensionMetadataName != null)
                {
                    // Unfortunately, no fast index from metadata name to type, so we have to iterate all nested types.
                    foreach (var type in container.GetTypeMembers())
                    {
                        if (type.MetadataName == extensionMetadataName)
                            result.AddIfNotNull(Construct(type, isUnboundGenericType, typeArguments));
                    }
                }
                else
                {
                    foreach (var type in container.GetTypeMembers(name, arity))
                    {
                        // if this is a file-local type, then only resolve to a file-local type from this same file
                        if (filePath != null)
                        {
                            if (!type.IsFileLocal ||
                                // note: if we found 'IsFile' returned true, we can assume DeclaringSyntaxReferences is non-empty.
                                type.DeclaringSyntaxReferences[0].SyntaxTree.FilePath != filePath)
                            {
                                continue;
                            }
                        }
                        else if (type.IsFileLocal)
                        {
                            // since this key lacks a file path it can't match against a file-local type
                            continue;
                        }
 
                        result.AddIfNotNull(Construct(type, isUnboundGenericType, typeArguments));
                    }
                }
            }
        }
 
        private static INamedTypeSymbol Construct(INamedTypeSymbol type, bool isUnboundGenericType, ITypeSymbol[] typeArguments)
        {
            var currentType = typeArguments.Length > 0 ? type.Construct(typeArguments) : type;
            currentType = isUnboundGenericType ? currentType.ConstructUnboundGenericType() : currentType;
            return currentType;
        }
    }
}