Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs
Original file line number Diff line number Diff line change
@@ -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<ITypeSymbol> typeArgsInScope)
{
foreach (ITypeSymbol genericArg in typeArgsInScope)
{
sb.Append(ToIdentifierCompatibleSubstring(genericArg, useUniqueName));
}
}

return sb.ToString();
}

/// <summary>
/// Type name, prefixed with containing type names if it is nested (e.g. ContainingType.NestedType).
/// </summary>
public static string ToMinimalDisplayString(this ITypeSymbol type) => type.ToDisplayString(s_minimalDisplayFormat);

public static List<ITypeSymbol>? GetAllTypeArgumentsInScope(this INamedTypeSymbol type)
{
if (!type.IsGenericType)
{
return null;
}

List<ITypeSymbol>? 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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using SourceGenerators;

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
Expand Down Expand Up @@ -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))
Expand All @@ -78,6 +80,27 @@ type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error ||
}

return true;

static bool IsAccessibleFromGenBinders(ITypeSymbol type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a "within" symbol. The symbol would be the generated binder extension class which doesn't exist yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you use another type symbol in the same assembly as a proxy for the one that will be generated?

Copy link
Member

@eiriktsarpalis eiriktsarpalis Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to use the current IAssemblySymbol as the 'within' parameter although I haven't tested that myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I considered the proxy approach. It didn't feel deterministic on first thought, but yes it would be a correct/better check. I'll also try using the assembly symbol.

Copy link
Contributor Author

@layomia layomia Sep 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a proxy would work in the common case, but we'd need to account for the assembly possibly having just one (e.g. a singular, simple target-config POCO with primitive fields). Any handwritten fallback would have a vastly different impl.

Comparing with assembly doesn't work - nested privates aren't visible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing with assembly doesn't work - nested privates aren't visible.

Are any of the types being generated nested within user-defined types? If not, I would expect its visibility to be equivalent to that of the assembly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing with assembly doesn't work - nested privates aren't visible.

I tried it again; the assembly check works. Must have not used the ! operator when I tried. Thanks.

{
if (type is IArrayTypeSymbol array)
{
return IsAccessibleFromGenBinders(array.ElementType);
}

if (type is INamedTypeSymbol namedType && namedType.GetAllTypeArgumentsInScope() is List<ITypeSymbol> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -138,7 +139,7 @@ void EmitMethods(MethodsToGen_ConfigurationBinder method, string additionalParam
EmitBlankLineIfRequired();
_writer.WriteLine($"/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>");
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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(CommonPath)\Roslyn\DiagnosticDescriptorHelper.cs" Link="Common\Roslyn\DiagnosticDescriptorHelper.cs" />
<Compile Include="$(CommonPath)\Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
<Compile Include="$(CommonPath)\SourceGenerators\SourceWriter.cs" Link="Common\SourceGenerators\SourceWriter.cs" />
<Compile Include="$(CommonPath)\SourceGenerators\TypeModelHelper.cs" Link="Common\SourceGenerators\TypeModelHelper.cs" />
<Compile Include="ConfigurationBindingGenerator.cs" />
<Compile Include="ConfigurationBindingGenerator.Emitter.cs" />
<Compile Include="ConfigurationBindingGenerator.Parser.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -26,6 +22,8 @@ public TypeSpec(ITypeSymbol type)

public string DisplayString { get; }

public string IdentifierCompatibleSubstring { get; }

public string? Namespace { get; }

public bool IsValueType { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
<value>The collection type is not supported: '{0}'.</value>
</data>
<data name="CouldNotDetermineTypeInfoMessageFormat" xml:space="preserve">
<value>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</value>
<value>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'.</value>
</data>
<data name="CouldNotDetermineTypeInfoTitle" xml:space="preserve">
<value>The target type for a binder call could not be determined</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">Pro volání vazače se nevygenerovala logika vazby. Nepodporované vstupní vzory zahrnují obecná volání a předávání zabalených objektů.</target>
<source>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'.</source>
<target state="needs-review-translation">Pro volání vazače se nevygenerovala logika vazby. Nepodporované vstupní vzory zahrnují obecná volání a předávání zabalených objektů.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">Für einen Binderaufruf wurde keine Bindungslogik generiert. Nicht unterstützte Eingabemuster umfassen generische Aufrufe und übergeben geschachtelte Objekte.</target>
<source>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'.</source>
<target state="needs-review-translation">Für einen Binderaufruf wurde keine Bindungslogik generiert. Nicht unterstützte Eingabemuster umfassen generische Aufrufe und übergeben geschachtelte Objekte.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">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.</target>
<source>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'.</source>
<target state="needs-review-translation">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.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">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.</target>
<source>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'.</source>
<target state="needs-review-translation">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.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">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.</target>
<source>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'.</source>
<target state="needs-review-translation">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.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">バインダー呼び出しのバインド ロジックが生成されませんでした。サポートされていない入力パターンとしては、ジェネリック呼び出し、ボックス化されたオブジェクトの受け渡しなどがあります。</target>
<source>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'.</source>
<target state="needs-review-translation">バインダー呼び出しのバインド ロジックが生成されませんでした。サポートされていない入力パターンとしては、ジェネリック呼び出し、ボックス化されたオブジェクトの受け渡しなどがあります。</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">바인더 호출에 대한 바인딩 논리가 생성되지 않았습니다. 지원되지 않는 입력 패턴에는 제네릭 호출 및 boxed 개체 전달이 포함됩니다.</target>
<source>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'.</source>
<target state="needs-review-translation">바인더 호출에 대한 바인딩 논리가 생성되지 않았습니다. 지원되지 않는 입력 패턴에는 제네릭 호출 및 boxed 개체 전달이 포함됩니다.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoMessageFormat">
<source>Binding logic was not generated for a binder call. Unsupported input patterns include generic calls and passing boxed objects.</source>
<target state="translated">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.</target>
<source>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'.</source>
<target state="needs-review-translation">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.</target>
<note />
</trans-unit>
<trans-unit id="CouldNotDetermineTypeInfoTitle">
Expand Down
Loading