Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using System.Collections.Concurrent;

namespace Meziantou.Analyzer.Internals;

internal sealed class CultureSensitiveFormattingContext(Compilation compilation)
{
private readonly HashSet<ISymbol> _excludedMethods = CreateExcludedMethods(compilation);
private readonly HashSet<ISymbol> _cultureInsensitiveMembers = CreateCultureInsensitiveMembers(compilation);

// The culture sensitivity of a type only depends on the type and the options, both of which are stable for a compilation
private readonly ConcurrentDictionary<CultureSensitivityCacheKey, CultureSensitivity> _cultureSensitivityCache = new();

public INamedTypeSymbol? FormatProviderSymbol { get; } = compilation.GetBestTypeByMetadataName("System.IFormatProvider");
public INamedTypeSymbol? CultureInfoSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Globalization.CultureInfo");
public INamedTypeSymbol? NumberStyleSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Globalization.NumberStyles");
Expand Down Expand Up @@ -524,6 +529,17 @@ private CultureSensitivity GetCultureSensitivity(ITypeSymbol? typeSymbol, Cultur
if (typeSymbol is null)
return CultureSensitivity.MaybeCultureSensitiveOpaqueRuntimeType;

var key = new CultureSensitivityCacheKey(typeSymbol, options);
if (_cultureSensitivityCache.TryGetValue(key, out var cachedCultureSensitivity))
return cachedCultureSensitivity;

var cultureSensitivity = GetCultureSensitivityCore(typeSymbol, options);
_cultureSensitivityCache[key] = cultureSensitivity;
return cultureSensitivity;
}

private CultureSensitivity GetCultureSensitivityCore(ITypeSymbol typeSymbol, CultureSensitiveOptions options)
{
if (MustUnwrapNullableOfT(options))
{
typeSymbol = typeSymbol.GetUnderlyingNullableTypeOrSelf();
Expand Down Expand Up @@ -633,7 +649,18 @@ bool IsFormattableType(ITypeSymbol type)
}

bool HasToStringWithFormatProvider(ITypeSymbol type)
=> type.GetAllMembers().OfType<IMethodSymbol>().Any(m => m is { Name: "ToString", IsStatic: false, ReturnType: { SpecialType: SpecialType.System_String }, Parameters: [var param1] } && param1.Type.IsOrInheritsFrom(FormatProviderSymbol) && m.DeclaredAccessibility is Accessibility.Public);
{
for (ITypeSymbol? currentType = type; currentType is not null; currentType = currentType.BaseType)
{
foreach (var member in currentType.GetMembers(nameof(ToString)))
{
if (member is IMethodSymbol { IsStatic: false, DeclaredAccessibility: Accessibility.Public, ReturnType.SpecialType: SpecialType.System_String, Parameters: [var param1] } && param1.Type.IsOrInheritsFrom(FormatProviderSymbol))
return true;
}
}

return false;
}
}

private CultureSensitivity GetCultureSensitivity(ITypeParameterSymbol typeParameter, CultureSensitiveOptions options)
Expand Down Expand Up @@ -952,4 +979,16 @@ private bool IsInterpolatedStringType(ITypeSymbol typeSymbol)

return false;
}

private readonly struct CultureSensitivityCacheKey(ITypeSymbol typeSymbol, CultureSensitiveOptions options) : IEquatable<CultureSensitivityCacheKey>
{
public ITypeSymbol TypeSymbol { get; } = typeSymbol;
public CultureSensitiveOptions Options { get; } = options;

public bool Equals(CultureSensitivityCacheKey other) => Options == other.Options && SymbolEqualityComparer.Default.Equals(TypeSymbol, other.TypeSymbol);

public override bool Equals(object? obj) => obj is CultureSensitivityCacheKey other && Equals(other);

public override int GetHashCode() => (SymbolEqualityComparer.Default.GetHashCode(TypeSymbol) * 397) ^ (int)Options;
}
}