File: AspireProvisioningProxyGenerator.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.Provisioning.Generators\Aspire.Hosting.Azure.Provisioning.Generators.csproj (Aspire.Hosting.Azure.Provisioning.Generators)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
 
namespace Aspire.Hosting.Azure.Provisioning.Generators;
 
[Generator(LanguageNames.CSharp)]
internal sealed class AspireProvisioningProxyGenerator : IIncrementalGenerator
{
    private const string AttributeMetadataName = "Aspire.Hosting.Azure.Provisioning.GenerateAspireProvisioningProxyAttribute";
    private const string BicepDictionaryMetadataName = "Azure.Provisioning.BicepDictionary<T>";
    private const string BicepListMetadataName = "Azure.Provisioning.BicepList<T>";
    private const string BicepValueBaseMetadataName = "Azure.Provisioning.BicepValue";
    private const string BicepValueMetadataName = "Azure.Provisioning.BicepValue<T>";
    private const string CoreProvisioningAssemblyName = "Azure.Provisioning";
    private const string AzureCoreAssemblyName = "Azure.Core";
    private const string AzureLocationMetadataName = "Azure.Core.AzureLocation";
    private const string ResourceIdentifierMetadataName = "Azure.Core.ResourceIdentifier";
    private const string ResourceTypeMetadataName = "Azure.Core.ResourceType";
    private const string SystemDataMetadataName = "Azure.Provisioning.SystemData";
    private const string BicepValueProxyTypeName = "global::Aspire.Hosting.Azure.Provisioning.BicepValueProxy";
    private const string ProvisionableResourceProxyTypeName = "global::Aspire.Hosting.Azure.Provisioning.ProvisionableResourceProxy";
    private const string AzureResourceInfrastructureTypeName = "global::Aspire.Hosting.Azure.AzureResourceInfrastructure";
    private const string FactoryClassName = "AzureResourceInfrastructureProvisioningExtensions";
    private const string DiagnosticCategory = "Aspire.Hosting.Azure.Provisioning";
    private const string ExperimentalDiagnosticId = "ASPIREAZUREPROVISIONING001";
    private const string ExperimentalUrlFormat = "https://aka.ms/aspire/diagnostics/{0}";
 
