Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement GetTypesByMetadataName #58366

Merged
merged 8 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
208 changes: 208 additions & 0 deletions src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3036,5 +3036,213 @@ void M1()
}

#endregion

#region GetTypesByMetadataName Tests

[Fact]
public void GetTypesByMetadataName_NotInSourceNotInReferences()
{
var comp = CreateCompilation("");
comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");
Assert.Empty(types);
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_SingleInSourceNotInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
var referenceComp = CreateCompilation("");

var comp = CreateCompilation(
$@"namespace N;
{accessibility} class C<T> {{}}", new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() });

comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");

Assert.Single(types);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_MultipleInSourceNotInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
var referenceComp = CreateCompilation("");

var comp = CreateCompilation(
$@"namespace N;
{accessibility} class C<T> {{}}
{accessibility} class C<T> {{}}", new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() });

comp.VerifyDiagnostics(
// (3,16): error CS0101: The namespace 'N' already contains a definition for 'C'
// internal class C<T> {}
Diagnostic(ErrorCode.ERR_DuplicateNameInNS, "C").WithArguments("C", "N").WithLocation(3, 8 + accessibility.Length)
);

var types = comp.GetTypesByMetadataName("N.C`1");

Assert.Single(types);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
Assert.Equal(2, types[0].Locations.Length);
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_SingleInSourceSingleInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
string source = $@"namespace N;
{accessibility} class C<T> {{}}";

var referenceComp = CreateCompilation(source);

referenceComp.VerifyDiagnostics();

var comp = CreateCompilation(source, new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() });
comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");

Assert.Equal(2, types.Length);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
Assert.Same(comp.Assembly.GetPublicSymbol(), types[0].ContainingAssembly);
AssertEx.Equal("N.C<T>", types[1].ToTestDisplayString());
if (useMetadataReference)
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
Assert.Same(referenceComp.Assembly.GetPublicSymbol(), types[1].ContainingAssembly);
}
else
{
Assert.False(types[1].IsInSource());
}
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_NotInSourceSingleInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
string source = @$"namespace N;
{accessibility} class C<T> {{}}";

var referenceComp = CreateCompilation(source);

referenceComp.VerifyDiagnostics();

var comp = CreateCompilation("", new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() });
comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");


Assert.Single(types);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
if (useMetadataReference)
{
Assert.Same(referenceComp.Assembly.GetPublicSymbol(), types[0].ContainingAssembly);
}
else
{
Assert.False(types[0].IsInSource());
}
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_NotInSourceMultipleInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
string source = @$"namespace N;
{accessibility} class C<T> {{}}";

var referenceComp1 = CreateCompilation(source);
referenceComp1.VerifyDiagnostics();

var referenceComp2 = CreateCompilation(source);
referenceComp2.VerifyDiagnostics();

var comp = CreateCompilation("", new[] { getReference(referenceComp1), getReference(referenceComp2) });
comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");

Assert.Equal(2, types.Length);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
AssertEx.Equal("N.C<T>", types[1].ToTestDisplayString());
if (useMetadataReference)
{
Assert.Same(referenceComp1.Assembly.GetPublicSymbol(), types[0].ContainingAssembly);
Assert.Same(referenceComp2.Assembly.GetPublicSymbol(), types[1].ContainingAssembly);
}
else
{
Assert.False(types[0].IsInSource());
Assert.False(types[1].IsInSource());
Assert.NotSame(types[0].ContainingAssembly, types[1].ContainingAssembly);
}

MetadataReference getReference(CSharpCompilation referenceComp1)
{
return useMetadataReference ? referenceComp1.ToMetadataReference() : referenceComp1.EmitToImageReference();
}
}

