File: SymbolKey.NamedTypeSymbolKey.cs
Web Access
Project: ..\..\..\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Collections.Immutable;
using Roslyn.Utilities;
 
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);
                visitor.WriteString(symbol.IsFileLocal
                    ? symbol.DeclaringSyntaxReferences[0].SyntaxTree.FilePath
                    : null);
                visitor.WriteBoolean(symbol.IsUnboundGenericType);
 
                if (!symbol.Equals(symbol.ConstructedFrom) && !symbol.IsUnboundGenericType)
                {
                    visitor.WriteSymbolKeyArray(symbol.TypeArguments);
                }
                else
                {
                    visitor.WriteSymbolKeyArray(ImmutableArray<ITypeSymbol>.Empty);
                }
            }
 
            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();
                var filePath = reader.ReadString();
                var isUnboundGenericType = reader.ReadBoolean();
 
                using var typeArguments = reader.ReadSymbolKeyArray<INamedTypeSymbol, ITypeSymbol>(
                    contextualSymbol,
                    getContextualSymbol: static (contextualType, i) => SafeGet(contextualType.TypeArguments, i),
                    out var typeArgumentsFailureReason);
 
                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
                    ? Array.Empty<ITypeSymbol>()
                    : typeArguments.Builder.ToArray();
 
                var normalResolution = ResolveNormalNamedType(
                    containingSymbolResolution, containingSymbolFailureReason,
                    name, 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,
                int arity,
                string? filePath,
                bool isUnboundGenericType,
                ITypeSymbol[] typeArgumentsArray,
                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(
                        result, nsOrType, name, arity, filePath,
                        isUnboundGenericType, typeArgumentsArray);
                }
 
                return CreateResolution(result, $"({nameof(NamedTypeSymbolKey)} failed)", out failureReason);
            }
 
            private static void Resolve(
                PooledArrayBuilder<INamedTypeSymbol> result,
                INamespaceOrTypeSymbol container,
                string name,
                int arity,
                string? filePath,
                bool isUnboundGenericType,
                ITypeSymbol[] typeArguments)
            {
                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;
            }
        }
    }
}