    private static readonly DiagnosticDescriptor s_unsupportedProperty = new(
        id: "ASPIREAZUREPROVISIONING002",
        title: "Unsupported provisioning property",
        messageFormat: "Property '{0}' on provisioning type '{1}' cannot be exported because {2}",
        category: DiagnosticCategory,
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
 
    private static readonly DiagnosticDescriptor s_unsupportedMethod = new(
        id: "ASPIREAZUREPROVISIONING003",
        title: "Unsupported provisioning method",
        messageFormat: "Method '{0}' on provisioning type '{1}' cannot be exported because {2}",
        category: DiagnosticCategory,
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
 
    private static readonly DiagnosticDescriptor s_duplicateProjectedMethod = new(
        id: "ASPIREAZUREPROVISIONING004",
        title: "Duplicate projected provisioning method",
        messageFormat: "Method '{0}' on provisioning type '{1}' projects to duplicate signature '{2}' and will not be exported",
        category: DiagnosticCategory,
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
 
    private static readonly DiagnosticDescriptor s_unsupportedRootType = new(
        id: "ASPIREAZUREPROVISIONING005",
        title: "Unsupported provisioning proxy root",
        messageFormat: "Type '{0}' cannot be used as a provisioning proxy root because it is not a non-generic, non-static class or struct",
        category: DiagnosticCategory,
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
 
    private static readonly DiagnosticDescriptor s_unsupportedMutableStructRoot = new(
        id: "ASPIREAZUREPROVISIONING006",
        title: "Unsupported mutable provisioning proxy root",
        messageFormat: "Type '{0}' cannot be used as a provisioning proxy root because mutable structs are not supported",
        category: DiagnosticCategory,
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
 
    private static readonly SymbolDisplayFormat s_typeDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat
        .WithMiscellaneousOptions(
            SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
            SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
 
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(static sourceContext =>
            sourceContext.AddSource(
                "GenerateAspireProvisioningProxyAttribute.g.cs",
                SourceText.From(
                    """
                    // <auto-generated/>
                    namespace Aspire.Hosting.Azure.Provisioning
                    {
                        #pragma warning disable ASPIREEXPORT018
                        [global::Aspire.Hosting.AspireExportProvider]
                        #pragma warning restore ASPIREEXPORT018
                        [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, AllowMultiple = true)]
                        internal sealed class GenerateAspireProvisioningProxyAttribute : global::System.Attribute
                        {
                            public GenerateAspireProvisioningProxyAttribute(global::System.Type type)
                            {
                            }
 
                            public bool IsInfrastructureRoot { get; set; } = true;
 
                            public bool IncludeContainingAssemblyTypes { get; set; }
 
                            public string[] ExcludedMemberNames { get; set; } = global::System.Array.Empty<string>();
                        }
                    }
                    """,
                    Encoding.UTF8)));
 
        var rootTypeGroups = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                AttributeMetadataName,
                static (_, _) => true,
                static (attributeContext, _) => GetRootTypes(attributeContext));
 
        context.RegisterSourceOutput(
            context.CompilationProvider.Combine(rootTypeGroups.Collect()),
            static (sourceContext, input) => Generate(
                sourceContext,
                GetGeneratedNamespace(input.Left.AssemblyName),
                input.Right.SelectMany(static roots => roots).ToImmutableArray()));
    }
 
    private static ImmutableArray<ProxyRoot> GetRootTypes(GeneratorAttributeSyntaxContext context)
    {
        var roots = ImmutableArray.CreateBuilder<ProxyRoot>();
 
        foreach (var attribute in context.Attributes)
        {
            if (attribute.ConstructorArguments.Length == 1 &&
                attribute.ConstructorArguments[0] is { Kind: TypedConstantKind.Type, Value: INamedTypeSymbol type })
            {
                var isInfrastructureRoot = true;
                var includeContainingAssemblyTypes = false;
                var excludedMembers = ImmutableArray.CreateBuilder<string>();
                foreach (var namedArgument in attribute.NamedArguments)
                {
                    if (namedArgument.Key == "IsInfrastructureRoot" &&
                        namedArgument.Value.Value is bool value)
                    {
                        isInfrastructureRoot = value;
                    }
 
                    if (namedArgument.Key == "IncludeContainingAssemblyTypes" &&
                        namedArgument.Value.Value is bool includeAssemblyTypes)
                    {
                        includeContainingAssemblyTypes = includeAssemblyTypes;
                    }
 
                    if (namedArgument.Key == "ExcludedMemberNames" &&
                        namedArgument.Value.Kind == TypedConstantKind.Array)
                    {
                        foreach (var excludedMember in namedArgument.Value.Values)
                        {
                            if (excludedMember.Value is string memberName)
                            {
                                excludedMembers.Add(memberName);
                            }
                        }
                    }
                }
 
                roots.Add(new ProxyRoot(
                    type,
                    isInfrastructureRoot,
                    includeContainingAssemblyTypes,
                    excludedMembers.ToImmutable()));
            }
        }
 
        return roots.ToImmutable();
    }
 
    private static void Generate(
        SourceProductionContext context,
        string generatedNamespace,
        ImmutableArray<ProxyRoot> roots)
    {
        if (roots.IsDefaultOrEmpty)
        {
            return;
        }
 
        var normalizedRoots = NormalizeRoots(roots);
        var expandedRoots = ExpandContainingAssemblyTypes(normalizedRoots);
        var validRoots = ImmutableArray.CreateBuilder<ProxyRoot>();
        foreach (var root in expandedRoots)
        {
            if (CanGenerateProxy(root.Type))
            {
                if (IsMutableStruct(root.Type))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        s_unsupportedMutableStructRoot,
                        GetDiagnosticLocation(root.Type),
                        root.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
                }
                else
                {
                    validRoots.Add(root);
                }
            }
            else
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    s_unsupportedRootType,
                    GetDiagnosticLocation(root.Type),
                    root.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
            }
        }
 
        if (validRoots.Count == 0)
        {
            return;
        }
 
        var rootTypes = validRoots.Select(static root => root.Type).ToImmutableArray();
        var excludedMemberNames = CreateExcludedMemberNames(validRoots);
        var discovery = DiscoverProxyTypes(rootTypes, excludedMemberNames);
        if (discovery.Types.Count == 0)
        {
            return;
        }
 
        var proxyNames = CreateProxyNames(discovery.Types, generatedNamespace);
        var collectionNames = CreateCollectionNames(discovery.Collections, proxyNames, generatedNamespace);
        var source = new StringBuilder(
            """
            // <auto-generated/>
            #nullable enable
 
            """);
        source.Append("namespace ").Append(generatedNamespace).AppendLine();
        source.AppendLine("{");
        source.AppendLine();
 
        foreach (var type in discovery.Types.OrderBy(static type => type.ToDisplayString(), StringComparer.Ordinal))
        {
            GenerateProxy(context, source, type, proxyNames, collectionNames, excludedMemberNames);
        }
 
        foreach (var collection in discovery.Collections
            .Where(collection => collectionNames.ContainsKey(collection.Type))
            .OrderBy(collection => collection.Type.ToDisplayString(), StringComparer.Ordinal))
        {
            GenerateCollectionProxy(source, collection, proxyNames, collectionNames);
        }
 
        GenerateFactoryClass(
            source,
            validRoots.ToImmutable(),
            discovery.Types,
            proxyNames,
            collectionNames);
 
        source.AppendLine("}");
        context.AddSource("AspireProvisioningProxies.g.cs", SourceText.From(source.ToString(), Encoding.UTF8));
    }
 
    private static ImmutableArray<ProxyRoot> NormalizeRoots(IEnumerable<ProxyRoot> roots)
    {
        return roots
            .GroupBy(static root => root.Type, SymbolEqualityComparer.Default)
            .Select(static group => new ProxyRoot(
                (INamedTypeSymbol)group.Key,
                group.Any(static root => root.IsInfrastructureRoot),
                group.Any(static root => root.IncludeContainingAssemblyTypes),
                group.SelectMany(static root => root.ExcludedMemberNames)
                    .Distinct(StringComparer.Ordinal)
                    .ToImmutableArray()))
            .ToImmutableArray();
    }
 
    private static ImmutableArray<ProxyRoot> ExpandContainingAssemblyTypes(ImmutableArray<ProxyRoot> roots)
    {
        var expandedRoots = roots.ToBuilder();
        var expandedAssemblies = new HashSet<IAssemblySymbol>(SymbolEqualityComparer.Default);
 
        foreach (var root in roots)
        {
            if (!root.IncludeContainingAssemblyTypes ||
                !expandedAssemblies.Add(root.Type.ContainingAssembly))
            {
                continue;
            }
 
            foreach (var type in EnumeratePublicProxyTypes(root.Type.ContainingAssembly.GlobalNamespace))
            {
                expandedRoots.Add(new ProxyRoot(
                    type,
                    isInfrastructureRoot: false,
                    includeContainingAssemblyTypes: false,
                    ImmutableArray<string>.Empty));
            }
        }
 
        return NormalizeRoots(expandedRoots);
    }
 
    private static IEnumerable<INamedTypeSymbol> EnumeratePublicProxyTypes(INamespaceSymbol namespaceSymbol)
    {
        foreach (var type in namespaceSymbol.GetTypeMembers())
        {
            foreach (var publicType in EnumeratePublicProxyTypes(type))
            {
                yield return publicType;
            }
        }
 
        foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
        {
            foreach (var publicType in EnumeratePublicProxyTypes(childNamespace))
            {
                yield return publicType;
            }
        }
    }
 
    private static IEnumerable<INamedTypeSymbol> EnumeratePublicProxyTypes(INamedTypeSymbol type)
    {
        if (type.DeclaredAccessibility != Accessibility.Public)
        {
            yield break;
        }
 
        if (CanGenerateProxy(type) && !IsMutableStruct(type))
        {
            yield return type;
        }
 
        foreach (var nestedType in type.GetTypeMembers())
        {
            foreach (var publicType in EnumeratePublicProxyTypes(nestedType))
            {
                yield return publicType;
            }
        }
    }
 
    private static string GetGeneratedNamespace(string? assemblyName)
    {
        if (string.IsNullOrEmpty(assemblyName))
        {
            return "Aspire.Hosting.Provisioning.Generated";
        }
 
        var namespaceName = new StringBuilder(assemblyName.Length + ".Generated".Length + 8);
        var segments = assemblyName.Split('.');
        for (var index = 0; index < segments.Length; index++)
        {
            if (index > 0)
            {
                namespaceName.Append('.');
            }
 
            namespaceName.Append(SanitizeNamespaceSegment(segments[index]));
        }
        namespaceName.Append(".Generated");
        return namespaceName.ToString();
    }
 
    private static HashSet<string> CreateExcludedMemberNames(
        ImmutableArray<ProxyRoot>.Builder roots)
    {
        var excludedMemberNames = new HashSet<string>(StringComparer.Ordinal);
        foreach (var root in roots)
        {
            excludedMemberNames.UnionWith(root.ExcludedMemberNames);
        }
 
        return excludedMemberNames;
    }
 
    private static DiscoveryResult DiscoverProxyTypes(
        ImmutableArray<INamedTypeSymbol> roots,
        HashSet<string> excludedMemberNames)
    {
        var types = new List<INamedTypeSymbol>();
        var collections = new List<CollectionShape>();
        var seenTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
        var seenCollections = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
        var rootNamespaces = new HashSet<string>(StringComparer.Ordinal);
 
        foreach (var root in roots)
        {
            rootNamespaces.Add(root.ContainingNamespace.ToDisplayString());
            AddType(root);
        }
 
        for (var index = 0; index < types.Count; index++)
        {
            var type = types[index];
 
            foreach (var property in GetExportableProperties(type))
            {
                if (!IsExcluded(property, excludedMemberNames))
                {
                    DiscoverMappedType(property.Type);
                }
            }
 
            foreach (var method in GetExportableMethods(type))
            {
                if (IsExcluded(method, excludedMemberNames))
                {
                    continue;
                }
 
                DiscoverMappedType(method.ReturnType);
                foreach (var parameter in method.Parameters)
                {
                    DiscoverMappedType(parameter.Type);
                }
            }
        }
 
        return new DiscoveryResult(types, collections);
 
        void DiscoverMappedType(ITypeSymbol type)
        {
            if (TryGetBicepValueArgument(type, out var valueType))
            {
                type = valueType;
            }
 
            type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
            if (TryGetNullableValueType(type, out var nullableValueType))
            {
                type = nullableValueType;
            }
 
            if (TryGetCollection(type, out var collectionKind, out var elementType, out var collectionType))
            {
                if (seenCollections.Add(collectionType))
                {
                    collections.Add(new CollectionShape(collectionType, elementType, collectionKind));
                    DiscoverMappedType(elementType);
                }
 
                return;
            }
 
            if (type is INamedTypeSymbol namedType &&
                namedType.TypeKind is TypeKind.Class or TypeKind.Struct &&
                !IsProvisionableResourceBase(namedType) &&
                namedType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) != BicepValueBaseMetadataName &&
                IsInProxyScope(namedType) &&
                !IsSimpleType(namedType))
            {
                AddType(namedType);
            }
        }
 
        bool IsInProxyScope(INamedTypeSymbol type)
        {
            // Core provisioning models and the small Azure.Core value-type graph they depend on are
            // duplicated as internal proxies in each opt-in package. Service SDK assemblies remain
            // namespace-bounded so one integration cannot pull in another service's entire API surface.
            if (IsSharedProxyType(type))
            {
                return true;
            }
 
            var namespaceName = type.ContainingNamespace.ToDisplayString();
            foreach (var rootNamespace in rootNamespaces)
            {
                if (namespaceName == rootNamespace ||
                    namespaceName.StartsWith(rootNamespace + ".", StringComparison.Ordinal))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        void AddType(INamedTypeSymbol type)
        {
            type = (INamedTypeSymbol)type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
            if (CanGenerateProxy(type) &&
                !IsMutableStruct(type) &&
                seenTypes.Add(type))
            {
                types.Add(type);
            }
        }
    }
 
    private static Dictionary<INamedTypeSymbol, string> CreateProxyNames(
        List<INamedTypeSymbol> types,
        string generatedNamespace)
    {
        var names = new Dictionary<INamedTypeSymbol, string>(SymbolEqualityComparer.Default);
        var coreTypePrefix = GetPackageIdentifier(generatedNamespace);
        var desiredNames = new Dictionary<INamedTypeSymbol, string>(SymbolEqualityComparer.Default);
 
        foreach (var type in types)
        {
            desiredNames.Add(
                type,
                (IsSharedProxyType(type) ? coreTypePrefix : string.Empty) +
                type.Name +
                "Proxy");
        }
 
        // Shared Azure.Provisioning types receive the package prefix, which can make their final
        // proxy identifier collide with a service SDK type. Resolve collisions only after applying
        // that prefix so every declaration and reference uses the same unique name.
        var groups = desiredNames
            .GroupBy(static pair => pair.Value, StringComparer.Ordinal)
            .OrderBy(static group => group.Key, StringComparer.Ordinal)
            .ToList();
        var usedNames = new HashSet<string>(
            groups.Where(static group => group.Count() == 1).Select(static group => group.Key),
            StringComparer.Ordinal);
 
        foreach (var group in groups)
        {
            if (group.Count() == 1)
            {
                var pair = group.Single();
                names.Add(pair.Key, pair.Value);
                continue;
            }
 
            var nameWithoutSuffix = group.Key.Substring(0, group.Key.Length - "Proxy".Length);
            var suffix = 1;
            foreach (var pair in group.OrderBy(
                static pair => pair.Key.ToDisplayString(),
                StringComparer.Ordinal))
            {
                string generatedName;
                do
                {
                    generatedName = nameWithoutSuffix +
                        "_" +
                        suffix.ToString(System.Globalization.CultureInfo.InvariantCulture) +
                        "Proxy";
                    suffix++;
                }
                while (!usedNames.Add(generatedName));
 
                names.Add(pair.Key, generatedName);
            }
        }
 
        return names;
    }
 
    private static string GetPackageIdentifier(string generatedNamespace)
    {
        const string generatedSuffix = ".Generated";
        var packageNamespace = generatedNamespace.EndsWith(generatedSuffix, StringComparison.Ordinal)
            ? generatedNamespace.Substring(0, generatedNamespace.Length - generatedSuffix.Length)
            : generatedNamespace;
        var separatorIndex = packageNamespace.LastIndexOf('.');
        return SanitizeIdentifier(packageNamespace.Substring(separatorIndex + 1));
    }
 
    private static Dictionary<INamedTypeSymbol, string> CreateCollectionNames(
        List<CollectionShape> collections,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        string generatedNamespace)
    {
        var supportedCollections = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
        var addedCollection = true;
 
        while (addedCollection)
        {
            addedCollection = false;
            foreach (var collection in collections)
            {
                if (!supportedCollections.Contains(collection.Type) &&
                    CanMapCollectionElement(collection.ElementType, proxyNames, supportedCollections))
                {
                    supportedCollections.Add(collection.Type);
                    addedCollection = true;
                }
            }
        }
 
        var names = new Dictionary<INamedTypeSymbol, string>(SymbolEqualityComparer.Default);
        var supportedShapes = collections.Where(collection => supportedCollections.Contains(collection.Type));
        var namePrefix = SanitizeIdentifier(generatedNamespace) + "_";
        foreach (var group in supportedShapes.GroupBy(
            collection => namePrefix + collection.Kind + "Of" + GetFriendlyTypeIdentifier(collection.ElementType) + "Proxy",
            StringComparer.Ordinal))
        {
            if (group.Count() == 1)
            {
                var collection = group.Single();
                names.Add(collection.Type, group.Key);
                continue;
            }
 
            var index = 0;
            foreach (var collection in group.OrderBy(
                static collection => collection.Type.ToDisplayString(),
                StringComparer.Ordinal))
            {
                var qualifiedElementName = SanitizeIdentifier(
                    collection.ElementType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
                names.Add(
                    collection.Type,
                    group.Key.Substring(0, group.Key.Length - "Proxy".Length) +
                    qualifiedElementName +
                    "_" +
                    (++index).ToString(System.Globalization.CultureInfo.InvariantCulture) +
                    "Proxy");
            }
        }
 
        return names;
    }
 
    private static bool CanMapCollectionElement(
        ITypeSymbol elementType,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        HashSet<INamedTypeSymbol> supportedCollections)
    {
        if (IsSimpleType(elementType) || IsIPAddress(elementType))
        {
            return true;
        }
 
        if (elementType is not INamedTypeSymbol namedType)
        {
            return false;
        }
 
        return IsProvisionableResourceBase(namedType) ||
            proxyNames.ContainsKey(namedType) ||
            supportedCollections.Contains(namedType);
    }
 
    private static string GetFriendlyTypeIdentifier(ITypeSymbol type)
    {
        if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType)
        {
            return SanitizeIdentifier(type.Name);
        }
 
        var name = new StringBuilder(namedType.Name);
        foreach (var argument in namedType.TypeArguments)
        {
            name.Append("Of").Append(GetFriendlyTypeIdentifier(argument));
        }
 
        return SanitizeIdentifier(name.ToString());
    }
 
    private static string SanitizeIdentifier(string value)
    {
        var identifier = new StringBuilder(value.Length + 1);
        foreach (var character in value)
        {
            identifier.Append(char.IsLetterOrDigit(character) || character == '_' ? character : '_');
        }
 
        if (identifier.Length == 0 || char.IsDigit(identifier[0]))
        {
            identifier.Insert(0, '_');
        }
 
        return identifier.ToString();
    }
 
    private static string SanitizeNamespaceSegment(string value)
    {
        var identifier = new StringBuilder(value.Length + 2);
        foreach (var character in value)
        {
            identifier.Append(char.IsLetterOrDigit(character) || character == '_' ? character : '_');
        }
 
        if (identifier.Length == 0 || !SyntaxFacts.IsIdentifierStartCharacter(identifier[0]))
        {
            identifier.Insert(0, '_');
        }
 
        while (!SyntaxFacts.IsValidIdentifier(identifier.ToString()) ||
            SyntaxFacts.GetKeywordKind(identifier.ToString()) != SyntaxKind.None ||
            SyntaxFacts.GetContextualKeywordKind(identifier.ToString()) != SyntaxKind.None)
        {
            identifier.Insert(0, '_');
        }
 
        return identifier.ToString();
    }
 
    private static void GenerateProxy(
        SourceProductionContext context,
        StringBuilder source,
        INamedTypeSymbol type,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        HashSet<string> excludedMemberNames)
    {
        var proxyName = proxyNames[type];
        var underlyingTypeName = type.ToDisplayString(s_typeDisplayFormat);
        var isProvisionableResource = IsProvisionableResource(type);
        var proxyBaseType = GetProxyBaseType(type, proxyNames);
 
        if (!AppendDocumentationComment(source, type, "    "))
        {
            AppendDocumentationSummary(source, "    ", $"Represents the {type.Name} Azure Provisioning model.");
        }
        source.AppendLine("    [global::Aspire.Hosting.AspireExportAttribute]");
        source.Append("    internal class ").Append(proxyName);
        if (proxyBaseType is not null)
        {
            source.Append(" : ").Append(proxyNames[proxyBaseType]);
        }
        else if (isProvisionableResource)
        {
            source.Append(" : ").Append(ProvisionableResourceProxyTypeName);
        }
        source.AppendLine();
        source.AppendLine("    {");
        source.Append("        internal ").Append(proxyName).Append('(').Append(underlyingTypeName).Append(" value)");
        if (proxyBaseType is not null || isProvisionableResource)
        {
            source.Append(" : base(value)");
        }
        source.AppendLine();
        source.AppendLine("        {");
        if (proxyBaseType is null && !isProvisionableResource)
        {
            if (type.IsValueType)
            {
                source.AppendLine("            Inner = value;");
            }
            else
            {
                source.AppendLine("            Inner = value ?? throw new global::System.ArgumentNullException(nameof(value));");
            }
        }
        source.AppendLine("        }");
        source.AppendLine();
        if (proxyBaseType is not null || isProvisionableResource)
        {
            source.Append("        internal new ").Append(underlyingTypeName)
                .AppendLine(" Inner => (" + underlyingTypeName + ")base.Inner;");
        }
        else
        {
            source.Append("        internal ").Append(underlyingTypeName).AppendLine(" Inner { get; }");
        }
 
        if (isProvisionableResource)
        {
            GenerateAddToMethod(source, proxyName, proxyBaseType is not null && IsProvisionableResource(proxyBaseType));
        }
 
        foreach (var property in GetExportableProperties(
            type,
            includeInherited: true,
            stopBeforeType: proxyBaseType))
        {
            if (IsExcluded(property, excludedMemberNames))
            {
                continue;
            }
 
            if (property.IsIndexer)
            {
                if (proxyNames.ContainsKey(property.ContainingType))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        s_unsupportedProperty,
                        GetDiagnosticLocation(property),
                        property.Name,
                        type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                        "indexers are not supported"));
                }
                continue;
            }
 
            if (isProvisionableResource && property.Name == "BicepIdentifier")
            {
                continue;
            }
 
            if (TryMapType(property.Type, proxyNames, collectionNames, out var mappedType))
            {
                GenerateProperty(source, property, mappedType, proxyName);
            }
            else
            {
                if (proxyNames.ContainsKey(property.ContainingType))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        s_unsupportedProperty,
                        GetDiagnosticLocation(property),
                        property.Name,
                        type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                        $"type '{property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)}' is not supported"));
                }
            }
        }
 
        var mappedMethods = GetMappedMethods(
            context,
            type,
            proxyNames,
            collectionNames,
            excludedMemberNames,
            GetInheritedProjectedMethodSignatures(
                proxyBaseType,
                proxyNames,
                collectionNames,
                excludedMemberNames),
            includeInherited: true,
            stopBeforeType: proxyBaseType);
        foreach (var methodGroup in mappedMethods.GroupBy(static method => method.Method.Name, StringComparer.Ordinal))
        {
            var overloads = methodGroup.OrderBy(static method => method.Signature, StringComparer.Ordinal).ToList();
            for (var index = 0; index < overloads.Count; index++)
            {
                GenerateMethod(source, overloads[index], proxyName, index, overloads.Count);
            }
        }
 
        source.AppendLine("    }");
        source.AppendLine();
    }
 
    private static void GenerateAddToMethod(StringBuilder source, string proxyName, bool hidesBaseMethod)
    {
        source.AppendLine();
        AppendDocumentationSummary(
            source,
            "        ",
            "Adds this provisioning resource to the Azure resource infrastructure.");
        AppendInfrastructureParameterDocumentation(source, "        ");
        AppendMethodExportAttribute(source, proxyName + ".addTo", "AddTo");
        source.Append("        internal ");
        if (hidesBaseMethod)
        {
            source.Append("new ");
        }
        source.Append("void AddTo(").Append(AzureResourceInfrastructureTypeName)
            .AppendLine(" infrastructure)");
        source.AppendLine("        {");
        source.AppendLine("            if (infrastructure is null)");
        source.AppendLine("            {");
        source.AppendLine("                throw new global::System.ArgumentNullException(nameof(infrastructure));");
        source.AppendLine("            }");
        source.AppendLine();
        source.AppendLine("            infrastructure.Add(Inner);");
        source.AppendLine("        }");
    }
 
    private static void GenerateProperty(
        StringBuilder source,
        IPropertySymbol property,
        MappedType mappedType,
        string proxyName)
    {
        var canRead = property.GetMethod?.DeclaredAccessibility == Accessibility.Public;
        var canWrite = property.SetMethod is { DeclaredAccessibility: Accessibility.Public, IsInitOnly: false };
        if (!canRead && !canWrite)
        {
            return;
        }
 
        source.AppendLine();
        if (!AppendDocumentationComment(source, property, "        ", includeValue: true))
        {
            var accessor = canRead && canWrite ? "Gets or sets" : canRead ? "Gets" : "Sets";
            AppendDocumentationSummary(
                source,
                "        ",
                $"{accessor} the {property.Name} provisioning property.");
        }
        AppendExperimentalAttribute(source);
        source.AppendLine("        [global::Aspire.Hosting.AspireExportAttribute]");
        if (mappedType is { Kind: MappedTypeKind.BicepValue, LiteralTypeName: not null })
        {
            AppendUnionAttribute(source, mappedType, "        ");
            source.AppendLine();
        }
        source.Append("        internal ").Append(mappedType.ExposedTypeName).Append(" @").Append(property.Name).AppendLine();
        source.AppendLine("        {");
 
        if (canRead)
        {
            source.Append("            get => ");
            AppendMappedFromUnderlying(source, "Inner.@" + property.Name, mappedType);
            source.AppendLine(";");
        }
 
        if (canWrite)
        {
            if (mappedType.Kind == MappedTypeKind.BicepValue)
            {
                GenerateBicepValueSetter(source, property, mappedType);
            }
            else
            {
                source.Append("            set => Inner.@").Append(property.Name).Append(" = ");
                AppendMappedToUnderlying(source, "value", mappedType);
                source.AppendLine(";");
            }
        }
 
        source.AppendLine("        }");
 
        if (canWrite && mappedType.Kind == MappedTypeKind.BicepValue)
        {
            source.AppendLine();
            AppendDocumentationSummary(
                source,
                "        ",
                $"Clears the {property.Name} provisioning property.");
            AppendMethodExportAttribute(source, proxyName + ".clear" + property.Name, "Clear" + property.Name);
            source.Append("        internal void Clear").Append(property.Name).AppendLine("()");
            source.AppendLine("        {");
            source.Append("            Inner.@").Append(property.Name).AppendLine(".ClearValue();");
            source.AppendLine("        }");
        }
    }
 
    private static void GenerateBicepValueSetter(
        StringBuilder source,
        IPropertySymbol property,
        MappedType mappedType)
    {
        source.AppendLine("            set");
        source.AppendLine("            {");
 
        if (mappedType.LiteralTypeName is not null)
        {
            source.Append("                if (value is ").Append(mappedType.LiteralTypeName).AppendLine(" literal)");
            source.AppendLine("                {");
            source.Append("                    Inner.@").Append(property.Name).AppendLine(" = literal;");
            source.AppendLine("                }");
            source.Append("                else if (value is ").Append(BicepValueProxyTypeName).AppendLine(" bicepValue)");
        }
        else
        {
            source.AppendLine("                if (value is not null)");
        }
 
        source.AppendLine("                {");
        if (mappedType.LiteralTypeName is not null)
        {
            source.Append("                    bicepValue.AssignTo(Inner.@").Append(property.Name).AppendLine(");");
        }
        else
        {
            source.Append("                    value.AssignTo(Inner.@").Append(property.Name).AppendLine(");");
        }
        source.AppendLine("                }");
        source.AppendLine("                else");
        source.AppendLine("                {");
        source.Append("                    throw new global::System.ArgumentException(\"Expected ");
        if (mappedType.LiteralTypeName is not null)
        {
            source.Append(mappedType.LiteralTypeName).Append(" or ");
        }
        source.Append(BicepValueProxyTypeName).AppendLine(".\", nameof(value));");
        source.AppendLine("                }");
        source.AppendLine("            }");
    }
 
    private static List<MappedMethod> GetMappedMethods(
        SourceProductionContext context,
        INamedTypeSymbol type,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        HashSet<string> excludedMemberNames,
        HashSet<string> projectedSignatures,
        bool includeInherited,
        INamedTypeSymbol? stopBeforeType)
    {
        var mappedMethods = new List<MappedMethod>();
 
        foreach (var method in GetExportableMethods(type, includeInherited, stopBeforeType).OrderBy(GetMethodSignature, StringComparer.Ordinal))
        {
            if (IsExcluded(method, excludedMemberNames))
            {
                continue;
            }
 
            if (!TryMapMethod(method, proxyNames, collectionNames, out var mappedMethod, out var failureReason))
            {
                if (proxyNames.ContainsKey(method.ContainingType))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        s_unsupportedMethod,
                        GetDiagnosticLocation(method),
                        method.Name,
                        type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                        failureReason));
                }
                continue;
            }
 
            var projectedSignature = GetProjectedMethodSignature(mappedMethod);
            if (projectedSignatures.Add(projectedSignature))
            {
                mappedMethods.Add(mappedMethod);
            }
            else
            {
                if (proxyNames.ContainsKey(method.ContainingType))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        s_duplicateProjectedMethod,
                        GetDiagnosticLocation(method),
                        method.Name,
                        type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                        projectedSignature));
                }
            }
        }
 
        return mappedMethods;
    }
 
    private static HashSet<string> GetInheritedProjectedMethodSignatures(
        INamedTypeSymbol? proxyBaseType,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        HashSet<string> excludedMemberNames)
    {
        var projectedSignatures = new HashSet<string>(StringComparer.Ordinal);
        for (var current = proxyBaseType; current is not null; current = GetProxyBaseType(current, proxyNames))
        {
            foreach (var method in GetExportableMethods(current, includeInherited: false))
            {
                if (!IsExcluded(method, excludedMemberNames) &&
                    TryMapMethod(method, proxyNames, collectionNames, out var mappedMethod, out _))
                {
                    projectedSignatures.Add(GetProjectedMethodSignature(mappedMethod));
                }
            }
        }
 
        return projectedSignatures;
    }
 
    private static string GetProjectedMethodSignature(MappedMethod method)
    {
        return method.Method.Name + "(" + string.Join(
            ",",
            method.Parameters.Select(static parameter => parameter.Type.ExposedTypeName)) + ")";
    }
 
    private static bool TryMapMethod(
        IMethodSymbol method,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        out MappedMethod mappedMethod,
        out string failureReason)
    {
        if (method.IsGenericMethod)
        {
            mappedMethod = null!;
            failureReason = "generic methods are not supported";
            return false;
        }
 
        if (method.ReturnsByRef || method.ReturnsByRefReadonly)
        {
            mappedMethod = null!;
            failureReason = "by-reference return values are not supported";
            return false;
        }
 
        if (!TryMapType(method.ReturnType, proxyNames, collectionNames, out var returnType))
        {
            mappedMethod = null!;
            failureReason = $"return type '{method.ReturnType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)}' is not supported";
            return false;
        }
 
        var mappedParameters = new List<MappedParameter>();
        foreach (var parameter in method.Parameters)
        {
            if (parameter.RefKind != RefKind.None)
            {
                mappedMethod = null!;
                failureReason = $"parameter '{parameter.Name}' uses unsupported ref kind '{parameter.RefKind}'";
                return false;
            }
 
            if (!TryMapType(parameter.Type, proxyNames, collectionNames, out var parameterType))
            {
                mappedMethod = null!;
                failureReason = $"parameter '{parameter.Name}' uses unsupported type '{parameter.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)}'";
                return false;
            }
 
            mappedParameters.Add(new MappedParameter(parameter, parameterType));
        }
 
        mappedMethod = new MappedMethod(
            method,
            returnType,
            AllocateGeneratedParameterNames(mappedParameters),
            GetMethodSignature(method));
        failureReason = string.Empty;
        return true;
    }
 
    private static void GenerateMethod(
        StringBuilder source,
        MappedMethod mappedMethod,
        string proxyName,
        int overloadIndex,
        int overloadCount)
    {
        var method = mappedMethod.Method;
        var methodName = ToCamelCase(method.Name);
        var capabilityId = proxyName + "." + methodName + ".method";
        if (overloadCount > 1)
        {
            capabilityId += ".overload" +
                (overloadIndex + 1).ToString(System.Globalization.CultureInfo.InvariantCulture) +
                "_" +
                GetProjectedSignatureSuffix(mappedMethod.Parameters);
        }
 
        source.AppendLine();
        if (!AppendDocumentationComment(
            source,
            method,
            "        ",
            parameters: [.. mappedMethod.Parameters.Select(static parameter =>
                new DocumentationParameter(parameter.Parameter.Name, parameter.GeneratedName))],
            includeReturns: !method.ReturnsVoid))
        {
            AppendDocumentationSummary(
                source,
                "        ",
                $"Invokes {method.Name} on the {method.ContainingType.Name} provisioning model.");
            AppendGeneratedParameterDocumentation(source, mappedMethod.Parameters);
            if (!method.ReturnsVoid)
            {
                AppendDocumentationElement(
                    source,
                    "        ",
                    new XElement("returns", $"The result of {method.Name}."),
                    parameterNames: null);
            }
        }
        AppendMethodExportAttribute(source, capabilityId, method.Name);
        source.Append("        internal ").Append(mappedMethod.ReturnType.ExposedTypeName).Append(" @").Append(method.Name).Append('(');
 
        for (var index = 0; index < mappedMethod.Parameters.Count; index++)
        {
            if (index > 0)
            {
                source.Append(", ");
            }
 
            AppendMappedParameter(source, mappedMethod.Parameters[index], includeDefaultValue: false);
        }
 
        source.AppendLine(")");
        source.AppendLine("        {");
 
        var invocation = new StringBuilder();
        invocation.Append("Inner.@").Append(method.Name).Append('(');
        for (var index = 0; index < mappedMethod.Parameters.Count; index++)
        {
            if (index > 0)
            {
                invocation.Append(", ");
            }
 
            var mappedParameter = mappedMethod.Parameters[index];
            AppendMappedToUnderlying(invocation, "@" + mappedParameter.GeneratedName, mappedParameter.Type);
        }
        invocation.Append(')');
 
        if (method.ReturnsVoid)
        {
            source.Append("            ").Append(invocation).AppendLine(";");
        }
        else
        {
            if (RequiresCapturedNullableReturnValue(mappedMethod.ReturnType))
            {
                AppendCapturedNullableReturnValue(source, invocation.ToString(), mappedMethod);
            }
            else
            {
                source.Append("            return ");
                AppendMappedFromUnderlying(source, invocation.ToString(), mappedMethod.ReturnType);
                source.AppendLine(";");
            }
        }
 
        source.AppendLine("        }");
    }
 
    private static string GetProjectedSignatureSuffix(List<MappedParameter> parameters)
    {
        return parameters.Count == 0
            ? "none"
            : SanitizeIdentifier(string.Join("_", parameters.Select(static parameter => parameter.Type.ExposedTypeName)));
    }
 
    private static bool RequiresCapturedNullableReturnValue(MappedType mappedType)
        => mappedType.IsNullable &&
        mappedType.Kind is MappedTypeKind.Proxy or MappedTypeKind.Collection or MappedTypeKind.ProvisionableResource;
 
    private static void AppendCapturedNullableReturnValue(
        StringBuilder source,
        string expression,
        MappedMethod mappedMethod)
    {
        var usedNames = new HashSet<string>(
            mappedMethod.Parameters.Select(static parameter => parameter.GeneratedName),
            StringComparer.Ordinal);
        var underlyingValueName = GetUniqueGeneratedLocalName(usedNames, "__underlyingValue");
        var mappedValueName = GetUniqueGeneratedLocalName(usedNames, "__mappedValue");
 
        source.Append("            var ").Append(underlyingValueName).Append(" = ").Append(expression).AppendLine(";");
        source.Append("            return ").Append(underlyingValueName).Append(" is { } ").Append(mappedValueName)
            .Append(" ? new ").Append(mappedMethod.ReturnType.ExposedTypeName.TrimEnd('?'))
            .Append('(').Append(mappedValueName).AppendLine(") : null;");
    }
 
    private static string GetUniqueGeneratedLocalName(HashSet<string> usedNames, string candidate)
    {
        while (!usedNames.Add(candidate))
        {
            candidate += "_";
        }
 
        return candidate;
    }
 
    private static void GenerateCollectionProxy(
        StringBuilder source,
        CollectionShape collection,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames)
    {
        var proxyName = collectionNames[collection.Type];
        var underlyingTypeName = collection.Type.ToDisplayString(s_typeDisplayFormat);
        var elementTypeName = collection.ElementType
            .WithNullableAnnotation(NullableAnnotation.NotAnnotated)
            .ToDisplayString(s_typeDisplayFormat);
        var simpleElement = IsSimpleType(collection.ElementType) || IsIPAddress(collection.ElementType);
        TryMapType(collection.ElementType, proxyNames, collectionNames, out var mappedElementType);
 
        source.AppendLine("    [global::Aspire.Hosting.AspireExportAttribute]");
        source.Append("    internal sealed class ").Append(proxyName).AppendLine();
        source.AppendLine("    {");
        source.Append("        internal ").Append(proxyName).Append('(').Append(underlyingTypeName).AppendLine(" value)");
        source.AppendLine("        {");
        source.AppendLine("            Inner = value ?? throw new global::System.ArgumentNullException(nameof(value));");
        source.AppendLine("        }");
        source.AppendLine();
        source.Append("        internal ").Append(underlyingTypeName).AppendLine(" Inner { get; }");
        source.AppendLine();
        AppendExperimentalAttribute(source);
        source.AppendLine("        [global::Aspire.Hosting.AspireExportAttribute]");
        source.AppendLine("        internal int Count => Inner.Count;");
 
        if (collection.Kind == CollectionKind.Dictionary)
        {
            source.AppendLine();
            AppendExperimentalAttribute(source);
            source.AppendLine("        [global::Aspire.Hosting.AspireExportAttribute]");
            source.AppendLine("        internal string[] Keys => new global::System.Collections.Generic.List<string>(Inner.Keys).ToArray();");
        }
 
        GenerateCollectionGetMethod(source, collection, proxyName, simpleElement, mappedElementType);
        GenerateCollectionSetMethod(source, collection, proxyName, elementTypeName, simpleElement, mappedElementType);
 
        if (collection.Kind == CollectionKind.List)
        {
            GenerateListMutationMethods(source, proxyName, elementTypeName, simpleElement, mappedElementType);
        }
        else
        {
            GenerateDictionaryMutationMethods(source, proxyName);
        }
 
        source.AppendLine("    }");
        source.AppendLine();
    }
 
    private static void GenerateCollectionGetMethod(
        StringBuilder source,
        CollectionShape collection,
        string proxyName,
        bool simpleElement,
        MappedType mappedElementType)
    {
        source.AppendLine();
        if (!simpleElement)
        {
            source.AppendLine("        // Expression-backed complex elements cannot be materialized as model proxies because");
            source.AppendLine("        // Azure.Provisioning does not expose a literal model instance for those values.");
        }
        AppendMethodExportAttribute(source, proxyName + ".get.method", "Get");
        source.Append("        internal ")
            .Append(simpleElement ? BicepValueProxyTypeName : mappedElementType.ExposedTypeName)
            .Append(" Get(")
            .Append(collection.Kind == CollectionKind.List ? "int index" : "string key")
            .AppendLine(")");
        source.AppendLine("        {");
 
        var indexExpression = collection.Kind == CollectionKind.List ? "index" : "key";
        if (simpleElement)
        {
            source.Append("            return ").Append(BicepValueProxyTypeName)
                .Append(".Create(Inner[").Append(indexExpression).AppendLine("]);");
        }
        else
        {
            source.Append("            var element = Inner[").Append(indexExpression).AppendLine("];");
            source.AppendLine("            if (((global::Azure.Provisioning.IBicepValue)element).Kind != global::Azure.Provisioning.BicepValueKind.Literal)");
            source.AppendLine("            {");
            source.AppendLine("                throw new global::System.InvalidOperationException(\"Expression-backed complex collection elements cannot be returned as model proxies.\");");
            source.AppendLine("            }");
            source.AppendLine();
            source.AppendLine("            var literal = element.Value;");
            if (!collection.ElementType.IsValueType)
            {
                source.AppendLine("            if (literal is null)");
                source.AppendLine("            {");
                source.AppendLine("                throw new global::System.InvalidOperationException(\"Null complex collection elements cannot be returned as model proxies.\");");
                source.AppendLine("            }");
                source.AppendLine();
            }
            source.Append("            return ");
            AppendMappedFromUnderlying(source, "literal", mappedElementType);
            source.AppendLine(";");
        }
 
        source.AppendLine("        }");
    }
 
    private static void GenerateCollectionSetMethod(
        StringBuilder source,
        CollectionShape collection,
        string proxyName,
        string elementTypeName,
        bool simpleElement,
        MappedType mappedElementType)
    {
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".set.method", "Set");
        source.Append("        internal void Set(")
            .Append(collection.Kind == CollectionKind.List ? "int index, " : "string key, ");
        AppendCollectionValueParameter(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(")");
        source.AppendLine("        {");
        source.Append("            Inner[")
            .Append(collection.Kind == CollectionKind.List ? "index" : "key")
            .Append("] = ");
        AppendCollectionValueToUnderlying(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(";");
        source.AppendLine("        }");
    }
 
    private static void GenerateListMutationMethods(
        StringBuilder source,
        string proxyName,
        string elementTypeName,
        bool simpleElement,
        MappedType mappedElementType)
    {
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".add.method", "Add");
        source.Append("        internal void Add(");
        AppendCollectionValueParameter(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(")");
        source.AppendLine("        {");
        source.Append("            Inner.Add(");
        AppendCollectionValueToUnderlying(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(");");
        source.AppendLine("        }");
 
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".insert.method", "Insert");
        source.Append("        internal void Insert(int index, ");
        AppendCollectionValueParameter(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(")");
        source.AppendLine("        {");
        source.Append("            Inner.Insert(index, ");
        AppendCollectionValueToUnderlying(source, elementTypeName, simpleElement, mappedElementType);
        source.AppendLine(");");
        source.AppendLine("        }");
 
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".removeAt.method", "RemoveAt");
        source.AppendLine("        internal void RemoveAt(int index)");
        source.AppendLine("        {");
        source.AppendLine("            Inner.RemoveAt(index);");
        source.AppendLine("        }");
 
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".clear.method", "Clear");
        source.AppendLine("        internal void Clear()");
        source.AppendLine("        {");
        source.AppendLine("            Inner.Clear();");
        source.AppendLine("        }");
    }
 
    private static void GenerateDictionaryMutationMethods(StringBuilder source, string proxyName)
    {
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".remove.method", "Remove");
        source.AppendLine("        internal bool Remove(string key)");
        source.AppendLine("        {");
        source.AppendLine("            return Inner.Remove(key);");
        source.AppendLine("        }");
 
        source.AppendLine();
        AppendMethodExportAttribute(source, proxyName + ".clear.method", "Clear");
        source.AppendLine("        internal void Clear()");
        source.AppendLine("        {");
        source.AppendLine("            Inner.Clear();");
        source.AppendLine("        }");
    }
 
    private static void AppendCollectionValueParameter(
        StringBuilder source,
        string elementTypeName,
        bool simpleElement,
        MappedType mappedElementType)
    {
        if (simpleElement)
        {
            // ATS carries IP address literals as strings; Convert<IPAddress> parses them for the SDK.
            source.Append("[global::Aspire.Hosting.AspireUnionAttribute(typeof(")
                .Append(BicepValueProxyTypeName)
                .Append("), typeof(")
                .Append(elementTypeName == "global::System.Net.IPAddress" ? "string" : elementTypeName)
                .Append("))] object value");
        }
        else
        {
            source.Append(mappedElementType.ExposedTypeName).Append(" value");
        }
    }
 
    private static void AppendCollectionValueToUnderlying(
        StringBuilder source,
        string elementTypeName,
        bool simpleElement,
        MappedType mappedElementType)
    {
        if (simpleElement)
        {
            source.Append(BicepValueProxyTypeName).Append(".Convert<").Append(elementTypeName).Append(">(value)");
        }
        else
        {
            AppendMappedToUnderlying(source, "value", mappedElementType);
        }
    }
 
    private static void GenerateFactoryClass(
        StringBuilder source,
        ImmutableArray<ProxyRoot> roots,
        List<INamedTypeSymbol> types,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames)
    {
        var concreteTypes = types
            .Where(static type => !type.IsAbstract)
            .OrderBy(static type => type.ToDisplayString(), StringComparer.Ordinal)
            .ToList();
        var factoryTypeNames = CreateFactoryTypeNames(types, proxyNames);
        // Lookup, creation, and static-property exports all share this extension class. Reserve
        // signatures across every phase so independently derived method names cannot collide.
        var factoryMethodSignatures = new HashSet<string>(StringComparer.Ordinal);
 
        source.AppendLine("    [global::Aspire.Hosting.AspireExportAttribute]");
        source.Append("    internal static class ").Append(FactoryClassName).AppendLine();
        source.AppendLine("    {");
 
        foreach (var root in roots
            .Where(root => root.IsInfrastructureRoot &&
                IsProvisionableResource(root.Type) &&
                proxyNames.ContainsKey(root.Type))
            .OrderBy(static root => root.Type.ToDisplayString(), StringComparer.Ordinal))
        {
            GenerateRootLookupMethod(
                source,
                root.Type,
                proxyNames[root.Type],
                factoryTypeNames[root.Type],
                factoryMethodSignatures);
        }
 
        foreach (var type in concreteTypes)
        {
            var proxyName = proxyNames[type];
            var factoryTypeName = factoryTypeNames[type];
            if (IsProvisionableResource(type))
            {
                GenerateResourceLookupMethods(
                    source,
                    type,
                    proxyName,
                    factoryTypeName,
                    factoryMethodSignatures);
 
                if (SelectConstructor(type, isProvisionableResource: true, proxyNames, collectionNames) is { } constructor)
                {
                    GenerateCreationMethod(
                        source,
                        type,
                        proxyName,
                        factoryTypeName,
                        constructor,
                        isProvisionableResource: true,
                        factoryMethodSignatures);
                }
            }
            else if (SelectConstructor(type, isProvisionableResource: false, proxyNames, collectionNames) is { } constructor)
            {
                GenerateCreationMethod(
                    source,
                    type,
                    proxyName,
                    factoryTypeName,
                    constructor,
                    isProvisionableResource: false,
                    factoryMethodSignatures);
            }
 
            GenerateStaticPropertyFactoryMethods(
                source,
                type,
                proxyNames,
                collectionNames,
                proxyName,
                factoryTypeName,
                factoryMethodSignatures);
        }
 
        source.AppendLine("    }");
        source.AppendLine();
    }
 
    private static Dictionary<INamedTypeSymbol, string> CreateFactoryTypeNames(
        List<INamedTypeSymbol> types,
        Dictionary<INamedTypeSymbol, string> proxyNames)
    {
        var names = new Dictionary<INamedTypeSymbol, string>(SymbolEqualityComparer.Default);
        foreach (var group in types.GroupBy(static type => type.Name, StringComparer.Ordinal))
        {
            var groupCount = group.Count();
            foreach (var type in group)
            {
                var proxyBasedName = proxyNames[type].Substring(0, proxyNames[type].Length - "Proxy".Length);
                names.Add(
                    type,
                    groupCount == 1 && !IsSharedProxyType(type)
                        ? type.Name
                        : proxyBasedName);
            }
        }
 
        return names;
    }
 
    private static void GenerateRootLookupMethod(
        StringBuilder source,
        INamedTypeSymbol type,
        string proxyName,
        string factoryTypeName,
        HashSet<string> factoryMethodSignatures)
    {
        var methodName = AllocateFactoryMethodName(
            factoryMethodSignatures,
            "Get" + factoryTypeName,
            parameterSignature: string.Empty);
        var underlyingTypeName = type.ToDisplayString(s_typeDisplayFormat);
        source.AppendLine();
        if (!AppendDocumentationComment(source, type, "        "))
        {
            AppendDocumentationSummary(
                source,
                "        ",
                $"Gets the {type.Name} provisioning resource for the current Aspire resource.");
        }
        AppendInfrastructureParameterDocumentation(source, "        ");
        AppendFactoryExportAttribute(source, proxyName, methodName, "root");
        source.Append("        internal static ").Append(proxyName).Append(' ').Append(methodName)
            .Append("(this ").Append(AzureResourceInfrastructureTypeName).AppendLine(" infrastructure)");
        source.AppendLine("        {");
        AppendInfrastructureNullCheck(source);
        source.AppendLine("            var bicepIdentifier = global::Aspire.Hosting.AzureResourceExtensions.GetBicepIdentifier(infrastructure.AspireResource);");
        source.Append("            var value = global::System.Linq.Enumerable.Single(")
            .Append("global::System.Linq.Enumerable.OfType<").Append(underlyingTypeName)
            .Append(">(infrastructure.GetProvisionableResources()), ")
            .AppendLine("value => value.BicepIdentifier == bicepIdentifier);");
        source.Append("            return new ").Append(proxyName).AppendLine("(value);");
        source.AppendLine("        }");
    }
 
    private static void GenerateResourceLookupMethods(
        StringBuilder source,
        INamedTypeSymbol type,
        string proxyName,
        string factoryTypeName,
        HashSet<string> factoryMethodSignatures)
    {
        var underlyingTypeName = type.ToDisplayString(s_typeDisplayFormat);
        var getMethodName = AllocateFactoryMethodName(
            factoryMethodSignatures,
            "Get" + factoryTypeName + "ByIdentifier",
            parameterSignature: "string");
        source.AppendLine();
        if (!AppendDocumentationComment(source, type, "        "))
        {
            AppendDocumentationSummary(
                source,
                "        ",
                $"Gets a {type.Name} provisioning resource by its Bicep identifier.");
        }
        AppendInfrastructureParameterDocumentation(source, "        ");
        source.Append("        ")
            .AppendLine("/// <param name=\"bicepIdentifier\">The Bicep identifier of the provisioning resource to return.</param>");
        AppendFactoryExportAttribute(source, proxyName, getMethodName, "byIdentifier");
        source.Append("        internal static ").Append(proxyName).Append(' ').Append(getMethodName)
            .Append("(this ").Append(AzureResourceInfrastructureTypeName)
            .AppendLine(" infrastructure, string bicepIdentifier)");
        source.AppendLine("        {");
        AppendInfrastructureNullCheck(source);
        source.Append("            var value = global::System.Linq.Enumerable.Single(")
            .Append("global::System.Linq.Enumerable.OfType<").Append(underlyingTypeName)
            .Append(">(infrastructure.GetProvisionableResources()), ")
            .AppendLine("value => value.BicepIdentifier == bicepIdentifier);");
        source.Append("            return new ").Append(proxyName).AppendLine("(value);");
        source.AppendLine("        }");
 
        var getAllMethodName = AllocateFactoryMethodName(
            factoryMethodSignatures,
            "Get" + PluralizeIdentifier(factoryTypeName),
            parameterSignature: string.Empty);
        source.AppendLine();
        if (!AppendDocumentationComment(source, type, "        "))
        {
            AppendDocumentationSummary(
                source,
                "        ",
                $"Gets all {type.Name} provisioning resources.");
        }
        AppendInfrastructureParameterDocumentation(source, "        ");
        AppendFactoryExportAttribute(source, proxyName, getAllMethodName, "all");
        source.Append("        internal static ").Append(proxyName).Append("[] ").Append(getAllMethodName)
            .Append("(this ").Append(AzureResourceInfrastructureTypeName).AppendLine(" infrastructure)");
        source.AppendLine("        {");
        AppendInfrastructureNullCheck(source);
        source.Append("            var values = new global::System.Collections.Generic.List<").Append(proxyName).AppendLine(">();");
        source.AppendLine("            foreach (var resource in infrastructure.GetProvisionableResources())");
        source.AppendLine("            {");
        source.Append("                if (resource is ").Append(underlyingTypeName).AppendLine(" value)");
        source.AppendLine("                {");
        source.Append("                    values.Add(new ").Append(proxyName).AppendLine("(value));");
        source.AppendLine("                }");
        source.AppendLine("            }");
        source.AppendLine();
        source.AppendLine("            return values.ToArray();");
        source.AppendLine("        }");
    }
 
    private static void GenerateCreationMethod(
        StringBuilder source,
        INamedTypeSymbol type,
        string proxyName,
        string factoryTypeName,
        SelectedConstructor constructor,
        bool isProvisionableResource,
        HashSet<string> factoryMethodSignatures)
    {
        var methodName = AllocateFactoryMethodName(
            factoryMethodSignatures,
            (isProvisionableResource ? "Add" : "Create") + factoryTypeName,
            GetFactoryParameterSignature(constructor.Parameters));
        var underlyingTypeName = type.ToDisplayString(s_typeDisplayFormat);
        source.AppendLine();
        var hasSourceDocumentation = AppendDocumentationComment(
            source,
            constructor.Constructor,
            "        ",
            parameters: [.. constructor.Parameters.Select(static parameter =>
                new DocumentationParameter(parameter.Parameter.Name, parameter.GeneratedName))]);
        if (!hasSourceDocumentation)
        {
            AppendGeneratedFactoryDocumentation(source, type, constructor.Parameters, isProvisionableResource);
        }
        AppendInfrastructureParameterDocumentation(source, "        ");
        AppendFactoryExportAttribute(source, proxyName, methodName, "factory");
        source.Append("        internal static ").Append(proxyName).Append(' ').Append(methodName)
            .Append("(this ").Append(AzureResourceInfrastructureTypeName).Append(" infrastructure");
 
        foreach (var parameter in constructor.Parameters)
        {
            source.Append(", ");
            AppendMappedParameter(source, parameter, includeDefaultValue: true);
        }
 
        source.AppendLine(")");
        source.AppendLine("        {");
        AppendInfrastructureNullCheck(source);
        source.Append("            var instance = new ").Append(underlyingTypeName).Append('(');
        for (var index = 0; index < constructor.Parameters.Count; index++)
        {
            if (index > 0)
            {
                source.Append(", ");
            }
 
            var parameter = constructor.Parameters[index];
            if (IsNullableFactoryProxyParameter(parameter))
            {
                source.Append('@').Append(parameter.GeneratedName)
                    .Append(" is null ? null : @").Append(parameter.GeneratedName).Append(".Inner");
            }
            else
            {
                AppendMappedToUnderlying(source, "@" + parameter.GeneratedName, parameter.Type);
            }
        }
        source.AppendLine(");");
        if (isProvisionableResource)
        {
            source.AppendLine("            infrastructure.Add(instance);");
        }
        source.AppendLine();
        source.Append("            return new ").Append(proxyName).AppendLine("(instance);");
        source.AppendLine("        }");
    }
 
    private static void GenerateStaticPropertyFactoryMethods(
        StringBuilder source,
        INamedTypeSymbol type,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        string proxyName,
        string factoryTypeName,
        HashSet<string> factoryMethodSignatures)
    {
        // AzureLocation exposes every known region as a static property. Exporting those convenience
        // constants into every provisioning package would dominate the opt-in API surface, while the
        // generated string constructor provides the same functionality for polyglot callers.
        if (IsAzureCoreType(type, AzureLocationMetadataName))
        {
            return;
        }
 
        foreach (var property in type.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(static property =>
                property.IsStatic &&
                !property.IsIndexer &&
                property.DeclaredAccessibility == Accessibility.Public &&
                property.GetMethod?.DeclaredAccessibility == Accessibility.Public)
            .OrderBy(static property => property.Name, StringComparer.Ordinal))
        {
            if (!TryMapType(property.Type, proxyNames, collectionNames, out var mappedType))
            {
                continue;
            }
 
            var methodName = AllocateFactoryMethodName(
                factoryMethodSignatures,
                "Get" + factoryTypeName + property.Name,
                parameterSignature: string.Empty);
            source.AppendLine();
            if (!AppendDocumentationComment(source, property, "        ", includeValue: true))
            {
                AppendDocumentationSummary(
                    source,
                    "        ",
                    $"Gets the {property.Name} value defined by {type.Name}.");
            }
            AppendInfrastructureParameterDocumentation(source, "        ");
            AppendFactoryExportAttribute(source, proxyName, methodName, "staticProperty");
            source.Append("        internal static ").Append(mappedType.ExposedTypeName).Append(' ')
                .Append(methodName).Append("(this ").Append(AzureResourceInfrastructureTypeName)
                .AppendLine(" infrastructure)");
            source.AppendLine("        {");
            AppendInfrastructureNullCheck(source);
            source.Append("            return ");
            AppendMappedFromUnderlying(
                source,
                type.ToDisplayString(s_typeDisplayFormat) + ".@" + property.Name,
                mappedType);
            source.AppendLine(";");
            source.AppendLine("        }");
        }
    }
 
    private static string AllocateFactoryMethodName(
        HashSet<string> factoryMethodSignatures,
        string preferredName,
        string parameterSignature)
    {
        var methodName = preferredName;
        var suffix = 2;
        while (!factoryMethodSignatures.Add(methodName + "(" + parameterSignature + ")"))
        {
            methodName = preferredName + suffix.ToString(System.Globalization.CultureInfo.InvariantCulture);
            suffix++;
        }
 
        return methodName;
    }
 
    private static string GetFactoryParameterSignature(List<MappedParameter> parameters)
    {
        var signature = new StringBuilder();
        for (var index = 0; index < parameters.Count; index++)
        {
            if (index > 0)
            {
                signature.Append(',');
            }
 
            var parameter = parameters[index];
            var typeName = parameter.Type.ExposedTypeName;
            if (parameter.Parameter.Type.IsReferenceType ||
                parameter.Type.Kind is MappedTypeKind.Proxy or MappedTypeKind.ProvisionableResource or MappedTypeKind.Collection)
            {
                typeName = typeName.TrimEnd('?');
            }
 
            signature.Append(typeName);
        }
 
        return signature.ToString();
    }
 
    private static SelectedConstructor? SelectConstructor(
        INamedTypeSymbol type,
        bool isProvisionableResource,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames)
    {
        var constructors = new List<SelectedConstructor>();
        foreach (var constructor in type.InstanceConstructors.Where(
            static constructor => constructor.DeclaredAccessibility == Accessibility.Public))
        {
            var mappedParameters = new List<MappedParameter>();
            var canMap = true;
            foreach (var parameter in constructor.Parameters)
            {
                if (parameter.RefKind != RefKind.None ||
                    !TryMapType(parameter.Type, proxyNames, collectionNames, out var mappedType) ||
                    mappedType.Kind is MappedTypeKind.BicepValue or MappedTypeKind.Collection)
                {
                    canMap = false;
                    break;
                }
 
                mappedParameters.Add(new MappedParameter(parameter, mappedType));
            }
 
            if (canMap)
            {
                constructors.Add(new SelectedConstructor(
                    constructor,
                    AllocateGeneratedParameterNames(mappedParameters, "infrastructure", "instance")));
            }
        }
 
        if (constructors.Count == 0)
        {
            return null;
        }
 
        if (type.IsValueType && constructors.Any(static constructor => !constructor.Constructor.IsImplicitlyDeclared))
        {
            constructors.RemoveAll(static constructor => constructor.Constructor.IsImplicitlyDeclared);
        }
 
        if (!isProvisionableResource)
        {
            var parameterless = constructors.FirstOrDefault(static constructor => constructor.Parameters.Count == 0);
            if (parameterless is not null)
            {
                return parameterless;
            }
        }
        else
        {
            var resourceConstructors = constructors.Where(static constructor =>
                constructor.Parameters.Count > 0 &&
                constructor.Parameters[0].Parameter.Name == "bicepIdentifier" &&
                constructor.Parameters[0].Parameter.Type.SpecialType == SpecialType.System_String).ToList();
            if (resourceConstructors.Count > 0)
            {
                constructors = resourceConstructors;
            }
        }
 
        return constructors
            .OrderBy(static constructor => constructor.Parameters.Count(
                static parameter => !parameter.Parameter.HasExplicitDefaultValue))
            .ThenBy(static constructor => constructor.Parameters.Count)
            .ThenBy(static constructor => constructor.Constructor.ToDisplayString(), StringComparer.Ordinal)
            .First();
    }
 
    private static void AppendMappedParameter(
        StringBuilder source,
        MappedParameter mappedParameter,
        bool includeDefaultValue)
    {
        AppendUnionAttribute(source, mappedParameter.Type, string.Empty);
        source.Append(mappedParameter.Type.ExposedTypeName);
        // Older SDKs have nullable-oblivious constructors such as
        // AgentPoolSnapshot(string bicepIdentifier, string resourceVersion = null).
        // Preserve that semantic default in the nullable-enabled generated signature.
        if (includeDefaultValue &&
            !mappedParameter.Type.ExposedTypeName.EndsWith("?", StringComparison.Ordinal) &&
            (IsNullableFactoryProxyParameter(mappedParameter) ||
                (mappedParameter.Parameter.Type.IsReferenceType &&
                    mappedParameter.Parameter is { HasExplicitDefaultValue: true, ExplicitDefaultValue: null })))
        {
            source.Append('?');
        }
        source.Append(" @").Append(mappedParameter.GeneratedName);
        if (includeDefaultValue &&
            mappedParameter.Parameter.HasExplicitDefaultValue &&
            TryRenderDefaultValue(mappedParameter.Parameter, out var defaultValue))
        {
            source.Append(" = ").Append(defaultValue);
        }
 
    }
 
    private static List<MappedParameter> AllocateGeneratedParameterNames(
        List<MappedParameter> parameters,
        params string[] reservedNames)
    {
        var namesRequiringTranslation = new HashSet<string>(reservedNames, StringComparer.Ordinal)
        {
            "arguments"
        };
        var usedNames = new HashSet<string>(
            parameters
                .Where(parameter => !namesRequiringTranslation.Contains(parameter.Parameter.Name))
                .Select(static parameter => parameter.Parameter.Name),
            StringComparer.Ordinal);
        usedNames.UnionWith(reservedNames);
        var mappedParameters = new List<MappedParameter>(parameters.Count);
 
        foreach (var parameter in parameters)
        {
            var generatedName = parameter.Parameter.Name;
            if (namesRequiringTranslation.Contains(generatedName))
            {
                // TypeScript AppHosts are ES modules, where strict mode forbids "arguments" as a
                // parameter name. Factory methods also reserve their receiver and generated local
                // names so valid Azure SDK constructors cannot produce duplicate C# declarations.
                generatedName = GetUniqueGeneratedLocalName(
                    usedNames,
                    generatedName == "arguments" ? "args" : generatedName);
            }
 
            mappedParameters.Add(new MappedParameter(parameter.Parameter, parameter.Type, generatedName));
        }
 
        return mappedParameters;
    }
 
    private static bool IsNullableFactoryProxyParameter(MappedParameter parameter)
    {
        return parameter.Type.Kind is MappedTypeKind.Proxy or MappedTypeKind.ProvisionableResource &&
            (parameter.Parameter.Type.NullableAnnotation == NullableAnnotation.Annotated ||
                parameter.Parameter is { HasExplicitDefaultValue: true, ExplicitDefaultValue: null });
    }
 
    private static bool TryRenderDefaultValue(IParameterSymbol parameter, out string value)
    {
        var defaultValue = parameter.ExplicitDefaultValue;
        if (defaultValue is null)
        {
            value = parameter.Type.IsValueType &&
                !(parameter.Type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T })
                    ? "default(" + parameter.Type.ToDisplayString(s_typeDisplayFormat) + ")"
                    : "null";
            return true;
        }
 
        if (parameter.Type.TypeKind == TypeKind.Enum)
        {
            value = "(" + parameter.Type.ToDisplayString(s_typeDisplayFormat) + ")" +
                Convert.ToString(defaultValue, System.Globalization.CultureInfo.InvariantCulture);
            return true;
        }
 
        switch (defaultValue)
        {
            case string stringValue:
                value = SymbolDisplay.FormatLiteral(stringValue, quote: true);
                return true;
            case char charValue:
                value = SymbolDisplay.FormatLiteral(charValue, quote: true);
                return true;
            case bool boolValue:
                value = boolValue ? "true" : "false";
                return true;
            case sbyte or byte or short or ushort or int:
                value = Convert.ToString(defaultValue, System.Globalization.CultureInfo.InvariantCulture)!;
                return true;
            case uint uintValue:
                value = uintValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "U";
                return true;
            case long longValue:
                value = longValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "L";
                return true;
            case ulong ulongValue:
                value = ulongValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "UL";
                return true;
            case float floatValue when float.IsNaN(floatValue):
                value = "global::System.Single.NaN";
                return true;
            case float floatValue when float.IsPositiveInfinity(floatValue):
                value = "global::System.Single.PositiveInfinity";
                return true;
            case float floatValue when float.IsNegativeInfinity(floatValue):
                value = "global::System.Single.NegativeInfinity";
                return true;
            case float floatValue:
                value = floatValue.ToString("G9", System.Globalization.CultureInfo.InvariantCulture) + "F";
                return true;
            case double doubleValue when double.IsNaN(doubleValue):
                value = "global::System.Double.NaN";
                return true;
            case double doubleValue when double.IsPositiveInfinity(doubleValue):
                value = "global::System.Double.PositiveInfinity";
                return true;
            case double doubleValue when double.IsNegativeInfinity(doubleValue):
                value = "global::System.Double.NegativeInfinity";
                return true;
            case double doubleValue:
                value = doubleValue.ToString("G17", System.Globalization.CultureInfo.InvariantCulture) + "D";
                return true;
            case decimal decimalValue:
                value = decimalValue.ToString(System.Globalization.CultureInfo.InvariantCulture) + "M";
                return true;
            default:
                value = string.Empty;
                return false;
        }
    }
 
    private static void AppendInfrastructureNullCheck(StringBuilder source)
    {
        source.AppendLine("            if (infrastructure is null)");
        source.AppendLine("            {");
        source.AppendLine("                throw new global::System.ArgumentNullException(nameof(infrastructure));");
        source.AppendLine("            }");
        source.AppendLine();
    }
 
    private static void AppendInfrastructureParameterDocumentation(StringBuilder source, string indentation)
    {
        source.Append(indentation)
            .AppendLine("/// <param name=\"infrastructure\">The Azure resource infrastructure that owns the exported provisioning resources.</param>");
    }
 
    private static void AppendDocumentationSummary(StringBuilder source, string indentation, string summary)
    {
        AppendDocumentationElement(
            source,
            indentation,
            new XElement("summary", summary),
            parameterNames: null);
    }
 
    private static bool AppendDocumentationComment(
        StringBuilder source,
        ISymbol symbol,
        string indentation,
        IReadOnlyList<DocumentationParameter>? parameters = null,
        bool includeReturns = false,
        bool includeValue = false)
    {
        var xml = symbol.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: default);
        if (string.IsNullOrWhiteSpace(xml))
        {
            return false;
        }
 
        XElement root;
        try
        {
            root = XElement.Parse("<root>" + xml + "</root>");
        }
        catch (System.Xml.XmlException)
        {
            return false;
        }
 
        root = root.Element("member") ?? root;
 
        Dictionary<string, string>? parameterNames = null;
        if (parameters is not null)
        {
            parameterNames = parameters.ToDictionary(
                static parameter => parameter.SourceName,
                static parameter => parameter.GeneratedName,
                StringComparer.Ordinal);
        }
 
        var emittedDocumentation = AppendDocumentationElement(source, indentation, root.Element("summary"), parameterNames);
        emittedDocumentation |= AppendDocumentationElement(source, indentation, root.Element("remarks"), parameterNames);
 
        if (includeValue)
        {
            emittedDocumentation |= AppendDocumentationElement(source, indentation, root.Element("value"), parameterNames);
        }
 
        if (includeReturns)
        {
            emittedDocumentation |= AppendDocumentationElement(source, indentation, root.Element("returns"), parameterNames);
        }
 
        if (parameters is not null)
        {
            foreach (var parameter in parameters)
            {
                var parameterElement = root.Elements("param").FirstOrDefault(element =>
                    string.Equals(element.Attribute("name")?.Value, parameter.SourceName, StringComparison.Ordinal))
                    ?? new XElement(
                        "param",
                        new XAttribute("name", parameter.SourceName),
                        $"The {parameter.SourceName} value.");
                emittedDocumentation |= AppendDocumentationElement(
                    source,
                    indentation,
                    parameterElement,
                    parameterNames,
                    replacementName: parameter.GeneratedName);
            }
        }
 
        return emittedDocumentation;
    }
 
    private static void AppendGeneratedFactoryDocumentation(
        StringBuilder source,
        INamedTypeSymbol type,
        IReadOnlyList<MappedParameter> parameters,
        bool isProvisionableResource)
    {
        AppendDocumentationElement(
            source,
            "        ",
            new XElement(
                "summary",
                $"{(isProvisionableResource ? "Adds" : "Creates")} a {type.Name} provisioning model."),
            parameterNames: null);
 
        foreach (var parameter in parameters)
        {
            AppendGeneratedParameterDocumentation(source, parameter);
        }
    }
 
    private static void AppendGeneratedParameterDocumentation(
        StringBuilder source,
        IEnumerable<MappedParameter> parameters)
    {
        foreach (var parameter in parameters)
        {
            AppendGeneratedParameterDocumentation(source, parameter);
        }
    }
 
    private static void AppendGeneratedParameterDocumentation(StringBuilder source, MappedParameter parameter)
    {
        AppendDocumentationElement(
            source,
            "        ",
            new XElement(
                "param",
                new XAttribute("name", parameter.GeneratedName),
                $"The {parameter.Parameter.Name} value."),
            parameterNames: null);
    }
 
    private static bool AppendDocumentationElement(
        StringBuilder source,
        string indentation,
        XElement? element,
        IReadOnlyDictionary<string, string>? parameterNames,
        string? replacementName = null)
    {
        if (element is null)
        {
            return false;
        }
 
        // Rehydrate the XML so renamed parameters (for example, arguments -> args) are
        // updated consistently while preserving safe XML documentation output.
        var rewritten = RewriteDocumentationElement(element, parameterNames, replacementName);
        foreach (var line in rewritten.ToString().Replace("\r\n", "\n").Split('\n'))
        {
            source.Append(indentation)
                .Append("/// ")
                .AppendLine(line);
        }
        return true;
    }
 
    private static XElement RewriteDocumentationElement(
        XElement element,
        IReadOnlyDictionary<string, string>? parameterNames,
        string? replacementName)
    {
        var rewritten = new XElement(
            element.Name,
            element.Attributes().Select(attribute => RewriteDocumentationAttribute(element, attribute, parameterNames, replacementName)),
            element.Nodes().Select(node => RewriteDocumentationNode(node, parameterNames)));
 
        return rewritten;
    }
 
    private static XNode RewriteDocumentationNode(XNode node, IReadOnlyDictionary<string, string>? parameterNames)
    {
        return node switch
        {
            XElement element => RewriteDocumentationElement(element, parameterNames, replacementName: null),
            XText text => new XText(text.Value),
            XComment comment => new XComment(comment.Value),
            _ => new XText(node.ToString())
        };
    }
 
    private static XAttribute RewriteDocumentationAttribute(
        XElement element,
        XAttribute attribute,
        IReadOnlyDictionary<string, string>? parameterNames,
        string? replacementName)
    {
        if (attribute.Name.LocalName == "name" &&
            element.Name.LocalName == "param" &&
            replacementName is not null)
        {
            return new XAttribute(attribute.Name, replacementName);
        }
 
        if (attribute.Name.LocalName == "name" &&
            element.Name.LocalName == "paramref" &&
            parameterNames is not null &&
            parameterNames.TryGetValue(attribute.Value, out var generatedName))
        {
            return new XAttribute(attribute.Name, generatedName);
        }
 
        return new XAttribute(attribute.Name, attribute.Value);
    }
 
    private static void AppendFactoryExportAttribute(
        StringBuilder source,
        string proxyName,
        string methodName,
        string suffix)
    {
        AppendMethodExportAttribute(
            source,
            FactoryClassName + "." + proxyName + "." + ToCamelCase(methodName) + "." + suffix,
            methodName);
    }
 
    private static void AppendMethodExportAttribute(StringBuilder source, string capabilityId, string methodName)
    {
        AppendExperimentalAttribute(source);
        source.Append("        [global::Aspire.Hosting.AspireExportAttribute(\"")
            .Append(capabilityId)
            .Append("\", MethodName = \"")
            .Append(ToCamelCase(methodName))
            .AppendLine("\")]");
    }
 
    private static void AppendExperimentalAttribute(StringBuilder source)
    {
        source.Append("        [global::System.Diagnostics.CodeAnalysis.ExperimentalAttribute(\"")
            .Append(ExperimentalDiagnosticId)
            .Append("\", UrlFormat = \"")
            .Append(ExperimentalUrlFormat)
            .AppendLine("\")]");
    }
 
    private static void AppendUnionAttribute(StringBuilder source, MappedType mappedType, string indentation)
    {
        if (mappedType is { Kind: MappedTypeKind.BicepValue, LiteralTypeName: not null })
        {
            source.Append(indentation)
                .Append("[global::Aspire.Hosting.AspireUnionAttribute(typeof(")
                .Append(BicepValueProxyTypeName)
                .Append("), typeof(")
                .Append(mappedType.LiteralTypeName)
                .Append("))] ");
        }
    }
 
    private static string ToCamelCase(string value)
    {
        return value.Length == 0
            ? value
            : char.ToLowerInvariant(value[0]) + value.Substring(1);
    }
 
    private static string PluralizeIdentifier(string value)
    {
        if (value.Length > 1 &&
            value.EndsWith("y", StringComparison.Ordinal) &&
            "aeiouAEIOU".IndexOf(value[value.Length - 2]) < 0)
        {
            return value.Substring(0, value.Length - 1) + "ies";
        }
 
        return value + "s";
    }
 
    private static INamedTypeSymbol? GetProxyBaseType(
        INamedTypeSymbol type,
        Dictionary<INamedTypeSymbol, string> proxyNames)
    {
        for (var current = type.BaseType; current is not null; current = current.BaseType)
        {
            if (proxyNames.ContainsKey(current))
            {
                return current;
            }
        }
 
        return null;
    }
 
    private static IEnumerable<IPropertySymbol> GetExportableProperties(
        INamedTypeSymbol type,
        bool includeInherited = true,
        INamedTypeSymbol? stopBeforeType = null)
    {
        var seenPropertyNames = new HashSet<string>(StringComparer.Ordinal);
        for (INamedTypeSymbol? current = type;
            current is not null &&
            !SymbolEqualityComparer.Default.Equals(current, stopBeforeType) &&
            current.SpecialType is not SpecialType.System_Object and not SpecialType.System_ValueType;
            current = includeInherited ? current.BaseType : null)
        {
            if (IsProvisionableFrameworkBase(current))
            {
                yield break;
            }
 
            foreach (var property in current.GetMembers().OfType<IPropertySymbol>())
            {
                if (!property.IsStatic &&
                    property.DeclaredAccessibility == Accessibility.Public &&
                    seenPropertyNames.Add(property.Name))
                {
                    yield return property;
                }
            }
 
        }
    }
 
    private static IEnumerable<IMethodSymbol> GetExportableMethods(
        INamedTypeSymbol type,
        bool includeInherited = true,
        INamedTypeSymbol? stopBeforeType = null)
    {
        var seenSignatures = new HashSet<string>(StringComparer.Ordinal);
        for (INamedTypeSymbol? current = type;
            current is not null &&
            !SymbolEqualityComparer.Default.Equals(current, stopBeforeType) &&
            current.SpecialType is not SpecialType.System_Object and not SpecialType.System_ValueType;
            current = includeInherited ? current.BaseType : null)
        {
            if (IsProvisionableFrameworkBase(current))
            {
                yield break;
            }
 
            foreach (var method in current.GetMembers().OfType<IMethodSymbol>())
            {
                if (!method.IsStatic &&
                    method.MethodKind == MethodKind.Ordinary &&
                    method.Name is not nameof(ToString) and not nameof(Equals) and not nameof(GetHashCode) &&
                    method.DeclaredAccessibility == Accessibility.Public &&
                    seenSignatures.Add(GetMethodSignature(method)))
                {
                    yield return method;
                }
            }
 
        }
    }
 
    private static string GetMethodSignature(IMethodSymbol method)
    {
        return method.Name + "(" + string.Join(
            ",",
            method.Parameters.Select(static parameter =>
                parameter.RefKind + ":" +
                parameter.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))) + ")";
    }
 
    private static bool TryMapType(
        ITypeSymbol type,
        Dictionary<INamedTypeSymbol, string> proxyNames,
        Dictionary<INamedTypeSymbol, string> collectionNames,
        out MappedType mappedType)
    {
        if (type.SpecialType == SpecialType.System_Void)
        {
            mappedType = new MappedType("void", MappedTypeKind.Direct);
            return true;
        }
 
        if (TryGetBicepValueArgument(type, out var bicepValueType))
        {
            var literalType = bicepValueType is INamedTypeSymbol
                {
                    OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
                    TypeArguments.Length: 1
                } nullableValueType
                    ? nullableValueType.TypeArguments[0]
                    : bicepValueType.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
            var literalTypeName = IsSimpleType(bicepValueType)
                ? literalType.ToDisplayString(s_typeDisplayFormat).TrimEnd('?')
                : null;
 
            mappedType = new MappedType(
                literalTypeName is null ? BicepValueProxyTypeName : "object",
                MappedTypeKind.BicepValue,
                literalTypeName,
                bicepValueType.ToDisplayString(s_typeDisplayFormat));
            return true;
        }
 
        if (IsSimpleType(type))
        {
            mappedType = new MappedType(type.ToDisplayString(s_typeDisplayFormat), MappedTypeKind.Direct);
            return true;
        }
 
        var isNullableValueType = TryGetNullableValueType(type, out var nullableProxyValueType);
        if (isNullableValueType)
        {
            type = nullableProxyValueType;
        }
 
        if (type is INamedTypeSymbol namedType)
        {
            if (proxyNames.TryGetValue(namedType, out var proxyName))
            {
                var isNullable = isNullableValueType || type.NullableAnnotation == NullableAnnotation.Annotated;
                mappedType = new MappedType(
                    proxyName + (isNullable ? "?" : string.Empty),
                    MappedTypeKind.Proxy,
                    isNullable: isNullable,
                    isNullableValueType: isNullableValueType);
                return true;
            }
 
            if (collectionNames.TryGetValue(namedType, out var collectionProxyName))
            {
                var isNullable = type.NullableAnnotation == NullableAnnotation.Annotated;
                mappedType = new MappedType(collectionProxyName + (isNullable ? "?" : string.Empty), MappedTypeKind.Collection, isNullable: isNullable);
                return true;
            }
 
            if (IsProvisionableResourceBase(namedType))
            {
                var isNullable = type.NullableAnnotation == NullableAnnotation.Annotated;
                mappedType = new MappedType(
                    ProvisionableResourceProxyTypeName + (isNullable ? "?" : string.Empty),
                    MappedTypeKind.ProvisionableResource,
                    isNullable: isNullable);
                return true;
            }
        }
 
        mappedType = default;
        return false;
    }
 
    private static bool TryGetBicepValueArgument(ITypeSymbol type, out ITypeSymbol valueType)
    {
        if (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType &&
            namedType.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == BicepValueMetadataName)
        {
            valueType = namedType.TypeArguments[0];
            return true;
        }
 
        valueType = null!;
        return false;
    }
 
    private static bool TryGetNullableValueType(ITypeSymbol type, out ITypeSymbol valueType)
    {
        if (type is INamedTypeSymbol
            {
                OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
                TypeArguments.Length: 1
            } nullableType)
        {
            valueType = nullableType.TypeArguments[0];
            return true;
        }
 
        valueType = null!;
        return false;
    }
 
    private static bool TryGetCollection(
        ITypeSymbol type,
        out CollectionKind kind,
        out ITypeSymbol elementType,
        out INamedTypeSymbol collectionType)
    {
        if (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType)
        {
            var metadataName = namedType.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
            if (metadataName == BicepListMetadataName)
            {
                kind = CollectionKind.List;
                elementType = namedType.TypeArguments[0];
                collectionType = namedType;
                return true;
            }
 
            if (metadataName == BicepDictionaryMetadataName)
            {
                kind = CollectionKind.Dictionary;
                elementType = namedType.TypeArguments[0];
                collectionType = namedType;
                return true;
            }
        }
 
        kind = default;
        elementType = null!;
        collectionType = null!;
        return false;
    }
 
    private static bool IsProvisionableResourceBase(INamedTypeSymbol type)
    {
        return type.MetadataName == "ProvisionableResource" &&
            type.ContainingNamespace.ToDisplayString() == "Azure.Provisioning.Primitives";
    }
 
    private static bool IsSharedProxyType(INamedTypeSymbol type)
    {
        return type.ContainingAssembly.Name == CoreProvisioningAssemblyName ||
            IsAzureCoreType(type, AzureLocationMetadataName) ||
            IsAzureCoreType(type, ResourceIdentifierMetadataName) ||
            IsAzureCoreType(type, ResourceTypeMetadataName);
    }
 
    private static bool IsAzureCoreType(INamedTypeSymbol type, string metadataName)
    {
        return type.ContainingAssembly.Name == AzureCoreAssemblyName &&
            type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == metadataName;
    }
 
    private static bool IsProvisionableFrameworkBase(INamedTypeSymbol type)
    {
        return type.ContainingNamespace.ToDisplayString() == "Azure.Provisioning.Primitives" &&
            type.MetadataName is
                "Provisionable" or
                "ProvisionableConstruct" or
                "NamedProvisionableConstruct" or
                "ProvisionableResource";
    }
 
    private static bool CanGenerateProxy(INamedTypeSymbol type)
    {
        return type.TypeKind is TypeKind.Class or TypeKind.Struct &&
            !type.IsGenericType &&
            !type.IsStatic;
    }
 
    private static bool IsMutableStruct(INamedTypeSymbol type)
    {
        return type.TypeKind == TypeKind.Struct && !type.IsReadOnly;
    }
 
    private static Location GetDiagnosticLocation(ISymbol symbol)
    {
        return symbol.Locations.FirstOrDefault(static location => location.IsInSource) ?? Location.None;
    }
 
    private static bool IsExcluded(
        ISymbol member,
        HashSet<string> excludedMemberNames)
    {
        return IsNativelyExcluded(member) ||
            excludedMemberNames.Contains(member.Name) ||
            excludedMemberNames.Contains(
                member.ContainingType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) +
                "." +
                member.Name);
    }
 
    private static bool IsNativelyExcluded(ISymbol member)
    {
        if (member is IPropertySymbol property &&
            property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) == SystemDataMetadataName)
        {
            return true;
        }
 
        if (member is IMethodSymbol
            {
                Name: "GetResourceNameRequirements",
                Parameters.Length: 0,
                OverriddenMethod: { } overriddenMethod
            })
        {
            for (var current = overriddenMethod; current is not null; current = current.OverriddenMethod)
            {
                if (IsProvisionableResourceBase(current.ContainingType))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    private static bool IsProvisionableResource(INamedTypeSymbol type)
    {
        for (INamedTypeSymbol? current = type; current is not null; current = current.BaseType)
        {
            if (IsProvisionableResourceBase(current))
            {
                return true;
            }
        }
 
        return false;
    }
 
    private static bool IsIPAddress(ITypeSymbol type)
    {
        return type.ToDisplayString(s_typeDisplayFormat) == "global::System.Net.IPAddress";
    }
 
    private static bool IsSimpleType(ITypeSymbol type)
    {
        if (type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } nullableType)
        {
            return IsSimpleType(nullableType.TypeArguments[0]);
        }
 
        if (type.TypeKind == TypeKind.Enum)
        {
            return true;
        }
 
        if (type.SpecialType is
            SpecialType.System_String or
            SpecialType.System_Char or
            SpecialType.System_Boolean or
            SpecialType.System_SByte or
            SpecialType.System_Byte or
            SpecialType.System_Int16 or
            SpecialType.System_UInt16 or
            SpecialType.System_Int32 or
            SpecialType.System_UInt32 or
            SpecialType.System_Int64 or
            SpecialType.System_UInt64 or
            SpecialType.System_Single or
            SpecialType.System_Double or
            SpecialType.System_Decimal)
        {
            return true;
        }
 
        var metadataName = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated)
            .ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
        return metadataName is
            "System.DateTime" or
            "System.DateTimeOffset" or
            "System.DateOnly" or
            "System.TimeOnly" or
            "System.TimeSpan" or
            "System.Guid" or
            "System.Uri";
    }
 
    private static void AppendMappedFromUnderlying(StringBuilder source, string expression, MappedType mappedType)
    {
        switch (mappedType.Kind)
        {
            case MappedTypeKind.Direct:
                source.Append(expression);
                break;
            case MappedTypeKind.BicepValue:
                source.Append(BicepValueProxyTypeName).Append(".Create(").Append(expression).Append(')');
                break;
            case MappedTypeKind.Proxy:
            case MappedTypeKind.Collection:
            case MappedTypeKind.ProvisionableResource:
                if (mappedType.IsNullable)
                {
                    source.Append(expression).Append(" is null ? null : new ")
                        .Append(mappedType.ExposedTypeName.TrimEnd('?')).Append('(').Append(expression);
                    if (mappedType.IsNullableValueType)
                    {
                        source.Append(".Value");
                    }
                    source.Append(')');
                }
                else
                {
                    source.Append("new ").Append(mappedType.ExposedTypeName).Append('(').Append(expression).Append(')');
                }
                break;
        }
    }
 
    private static void AppendMappedToUnderlying(StringBuilder source, string expression, MappedType mappedType)
    {
        if (mappedType.Kind is MappedTypeKind.Proxy or MappedTypeKind.Collection or MappedTypeKind.ProvisionableResource)
        {
            source.Append(expression).Append(mappedType.IsNullable ? "?.Inner" : ".Inner");
        }
        else if (mappedType.Kind == MappedTypeKind.BicepValue)
        {
            source.Append(BicepValueProxyTypeName)
                .Append(".Convert<")
                .Append(mappedType.ValueTypeName)
                .Append(">(")
                .Append(expression)
                .Append(')');
        }
        else
        {
            source.Append(expression);
        }
    }
 
    private enum CollectionKind
    {
        List,
        Dictionary
    }
 
    private enum MappedTypeKind
    {
        Direct,
        BicepValue,
        Proxy,
        Collection,
        ProvisionableResource
    }
 
    private sealed class DiscoveryResult
    {
        public DiscoveryResult(List<INamedTypeSymbol> types, List<CollectionShape> collections)
        {
            Types = types;
            Collections = collections;
        }
 
        public List<INamedTypeSymbol> Types { get; }
 
        public List<CollectionShape> Collections { get; }
    }
 
    private readonly struct ProxyRoot
    {
        public ProxyRoot(
            INamedTypeSymbol type,
            bool isInfrastructureRoot,
            bool includeContainingAssemblyTypes,
            ImmutableArray<string> excludedMemberNames)
        {
            Type = type;
            IsInfrastructureRoot = isInfrastructureRoot;
            IncludeContainingAssemblyTypes = includeContainingAssemblyTypes;
            ExcludedMemberNames = excludedMemberNames;
        }
 
        public INamedTypeSymbol Type { get; }
 
        public bool IsInfrastructureRoot { get; }
 
        public bool IncludeContainingAssemblyTypes { get; }
 
        public ImmutableArray<string> ExcludedMemberNames { get; }
    }
 
    private readonly struct CollectionShape
    {
        public CollectionShape(INamedTypeSymbol type, ITypeSymbol elementType, CollectionKind kind)
        {
            Type = type;
            ElementType = elementType;
            Kind = kind;
        }
 
        public INamedTypeSymbol Type { get; }
 
        public ITypeSymbol ElementType { get; }
 
        public CollectionKind Kind { get; }
    }
 
    private sealed class MappedMethod
    {
        public MappedMethod(
            IMethodSymbol method,
            MappedType returnType,
            List<MappedParameter> parameters,
            string signature)
        {
            Method = method;
            ReturnType = returnType;
            Parameters = parameters;
            Signature = signature;
        }
 
        public IMethodSymbol Method { get; }
 
        public MappedType ReturnType { get; }
 
        public List<MappedParameter> Parameters { get; }
 
        public string Signature { get; }
    }
 
    private sealed class SelectedConstructor
    {
        public SelectedConstructor(IMethodSymbol constructor, List<MappedParameter> parameters)
        {
            Constructor = constructor;
            Parameters = parameters;
        }
 
        public IMethodSymbol Constructor { get; }
 
        public List<MappedParameter> Parameters { get; }
    }
 
    private readonly struct MappedParameter
    {
        public MappedParameter(IParameterSymbol parameter, MappedType type)
            : this(parameter, type, parameter.Name)
        {
        }
 
        public MappedParameter(IParameterSymbol parameter, MappedType type, string generatedName)
        {
            Parameter = parameter;
            Type = type;
            GeneratedName = generatedName;
        }
 
        public IParameterSymbol Parameter { get; }
 
        public MappedType Type { get; }
 
        public string GeneratedName { get; }
    }
 
    private readonly struct DocumentationParameter
    {
        public DocumentationParameter(string sourceName, string generatedName)
        {
            SourceName = sourceName;
            GeneratedName = generatedName;
        }
 
        public string SourceName { get; }
 
        public string GeneratedName { get; }
    }
 
    private readonly struct MappedType
    {
        public MappedType(
            string exposedTypeName,
            MappedTypeKind kind,
            string? literalTypeName = null,
            string? valueTypeName = null,
            bool isNullable = false,
            bool isNullableValueType = false)
        {
            ExposedTypeName = exposedTypeName;
            Kind = kind;
            LiteralTypeName = literalTypeName;
            ValueTypeName = valueTypeName;
            IsNullable = isNullable;
            IsNullableValueType = isNullableValueType;
        }
 
        public string ExposedTypeName { get; }
 
        public MappedTypeKind Kind { get; }
 
        public string? LiteralTypeName { get; }
 
        public string? ValueTypeName { get; }
 
        public bool IsNullable { get; }
 
        public bool IsNullableValueType { get; }
    }
}