[Theory]
[CombinatorialData]
public void GetTypesByMetadataName_SingleInSourceMultipleInReferences(
bool useMetadataReference,
[CombinatorialValues("public", "internal")] string accessibility)
{
string source = @$"namespace N;
{accessibility} class C<T> {{}}";

var referenceComp1 = CreateCompilation(source);
referenceComp1.VerifyDiagnostics();

var referenceComp2 = CreateCompilation(source);
referenceComp2.VerifyDiagnostics();

var comp = CreateCompilation(source, new[] { getReference(referenceComp1), getReference(referenceComp2) });
comp.VerifyDiagnostics();

var types = comp.GetTypesByMetadataName("N.C`1");

Assert.Equal(3, types.Length);
AssertEx.Equal("N.C<T>", types[0].ToTestDisplayString());
Assert.Same(comp.Assembly.GetPublicSymbol(), types[0].ContainingAssembly);
AssertEx.Equal("N.C<T>", types[1].ToTestDisplayString());
AssertEx.Equal("N.C<T>", types[2].ToTestDisplayString());

if (useMetadataReference)
{
Assert.Same(referenceComp1.Assembly.GetPublicSymbol(), types[1].ContainingAssembly);
Assert.Same(referenceComp2.Assembly.GetPublicSymbol(), types[2].ContainingAssembly);
}
else
{
Assert.False(types[1].IsInSource());
Assert.False(types[2].IsInSource());
Assert.NotSame(types[1].ContainingAssembly, types[2].ContainingAssembly);
}

MetadataReference getReference(CSharpCompilation referenceComp1)
{
return useMetadataReference ? referenceComp1.ToMetadataReference() : referenceComp1.EmitToImageReference();
}
}

#endregion
333fred marked this conversation as resolved.
Show resolved Hide resolved
}
}
84 changes: 78 additions & 6 deletions src/Compilers/Core/Portable/Compilation/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1025,28 +1025,101 @@ public INamedTypeSymbol CreateNativeIntegerTypeSymbol(bool signed)
private readonly ConcurrentCache<string, INamedTypeSymbol?> _getTypeCache =
new ConcurrentCache<string, INamedTypeSymbol?>(50, ReferenceEqualityComparer.Instance);

private readonly ConcurrentCache<string, ImmutableArray<INamedTypeSymbol>> _getTypesCache =
new ConcurrentCache<string, ImmutableArray<INamedTypeSymbol>>(50, ReferenceEqualityComparer.Instance);

/// <summary>
/// Gets the type within the compilation's assembly and all referenced assemblies (other than
/// those that can only be referenced via an extern alias) using its canonical CLR metadata name.
/// </summary>
/// <returns>Null if the type can't be found.</returns>
/// This lookup follows the following order:
/// <list type="number">
/// <item><description>If the type is found in the compilation's assembly, that type is returned.</description></item>
/// <item><description>Next, the core library (the library that defines <c>System.Object</c>) is searched. If the type is found there, that type is returned.</description></item>
333fred marked this conversation as resolved.
Show resolved Hide resolved
333fred marked this conversation as resolved.
Show resolved Hide resolved
/// <item><description>
/// Finally, all remaining referenced assemblies are searched. If one and only one type matching the provided metadata name is found, that
/// single type is returned. Accessibility is ignored for this check.
/// </description></item>
/// </list>
/// </summary>
/// <returns>Null if the type can't be found or there was an ambiguity during lookup.</returns>
/// <remarks>
/// Since VB does not have the concept of extern aliases, it considers all referenced assemblies.
/// <br/>
333fred marked this conversation as resolved.
Show resolved Hide resolved
/// Because accessibility to the current assembly is ignored when searching for types that match the provided metadata name, if multiple referenced
/// assemblies define the same type symbol (as often happens when users copy well-known types from the BCL or other sources) then this API will return null,
/// even if all but one of those symbols would be otherwise inaccessible to user-written code in the current assembly. If it is not an error for a type to exist
/// in multiple referenced assemblies, consider using <see cref="GetTypesByMetadataName(string)" /> instead and filtering the results for the symbol required.
333fred marked this conversation as resolved.
Show resolved Hide resolved
/// </remarks>
public INamedTypeSymbol? GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
if (!_getTypeCache.TryGetValue(fullyQualifiedMetadataName, out INamedTypeSymbol? val))
{
val = CommonGetTypeByMetadataName(fullyQualifiedMetadataName);
// Ignore if someone added the same value before us
_ = _getTypeCache.TryAdd(fullyQualifiedMetadataName, val);
var result = _getTypeCache.TryAdd(fullyQualifiedMetadataName, val);
Debug.Assert(result || (_getTypeCache.TryGetValue(fullyQualifiedMetadataName, out var addedType) && ReferenceEquals(addedType, val)));
}
return val;
}

protected abstract INamedTypeSymbol? CommonGetTypeByMetadataName(string metadataName);

