File: RQName\RQNodeBuilder.cs
Web Access
Project: src\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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Features.RQName.Nodes;
 
namespace Microsoft.CodeAnalysis.Features.RQName;
 
internal static class RQNodeBuilder
{
    /// <summary>
    /// Builds the RQName for a given symbol.
    /// </summary>
    /// <returns>The node if it could be created, otherwise null</returns>
    public static RQNode? Build(ISymbol symbol)
        => symbol switch
        {
            INamespaceSymbol namespaceSymbol => BuildNamespace(namespaceSymbol),
            INamedTypeSymbol namedTypeSymbol => BuildUnconstructedNamedType(namedTypeSymbol),
            IMethodSymbol methodSymbol => BuildMethod(methodSymbol),
            IFieldSymbol fieldSymbol => BuildField(fieldSymbol),
            IEventSymbol eventSymbol => BuildEvent(eventSymbol),
            IPropertySymbol propertySymbol => BuildProperty(propertySymbol),
            _ => null,
        };
 
    private static RQNamespace BuildNamespace(INamespaceSymbol @namespace)
        => new(RQNodeBuilder.GetNameParts(@namespace));
 
    private static IList<string> GetNameParts(INamespaceSymbol @namespace)
    {
        var parts = new List<string>();
 
        if (@namespace == null)
        {
            return parts;
        }
 
        while (!@namespace.IsGlobalNamespace)
        {
            parts.Add(@namespace.Name);
            @namespace = @namespace.ContainingNamespace;
        }
 
        parts.Reverse();
        return parts;
    }
 
    private static RQUnconstructedType? BuildUnconstructedNamedType(INamedTypeSymbol type)
    {
        // Anything that is a valid RQUnconstructed types is ALWAYS safe for public APIs
 
        if (type == null)
        {
            return null;
        }
 
        // Anonymous types are unsupported
        if (type.IsAnonymousType)
        {
            return null;
        }
 
        // the following types are supported for BuildType() used in signatures, but are not supported
        // for UnconstructedTypes
        if (type != type.ConstructedFrom || type.SpecialType == SpecialType.System_Void)
        {
            return null;
        }
 
        // make an RQUnconstructedType
        var namespaceNames = RQNodeBuilder.GetNameParts(@type.ContainingNamespace);
        var typeInfos = new List<RQUnconstructedTypeInfo>();
 
        for (var currentType = type; currentType != null; currentType = currentType.ContainingType)
        {
            typeInfos.Insert(0, new RQUnconstructedTypeInfo(currentType.Name, currentType.TypeParameters.Length));
        }
 
        return new RQUnconstructedType(namespaceNames, typeInfos);
    }
 
    private static RQMember? BuildField(IFieldSymbol symbol)
    {
        var containingType = BuildUnconstructedNamedType(symbol.ContainingType);
 
        if (containingType == null)
        {
            return null;
        }
 
        return new RQMemberVariable(containingType, symbol.Name);
    }
 
    private static RQProperty? BuildProperty(IPropertySymbol symbol)
    {
        RQMethodPropertyOrEventName name = symbol.IsIndexer
            ? RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryIndexerName()
            : RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryPropertyName(symbol.Name);
 
        if (symbol.ExplicitInterfaceImplementations.Any())
        {
            if (symbol.ExplicitInterfaceImplementations.Length > 1)
            {
                return null;
            }
 
            var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);
 
            if (interfaceType != null)
            {
                name = new RQExplicitInterfaceMemberName(
                    interfaceType,
                    (RQOrdinaryMethodPropertyOrEventName)name);
            }
        }
 
        var containingType = BuildUnconstructedNamedType(symbol.ContainingType);
 
        if (containingType == null)
        {
            return null;
        }
 
        var parameterList = BuildParameterList(symbol.Parameters);
 
        if (parameterList == null)
        {
            return null;
        }
 
