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
Expand Up @@ -31,7 +31,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics
/// </remarks>
public sealed class AnalyzerFileReference : AnalyzerReference, IEquatable<AnalyzerReference>
{
private delegate IEnumerable<string> AttributeLanguagesFunc(PEModule module, CustomAttributeHandle attribute);
private delegate ImmutableArray<string> AttributeLanguagesFunc(PEModule module, CustomAttributeHandle attribute);

public override string FullPath { get; }

Expand Down Expand Up @@ -224,48 +224,63 @@ private static ImmutableSortedDictionary<string, ImmutableHashSet<string>> GetAn
{
using var assembly = AssemblyMetadata.CreateFromFile(fullPath);

// This is longer than strictly necessary to avoid thrashing the GC with string allocations
// in the call to GetFullyQualifiedTypeNames. Specifically, this checks for the presence of
// supported languages prior to creating the type names.
var typeNameMap = from module in assembly.GetModules()
from typeDefHandle in module.MetadataReader.TypeDefinitions
let typeDef = module.MetadataReader.GetTypeDefinition(typeDefHandle)
let supportedLanguages = GetSupportedLanguages(typeDef, module.Module, attributeType, languagesFunc)
where supportedLanguages.Any()
let typeName = GetFullyQualifiedTypeName(typeDef, module.Module)
from supportedLanguage in supportedLanguages
group typeName by supportedLanguage;

return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.ToImmutableHashSet(), StringComparer.OrdinalIgnoreCase);
Dictionary<string, ImmutableHashSet<string>.Builder> typeNameMap = new Dictionary<string, ImmutableHashSet<string>.Builder>(StringComparer.OrdinalIgnoreCase);
Copy link
Member

Choose a reason for hiding this comment

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

Should we use ImmutableSegmentedHashSet instead?

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 didn't see these hitting the LOH in the profile that I looked at, so I'd prefer to keep as-is, without that evidence to back up such a change.


foreach (var module in assembly.GetModules())
{
foreach (var typeDefHandle in module.MetadataReader.TypeDefinitions)
{
var typeDef = module.MetadataReader.GetTypeDefinition(typeDefHandle);
var supportedLanguages = GetSupportedLanguages(typeDef, module.Module, attributeType, languagesFunc);

// PERF: avoid calling GetFullyQualifiedTypeName when no supported languages.
if (supportedLanguages.Length > 0)
{
var typeName = GetFullyQualifiedTypeName(typeDef, module.Module);
foreach (var supportedLanguage in supportedLanguages)
{
if (!typeNameMap.TryGetValue(supportedLanguage, out var builder))
{
builder = ImmutableHashSet.CreateBuilder<string>();
typeNameMap.Add(supportedLanguage, builder);
}

builder.Add(typeName);
}
}
}
}

return typeNameMap.ToImmutableSortedDictionary(g => g.Key, g => g.Value.ToImmutable(), StringComparer.OrdinalIgnoreCase);
}

private static IEnumerable<string> GetSupportedLanguages(TypeDefinition typeDef, PEModule peModule, Type attributeType, AttributeLanguagesFunc languagesFunc)
private static ImmutableArray<string> GetSupportedLanguages(TypeDefinition typeDef, PEModule peModule, Type attributeType, AttributeLanguagesFunc languagesFunc)
{
IEnumerable<string>? result = null;
ImmutableArray<string> result = [];
foreach (CustomAttributeHandle customAttrHandle in typeDef.GetCustomAttributes())
{
if (peModule.IsTargetAttribute(customAttrHandle, attributeType.Namespace!, attributeType.Name, ctor: out _))
{
if (languagesFunc(peModule, customAttrHandle) is { } attributeSupportedLanguages)
{
if (result is null)
if (result.IsDefaultOrEmpty)
{
result = attributeSupportedLanguages;
}
else
{
// This is a slow path, but only occurs if a single type has multiple
// DiagnosticAnalyzerAttribute instances applied to it.
result = result.Concat(attributeSupportedLanguages);
result = result.AddRange(attributeSupportedLanguages);
}
}
}
}

return result ?? SpecializedCollections.EmptyEnumerable<string>();
return result;
}

private static IEnumerable<string> GetDiagnosticsAnalyzerSupportedLanguages(PEModule peModule, CustomAttributeHandle customAttrHandle)
private static ImmutableArray<string> GetDiagnosticsAnalyzerSupportedLanguages(PEModule peModule, CustomAttributeHandle customAttrHandle)
{
// The DiagnosticAnalyzerAttribute has one constructor, which has a string parameter for the
// first supported language and an array parameter for additional supported languages.
Expand All @@ -274,7 +289,7 @@ private static IEnumerable<string> GetDiagnosticsAnalyzerSupportedLanguages(PEMo
return ReadLanguagesFromAttribute(ref argsReader);
}

private static IEnumerable<string> GetGeneratorSupportedLanguages(PEModule peModule, CustomAttributeHandle customAttrHandle)
private static ImmutableArray<string> GetGeneratorSupportedLanguages(PEModule peModule, CustomAttributeHandle customAttrHandle)
{
// The GeneratorAttribute has two constructors: one default, and one with a string parameter for the
// first supported language and an array parameter for additional supported languages.
Expand All @@ -293,7 +308,7 @@ private static IEnumerable<string> GetGeneratorSupportedLanguages(PEModule peMod

// https://github.com/dotnet/roslyn/issues/53994 tracks re-enabling nullable and fixing this method
#nullable disable
private static IEnumerable<string> ReadLanguagesFromAttribute(ref BlobReader argsReader)
private static ImmutableArray<string> ReadLanguagesFromAttribute(ref BlobReader argsReader)
{
if (argsReader.Length > 4)
{
Expand All @@ -303,22 +318,22 @@ private static IEnumerable<string> ReadLanguagesFromAttribute(ref BlobReader arg
string firstLanguageName;
if (!PEModule.CrackStringInAttributeValue(out firstLanguageName, ref argsReader))
{
return SpecializedCollections.EmptyEnumerable<string>();
return [];
}

ImmutableArray<string> additionalLanguageNames;
if (PEModule.CrackStringArrayInAttributeValue(out additionalLanguageNames, ref argsReader))
{
if (additionalLanguageNames.Length == 0)
{
return SpecializedCollections.SingletonEnumerable(firstLanguageName);
return [firstLanguageName];
}

return additionalLanguageNames.Insert(0, firstLanguageName);
}
}
}
return SpecializedCollections.EmptyEnumerable<string>();
return [];
}

#nullable enable
Expand Down