Skip to content

Commit

Permalink
Minimize net diff of binder-gen visibility/identifier-clash fix
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia committed Sep 12, 2023
1 parent e5fdbcb commit c3293ea
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 110 deletions.
84 changes: 0 additions & 84 deletions src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs
Original file line number Diff line number Diff line change
@@ -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<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)
Expand All @@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -937,19 +937,4 @@ private void RegisterTypeDiagnostic(ITypeSymbol causingType, InvocationDiagnosti
}
}
}

public static class ParserExtensions
{
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<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);
}

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<ITypeSymbol> typeArgsInScope)
{
foreach (ITypeSymbol genericArg in typeArgsInScope)
{
sb.Append(ToIdentifierCompatibleSubstring(genericArg));
}
}

return sb.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<Compile Include="Helpers\Parser\BinderInvocation.cs" />
<Compile Include="Helpers\Parser\ConfigurationBinder.cs" />
<Compile Include="Helpers\Parser\Diagnostics.cs" />
<Compile Include="Helpers\Parser\Extensions.cs" />
<Compile Include="Helpers\Parser\OptionsBuilderConfigurationExtensions.cs" />
<Compile Include="Helpers\Parser\OptionsConfigurationServiceCollectionExtensions.cs" />
<Compile Include="Model\CollectionSpec.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +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
{
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; }
Expand All @@ -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;
}

Expand Down
37 changes: 33 additions & 4 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<ITypeSymbol> typeArgsInScope)
{
foreach (ITypeSymbol genericArg in typeArgsInScope)
{
sb.Append(GetTypeInfoPropertyName(genericArg));
}
}

return sb.ToString();
}

private bool TryGetDeserializationConstructor(
ITypeSymbol type,
bool useDefaultCtorInAnnotatedStructs,
Expand Down

0 comments on commit c3293ea

Please sign in to comment.