Skip to content

Commit 7ab055d

Browse files
committed
Use vendored GetBestTypeByMetadataName instead
1 parent 2be0b92 commit 7ab055d

3 files changed

Lines changed: 121 additions & 3 deletions

File tree

src/NetEscapades.EnumGenerators/Diagnostics/AnalyzerHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public static class AnalyzerHelpers
99
public static (INamedTypeSymbol? enumExtensionsAttr, ExternalEnumDictionary? externalEnumTypes) GetEnumExtensionAttributes(Compilation compilation)
1010
{
1111
var enumExtensionsAttr =
12-
compilation.GetTypeByMetadataName(Attributes.EnumExtensionsAttribute);
12+
compilation.GetBestTypeByMetadataName(Attributes.EnumExtensionsAttribute);
1313
var externalEnumExtensionsAttr =
14-
compilation.GetTypeByMetadataName(Attributes.ExternalEnumExtensionsAttribute);
14+
compilation.GetBestTypeByMetadataName(Attributes.ExternalEnumExtensionsAttribute);
1515

1616
if (enumExtensionsAttr is null)
1717
{

src/NetEscapades.EnumGenerators/Diagnostics/CodeFixProviderBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private static async Task<Document> FixAllAsync(
7171
continue;
7272
}
7373

74-
var type = editor.SemanticModel.Compilation.GetTypeByMetadataName(extensionTypeName);
74+
var type = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(extensionTypeName);
7575
if (type is null)
7676
{
7777
continue;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
2+
using System.Diagnostics;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace NetEscapades.EnumGenerators.Diagnostics;
6+
7+
internal static class CompilationExtensions
8+
{
9+
// Copy from https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs#L11-L68
10+
/// <summary>
11+
/// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
12+
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
13+
/// following rules.
14+
///
15+
/// <list type="number">
16+
/// <item><description>
17+
/// If only one type with the given name is found within the compilation and its referenced assemblies, that
18+
/// type is returned regardless of accessibility.
19+
/// </description></item>
20+
/// <item><description>
21+
/// If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
22+
/// </description></item>
23+
/// <item><description>
24+
/// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
25+
/// <paramref name="compilation"/>, that symbol is returned.
26+
/// </description></item>
27+
/// <item><description>
28+
/// Otherwise, this method returns <see langword="null"/>.
29+
/// </description></item>
30+
/// </list>
31+
/// </summary>
32+
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
33+
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
34+
/// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
35+
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
36+
{
37+
INamedTypeSymbol? type = null;
38+
39+
foreach (var currentType in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName))
40+
{
41+
if (ReferenceEquals(currentType.ContainingAssembly, compilation.Assembly))
42+
{
43+
Debug.Assert(type is null);
44+
return currentType;
45+
}
46+
47+
switch (currentType.GetResultantVisibility())
48+
{
49+
case SymbolVisibility.Public:
50+
case SymbolVisibility.Internal when currentType.ContainingAssembly.GivesAccessTo(compilation.Assembly):
51+
break;
52+
53+
default:
54+
continue;
55+
}
56+
57+
if (type is object)
58+
{
59+
// Multiple visible types with the same metadata name are present
60+
return null;
61+
}
62+
63+
type = currentType;
64+
}
65+
66+
return type;
67+
}
68+
69+
// Copy from https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs#L28-L73
70+
private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
71+
{
72+
// Start by assuming it's visible.
73+
var visibility = SymbolVisibility.Public;
74+
switch (symbol.Kind)
75+
{
76+
case SymbolKind.Alias:
77+
// Aliases are uber private. They're only visible in the same file that they
78+
// were declared in.
79+
return SymbolVisibility.Private;
80+
case SymbolKind.Parameter:
81+
// Parameters are only as visible as their containing symbol
82+
return GetResultantVisibility(symbol.ContainingSymbol);
83+
case SymbolKind.TypeParameter:
84+
// Type Parameters are private.
85+
return SymbolVisibility.Private;
86+
}
87+
88+
while (symbol is not null && symbol.Kind != SymbolKind.Namespace)
89+
{
90+
switch (symbol.DeclaredAccessibility)
91+
{
92+
// If we see anything private, then the symbol is private.
93+
case Accessibility.NotApplicable:
94+
case Accessibility.Private:
95+
return SymbolVisibility.Private;
96+
// If we see anything internal, then knock it down from public to
97+
// internal.
98+
case Accessibility.Internal:
99+
case Accessibility.ProtectedAndInternal:
100+
visibility = SymbolVisibility.Internal;
101+
break;
102+
// For anything else (Public, Protected, ProtectedOrInternal), the
103+
// symbol stays at the level we've gotten so far.
104+
}
105+
106+
symbol = symbol.ContainingSymbol;
107+
}
108+
109+
return visibility;
110+
}
111+
112+
private enum SymbolVisibility
113+
{
114+
Public,
115+
Internal,
116+
Private,
117+
}
118+
}

0 commit comments

Comments
 (0)