From e5fdbcbcb2ae3e655c5032a0600286a54219c775 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 5 Sep 2023 20:57:20 -0700 Subject: [PATCH 1/4] Fix binder gen compile issues due to inaccessible members and identifier name clashes --- .../src/SourceGenerators/TypeModelHelper.cs | 120 +++++++++++++ .../ConfigurationBindingGenerator.Parser.cs | 23 +++ .../Helpers/Emitter/ConfigurationBinder.cs | 3 +- .../gen/Helpers/Emitter/CoreBindingHelpers.cs | 2 +- .../gen/Helpers/Emitter/Helpers.cs | 13 +- ...nfiguration.Binder.SourceGeneration.csproj | 1 + .../gen/Model/TypeSpec.cs | 12 +- .../gen/Resources/Strings.resx | 2 +- .../gen/Resources/xlf/Strings.cs.xlf | 4 +- .../gen/Resources/xlf/Strings.de.xlf | 4 +- .../gen/Resources/xlf/Strings.es.xlf | 4 +- .../gen/Resources/xlf/Strings.fr.xlf | 4 +- .../gen/Resources/xlf/Strings.it.xlf | 4 +- .../gen/Resources/xlf/Strings.ja.xlf | 4 +- .../gen/Resources/xlf/Strings.ko.xlf | 4 +- .../gen/Resources/xlf/Strings.pl.xlf | 4 +- .../gen/Resources/xlf/Strings.pt-BR.xlf | 4 +- .../gen/Resources/xlf/Strings.ru.xlf | 4 +- .../gen/Resources/xlf/Strings.tr.xlf | 4 +- .../gen/Resources/xlf/Strings.zh-Hans.xlf | 4 +- .../gen/Resources/xlf/Strings.zh-Hant.xlf | 4 +- .../tests/Common/ConfigurationBinderTests.cs | 2 +- .../ConfigurationBinderTests.Generator.cs | 166 ++++++++++++++++++ .../gen/Helpers/RoslynExtensions.cs | 22 --- .../gen/JsonSourceGenerator.Parser.cs | 35 +--- .../System.Text.Json.SourceGeneration.targets | 1 + 26 files changed, 353 insertions(+), 101 deletions(-) create mode 100644 src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs diff --git a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs new file mode 100644 index 00000000000000..b408ed5c003565 --- /dev/null +++ b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGenerators +{ + internal static class TypeModelHelper + { + private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type, bool useUniqueName) + { + if (type is IArrayTypeSymbol arrayType) + { + int rank = arrayType.Rank; + string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... + return ToIdentifierCompatibleSubstring(arrayType.ElementType, useUniqueName) + suffix; + } + + StringBuilder? sb = null; + string? symbolName = null; + + if (useUniqueName) + { + string uniqueDisplayString = type.ToMinimalDisplayString(); + PopulateIdentifierCompatibleSubstring(sb = new(), uniqueDisplayString); + } + else + { + symbolName = type.Name; + } + +#if DEBUG + bool usingUniqueName = (symbolName is null || sb is not null) && useUniqueName; + bool usingSymbolName = (symbolName is not null && sb is null) && !useUniqueName; + bool configuredNameCorrectly = (usingUniqueName && !usingSymbolName) || (!usingUniqueName && usingSymbolName); + Debug.Assert(configuredNameCorrectly); +#endif + + if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + { + return symbolName ?? sb!.ToString(); + } + + if (sb is null) + { + (sb = new()).Append(symbolName!); + } + + Debug.Assert(sb.Length > 0); + + if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) + { + foreach (ITypeSymbol genericArg in typeArgsInScope) + { + sb.Append(ToIdentifierCompatibleSubstring(genericArg, useUniqueName)); + } + } + + return sb.ToString(); + } + + /// + /// Type name, prefixed with containing type names if it is nested (e.g. ContainingType.NestedType). + /// + public static string ToMinimalDisplayString(this ITypeSymbol type) => type.ToDisplayString(s_minimalDisplayFormat); + + public static List? GetAllTypeArgumentsInScope(this INamedTypeSymbol type) + { + if (!type.IsGenericType) + { + return null; + } + + List? args = null; + TraverseContainingTypes(type); + return args; + + void TraverseContainingTypes(INamedTypeSymbol current) + { + if (current.ContainingType is INamedTypeSymbol parent) + { + TraverseContainingTypes(parent); + } + + if (!current.TypeArguments.IsEmpty) + { + (args ??= new()).AddRange(current.TypeArguments); + } + } + } + + private static void PopulateIdentifierCompatibleSubstring(StringBuilder sb, string input) + { + foreach (char c in input) + { + if (c is '[') + { + sb.Append("Array"); + } + else if (c is ',') + { + sb.Append("Comma"); + } + else if (char.IsLetterOrDigit(c)) + { + sb.Append(c); + } + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs index 64db4eb58b1f77..9275a294a47b59 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -9,6 +9,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -70,6 +71,7 @@ private static bool IsValidRootConfigType(ITypeSymbol? type) { if (type is null || type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || + !IsAccessibleFromGenBinders(type) || type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || type.IsRefLikeType || ContainsGenericParameters(type)) @@ -78,6 +80,27 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || } return true; + + static bool IsAccessibleFromGenBinders(ITypeSymbol type) + { + if (type is IArrayTypeSymbol array) + { + return IsAccessibleFromGenBinders(array.ElementType); + } + + if (type is INamedTypeSymbol namedType && namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) + { + foreach (ITypeSymbol genericArg in typeArgsInScope) + { + if (!IsAccessibleFromGenBinders(genericArg)) + { + return false; + } + } + } + + return type.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.Friend; + } } private TypeSpec? GetTargetTypeForRootInvocation(ITypeSymbol? type, Location? invocationLocation) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs index c7ead12d65589d..c420097d99d6ae 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -138,7 +139,7 @@ void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParam EmitBlankLineIfRequired(); _writer.WriteLine($"/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively."); EmitInterceptsLocationAnnotations(interceptorInfoList); - EmitStartBlock($"public static void {Identifier.Bind}_{type.DisplayString.ToIdentifierSubstring()}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})"); + EmitStartBlock($"public static void {Identifier.Bind}_{type.IdentifierCompatibleSubstring}(this {Identifier.IConfiguration} {Identifier.configuration}, {additionalParams})"); if (type.NeedsMemberBinding) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs index bb2bd7ac61d15a..089095aa472d5b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelpers.cs @@ -923,7 +923,7 @@ private static string GetConditionKindExpr(ref bool isFirstType) } private static string GetConfigKeyCacheFieldName(ObjectSpec type) => - $"s_configKeys_{type.DisplayString.ToIdentifierSubstring()}"; + $"s_configKeys_{type.IdentifierCompatibleSubstring}"; } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs index 60ce9f681e077f..fe59e46f73fc0b 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs @@ -245,18 +245,7 @@ private bool EmitInitException(TypeSpec type) private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}"; private static string GetInitalizeMethodDisplayString(ObjectSpec type) => - $"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayString.ToIdentifierSubstring()}"; + $"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.IdentifierCompatibleSubstring}"; } } - - internal static class EmitterExtensions - { - public static string ToIdentifierSubstring(this string typeDisplayName) => - typeDisplayName - .Replace("[]", nameof(Array)) - .Replace(", ", string.Empty) - .Replace(".", string.Empty) - .Replace("<", string.Empty) - .Replace(">", string.Empty); - } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index 5b10a6f8e06c6b..38925330004853 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -25,6 +25,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs index 6f5e26bf3f6d38..06bf839b474632 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs @@ -2,22 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; +using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal abstract record TypeSpec { - private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - public TypeSpec(ITypeSymbol type) { IsValueType = type.IsValueType; Namespace = type.ContainingNamespace?.ToDisplayString(); - DisplayString = type.ToDisplayString(s_minimalDisplayFormat); + DisplayString = type.ToMinimalDisplayString(); + IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(useUniqueName: true); Name = Namespace + "." + DisplayString.Replace(".", "+"); IsInterface = type.TypeKind is TypeKind.Interface; } @@ -26,6 +22,8 @@ public TypeSpec(ITypeSymbol type) public string DisplayString { get; } + public string IdentifierCompatibleSubstring { get; } + public string? Namespace { get; } public bool IsValueType { get; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx index 3978cbaac6ce48..be66da59c6b5a7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/Strings.resx @@ -121,7 +121,7 @@ The collection type is not supported: '{0}'. - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. The target type for a binder call could not be determined diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf index 8c264bc592c9c0..3bc09d74a32702 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.cs.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - Pro volání vazače se nevygenerovala logika vazby. Nepodporované vstupní vzory zahrnují obecná volání a předávání zabalených objektů. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + Pro volání vazače se nevygenerovala logika vazby. Nepodporované vstupní vzory zahrnují obecná volání a předávání zabalených objektů. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf index 37100d25efae20..4ef47bf55a3fb4 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.de.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - Für einen Binderaufruf wurde keine Bindungslogik generiert. Nicht unterstützte Eingabemuster umfassen generische Aufrufe und übergeben geschachtelte Objekte. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + Für einen Binderaufruf wurde keine Bindungslogik generiert. Nicht unterstützte Eingabemuster umfassen generische Aufrufe und übergeben geschachtelte Objekte. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf index 00bf90592cc311..f74c0d799d2a5d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.es.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - No se generó la lógica de enlace para una llamada de enlazador. Los patrones de entrada no admitidos incluyen llamadas genéricas y pasar objetos en cuadros. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + No se generó la lógica de enlace para una llamada de enlazador. Los patrones de entrada no admitidos incluyen llamadas genéricas y pasar objetos en cuadros. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf index 847bc6155aaaee..739f6a85c79492 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.fr.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - La logique de liaison n’a pas été générée pour un appel de classeur. Les modèles d’entrée non pris en charge incluent les appels génériques et les objets boxed de passage. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + La logique de liaison n’a pas été générée pour un appel de classeur. Les modèles d’entrée non pris en charge incluent les appels génériques et les objets boxed de passage. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf index 9ee871268cff5a..16a25f5fef4a88 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.it.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - La logica di binding non è stata generata per una chiamata binder. I modelli di input non supportati includono chiamate generiche e il passaggio di oggetti in caselle. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + La logica di binding non è stata generata per una chiamata binder. I modelli di input non supportati includono chiamate generiche e il passaggio di oggetti in caselle. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf index b7d5becee73ca2..260aa898a04f16 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ja.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - バインダー呼び出しのバインド ロジックが生成されませんでした。サポートされていない入力パターンとしては、ジェネリック呼び出し、ボックス化されたオブジェクトの受け渡しなどがあります。 + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + バインダー呼び出しのバインド ロジックが生成されませんでした。サポートされていない入力パターンとしては、ジェネリック呼び出し、ボックス化されたオブジェクトの受け渡しなどがあります。 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf index 4c94c55903310e..5b73d60a47d613 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ko.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - 바인더 호출에 대한 바인딩 논리가 생성되지 않았습니다. 지원되지 않는 입력 패턴에는 제네릭 호출 및 boxed 개체 전달이 포함됩니다. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + 바인더 호출에 대한 바인딩 논리가 생성되지 않았습니다. 지원되지 않는 입력 패턴에는 제네릭 호출 및 boxed 개체 전달이 포함됩니다. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf index 808afa9723f55a..8a675ed3e3fa03 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pl.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - Nie wygenerowano logiki powiązania dla wywołania integratora. Nieobsługiwane wzorce wejściowe obejmują wywołania ogólne i przekazywanie obiektów w ramce. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + Nie wygenerowano logiki powiązania dla wywołania integratora. Nieobsługiwane wzorce wejściowe obejmują wywołania ogólne i przekazywanie obiektów w ramce. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf index 9f723e7e6af48b..5f54f60e4c13cc 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.pt-BR.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - A lógica de associação não foi gerada para uma chamada de associador. Os padrões de entrada sem suporte incluem chamadas genéricas e passagem de objetos em caixa. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + A lógica de associação não foi gerada para uma chamada de associador. Os padrões de entrada sem suporte incluem chamadas genéricas e passagem de objetos em caixa. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf index cc2f61c6e9d514..bf40ee5af2c390 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.ru.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - Логика привязки не была создана для вызова модуля привязки. К неподдерживаемым шаблонам ввода относятся универсальные вызовы и передача упакованных объектов. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + Логика привязки не была создана для вызова модуля привязки. К неподдерживаемым шаблонам ввода относятся универсальные вызовы и передача упакованных объектов. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf index 06aaaded5cf2c1..732554a8893005 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.tr.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - Bir bağlayıcı çağrısı için bağlama mantığı oluşturulmadı. Desteklenmeyen giriş desenleri genel çağrılar ve geçici kutulu nesneler içeriyor. + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + Bir bağlayıcı çağrısı için bağlama mantığı oluşturulmadı. Desteklenmeyen giriş desenleri genel çağrılar ve geçici kutulu nesneler içeriyor. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf index ef3affe4e22bcc..f35cf502782512 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - 未为联编程序调用生成绑定逻辑。不支持的输入模式包括泛型调用和传递装箱对象。 + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + 未为联编程序调用生成绑定逻辑。不支持的输入模式包括泛型调用和传递装箱对象。 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf index 39dd659d8890dd..ff843029507c1f 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -8,8 +8,8 @@ - Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects. - 未產生文件夾呼叫的繫結邏輯。不支援的輸入模式包括一般呼叫和傳遞方塊物件。 + Binding logic was not generated for a binder call. Unsupported input patterns include generic calls, passing boxed objects, and passing types that are not 'public' or 'internal'. + 未產生文件夾呼叫的繫結邏輯。不支援的輸入模式包括一般呼叫和傳遞方塊物件。 diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index a7e0474028172b..3846145f71486a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -30,7 +30,7 @@ public ConfigurationBinderTestsBase() } } - public sealed partial class ConfigurationBinderTests : ConfigurationBinderTestsBase + public partial class ConfigurationBinderTests : ConfigurationBinderTestsBase { [Fact] public void BindWithNestedTypesWithReadOnlyProperties() diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBinderTests.Generator.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBinderTests.Generator.cs index 0e4a97b85b548a..1ffb263c836a1c 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBinderTests.Generator.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBinderTests.Generator.cs @@ -1,6 +1,8 @@ // 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 Microsoft.Extensions.Configuration; using Xunit; @@ -249,5 +251,169 @@ private static void AssertRecordIsBound(GeolocationRecord record, int longitude, { Assert.Equal((longitude, latitude), (record.Longitude, record.Latitude)); } + + // These are regression tests for https://github.com/dotnet/runtime/issues/90976 + // Ensure that every emitted identifier name is unique, otherwise name clashes + // will occur and cause compilation to fail. + + [Fact] + public void NameClashTests_NamingPatternsThatCouldCauseClashes() + { + // Potential class between type with closed generic & non generic type. + // Both types start with same substring. The generic arg type's name is + // the same as the suffix of the non generic type's name. + // Manifested in https://github.com/dotnet/runtime/issues/90976. + + IConfiguration configuration = TestHelpers.GetConfigurationFromJsonString(@"{""Value"":1}"); + + var c1 = new Cint(); + var c2 = new C(); + + configuration.Bind(c1); + configuration.Bind(c2); + Assert.Equal(1, c1.Value); + Assert.Equal(1, c2.Value); + } + + internal class C + { + public int Value { get; set; } + } + + internal class Cint + { + public int Value { get; set; } + } + + [Fact] + public void NameClashTests_SameTypeName() + { + // Both types have the same name, but one is a nested type. + + IConfiguration configuration = TestHelpers.GetConfigurationFromJsonString(@"{""Value"":1}"); + + var c1 = new ClassWithThisIdentifier(); + var c2 = new ClassWithThisIdentifier_Wrapper.ClassWithThisIdentifier(); + + configuration.Bind(c1); + configuration.Bind(c2); + Assert.Equal(1, c1.Value); + Assert.Equal(1, c2.Value); + } + + internal class ClassWithThisIdentifier + { + public int Value { get; set; } + } + + internal class ClassWithThisIdentifier_Wrapper + { + internal class ClassWithThisIdentifier + { + public int Value { get; set; } + } + } + + /// + /// These are regression tests for https://github.com/dotnet/runtime/issues/90909. + /// Ensure that we don't emit root interceptors to handle types/members that + /// are inaccessible to the generated helpers. Tests for inaccessbile transitive members + /// are covered in the shared (reflection/src-gen) , + /// e.g. . + /// + /// + /// In these cases, binding calls will fallback to reflection, as with all cases where + /// we can't correctly resolve the type, such as generic call patterns and boxed objects. + /// + [Fact] + public void MemberAccessibility_InaccessibleNestedTypeAsRootConfig() + { + IConfiguration configuration = TestHelpers.GetConfigurationFromJsonString(@"{""Value"":1}"); + + // Ensure no compilation errors; types are skipped. + +#pragma warning disable SYSLIB1104 // Binding logic was not generated for a binder call. + var c1 = new InaccessibleClass_1(); + configuration.Bind(c1); + var c2 = configuration.Get(); + var c3 = configuration.Get(); + + // Generic collections. + + configuration = TestHelpers + .GetConfigurationFromJsonString(@"{""Array"": [{""Value"":1}]}") + .GetSection("Array"); + var c4 = configuration.Get(); + var c5 = configuration.Get>(); + + // Generic types. + + Action? configureOptions = options => options.BindNonPublicProperties = true; + string GetNestedObjectPayload(string propName) => $$""" + { + "{{propName}}": { + "Value": 1 + } + } + """; + + configuration = TestHelpers.GetConfigurationFromJsonString(GetNestedObjectPayload("item1")); + var c6 = configuration.Get>(configureOptions); + + configuration = TestHelpers.GetConfigurationFromJsonString(GetNestedObjectPayload("protectedMember")); + var c7 = configuration.Get>(configureOptions); + var c8 = configuration.Get>(configureOptions); + + configuration = TestHelpers.GetConfigurationFromJsonString(GetNestedObjectPayload("publicMember")); + var c9 = configuration.Get>(configureOptions); + var c10 = configuration.Get>(configureOptions); +#pragma warning disable SYSLIB1104 + + // Reflection fallback. + + Assert.Equal(1, c1.Value); + Assert.Equal(1, c2.Value); + Assert.Equal(1, c3.Value); + + Assert.Equal(1, c4[0].Value); + Assert.Equal(1, c5[0].Value); + Assert.Equal(1, c6["item1"].Value); + + Assert.Equal(1, c7.GetProtectedMember.Value); + Assert.Equal(1, c8.GetProtectedMember.Value); + Assert.Equal(1, c9.PublicMember.Value); + Assert.Equal(1, c10.PublicMember.Value); + } + + private class InaccessibleClass_1() + { + public int Value { get; set; } + } + + protected record InaccessibleClass_2(int Value); + + protected internal class InaccessibleClass_3 + { + public InaccessibleClass_3(int value) => Value = value; + + public int Value { get; } + } + + internal class AccessibleGenericClass + { + protected T ProtectedMember { get; set; } + + public T GetProtectedMember => ProtectedMember; + } + + private class InaccessibleGenericClass + { + public T PublicMember { get; set; } + } + + public class AccessibleClass() + { + public int Value { get; set; } + } } } diff --git a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs index b0cb90e33d1476..7d280ce7603c2d 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/RoslynExtensions.cs @@ -209,28 +209,6 @@ public static bool IsNullableValueType(this ITypeSymbol type, [NotNullWhen(true) return false; } - public static ITypeSymbol[] GetAllTypeArgumentsInScope(this INamedTypeSymbol type) - { - if (!type.IsGenericType) - { - return Array.Empty(); - } - - var args = new List(); - TraverseContainingTypes(type); - return args.ToArray(); - - void TraverseContainingTypes(INamedTypeSymbol current) - { - if (current.ContainingType is INamedTypeSymbol parent) - { - TraverseContainingTypes(parent); - } - - args.AddRange(current.TypeArguments); - } - } - public static ITypeSymbol GetMemberType(this ISymbol member) { Debug.Assert(member is IFieldSymbol or IPropertySymbol); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 3242dcc5faa4c2..6cd21a9b50ea92 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using SourceGenerators; namespace System.Text.Json.SourceGeneration { @@ -589,17 +590,19 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener } var typeRef = new TypeRef(type); - string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? GetTypeInfoPropertyName(type); + string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? + type.ToIdentifierCompatibleSubstring(useUniqueName: false /* We leave identifier name conflicts to users, as codified below. */); if (classType is ClassType.TypeUnsupportedBySourceGen) { ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeToGenerate.AttributeLocation ?? typeToGenerate.Location, type.ToDisplayString()); } + // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. if (!_generatedContextAndTypeNames.Add((contextType.Name, typeInfoPropertyName))) { // The context name/property name combination will result in a conflict in generated types. - // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. + // This is by design; we leave conflict resolution to the user. ReportDiagnostic(DiagnosticDescriptors.DuplicateTypeName, typeToGenerate.AttributeLocation ?? _contextClassLocation, typeInfoPropertyName); classType = ClassType.TypeUnsupportedBySourceGen; } @@ -1589,34 +1592,6 @@ private static string DeterminePropertyNameFieldName(string effectiveJsonPropert return null; } - private static string GetTypeInfoPropertyName(ITypeSymbol type) - { - if (type is IArrayTypeSymbol arrayType) - { - int rank = arrayType.Rank; - string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... - return GetTypeInfoPropertyName(arrayType.ElementType) + suffix; - } - - if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) - { - return type.Name; - } - - StringBuilder sb = new(); - - string name = namedType.Name; - - sb.Append(name); - - foreach (ITypeSymbol genericArg in namedType.GetAllTypeArgumentsInScope()) - { - sb.Append(GetTypeInfoPropertyName(genericArg)); - } - - return sb.ToString(); - } - private bool TryGetDeserializationConstructor( ITypeSymbol type, bool useDefaultCtorInAnnotatedStructs, diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets index 364f6e1f6682f7..4020a05cb421db 100644 --- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets +++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets @@ -31,6 +31,7 @@ + From c3293eab2350204e552910d72cec4be0dc494e53 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 12 Sep 2023 15:12:47 -0700 Subject: [PATCH 2/4] Minimize net diff of binder-gen visibility/identifier-clash fix --- .../src/SourceGenerators/TypeModelHelper.cs | 84 ----------------- .../ConfigurationBindingGenerator.Parser.cs | 15 ---- .../gen/Helpers/Parser/Extensions.cs | 89 +++++++++++++++++++ ...nfiguration.Binder.SourceGeneration.csproj | 1 + .../gen/Model/TypeSpec.cs | 10 +-- .../gen/JsonSourceGenerator.Parser.cs | 37 +++++++- 6 files changed, 126 insertions(+), 110 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs diff --git a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs index b408ed5c003565..73c19d61ca1225 100644 --- a/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs +++ b/src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs @@ -1,78 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text; using Microsoft.CodeAnalysis; using System.Collections.Generic; -using System.Diagnostics; namespace SourceGenerators { internal static class TypeModelHelper { - private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( - globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); - - public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type, bool useUniqueName) - { - if (type is IArrayTypeSymbol arrayType) - { - int rank = arrayType.Rank; - string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... - return ToIdentifierCompatibleSubstring(arrayType.ElementType, useUniqueName) + suffix; - } - - StringBuilder? sb = null; - string? symbolName = null; - - if (useUniqueName) - { - string uniqueDisplayString = type.ToMinimalDisplayString(); - PopulateIdentifierCompatibleSubstring(sb = new(), uniqueDisplayString); - } - else - { - symbolName = type.Name; - } - -#if DEBUG - bool usingUniqueName = (symbolName is null || sb is not null) && useUniqueName; - bool usingSymbolName = (symbolName is not null && sb is null) && !useUniqueName; - bool configuredNameCorrectly = (usingUniqueName && !usingSymbolName) || (!usingUniqueName && usingSymbolName); - Debug.Assert(configuredNameCorrectly); -#endif - - if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) - { - return symbolName ?? sb!.ToString(); - } - - if (sb is null) - { - (sb = new()).Append(symbolName!); - } - - Debug.Assert(sb.Length > 0); - - if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) - { - foreach (ITypeSymbol genericArg in typeArgsInScope) - { - sb.Append(ToIdentifierCompatibleSubstring(genericArg, useUniqueName)); - } - } - - return sb.ToString(); - } - - /// - /// Type name, prefixed with containing type names if it is nested (e.g. ContainingType.NestedType). - /// - public static string ToMinimalDisplayString(this ITypeSymbol type) => type.ToDisplayString(s_minimalDisplayFormat); - public static List? GetAllTypeArgumentsInScope(this INamedTypeSymbol type) { if (!type.IsGenericType) @@ -97,24 +32,5 @@ void TraverseContainingTypes(INamedTypeSymbol current) } } } - - private static void PopulateIdentifierCompatibleSubstring(StringBuilder sb, string input) - { - foreach (char c in input) - { - if (c is '[') - { - sb.Append("Array"); - } - else if (c is ',') - { - sb.Append("Comma"); - } - else if (char.IsLetterOrDigit(c)) - { - sb.Append(c); - } - } - } } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs index 9275a294a47b59..1ab7370b058be2 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -937,19 +937,4 @@ private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosti } } } - - public static class ParserExtensions - { - public static void RegisterCacheEntry(this Dictionary cache, TKey key, TEntry entry) - where TKey : notnull - where TValue : ICollection, new() - { - if (!cache.TryGetValue(key, out TValue? entryCollection)) - { - cache[key] = entryCollection = new TValue(); - } - - entryCollection.Add(entry); - } - } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs new file mode 100644 index 00000000000000..1b76f3dac21c42 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs @@ -0,0 +1,89 @@ +// 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.Diagnostics; +using System.Text; +using Microsoft.CodeAnalysis; +using SourceGenerators; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + internal static class ParserExtensions + { + private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + public static void RegisterCacheEntry(this Dictionary cache, TKey key, TEntry entry) + where TKey : notnull + where TValue : ICollection, new() + { + if (!cache.TryGetValue(key, out TValue? entryCollection)) + { + cache[key] = entryCollection = new TValue(); + } + + entryCollection.Add(entry); + } + + public static string ToMinimalDisplayString(this ITypeSymbol type) => type.ToDisplayString(s_minimalDisplayFormat); + + public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type, string? displayString = null) + { + if (type is IArrayTypeSymbol arrayType) + { + int rank = arrayType.Rank; + string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... + return ToIdentifierCompatibleSubstring(arrayType.ElementType) + suffix; + } + + displayString ??= type.ToMinimalDisplayString(); + StringBuilder? sb = null; + + foreach (char c in displayString) + { + if (c is '[') + { + GetSb().Append("Array"); + } + else if (c is ',') + { + GetSb().Append("Comma"); + } + else if (char.IsLetterOrDigit(c)) + { + GetSb().Append(c); + } + + StringBuilder GetSb() => sb ??= new(); + } + + displayString = sb?.ToString() ?? displayString; + + if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + { + return displayString; + } + + if (sb is null) + { + (sb = new()).Append(displayString); + } + + Debug.Assert(sb.Length > 0); + + if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) + { + foreach (ITypeSymbol genericArg in typeArgsInScope) + { + sb.Append(ToIdentifierCompatibleSubstring(genericArg)); + } + } + + return sb.ToString(); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj index 38925330004853..f63ddec1f4b72e 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj @@ -41,6 +41,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs index 06bf839b474632..53be1fa9e692dd 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; -using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { @@ -10,12 +9,11 @@ internal abstract record TypeSpec { public TypeSpec(ITypeSymbol type) { - IsValueType = type.IsValueType; Namespace = type.ContainingNamespace?.ToDisplayString(); DisplayString = type.ToMinimalDisplayString(); - IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(useUniqueName: true); - Name = Namespace + "." + DisplayString.Replace(".", "+"); - IsInterface = type.TypeKind is TypeKind.Interface; + Name = (Namespace is null ? string.Empty : Namespace + ".") + DisplayString.Replace(".", "+"); + IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(DisplayString); + IsValueType = type.IsValueType; } public string Name { get; } @@ -40,8 +38,6 @@ public TypeSpec(ITypeSymbol type) public virtual TypeSpec EffectiveType => this; - public bool IsInterface { get; } - protected bool CanInitComplexObject() => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 6cd21a9b50ea92..0f3b11b038bc93 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -590,19 +590,17 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener } var typeRef = new TypeRef(type); - string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? - type.ToIdentifierCompatibleSubstring(useUniqueName: false /* We leave identifier name conflicts to users, as codified below. */); + string typeInfoPropertyName = typeToGenerate.TypeInfoPropertyName ?? GetTypeInfoPropertyName(type); if (classType is ClassType.TypeUnsupportedBySourceGen) { ReportDiagnostic(DiagnosticDescriptors.TypeNotSupported, typeToGenerate.AttributeLocation ?? typeToGenerate.Location, type.ToDisplayString()); } - // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. if (!_generatedContextAndTypeNames.Add((contextType.Name, typeInfoPropertyName))) { // The context name/property name combination will result in a conflict in generated types. - // This is by design; we leave conflict resolution to the user. + // Workaround for https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used. ReportDiagnostic(DiagnosticDescriptors.DuplicateTypeName, typeToGenerate.AttributeLocation ?? _contextClassLocation, typeInfoPropertyName); classType = ClassType.TypeUnsupportedBySourceGen; } @@ -1592,6 +1590,37 @@ private static string DeterminePropertyNameFieldName(string effectiveJsonPropert return null; } + private static string GetTypeInfoPropertyName(ITypeSymbol type) + { + if (type is IArrayTypeSymbol arrayType) + { + int rank = arrayType.Rank; + string suffix = rank == 1 ? "Array" : $"Array{rank}D"; // Array, Array2D, Array3D, ... + return GetTypeInfoPropertyName(arrayType.ElementType) + suffix; + } + + if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + { + return type.Name; + } + + StringBuilder sb = new(); + + string name = namedType.Name; + + sb.Append(name); + + if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) + { + foreach (ITypeSymbol genericArg in typeArgsInScope) + { + sb.Append(GetTypeInfoPropertyName(genericArg)); + } + } + + return sb.ToString(); + } + private bool TryGetDeserializationConstructor( ITypeSymbol type, bool useDefaultCtorInAnnotatedStructs, From c3bfa63fa814817f584595b318b335e858b7c740 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 12 Sep 2023 17:55:39 -0700 Subject: [PATCH 3/4] Improve identifier formatting --- .../gen/Helpers/Parser/Extensions.cs | 43 ++------ .../gen/Model/TypeSpec.cs | 10 +- .../Baselines/EmptyConfigType.generated.txt | 104 ++++++++++++++++++ 3 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs index 1b76f3dac21c42..0ac57e88ed9ee5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/Extensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis; using SourceGenerators; @@ -11,10 +10,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal static class ParserExtensions { - private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( + private static readonly SymbolDisplayFormat s_identifierCompatibleFormat = new SymbolDisplayFormat( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + genericsOptions: SymbolDisplayGenericsOptions.None, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); public static void RegisterCacheEntry(this Dictionary cache, TKey key, TEntry entry) @@ -29,9 +28,7 @@ public static void RegisterCacheEntry(this Dictionary type.ToDisplayString(s_minimalDisplayFormat); - - public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type, string? displayString = null) + public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type) { if (type is IArrayTypeSymbol arrayType) { @@ -40,40 +37,16 @@ public static string ToIdentifierCompatibleSubstring(this ITypeSymbol type, stri return ToIdentifierCompatibleSubstring(arrayType.ElementType) + suffix; } - displayString ??= type.ToMinimalDisplayString(); - StringBuilder? sb = null; + string displayString = type.ContainingType is null + ? type.Name + : type.ToDisplayString(s_identifierCompatibleFormat).Replace(".", string.Empty); - foreach (char c in displayString) - { - if (c is '[') - { - GetSb().Append("Array"); - } - else if (c is ',') - { - GetSb().Append("Comma"); - } - else if (char.IsLetterOrDigit(c)) - { - GetSb().Append(c); - } - - StringBuilder GetSb() => sb ??= new(); - } - - displayString = sb?.ToString() ?? displayString; - - if (type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + if (type is not INamedTypeSymbol { IsGenericType: true } namedType) { return displayString; } - if (sb is null) - { - (sb = new()).Append(displayString); - } - - Debug.Assert(sb.Length > 0); + StringBuilder sb = new(displayString); if (namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) { diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs index 53be1fa9e692dd..cf03e58e5231b1 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs @@ -7,12 +7,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal abstract record TypeSpec { + private static readonly SymbolDisplayFormat s_minimalDisplayFormat = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + public TypeSpec(ITypeSymbol type) { Namespace = type.ContainingNamespace?.ToDisplayString(); - DisplayString = type.ToMinimalDisplayString(); + DisplayString = type.ToDisplayString(s_minimalDisplayFormat); Name = (Namespace is null ? string.Empty : Namespace + ".") + DisplayString.Replace(".", "+"); - IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(DisplayString); + IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring(); IsValueType = type.IsValueType; } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt new file mode 100644 index 00000000000000..c3db7a0ff408a5 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/EmptyConfigType.generated.txt @@ -0,0 +1,104 @@ +// +#nullable enable +#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code. + +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.CompilerServices; + + [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")] + file static class BindingExtensions + { + #region IConfiguration extensions. + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 12, 23)] + public static void Bind_TypeWithNoMembers(this IConfiguration configuration, object? instance) + { + } + + /// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively. + [InterceptsLocation(@"src-0.cs", 15, 23)] + public static void Bind_TypeWithNoMembers_Wrapper(this IConfiguration configuration, object? instance) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (instance is null) + { + return; + } + + var typedObj = (TypeWithNoMembers_Wrapper)instance; + BindCore(configuration, ref typedObj, binderOptions: null); + } + #endregion IConfiguration extensions. + + #region Core binding extensions. + private readonly static Lazy> s_configKeys_TypeWithNoMembers_Wrapper = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { "Member" }); + + public static void BindCore(IConfiguration configuration, ref TypeWithNoMembers_Wrapper instance, BinderOptions? binderOptions) + { + ValidateConfigurationKeys(typeof(TypeWithNoMembers_Wrapper), s_configKeys_TypeWithNoMembers_Wrapper, configuration, binderOptions); + + if (AsConfigWithChildren(configuration.GetSection("Member")) is IConfigurationSection section0) + { + instance.Member ??= new TypeWithNoMembers(); + } + } + + + /// If required by the binder options, validates that there are no unknown keys in the input configuration object. + public static void ValidateConfigurationKeys(Type type, Lazy> keys, IConfiguration configuration, BinderOptions? binderOptions) + { + if (binderOptions?.ErrorOnUnknownConfiguration is true) + { + List? temp = null; + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + if (!keys.Value.Contains(section.Key)) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}"); + } + } + } + + public static IConfiguration? AsConfigWithChildren(IConfiguration configuration) + { + foreach (IConfigurationSection _ in configuration.GetChildren()) + { + return configuration; + } + return null; + } + #endregion Core binding extensions. + } +} From 95e712ff075d14db00f4a1904fe918a2403babcb Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Wed, 13 Sep 2023 16:32:04 -0700 Subject: [PATCH 4/4] Use built-in API for accessiblity check --- .../ConfigurationBindingGenerator.Parser.cs | 25 ++----------------- .../gen/Model/KnownTypeSymbols.cs | 4 +++ 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs index 1ab7370b058be2..deb0f4c0aabc0d 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs @@ -67,11 +67,11 @@ public Parser(SourceProductionContext context, KnownTypeSymbols typeSymbols, Imm return _sourceGenSpec; } - private static bool IsValidRootConfigType(ITypeSymbol? type) + private bool IsValidRootConfigType(ITypeSymbol? type) { if (type is null || type.SpecialType is SpecialType.System_Object or SpecialType.System_Void || - !IsAccessibleFromGenBinders(type) || + !_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) || type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || type.IsRefLikeType || ContainsGenericParameters(type)) @@ -80,27 +80,6 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error || } return true; - - static bool IsAccessibleFromGenBinders(ITypeSymbol type) - { - if (type is IArrayTypeSymbol array) - { - return IsAccessibleFromGenBinders(array.ElementType); - } - - if (type is INamedTypeSymbol namedType && namedType.GetAllTypeArgumentsInScope() is List typeArgsInScope) - { - foreach (ITypeSymbol genericArg in typeArgsInScope) - { - if (!IsAccessibleFromGenBinders(genericArg)) - { - return false; - } - } - } - - return type.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.Friend; - } } private TypeSpec? GetTargetTypeForRootInvocation(ITypeSymbol? type, Location? invocationLocation) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/KnownTypeSymbols.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/KnownTypeSymbols.cs index e3a4f67ed396b4..e381dc9c7c43ee 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/KnownTypeSymbols.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/KnownTypeSymbols.cs @@ -13,6 +13,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { internal sealed record KnownTypeSymbols { + public CSharpCompilation Compilation { get; } + public INamedTypeSymbol String { get; } public INamedTypeSymbol? CultureInfo { get; } public INamedTypeSymbol? DateOnly { get; } @@ -57,6 +59,8 @@ internal sealed record KnownTypeSymbols public KnownTypeSymbols(CSharpCompilation compilation) { + Compilation = compilation; + // Primitives (needed because they are Microsoft.CodeAnalysis.SpecialType.None) CultureInfo = compilation.GetBestTypeByMetadataName(typeof(CultureInfo)); DateOnly = compilation.GetBestTypeByMetadataName("System.DateOnly");