| File: Parser\Extensions.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Configuration.Binder\gen\Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj (Microsoft.Extensions.Configuration.Binder.SourceGeneration) |
// 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.Generic; using System.Text; using Microsoft.CodeAnalysis; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { internal sealed partial class Parser { private readonly struct TypeParseInfo { public ITypeSymbol TypeSymbol { get; private init; } /// <summary> /// <see cref="System.Type.FullName"/> like rendering of the symbol name. /// </summary> public string FullName { get; private init; } public MethodsToGen BindingOverload { get; private init; } public BinderInvocation BinderInvocation { get; private init; } public ContainingTypeDiagnosticInfo? ContainingTypeDiagnosticInfo { get; private init; } public static TypeParseInfo Create(ITypeSymbol typeSymbol, MethodsToGen overload, BinderInvocation invocation, ContainingTypeDiagnosticInfo? containingTypeDiagInfo = null) => new TypeParseInfo { TypeSymbol = typeSymbol, FullName = typeSymbol.GetFullName(), BindingOverload = overload, BinderInvocation = invocation, ContainingTypeDiagnosticInfo = containingTypeDiagInfo, }; public TypeParseInfo ToTransitiveTypeParseInfo(ITypeSymbol memberType, DiagnosticDescriptor? diagDescriptor = null, string? memberName = null) { ContainingTypeDiagnosticInfo? diagnosticInfo = diagDescriptor is null ? null : new() { FullName = FullName, Descriptor = diagDescriptor, MemberName = memberName, ContainingTypeInfo = ContainingTypeDiagnosticInfo, }; return Create(memberType, BindingOverload, BinderInvocation, diagnosticInfo); } } private sealed class ContainingTypeDiagnosticInfo { /// <summary> /// <see cref="System.Type.FullName"/> like rendering of the symbol name. /// </summary> public required string FullName { get; init; } public required string? MemberName { get; init; } public required DiagnosticDescriptor Descriptor { get; init; } public required ContainingTypeDiagnosticInfo? ContainingTypeInfo { get; init; } } } } internal static class ParserExtensions { private static readonly SymbolDisplayFormat s_identifierCompatibleFormat = new SymbolDisplayFormat( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes ); private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); public static void RegisterCacheEntry<TKey, TValue, TEntry>(this Dictionary<TKey, TValue> cache, TKey key, TEntry entry) where TKey : notnull where TValue : ICollection<TEntry>, new() { if (!cache.TryGetValue(key, out TValue? entryCollection)) { cache[key] = entryCollection = new TValue(); } entryCollection.Add(entry); } /// <summary> /// Returns unique identifier compatible name based on the fully qualified type name, including fully qualified type arguments. /// Names are truncated at 500 characters. (1023 is maximum identifier length) /// </summary> /// <remarks> /// When names are longer than 500 characters a hash code of the original string is appended to maintain identifier uniqueness. /// Examples: /// Namespace1.Namespace2.Type -> Namespace1__Namespace2__Type ; /// Namespace.GenericType<Namespace.Type> -> Namespace__GenericType___Namespace__Type___ ; /// </remarks> public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) { StringBuilder sb = new StringBuilder(50); ToIdentifierCompatibleSubstringBuilder(type, sb); //Replace . with __; remove invalid characters sb.Replace(".", "__").Replace("[", "").Replace("]", ""); // string.GetHashCode uses randomized string hashing. // Causes false negatives in Baseline tests if threshold is too small if (sb.Length > 500) { string hash = ((uint)sb.ToString().GetHashCode()).ToString("D10"); sb.Remove(490, sb.Length - 490).Append(hash); } return sb.ToString(); } private static void ToIdentifierCompatibleSubstringBuilder(ITypeSymbol type, StringBuilder displaySubstring) { if (type is IArrayTypeSymbol arrayType) { int rank = arrayType.Rank; string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... ToIdentifierCompatibleSubstringBuilder(arrayType.ElementType, displaySubstring); displaySubstring.Append(suffix); } displaySubstring.Append(type.ToDisplayString(s_identifierCompatibleFormat)); if (type is not INamedTypeSymbol { IsGenericType: true } namedType) { return; } if (namedType.GetAllTypeArgumentsInScope() is List<ITypeSymbol> typeArgsInScope) { displaySubstring.Append("___"); foreach (ITypeSymbol genericArg in typeArgsInScope) { ToIdentifierCompatibleSubstringBuilder(genericArg, displaySubstring); } displaySubstring.Append("___"); } return; } public static (string DisplayString, string FullName) GetTypeNames(this ITypeSymbol type) { string? @namespace = type.ContainingNamespace is { IsGlobalNamespace: false } containingNamespace ? containingNamespace.ToDisplayString() : null; string displayString = type.ToDisplayString(s_minimalDisplayFormat); string fullname = (@namespace is null ? string.Empty : @namespace + ".") + displayString.Replace(".", "+"); return (displayString, fullname); } public static string GetFullName(this ITypeSymbol type) => GetTypeNames(type).FullName; } }