File: Symbols\Source\SourceNamedTypeSymbol_Extension.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    internal partial class SourceNamedTypeSymbol
    {
        private ExtensionInfo _lazyExtensionInfo;
 
        private class ExtensionInfo
        {
            public MethodSymbol? LazyExtensionMarker = ErrorMethodSymbol.UnknownMethod;
            public ParameterSymbol? LazyExtensionParameter;
            public ImmutableDictionary<MethodSymbol, MethodSymbol>? LazyImplementationMap;
        }
 
        internal override string ExtensionName
        {
            get
            {
                if (!IsExtension)
                {
                    throw ExceptionUtilities.Unreachable();
                }
 
                MergedNamespaceOrTypeDeclaration declaration;
                if (ContainingType is not null)
                {
                    declaration = ((SourceNamedTypeSymbol)this.ContainingType).declaration;
                }
                else
                {
                    declaration = ((SourceNamespaceSymbol)this.ContainingSymbol).MergedDeclaration;
                }
 
                int index = 0;
                foreach (Declaration child in declaration.Children)
                {
                    if (child == this.declaration)
                    {
                        return GeneratedNames.MakeExtensionName(index);
                    }
 
                    if (child.Kind == DeclarationKind.Extension)
                    {
                        index++;
                    }
                }
 
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        /// <summary>
        /// This name uses an IL-looking format to encode CLR-level information of an extension block (ie. arity, constraints, extended type).
        /// It is meant be to hashed to produce the content-based name for the extension grouping type.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when appending to a StringBuilder past the <see cref="StringBuilder.MaxCapacity"/> limit.</exception>
        internal string ComputeExtensionGroupingRawName()
        {
            Debug.Assert(this.IsExtension && this.IsDefinition);
 
            var pooledBuilder = PooledStringBuilder.GetInstance();
            var builder = pooledBuilder.Builder;
            builder.Append("extension");
 
            if (this.Arity > 0)
            {
                builder.Append("<");
 
                foreach (var typeParameter in this.TypeParameters)
                {
                    if (typeParameter.Ordinal > 0)
                    {
                        builder.Append(", ");
                    }
 
                    appendTypeParameterDeclaration(typeParameter, builder);
                }
 
                builder.Append(">");
            }
 
            builder.Append("(");
            if (this.ExtensionParameter is { } extensionParameter)
            {
                TypeWithAnnotations extendedType = extensionParameter.TypeWithAnnotations;
                AppendClrType(extendedType.Type, extendedType.CustomModifiers, builder);
                Debug.Assert(extendedType.CustomModifiers.IsEmpty);
                // We intentionally ignore refness
            }
 
            builder.Append(")");
 
            return pooledBuilder.ToStringAndFree();
 
            static void appendTypeParameterDeclaration(TypeParameterSymbol typeParameter, StringBuilder builder)
            {
                if (typeParameter.HasReferenceTypeConstraint)
                {
                    builder.Append("class ");
                }
                else if (typeParameter.HasValueTypeConstraint || typeParameter.HasUnmanagedTypeConstraint)
                {
                    builder.Append("valuetype ");
                }
 
                if (typeParameter.AllowsRefLikeType)
                {
                    builder.Append("byreflike ");
                }
 
                if (typeParameter.HasConstructorConstraint || typeParameter.HasValueTypeConstraint || typeParameter.HasUnmanagedTypeConstraint)
                {
                    builder.Append(".ctor ");
                }
 
                appendTypeParameterTypeConstraints(typeParameter, builder);
 
                // Note: skipping identifier and variance
                if (builder[builder.Length - 1] == ' ')
                {
                    builder.Remove(startIndex: builder.Length - 1, length: 1);
                }
            }
 
            static void appendTypeParameterTypeConstraints(TypeParameterSymbol typeParameter, StringBuilder builder)
            {
                ImmutableArray<TypeWithAnnotations> typeConstraints = typeParameter.GetConstraintTypes(ConsList<TypeParameterSymbol>.Empty);
                if (typeConstraints.IsEmpty && !typeParameter.HasUnmanagedTypeConstraint && !typeParameter.HasValueTypeConstraint)
                {
                    return;
                }
 
                var typeConstraintStrings = ArrayBuilder<string>.GetInstance(typeConstraints.Length);
                foreach (var typeConstraint in typeConstraints)
                {
                    var constraintBuilder = PooledStringBuilder.GetInstance();
                    Debug.Assert(typeConstraint.CustomModifiers.IsEmpty);
                    AppendClrType(typeConstraint.Type, typeConstraint.CustomModifiers, constraintBuilder.Builder);
                    typeConstraintStrings.Add(constraintBuilder.ToStringAndFree());
                }
 
                if (typeParameter.HasUnmanagedTypeConstraint)
                {
                    typeConstraintStrings.Add("System.ValueType modreq(System.Runtime.InteropServices.UnmanagedType)");
                }
                else if (typeParameter.HasValueTypeConstraint)
                {
                    typeConstraintStrings.Add("System.ValueType");
                }
 
                typeConstraintStrings.Sort(StringComparer.Ordinal); // Actual order doesn't matter - just want to be deterministic
 
                builder.Append('(');
                for (int i = 0; i < typeConstraintStrings.Count; i++)
                {
                    if (i > 0)
                    {
                        builder.Append(", ");
                    }
 
                    builder.Append(typeConstraintStrings[i]);
                }
 
                typeConstraintStrings.Free();
                builder.Append(")");
            }
        }
 
        /// <summary>
        /// Outputs the CLR-level information for a type in an IL-looking format.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when appending to a StringBuilder past the <see cref="StringBuilder.MaxCapacity"/> limit.</exception>
        static void AppendClrType(TypeSymbol type, ImmutableArray<CustomModifier> modifiers, StringBuilder builder)
        {
            if (type is NamedTypeSymbol namedType)
            {
                appendNamedType(type, builder, namedType);
            }
            else if (type is TypeParameterSymbol typeParameter)
            {
                appendTypeParameterReference(typeParameter, builder);
            }
            else if (type is ArrayTypeSymbol array)
            {
                appendArrayType(array, builder);
            }
            else if (type is PointerTypeSymbol pointer)
            {
                TypeWithAnnotations pointedAtType = pointer.PointedAtTypeWithAnnotations;
                AppendClrType(pointedAtType.Type, pointedAtType.CustomModifiers, builder);
                builder.Append('*');
            }
            else if (type is FunctionPointerTypeSymbol functionPointer)
            {
                appendFunctionPointerType(functionPointer, builder);
            }
            else if (type is DynamicTypeSymbol)
            {
                builder.Append("System.Object");
            }
            else
            {
                throw ExceptionUtilities.UnexpectedValue(type);
            }
 
            appendModifiers(modifiers, builder);
            return;
 
            static void appendNamedType(TypeSymbol type, StringBuilder builder, NamedTypeSymbol namedType)
            {
                if (namedType.SpecialType == SpecialType.System_Void)
                {
                    builder.Append("void");
                    return;
                }
 
                if (namedType.Name == "void" && namedType.IsTopLevelType() && namedType.ContainingNamespace.IsGlobalNamespace)
                {
                    builder.Append("'void'");
                    return;
                }
 
                // Note: in valid IL, we need a "class" or "valuetype" keyword in many contexts
                appendNamespace(namedType.ContainingNamespace, builder);
                appendContainingType(namedType, builder);
                builder.Append(namedType.MetadataName);
                appendTypeArguments(namedType, builder);
            }
 
            static void appendNamespace(NamespaceSymbol ns, StringBuilder builder)
            {
                if (ns is not null && !ns.IsGlobalNamespace)
                {
                    appendNamespace(ns.ContainingNamespace, builder);
                    builder.Append(ns.Name);
                    builder.Append('.');
                }
            }
 
            static void appendContainingType(NamedTypeSymbol namedType, StringBuilder builder)
            {
                // Note: using slash for nested type to match CIL: ECMA-335 I.10.7.2
                if (namedType.ContainingType is { } containingType)
                {
                    appendContainingType(containingType, builder);
                    builder.Append(containingType.MetadataName);
                    builder.Append('/');
                }
            }
 
            static void appendTypeArguments(NamedTypeSymbol namedType, StringBuilder builder)
            {
                if (namedType.IsUnboundGenericType)
                {
                    return;
                }
 
                var typeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
                namedType.GetAllTypeArgumentsNoUseSiteDiagnostics(typeArguments);
                if (typeArguments.Count > 0)
                {
                    builder.Append('<');
                    for (int i = 0; i < typeArguments.Count; i++)
                    {
                        if (i > 0)
                        {
                            builder.Append(", ");
                        }
 
                        TypeWithAnnotations typeArgument = typeArguments[i];
                        AppendClrType(typeArgument.Type, typeArgument.CustomModifiers, builder);
                    }
 
                    builder.Append('>');
                }
 
                typeArguments.Free();
            }
 
            static void appendTypeParameterReference(TypeParameterSymbol typeParameter, StringBuilder builder)
            {
                if (typeParameter.ContainingType.IsExtension)
                {
                    builder.Append("!");
                    builder.Append(StringExtensions.GetNumeral(typeParameter.Ordinal));
                }
                else
                {
                    // error scenario
                    builder.Append("!");
                    builder.Append(typeParameter.Name);
                }
            }
 
            static void appendArrayType(ArrayTypeSymbol array, StringBuilder builder)
            {
                Debug.Assert(array.Sizes.IsEmpty && array.LowerBounds.IsDefault); // We only deal with source array types
 
                TypeWithAnnotations elementType = array.ElementTypeWithAnnotations;
                AppendClrType(elementType.Type, elementType.CustomModifiers, builder);
                builder.Append('[');
                for (int i = 1; i < array.Rank; i++)
                {
                    builder.Append(',');
                }
 
                builder.Append(']');
            }
 
            static void appendFunctionPointerType(FunctionPointerTypeSymbol functionPointer, StringBuilder builder)
            {
                builder.Append("method ");
 
                // When calling convention is a single one of the four special calling conventions, we just use flags
                // Otherwise, we use "unmanaged" flag and also add return modifiers below
                FunctionPointerMethodSymbol signature = functionPointer.Signature;
                string? callingConvention = signature.CallingConvention switch
                {
                    Cci.CallingConvention.Default => null, // managed is the default
                    Cci.CallingConvention.Unmanaged => "unmanaged ",
                    Cci.CallingConvention.CDecl => "unmanaged cdecl ",
                    Cci.CallingConvention.Standard => "unmanaged stdcall ",
                    Cci.CallingConvention.ThisCall => "unmanaged thiscall ",
                    Cci.CallingConvention.FastCall => "unmanaged fastcall ",
                    _ => throw ExceptionUtilities.UnexpectedValue(signature.CallingConvention)
                };
 
                builder.Append(callingConvention);
 
                TypeWithAnnotations returnType = signature.ReturnTypeWithAnnotations;
                AppendClrType(returnType.Type, returnType.CustomModifiers, builder);
                if (signature.RefKind != RefKind.None)
                {
                    builder.Append('&');
                    appendModifiers(signature.RefCustomModifiers, builder);
                }
 
                builder.Append(" *(");
                var parameters = signature.Parameters;
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (i > 0)
                    {
                        builder.Append(", ");
                    }
 
                    ParameterSymbol parameter = parameters[i];
                    TypeWithAnnotations parameterType = parameter.TypeWithAnnotations;
                    AppendClrType(parameterType.Type, parameterType.CustomModifiers, builder);
                    if (parameter.RefKind != RefKind.None)
                    {
                        builder.Append('&');
                        appendModifiers(parameter.RefCustomModifiers, builder);
                    }
                }
 
                builder.Append(')');
            }
 
            static void appendModifiers(ImmutableArray<CustomModifier> customModifiers, StringBuilder builder)
            {
                // Order of modifiers is significant in metadata so we preserve the order.
                // We reverse order of modifiers to match CIL order
                for (int i = customModifiers.Length - 1; i >= 0; i--)
                {
                    var modifier = customModifiers[i];
                    var modifierBuilder = PooledStringBuilder.GetInstance();
                    modifierBuilder.Builder.Append(modifier.IsOptional ? " modopt(" : " modreq(");
 
                    AppendClrType(((CSharpCustomModifier)modifier).ModifierSymbol, modifiers: [], modifierBuilder.Builder);
                    modifierBuilder.Builder.Append(')');
 
                    builder.Append(modifierBuilder.ToStringAndFree());
                }
            }
        }
 
        /// <summary>
        /// This name uses a C#-looking format to encode C#-level information of an extension block (ie. arity, constraints, extended type, attributes and C#-isms like tuple names).
        /// It is meant to be hashed to produce the content-based name for the extension marker type.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when appending to a StringBuilder past the <see cref="StringBuilder.MaxCapacity"/> limit.</exception>
        internal string ComputeExtensionMarkerRawName()
        {
            Debug.Assert(this.IsExtension && this.IsDefinition);
 
            var pooledStringBuilder = PooledStringBuilder.GetInstance();
            StringBuilder builder = pooledStringBuilder.Builder;
            builder.Append("extension");
 
            if (this.Arity > 0)
            {
                builder.Append("<");
 
                foreach (TypeParameterSymbol typeParameter in this.TypeParameters)
                {
                    if (typeParameter.Ordinal > 0)
                    {
                        builder.Append(", ");
                    }
 
                    appendAttributes(typeParameter.GetAttributes(), builder);
                    appendIdentifier(typeParameter.Name, builder);
                }
 
                builder.Append(">");
            }
 
            builder.Append("(");
            if (this.ExtensionParameter is { } extensionParameter)
            {
                // We ignore "params" because it's not legal
                if (extensionParameter.DeclaredScope != ScopedKind.None)
                {
                    builder.Append("scoped ");
                }
 
                appendRefKind(extensionParameter.RefKind, builder, forParameter: true);
                appendAttributes(extensionParameter.GetAttributes(), builder);
                appendTypeWithAnnotation(extensionParameter.TypeWithAnnotations, builder);
 
                string name = extensionParameter.Name;
                if (name != "")
                {
                    builder.Append(' ');
                    appendIdentifier(name, builder);
                }
            }
 
            builder.Append(")");
 
            foreach (TypeParameterSymbol typeParameter in this.TypeParameters)
            {
                if (typeParameterHasConstraints(typeParameter))
                {
                    appendTypeParameterConstraints(typeParameter, builder);
                }
            }
 
            return pooledStringBuilder.ToStringAndFree();
 
            static bool typeParameterHasConstraints(TypeParameterSymbol typeParameter)
            {
                return !typeParameter.ConstraintTypesNoUseSiteDiagnostics.IsEmpty || typeParameter.HasConstructorConstraint ||
                    typeParameter.HasReferenceTypeConstraint || typeParameter.HasValueTypeConstraint ||
                    typeParameter.HasNotNullConstraint || typeParameter.AllowsRefLikeType;
            }
 
            static void appendTypeWithAnnotation(TypeWithAnnotations type, StringBuilder builder)
            {
                appendType(type.Type, builder);
                Debug.Assert(type.CustomModifiers.IsEmpty);
 
                if (!type.Type.IsValueType)
                {
                    appendAnnotation(builder, type.NullableAnnotation);
                }
            }
 
            static void appendAnnotation(StringBuilder builder, NullableAnnotation annotation)
            {
                switch (annotation)
                {
                    case NullableAnnotation.Annotated:
                        builder.Append('?');
                        break;
                    case NullableAnnotation.NotAnnotated:
                        builder.Append('!');
                        break;
                    case NullableAnnotation.Oblivious:
                        break;
                    case NullableAnnotation.Ignored:
                        Debug.Assert(false);
                        break;
                    default:
                        throw ExceptionUtilities.UnexpectedValue(annotation);
                }
            }
 
            static void appendType(TypeSymbol type, StringBuilder builder)
            {
                if (type is NamedTypeSymbol namedType)
                {
                    appendNamedType(namedType, builder);
                }
                else if (type is TypeParameterSymbol { Name: var typeParameterName })
                {
                    appendIdentifier(typeParameterName, builder, forTypeConstraint: true);
                }
                else if (type is ArrayTypeSymbol array)
                {
                    appendArrayType(array, builder);
                }
                else if (type is PointerTypeSymbol pointer)
                {
                    appendTypeWithAnnotation(pointer.PointedAtTypeWithAnnotations, builder);
                    builder.Append('*');
                }
                else if (type is FunctionPointerTypeSymbol functionPointer)
                {
                    appendFunctionPointerType(functionPointer, builder);
                }
                else if (type is DynamicTypeSymbol)
                {
                    builder.Append("dynamic");
                }
                else
                {
                    throw ExceptionUtilities.UnexpectedValue(type);
                }
            }
 
            static void appendNamedType(NamedTypeSymbol namedType, StringBuilder builder)
            {
                Debug.Assert(namedType.CustomModifierCount() == 0);
 
                if (namedType.SpecialType == SpecialType.System_Void)
                {
                    builder.Append("void");
                    return;
                }
 
                if (namedType.IsTupleType)
                {
                    builder.Append('(');
                    ImmutableArray<string?> elementNames = namedType.TupleElementNames;
                    ImmutableArray<TypeWithAnnotations> tupleElementTypes = namedType.TupleElementTypesWithAnnotations;
                    for (int i = 0; i < tupleElementTypes.Length; i++)
                    {
                        TypeWithAnnotations elementType = tupleElementTypes[i];
                        if (i > 0)
                        {
                            builder.Append(", ");
                        }
 
                        appendTypeWithAnnotation(elementType, builder);
 
                        if (!elementNames.IsDefault && elementNames[i] is { } name)
                        {
                            builder.Append(" ");
                            appendIdentifier(name, builder);
                        }
 
                        Debug.Assert(elementType.CustomModifiers.IsEmpty);
                    }
 
                    builder.Append(')');
                    return;
                }
 
                appendNamespace(namedType.ContainingNamespace, builder);
                appendContainingType(namedType, builder);
                appendIdentifier(namedType.Name, builder);
                appendTypeArguments(namedType, builder);
            }
 
            static void appendIdentifier(string name, StringBuilder builder, bool forTypeConstraint = false)
            {
                // We technically only need to escape all contextual keywords, but do it generally for simplicity and robustness
                if (SyntaxFacts.GetKeywordKind(name) != SyntaxKind.None
                    || SyntaxFacts.GetContextualKeywordKind(name) != SyntaxKind.None
                    || name is "dynamic" or "notnull")
                {
                    builder.Append('@');
                }
 
                builder.Append(name);
            }
 
            static void appendNamespace(NamespaceSymbol ns, StringBuilder builder)
            {
                if (ns is not null && !ns.IsGlobalNamespace)
                {
                    appendNamespace(ns.ContainingNamespace, builder);
                    builder.Append(ns.Name);
                    builder.Append('.');
                }
            }
 
            static void appendContainingType(NamedTypeSymbol namedType, StringBuilder builder)
            {
                if (namedType.ContainingType is { } containingType)
                {
                    appendContainingType(containingType, builder);
                    builder.Append(containingType.Name);
                    appendTypeArguments(containingType, builder);
                    builder.Append('.');
                }
            }
 
            static void appendTypeArguments(NamedTypeSymbol namedType, StringBuilder builder)
            {
                ImmutableArray<TypeWithAnnotations> typeArguments = namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
                if (typeArguments.Length > 0)
                {
                    builder.Append('<');
                    for (int i = 0; i < typeArguments.Length; i++)
                    {
                        if (i > 0)
                        {
                            builder.Append(", ");
                        }
 
                        appendTypeWithAnnotation(typeArguments[i], builder);
                        Debug.Assert(typeArguments[i].CustomModifiers.IsEmpty);
                    }
 
                    builder.Append('>');
                }
            }
 
            static void appendTypeParameterConstraints(TypeParameterSymbol typeParam, StringBuilder builder)
            {
                builder.Append(" where ");
                appendIdentifier(typeParam.Name, builder);
                builder.Append(" : ");
 
                bool needComma = false;
 
                if (typeParam.HasReferenceTypeConstraint)
                {
                    builder.Append("class");
 
                    switch (typeParam.ReferenceTypeConstraintIsNullable)
                    {
                        case true:
                            builder.Append('?');
                            break;
 
                        case false:
                            builder.Append('!');
                            break;
                    }
 
                    needComma = true;
                }
                else if (typeParam.HasUnmanagedTypeConstraint)
                {
                    builder.Append("unmanaged");
                    needComma = true;
                }
                else if (typeParam.HasValueTypeConstraint)
                {
                    builder.Append("struct");
                    needComma = true;
                }
                else if (typeParam.HasNotNullConstraint)
                {
                    builder.Append("notnull");
                    needComma = true;
                }
 
                if (typeParam.ConstraintTypesNoUseSiteDiagnostics.Length > 0)
                {
                    appendTypeConstraints(typeParam, builder, ref needComma);
                }
 
                if (typeParam.HasConstructorConstraint)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    builder.Append("new()");
                    needComma = true;
                }
 
                if (typeParam.AllowsRefLikeType)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    builder.Append("allows ref struct");
                }
            }
 
            static void appendTypeConstraints(TypeParameterSymbol typeParam, StringBuilder builder, ref bool needComma)
            {
                ImmutableArray<TypeWithAnnotations> contraintTypes = typeParam.ConstraintTypesNoUseSiteDiagnostics;
                var typeConstraintsBuilder = ArrayBuilder<string>.GetInstance(contraintTypes.Length);
                for (int i = 0; i < contraintTypes.Length; i++)
                {
                    var stringBuilder = PooledStringBuilder.GetInstance();
                    appendTypeWithAnnotation(contraintTypes[i], stringBuilder.Builder);
                    typeConstraintsBuilder.Add(stringBuilder.ToStringAndFree());
                }
 
                typeConstraintsBuilder.Sort(StringComparer.Ordinal); // Actual order doesn't matter - just want to be deterministic
 
                foreach (string typeConstraint in typeConstraintsBuilder)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    builder.Append(typeConstraint);
                    needComma = true;
                }
 
                typeConstraintsBuilder.Free();
            }
 
            static void appendArrayType(ArrayTypeSymbol array, StringBuilder builder)
            {
                // Note: we're using the inner dimensions first order (whether nullability annotations are present or not)
                appendTypeWithAnnotation(array.ElementTypeWithAnnotations, builder);
                builder.Append('[');
                for (int i = 1; i < array.Rank; i++)
                {
                    builder.Append(',');
                }
 
                builder.Append(']');
            }
 
            static void appendFunctionPointerType(FunctionPointerTypeSymbol functionPointer, StringBuilder builder)
            {
                builder.Append("delegate*");
 
                FunctionPointerMethodSymbol signature = functionPointer.Signature;
                string? callingConvention = signature.CallingConvention switch
                {
                    Cci.CallingConvention.Default => null, // managed is the default
                    Cci.CallingConvention.Unmanaged => " unmanaged",
                    Cci.CallingConvention.CDecl => " unmanaged[CDecl]",
                    Cci.CallingConvention.Standard => " unmanaged[Stdcall]",
                    Cci.CallingConvention.ThisCall => " unmanaged[Thiscall]",
                    Cci.CallingConvention.FastCall => " unmanaged[Fastcall]",
                    _ => throw ExceptionUtilities.UnexpectedValue(signature.CallingConvention)
                };
 
                builder.Append(callingConvention);
                appendCallingConventionTypes(signature.UnmanagedCallingConventionTypes, builder);
 
                bool needComma = false;
                builder.Append('<');
                ImmutableArray<ParameterSymbol> parameters = signature.Parameters;
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    ParameterSymbol parameter = parameters[i];
                    appendRefKind(parameter.RefKind, builder, forParameter: true);
                    appendTypeWithAnnotation(parameter.TypeWithAnnotations, builder);
                    needComma = true;
                }
 
                if (needComma)
                {
                    builder.Append(", ");
                }
 
                appendRefKind(signature.RefKind, builder);
                appendTypeWithAnnotation(signature.ReturnTypeWithAnnotations.WithModifiers([]), builder);
 
                builder.Append('>');
            }
 
            static void appendRefKind(RefKind refKind, StringBuilder builder, bool forParameter = false)
            {
                builder.Append(refKind switch
                {
                    RefKind.None => "",
                    RefKind.Ref => "ref ",
                    RefKind.Out => "out ",
                    RefKind.In when forParameter => "in ",
                    RefKind.RefReadOnly => "ref readonly ",
                    RefKind.RefReadOnlyParameter => "ref readonly ",
                    _ => throw ExceptionUtilities.UnexpectedValue(refKind)
                });
            }
 
            static void appendCallingConventionTypes(ImmutableArray<NamedTypeSymbol> callingConventionTypes, StringBuilder builder)
            {
                if (callingConventionTypes.IsEmpty)
                {
                    return;
                }
 
                builder.Append('[');
                for (int i = 0; i < callingConventionTypes.Length; i++)
                {
                    if (i > 0)
                    {
                        builder.Append(", ");
                    }
 
                    Debug.Assert(callingConventionTypes[i].Name.StartsWith("CallConv", StringComparison.Ordinal));
 
                    builder.Append(callingConventionTypes[i].Name["CallConv".Length..]);
                }
 
                builder.Append(']');
            }
 
            static void appendAttributes(ImmutableArray<CSharpAttributeData> attributes, StringBuilder builder)
            {
                if (attributes.IsEmpty)
                {
                    return;
                }
 
                ArrayBuilder<string> attributesBuilder = ArrayBuilder<string>.GetInstance(attributes.Length);
 
                foreach (CSharpAttributeData attribute in attributes)
                {
                    var stringBuilder = PooledStringBuilder.GetInstance();
                    appendAttribute(attribute, stringBuilder.Builder);
                    attributesBuilder.Add(stringBuilder.ToStringAndFree());
                }
 
                attributesBuilder.Sort(StringComparer.Ordinal); // Actual order doesn't matter - just want to be deterministic
 
                for (int i = 0; i < attributesBuilder.Count; i++)
                {
                    builder.Append('[');
                    builder.Append(attributesBuilder[i]);
                    builder.Append("] ");
                }
 
                attributesBuilder.Free();
            }
 
            static void appendAttribute(CSharpAttributeData attribute, StringBuilder builder)
            {
                Debug.Assert(attribute.AttributeClass is not null);
                appendType(attribute.AttributeClass, builder);
 
                if (attribute.AttributeConstructor is { } attributeConstructor)
                {
                    appendAttributeSignature(attributeConstructor, builder);
                }
 
                if (attribute.CommonConstructorArguments.IsEmpty && attribute.CommonNamedArguments.IsEmpty)
                {
                    return;
                }
 
                builder.Append('(');
                bool needComma = false;
                foreach (TypedConstant argument in attribute.CommonConstructorArguments)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    appendAttributeArgument(argument, builder);
                    needComma = true;
                }
 
                if (!attribute.CommonNamedArguments.IsEmpty)
                {
                    var namedArgumentsBuilder = ArrayBuilder<string>.GetInstance(attribute.CommonNamedArguments.Length);
                    foreach (KeyValuePair<string, TypedConstant> namedArgument in attribute.CommonNamedArguments)
                    {
                        var stringBuilder = PooledStringBuilder.GetInstance();
                        appendAttributeNamedArgument(namedArgument, stringBuilder.Builder);
                        namedArgumentsBuilder.Add(stringBuilder.ToStringAndFree());
                    }
 
                    namedArgumentsBuilder.Sort(StringComparer.Ordinal); // Actual order doesn't matter - just want to be deterministic
 
                    foreach (string namedArgument in namedArgumentsBuilder)
                    {
                        if (needComma)
                        {
                            builder.Append(", ");
                        }
 
                        builder.Append(namedArgument);
                        needComma = true;
                    }
 
                    namedArgumentsBuilder.Free();
                }
 
                builder.Append(')');
            }
 
            static void appendAttributeArgument(TypedConstant argument, StringBuilder builder)
            {
                if (argument.Kind == TypedConstantKind.Error)
                {
                    builder.Append("error");
                    return;
                }
 
                if (argument.IsNull)
                {
                    Debug.Assert(argument.TypeInternal is not null);
                    builder.Append("null");
                    return;
                }
 
                switch (argument.Kind)
                {
                    case TypedConstantKind.Primitive:
                        appendPrimitive(argument.Value, builder);
                        break;
 
                    case TypedConstantKind.Enum:
                        Debug.Assert(argument.TypeInternal is not null);
 
                        appendPrimitive(argument.Value, builder);
                        break;
 
                    case TypedConstantKind.Type:
                        Debug.Assert(argument.ValueInternal is not null);
                        builder.Append("typeof(");
                        AppendClrType((TypeSymbol)argument.ValueInternal, modifiers: [], builder);
                        builder.Append(')');
                        break;
 
                    case TypedConstantKind.Array:
                        Debug.Assert(argument.TypeInternal is not null);
                        builder.Append("[");
                        bool needComma = false;
                        foreach (TypedConstant element in argument.Values)
                        {
                            if (needComma)
                            {
                                builder.Append(", ");
                            }
 
                            appendAttributeArgument(element, builder);
                            needComma = true;
                        }
 
                        builder.Append("]");
                        break;
 
                    default:
                        throw ExceptionUtilities.UnexpectedValue(argument.Kind);
                }
            }
 
            static void appendAttributeNamedArgument(KeyValuePair<string, TypedConstant> namedArgument, StringBuilder builder)
            {
                appendIdentifier(namedArgument.Key, builder);
                builder.Append(" = ");
                appendAttributeArgument(namedArgument.Value, builder);
            }
 
            static void appendPrimitive(object? value, StringBuilder builder)
            {
                const ObjectDisplayOptions options = ObjectDisplayOptions.UseQuotes | ObjectDisplayOptions.EscapeNonPrintableCharacters;
 
                switch (value)
                {
                    case bool b:
                        builder.Append(b ? "true" : "false");
                        break;
                    case sbyte sb:
                        builder.Append(sb.ToString(CultureInfo.InvariantCulture));
                        break;
                    case short sh:
                        builder.Append(sh.ToString(CultureInfo.InvariantCulture));
                        break;
                    case int i:
                        builder.Append(i.ToString(CultureInfo.InvariantCulture));
                        break;
                    case long l:
                        builder.Append(l.ToString(CultureInfo.InvariantCulture));
                        break;
                    case byte by:
                        builder.Append(by.ToString(CultureInfo.InvariantCulture));
                        break;
                    case ushort us:
                        builder.Append(us.ToString(CultureInfo.InvariantCulture));
                        break;
                    case uint ui:
                        builder.Append(ui.ToString(CultureInfo.InvariantCulture));
                        break;
                    case ulong ul:
                        builder.Append(ul.ToString(CultureInfo.InvariantCulture));
                        break;
                    case float f:
                        // Note: we're printing as bits to avoid any loss
                        int i2 = BitConverter.ToInt32(BitConverter.GetBytes(f), startIndex: 0);
                        // Do we need some adjustment for endianness? Tracked by https://github.com/dotnet/roslyn/issues/79374
                        builder.Append(i2.ToString(CultureInfo.InvariantCulture));
                        break;
                    case double d:
                        // Note: we're printing as bits to avoid any loss
                        long l2 = BitConverter.DoubleToInt64Bits(d);
                        // Do we need some adjustment for endianness? Tracked by https://github.com/dotnet/roslyn/issues/79374
                        builder.Append(l2.ToString(CultureInfo.InvariantCulture));
                        break;
                    case char c:
                        builder.Append(ObjectDisplay.FormatLiteral(c, options));
                        break;
                    case string s:
                        builder.Append(ObjectDisplay.FormatLiteral(s, options));
                        break;
                }
            }
 
            static void appendAttributeSignature(MethodSymbol constructor, StringBuilder builder)
            {
                builder.Append("/*(");
                bool needComma = false;
                foreach (var parameter in constructor.Parameters)
                {
                    if (needComma)
                    {
                        builder.Append(", ");
                    }
 
                    TypeWithAnnotations parameterType = parameter.TypeWithAnnotations;
                    AppendClrType(parameterType.Type, parameterType.CustomModifiers, builder);
                    needComma = true;
                }
 
                builder.Append(")*/");
            }
        }
 
        internal sealed override ParameterSymbol? ExtensionParameter
        {
            get
            {
                if (!IsExtension)
                {
                    return null;
                }
 
                var markerMethod = TryGetOrCreateExtensionMarker();
 
                if (_lazyExtensionInfo.LazyExtensionParameter == null && markerMethod is { Parameters: [var parameter, ..] })
                {
                    Interlocked.CompareExchange(ref _lazyExtensionInfo.LazyExtensionParameter, new ReceiverParameterSymbol(this, parameter), null);
                }
 
                return _lazyExtensionInfo.LazyExtensionParameter;
            }
        }
 
        public sealed override MethodSymbol? TryGetCorrespondingExtensionImplementationMethod(MethodSymbol method)
        {
            Debug.Assert(this.IsExtension);
            Debug.Assert(method.IsDefinition);
            Debug.Assert(method.ContainingType == (object)this);
 
            var containingType = this.ContainingType;
 
            if (containingType is null)
            {
                return null;
            }
 
            if (_lazyExtensionInfo is null)
            {
                Interlocked.CompareExchange(ref _lazyExtensionInfo, new ExtensionInfo(), null);
            }
 
            if (_lazyExtensionInfo.LazyImplementationMap is null)
            {
                var builder = ImmutableDictionary.CreateBuilder<MethodSymbol, MethodSymbol>(ReferenceEqualityComparer.Instance);
 
                builder.AddRange(
                    containingType.GetMembersUnordered().OfType<SourceExtensionImplementationMethodSymbol>().
                    Select(static m => new KeyValuePair<MethodSymbol, MethodSymbol>(m.UnderlyingMethod, m)));
 
                Interlocked.CompareExchange(ref _lazyExtensionInfo.LazyImplementationMap, builder.ToImmutable(), null);
            }
 
            return _lazyExtensionInfo.LazyImplementationMap.GetValueOrDefault(method);
        }
 
        protected sealed override MethodSymbol? CreateSynthesizedExtensionMarker()
        {
            return TryGetOrCreateExtensionMarker();
        }
 
        [MemberNotNull(nameof(_lazyExtensionInfo))]
        private MethodSymbol? TryGetOrCreateExtensionMarker()
        {
            Debug.Assert(IsExtension);
 
            if (_lazyExtensionInfo is null)
            {
                Interlocked.CompareExchange(ref _lazyExtensionInfo, new ExtensionInfo(), null);
            }
 
            if (_lazyExtensionInfo.LazyExtensionMarker == (object)ErrorMethodSymbol.UnknownMethod)
            {
                Interlocked.CompareExchange(ref _lazyExtensionInfo.LazyExtensionMarker, tryCreateExtensionMarker(), ErrorMethodSymbol.UnknownMethod);
            }
 
            return _lazyExtensionInfo.LazyExtensionMarker;
 
            MethodSymbol? tryCreateExtensionMarker()
            {
                var syntax = (ExtensionBlockDeclarationSyntax)this.GetNonNullSyntaxNode();
                var parameterList = syntax.ParameterList;
                Debug.Assert(parameterList is not null);
 
                if (parameterList is null)
                {
                    return null;
                }
 
                int count = parameterList.Parameters.Count;
                Debug.Assert(count > 0);
 
                return new SynthesizedExtensionMarker(this, parameterList);
            }
        }
 
        internal static Symbol? GetCompatibleSubstitutedMember(CSharpCompilation compilation, Symbol extensionMember, TypeSymbol receiverType)
        {
            Debug.Assert(extensionMember.GetIsNewExtensionMember());
 
            NamedTypeSymbol extension = extensionMember.ContainingType;
            if (extension.ExtensionParameter is null)
            {
                return null;
            }
 
            Symbol result;
            if (extensionMember.IsDefinition)
            {
                NamedTypeSymbol? constructedExtension = inferExtensionTypeArguments(extension, receiverType, compilation);
                if (constructedExtension is null)
                {
                    return null;
                }
 
                result = extensionMember.SymbolAsMember(constructedExtension);
            }
            else
            {
                result = extensionMember;
            }
 
            Debug.Assert(result.ContainingType.ExtensionParameter is not null);
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            Conversion conversion = compilation.Conversions.ConvertExtensionMethodThisArg(parameterType: result.ContainingType.ExtensionParameter.Type, receiverType, ref discardedUseSiteInfo, isMethodGroupConversion: false);
            if (!conversion.Exists)
            {
                return null;
            }
 
            return result;
 
            static NamedTypeSymbol? inferExtensionTypeArguments(NamedTypeSymbol extension, TypeSymbol receiverType, CSharpCompilation compilation)
            {
                if (extension.Arity == 0)
                {
                    return extension;
                }
 
                TypeConversions conversions = extension.ContainingAssembly.CorLibrary.TypeConversions;
 
                // Note: we create a value for purpose of inferring type arguments even when the receiver type is static
                var syntax = (CSharpSyntaxNode)CSharpSyntaxTree.Dummy.GetRoot();
                var receiverValue = new BoundLiteral(syntax, ConstantValue.Bad, receiverType) { WasCompilerGenerated = true };
 
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
                ImmutableArray<TypeWithAnnotations> typeArguments = MethodTypeInferrer.InferTypeArgumentsFromReceiverType(extension, receiverValue, compilation, conversions, ref discardedUseSiteInfo);
                if (typeArguments.IsDefault || typeArguments.Any(t => !t.HasType))
                {
                    return null;
                }
 
                var result = extension.Construct(typeArguments);
 
                var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false,
                    NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo<AssemblySymbol>.Discarded);
 
                bool success = result.CheckConstraints(constraintArgs);
                if (!success)
                {
                    return null;
                }
 
                return result;
            }
        }
    }
}