#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Gets all types with the compilation's assembly and all referenced assemblies that have the
/// given canonical CLR metadata name. Accessibility to the current assembly is ignored when
/// searching for matching type names.
/// </summary>
333fred marked this conversation as resolved.
Show resolved Hide resolved
/// <returns>Empty array if no types match. Otherwise, all types that match the name, current assembly first if present.</returns>
public ImmutableArray<INamedTypeSymbol> GetTypesByMetadataName(string fullyQualifiedMetadataName)
{
if (!_getTypesCache.TryGetValue(fullyQualifiedMetadataName, out ImmutableArray<INamedTypeSymbol> val))
{
val = getTypesByMetadataNameImpl();
var result = _getTypesCache.TryAdd(fullyQualifiedMetadataName, val);
Debug.Assert(result
|| (_getTypesCache.TryGetValue(fullyQualifiedMetadataName, out var addedArray)
&& addedArray.Zip(val, (added, calculated) => (added, calculated)).All(el => ReferenceEquals(el.added, el.calculated))));
333fred marked this conversation as resolved.
Show resolved Hide resolved
}

return val;

ImmutableArray<INamedTypeSymbol> getTypesByMetadataNameImpl()
{
ArrayBuilder<INamedTypeSymbol>? typesByMetadataName = null;

// Start with the current assembly, then corlib, then look through all references, to mimic GetTypeByMetadataName search order.

addIfNotNull(Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName));

var corLib = (IAssemblySymbol)((IAssemblySymbolInternal)Assembly).CorLibrary;
333fred marked this conversation as resolved.
Show resolved Hide resolved

333fred marked this conversation as resolved.
Show resolved Hide resolved
if (!ReferenceEquals(corLib, Assembly))
{
addIfNotNull(corLib.GetTypeByMetadataName(fullyQualifiedMetadataName));
}

foreach (var referencedAssembly in SourceModule.ReferencedAssemblySymbols)
{
if (ReferenceEquals(referencedAssembly, corLib))
{
continue;
}

addIfNotNull(referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName));
}

return typesByMetadataName?.ToImmutableAndFree() ?? ImmutableArray<INamedTypeSymbol>.Empty;

void addIfNotNull(INamedTypeSymbol? toAdd)
{
if (toAdd != null)
{
typesByMetadataName ??= ArrayBuilder<INamedTypeSymbol>.GetInstance();
typesByMetadataName.Add(toAdd);
}
}
}
}

/// <summary>
/// Returns a new INamedTypeSymbol with the given element types and
/// (optional) element names, locations, and nullable annotations.
Expand Down Expand Up @@ -1087,7 +1160,6 @@ public INamedTypeSymbol CreateTupleTypeSymbol(

return CommonCreateTupleTypeSymbol(elementTypes, elementNames, elementLocations, elementNullableAnnotations);
}
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters

/// <summary>
/// Returns a new INamedTypeSymbol with the given element types, names, and locations.
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Microsoft.CodeAnalysis.WellKnownGeneratorOutputs.ImplementationSourceOutpu
const Microsoft.CodeAnalysis.WellKnownGeneratorOutputs.SourceOutput = "SourceOutput" -> string!
const Microsoft.CodeAnalysis.WellKnownMemberNames.PrintMembersMethodName = "PrintMembers" -> string!
Microsoft.CodeAnalysis.Compilation.EmitDifference(Microsoft.CodeAnalysis.Emit.EmitBaseline! baseline, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.Emit.SemanticEdit>! edits, System.Func<Microsoft.CodeAnalysis.ISymbol!, bool>! isAddedSymbol, System.IO.Stream! metadataStream, System.IO.Stream! ilStream, System.IO.Stream! pdbStream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitDifferenceResult!
Microsoft.CodeAnalysis.Compilation.GetTypesByMetadataName(string! fullyQualifiedMetadataName) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.INamedTypeSymbol!>
Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.ChangedTypes.get -> System.Collections.Immutable.ImmutableArray<System.Reflection.Metadata.TypeDefinitionHandle>
Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedMethods.get -> System.Collections.Immutable.ImmutableArray<System.Reflection.Metadata.MethodDefinitionHandle>
Microsoft.CodeAnalysis.Emit.SemanticEditKind.Replace = 4 -> Microsoft.CodeAnalysis.Emit.SemanticEditKind
Expand Down
Loading