File: RuntimeSource\Configuration.Binder\Specs\TypeIndex.cs
Web Access
Project: src\src\Tools\ConfigurationSchemaGenerator\ConfigurationSchemaGenerator.csproj (ConfigurationSchemaGenerator)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using SourceGenerators;
 
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
    internal sealed class TypeIndex(IEnumerable<TypeSpec> typeSpecs)
    {
        private readonly Dictionary<TypeRef, TypeSpec> _index = typeSpecs.ToDictionary(spec => spec.TypeRef);
 
        public bool CanBindTo(TypeRef typeRef) => GetEffectiveTypeSpec(typeRef) switch
        {
            SimpleTypeSpec => true,
            ComplexTypeSpec complexTypeSpec => CanInstantiate(complexTypeSpec) || HasBindableMembers(complexTypeSpec),
            _ => throw new InvalidOperationException(),
        };
 
        public bool CanInstantiate(ComplexTypeSpec typeSpec) => typeSpec switch
        {
            ObjectSpec objectSpec => objectSpec is { InstantiationStrategy: not ObjectInstantiationStrategy.None, InitExceptionMessage: null },
            DictionarySpec dictionarySpec => KeyIsSupported(dictionarySpec),
            CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef),
            _ => throw new InvalidOperationException(),
        };
 
        public bool HasBindableMembers(ComplexTypeSpec typeSpec) =>
            typeSpec switch
            {
                ObjectSpec objectSpec => objectSpec.Properties?.Any(ShouldBindTo) is true,
                DictionarySpec dictSpec => KeyIsSupported(dictSpec) && CanBindTo(dictSpec.ElementTypeRef),
                CollectionSpec collectionSpec => CanBindTo(collectionSpec.ElementTypeRef),
                _ => throw new InvalidOperationException(),
            };
 
        public bool ShouldBindTo(PropertySpec property)
        {
            TypeSpec propTypeSpec = GetEffectiveTypeSpec(property.TypeRef);
            return IsAccessible() && !IsCollectionAndCannotOverride() && !IsDictWithUnsupportedKey();
 
            bool IsAccessible() => property.CanGet || property.CanSet;
 
            bool IsDictWithUnsupportedKey() => propTypeSpec is DictionarySpec dictionarySpec && !KeyIsSupported(dictionarySpec);
 
            bool IsCollectionAndCannotOverride() => !property.CanSet &&
                propTypeSpec is CollectionWithCtorInitSpec
                {
                    InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary
                };
        }
 
        public TypeSpec GetEffectiveTypeSpec(TypeRef typeRef)
        {
            TypeSpec typeSpec = GetTypeSpec(typeRef);
            return GetEffectiveTypeSpec(typeSpec);
        }
 
        public TypeSpec GetEffectiveTypeSpec(TypeSpec typeSpec)
        {
            TypeRef effectiveRef = typeSpec.EffectiveTypeRef;
            TypeSpec effectiveSpec = effectiveRef == typeSpec.TypeRef ? typeSpec : _index[effectiveRef];
            return effectiveSpec;
        }
 
        public TypeSpec GetTypeSpec(TypeRef typeRef) => _index[typeRef];
 
        public static string GetInstantiationTypeDisplayString(CollectionWithCtorInitSpec type)
        {
            CollectionInstantiationConcreteType concreteType = type.InstantiationConcreteType;
            return concreteType is CollectionInstantiationConcreteType.Self
                ? type.TypeRef.FullyQualifiedName
                : GetGenericTypeDisplayString(type, concreteType);
        }
 
        public static string GetPopulationCastTypeDisplayString(CollectionWithCtorInitSpec type)
        {
            CollectionPopulationCastType castType = type.PopulationCastType;
            Debug.Assert(castType is not CollectionPopulationCastType.NotApplicable);
            return GetGenericTypeDisplayString(type, castType);
        }
 
        public static string GetGenericTypeDisplayString(CollectionWithCtorInitSpec type, Enum genericProxyTypeName)
        {
            string proxyTypeNameStr = genericProxyTypeName.ToString();
            string elementTypeFQN = type.ElementTypeRef.FullyQualifiedName;
 
            if (type is EnumerableSpec)
            {
                return $"{proxyTypeNameStr}<{elementTypeFQN}>";
            }
 
            string keyTypeDisplayString = ((DictionarySpec)type).KeyTypeRef.FullyQualifiedName;
            return $"{proxyTypeNameStr}<{keyTypeDisplayString}, {elementTypeFQN}>";
        }
 
        public bool KeyIsSupported(DictionarySpec typeSpec) =>
            // Only types that are parsable from string are supported.
            // Nullable keys not allowed; that would cause us to emit
            // code that violates dictionary key notnull constraint.
            GetTypeSpec(typeSpec.KeyTypeRef) is ParsableFromStringSpec;
 
        public static string GetConfigKeyCacheFieldName(ObjectSpec type) => $"s_configKeys_{type.IdentifierCompatibleSubstring}";
 
        public static string GetParseMethodName(ParsableFromStringSpec type)
        {
            Debug.Assert(type.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue);
 
            if (type.StringParsableTypeKind is StringParsableTypeKind.ByteArray)
            {
                return "ParseByteArray";
            }
 
            string displayString = type.TypeRef.FullyQualifiedName;
 
            const string GlobalPrefix = "global::";
            if (displayString.StartsWith(GlobalPrefix))
            {
                displayString = displayString.Substring(GlobalPrefix.Length);
            }
 
            Debug.Assert(displayString.Length > 0);
            if (char.IsLower(displayString[0]))
            {
                displayString = char.ToUpperInvariant(displayString[0]) + displayString.Substring(1);
            }
 
            if (displayString.Contains('.'))
            {
                displayString = displayString.Replace(".", "");
            }
 
            return "Parse" + displayString;
        }
    }
}