        return new RQProperty(containingType, name, typeParameterCount: 0, parameters: parameterList);
    }
 
    private static IList<RQParameter>? BuildParameterList(ImmutableArray<IParameterSymbol> parameters)
    {
        var parameterList = new List<RQParameter>();
 
        foreach (var parameter in parameters)
        {
            var parameterType = BuildType(parameter.Type);
 
            if (parameterType == null)
            {
                return null;
            }
 
            if (parameter.RefKind == RefKind.Out)
            {
                parameterList.Add(new RQOutParameter(parameterType));
            }
            else if (parameter.RefKind == RefKind.Ref)
            {
                parameterList.Add(new RQRefParameter(parameterType));
            }
            else
            {
                parameterList.Add(new RQNormalParameter(parameterType));
            }
        }
 
        return parameterList;
    }
 
    private static RQEvent? BuildEvent(IEventSymbol symbol)
    {
        var containingType = BuildUnconstructedNamedType(symbol.ContainingType);
 
        if (containingType == null)
        {
            return null;
        }
 
        RQMethodPropertyOrEventName name = RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryEventName(symbol.Name);
 
        if (symbol.ExplicitInterfaceImplementations.Any())
        {
            if (symbol.ExplicitInterfaceImplementations.Length > 1)
            {
                return null;
            }
 
            var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);
 
            if (interfaceType != null)
            {
                name = new RQExplicitInterfaceMemberName(interfaceType, (RQOrdinaryMethodPropertyOrEventName)name);
            }
        }
 
        return new RQEvent(containingType, name);
    }
 
    private static RQMethod? BuildMethod(IMethodSymbol symbol)
    {
        if (symbol.MethodKind is MethodKind.UserDefinedOperator or
            MethodKind.BuiltinOperator or
            MethodKind.EventAdd or
            MethodKind.EventRemove or
            MethodKind.PropertySet or
            MethodKind.PropertyGet)
        {
            return null;
        }
 
        RQMethodPropertyOrEventName name;
 
        if (symbol.MethodKind == MethodKind.Constructor)
        {
            name = RQOrdinaryMethodPropertyOrEventName.CreateConstructorName();
        }
        else if (symbol.MethodKind == MethodKind.Destructor)
        {
            name = RQOrdinaryMethodPropertyOrEventName.CreateDestructorName();
        }
        else
        {
            name = RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryMethodName(symbol.Name);
        }
 
        if (symbol.ExplicitInterfaceImplementations.Any())
        {
            if (symbol.ExplicitInterfaceImplementations.Length > 1)
            {
                return null;
            }
 
            var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);
 
            if (interfaceType != null)
            {
                name = new RQExplicitInterfaceMemberName(interfaceType, (RQOrdinaryMethodPropertyOrEventName)name);
            }
        }
 
        var containingType = BuildUnconstructedNamedType(symbol.ContainingType);
 
        if (containingType == null)
        {
            return null;
        }
 
        var typeParamCount = symbol.TypeParameters.Length;
        var parameterList = BuildParameterList(symbol.Parameters);
 
        if (parameterList == null)
        {
            return null;
        }
 
        return new RQMethod(containingType, name, typeParamCount, parameterList);
    }
 
    private static RQType? BuildType(ITypeSymbol symbol)
    {
        if (symbol.IsAnonymousType)
        {
            return null;
        }
 
        if (symbol.SpecialType == SpecialType.System_Void)
        {
            return RQVoidType.Singleton;
        }
        else if (symbol is IPointerTypeSymbol pointerType)
        {
            var pointedAtType = BuildType(pointerType.PointedAtType);
            if (pointedAtType == null)
            {
                return null;
            }
 
            return new RQPointerType(pointedAtType);
        }
        else if (symbol is IArrayTypeSymbol arrayType)
        {
            var elementType = BuildType(arrayType.ElementType);
            if (elementType == null)
            {
                return null;
            }
 
            return new RQArrayType(arrayType.Rank, elementType);
        }
        else if (symbol.TypeKind == TypeKind.TypeParameter)
        {
            return new RQTypeVariableType(symbol.Name);
        }
        else if (symbol.TypeKind == TypeKind.Unknown)
        {
            return new RQErrorType(symbol.Name);
        }
        else if (symbol.TypeKind == TypeKind.Dynamic)
        {
            // NOTE: Because RQNames were defined as an interchange format before C# had "dynamic", and we didn't want 
            // all consumers to have to update their logic to crack the attributes about whether something is object or
            // not, we just erase dynamic to object here.
            return RQType.ObjectType;
        }
        else if (symbol is INamedTypeSymbol namedTypeSymbol)
        {
            var definingType = namedTypeSymbol.ConstructedFrom ?? namedTypeSymbol;
 
            var typeChain = new List<INamedTypeSymbol>();
            var type = namedTypeSymbol;
            typeChain.Add(namedTypeSymbol);
 
            while (type.ContainingType != null)
            {
                type = type.ContainingType;
                typeChain.Add(type);
            }
 
            typeChain.Reverse();
 
            var typeArgumentList = new List<RQType>();
 
            foreach (var entry in typeChain)
            {
                foreach (var typeArgument in entry.TypeArguments)
                {
                    var rqType = BuildType(typeArgument);
                    if (rqType == null)
                    {
                        return null;
                    }
 
                    typeArgumentList.Add(rqType);
                }
            }
 
            var containingType = BuildUnconstructedNamedType(definingType);
 
            if (containingType == null)
            {
                return null;
            }
 
            return new RQConstructedType(containingType, typeArgumentList);
        }
        else
        {
            return null;
        }
    }
}