diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs index d1a03c60f4a71..6e608104926e4 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs @@ -3036,5 +3036,282 @@ 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 {{}}", new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() }); + + comp.VerifyDiagnostics(); + + var types = comp.GetTypesByMetadataName("N.C`1"); + + Assert.Single(types); + AssertEx.Equal("N.C", 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 {{}} +{accessibility} class C {{}}", new[] { useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference() }); + + comp.VerifyDiagnostics( + // (3,16): error CS0101: The namespace 'N' already contains a definition for 'C' + // internal class C {} + 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", 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 {{}}"; + + var referenceComp = CreateCompilation(source); + + referenceComp.VerifyDiagnostics(); + + MetadataReference reference = useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference(); + var comp = CreateCompilation(source, new[] { reference }); + comp.VerifyDiagnostics(); + + var types = comp.GetTypesByMetadataName("N.C`1"); + + Assert.Equal(2, types.Length); + AssertEx.Equal("N.C", types[0].ToTestDisplayString()); + Assert.Same(comp.Assembly.GetPublicSymbol(), types[0].ContainingAssembly); + AssertEx.Equal("N.C", types[1].ToTestDisplayString()); + + var assembly1 = comp.GetAssemblyOrModuleSymbol(reference).GetPublicSymbol(); + Assert.Same(types[1].ContainingAssembly, assembly1); + } + + [Theory] + [CombinatorialData] + public void GetTypesByMetadataName_NotInSourceSingleInReferences( + bool useMetadataReference, + [CombinatorialValues("public", "internal")] string accessibility) + { + string source = @$"namespace N; +{accessibility} class C {{}}"; + + var referenceComp = CreateCompilation(source); + + referenceComp.VerifyDiagnostics(); + + MetadataReference reference = useMetadataReference ? referenceComp.ToMetadataReference() : referenceComp.EmitToImageReference(); + var comp = CreateCompilation("", new[] { reference }); + comp.VerifyDiagnostics(); + + var types = comp.GetTypesByMetadataName("N.C`1"); + + + Assert.Single(types); + AssertEx.Equal("N.C", types[0].ToTestDisplayString()); + + var assembly1 = comp.GetAssemblyOrModuleSymbol(reference).GetPublicSymbol(); + Assert.Same(types[0].ContainingAssembly, assembly1); + } + + [Theory] + [CombinatorialData] + public void GetTypesByMetadataName_NotInSourceMultipleInReferences( + bool useMetadataReference, + [CombinatorialValues("public", "internal")] string accessibility) + { + string source = @$"namespace N; +{accessibility} class C {{}}"; + + var referenceComp1 = CreateCompilation(source); + referenceComp1.VerifyDiagnostics(); + + var referenceComp2 = CreateCompilation(source); + referenceComp2.VerifyDiagnostics(); + + MetadataReference reference1 = getReference(referenceComp1); + MetadataReference reference2 = getReference(referenceComp2); + var comp = CreateCompilation("", new[] { reference1, reference2 }); + comp.VerifyDiagnostics(); + + var types = comp.GetTypesByMetadataName("N.C`1"); + + Assert.Equal(2, types.Length); + AssertEx.Equal("N.C", types[0].ToTestDisplayString()); + AssertEx.Equal("N.C", types[1].ToTestDisplayString()); + + var assembly1 = comp.GetAssemblyOrModuleSymbol(reference1).GetPublicSymbol(); + Assert.Same(types[0].ContainingAssembly, assembly1); + + var assembly2 = comp.GetAssemblyOrModuleSymbol(reference2).GetPublicSymbol(); + Assert.Same(types[1].ContainingAssembly, assembly2); + + 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 {{}}"; + + var referenceComp1 = CreateCompilation(source); + referenceComp1.VerifyDiagnostics(); + + var referenceComp2 = CreateCompilation(source); + referenceComp2.VerifyDiagnostics(); + + MetadataReference reference1 = getReference(referenceComp1); + MetadataReference reference2 = getReference(referenceComp2); + var comp = CreateCompilation(source, new[] { reference1, reference2 }); + comp.VerifyDiagnostics(); + + var types = comp.GetTypesByMetadataName("N.C`1"); + + Assert.Equal(3, types.Length); + AssertEx.Equal("N.C", types[0].ToTestDisplayString()); + Assert.Same(comp.Assembly.GetPublicSymbol(), types[0].ContainingAssembly); + AssertEx.Equal("N.C", types[1].ToTestDisplayString()); + AssertEx.Equal("N.C", types[2].ToTestDisplayString()); + + var assembly1 = comp.GetAssemblyOrModuleSymbol(reference1).GetPublicSymbol(); + Assert.Same(types[1].ContainingAssembly, assembly1); + + var assembly2 = comp.GetAssemblyOrModuleSymbol(reference2).GetPublicSymbol(); + Assert.Same(types[2].ContainingAssembly, assembly2); + + MetadataReference getReference(CSharpCompilation referenceComp1) + { + return useMetadataReference ? referenceComp1.ToMetadataReference() : referenceComp1.EmitToImageReference(); + } + } + + [Fact] + public void GetTypesByMetadataName_Ordering() + { + var corlibSource = @" +namespace System +{ + public class Object {} + public class Void {} +} + +public class C {} +"; + + var corlib = CreateEmptyCompilation(corlibSource); + var corlibReference = corlib.EmitToImageReference(); + + var other = CreateEmptyCompilation(@"public class C {}", new[] { corlibReference }); + var otherReference = other.EmitToImageReference(); + + var current = CreateEmptyCompilation(@"public class C {}", new[] { otherReference, corlibReference }); + current.VerifyDiagnostics(); + + var types = current.GetTypesByMetadataName("C"); + + AssertEx.Equal(types.Select(t => t.ToTestDisplayString()), new[] { "C", "C", "C" }); + Assert.Same(types[0].ContainingAssembly, current.Assembly.GetPublicSymbol()); + + var corlibAssembly = current.GetAssemblyOrModuleSymbol(corlibReference).GetPublicSymbol(); + Assert.Same(types[1].ContainingAssembly, corlibAssembly); + + var otherAssembly = current.GetAssemblyOrModuleSymbol(otherReference).GetPublicSymbol(); + Assert.Same(types[2].ContainingAssembly, otherAssembly); + } + + [Fact] + public void GetTypeByMetadataName_CorLibViaExtern() + { + var corlibSource = @" +namespace System +{ + public class Object {} + public class Void {} +} + +public class C {} +"; + + var corlib = CreateEmptyCompilation(corlibSource); + var corlibReference = corlib.EmitToImageReference(aliases: ImmutableArray.Create("corlib")); + + var current = CreateEmptyCompilation(@"", new[] { corlibReference }); + current.VerifyDiagnostics(); + + var type = ((Compilation)current).GetTypeByMetadataName("C"); + Assert.NotNull(type); + + var corlibAssembly = current.GetAssemblyOrModuleSymbol(corlibReference).GetPublicSymbol(); + Assert.Same(type.ContainingAssembly, corlibAssembly); + } + + [Fact] + public void GetTypeByMetadataName_OtherViaExtern() + { + var corlibSource = @" +namespace System +{ + public class Object {} + public class Void {} +} +"; + + var corlib = CreateEmptyCompilation(corlibSource); + var corlibReference = corlib.EmitToImageReference(); + + var other = CreateEmptyCompilation(@"public class C {}", new[] { corlibReference }); + var otherReference = other.EmitToImageReference(aliases: ImmutableArray.Create("other")); + + var current = CreateEmptyCompilation(@"", new[] { otherReference, corlibReference }); + current.VerifyDiagnostics(); + + var type = ((Compilation)current).GetTypeByMetadataName("C"); + Assert.Null(type); + } + + #endregion } } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index 037ea63240a7d..d48762238a837 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -1025,28 +1025,122 @@ public INamedTypeSymbol CreateNativeIntegerTypeSymbol(bool signed) private readonly ConcurrentCache _getTypeCache = new ConcurrentCache(50, ReferenceEqualityComparer.Instance); + private readonly ConcurrentCache> _getTypesCache = + new ConcurrentCache>(50, ReferenceEqualityComparer.Instance); + /// /// 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. - /// - /// Null if the type can't be found. + /// This lookup follows the following order: + /// + /// If the type is found in the compilation's assembly, that type is returned. + /// + /// Next, the core library (the library that defines System.Object and has no assembly references) is searched. + /// If the type is found there, that type is returned. + /// + /// + /// Finally, all remaining referenced non-extern 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. + /// + /// + /// + /// Null if the type can't be found or there was an ambiguity during lookup. /// + /// /// Since VB does not have the concept of extern aliases, it considers all referenced assemblies. + /// + /// + /// In C#, if the core library is referenced as an extern assembly, it will be searched. All other extern-aliased assemblies will not be searched. + /// + /// + /// 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. For fine-grained control over ambiguity + /// resolution, consider using instead and filtering the results for the symbol required. + /// + /// + /// Assemblies can contain multiple modules. Within each assembly, the search is performed based on module's position in the module list of that assembly. When + /// a match is found in one module in an assembly, no further modules within that assembly are searched. + /// + /// Type forwarders are ignored, and not considered part of the assembly where the TypeForwardAttribute is written. /// 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 + /// + /// 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. + /// + /// Empty array if no types match. Otherwise, all types that match the name, current assembly first if present. + /// + /// + /// Assemblies can contain multiple modules. Within each assembly, the search is performed based on module's position in the module list of that assembly. When + /// a match is found in one module in an assembly, no further modules within that assembly are searched. + /// + /// Type forwarders are ignored, and not considered part of the assembly where the TypeForwardAttribute is written. + /// + public ImmutableArray GetTypesByMetadataName(string fullyQualifiedMetadataName) + { + if (!_getTypesCache.TryGetValue(fullyQualifiedMetadataName, out ImmutableArray val)) + { + val = getTypesByMetadataNameImpl(); + var result = _getTypesCache.TryAdd(fullyQualifiedMetadataName, val); + Debug.Assert(result + || (_getTypesCache.TryGetValue(fullyQualifiedMetadataName, out var addedArray) + && Enumerable.SequenceEqual(addedArray, val, ReferenceEqualityComparer.Instance))); + } + + return val; + + ImmutableArray getTypesByMetadataNameImpl() + { + ArrayBuilder? 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 = ObjectType.ContainingAssembly; + + 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.Empty; + + void addIfNotNull(INamedTypeSymbol? toAdd) + { + if (toAdd != null) + { + typesByMetadataName ??= ArrayBuilder.GetInstance(); + typesByMetadataName.Add(toAdd); + } + } + } + } + /// /// Returns a new INamedTypeSymbol with the given element types and /// (optional) element names, locations, and nullable annotations. @@ -1087,7 +1181,6 @@ public INamedTypeSymbol CreateTupleTypeSymbol( return CommonCreateTupleTypeSymbol(elementTypes, elementNames, elementLocations, elementNullableAnnotations); } -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters /// /// Returns a new INamedTypeSymbol with the given element types, names, and locations. diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 02161a15fe460..ad76fcb80cd4d 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -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! edits, System.Func! 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.Emit.EmitDifferenceResult.ChangedTypes.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedMethods.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Emit.SemanticEditKind.Replace = 4 -> Microsoft.CodeAnalysis.Emit.SemanticEditKind diff --git a/src/Compilers/VisualBasic/Test/Symbol/CompilationAPITests.vb b/src/Compilers/VisualBasic/Test/Symbol/CompilationAPITests.vb new file mode 100644 index 0000000000000..b0f3ed506cc9f --- /dev/null +++ b/src/Compilers/VisualBasic/Test/Symbol/CompilationAPITests.vb @@ -0,0 +1,226 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Roslyn.Test.Utilities + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests + + Public Class CompilationAPITests + Inherits BasicTestBase + + + Public Sub GetTypesByMetadataName_NotInSourceNotInReferences() + Dim comp = CreateCompilation("") + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + Assert.Empty(types) + End Sub + + + Public Sub GetTypesByMetadataName_SingleInSourceNotInReferences(useMetadataReferences As Boolean, accessibility As String) + Dim referenceComp = CreateCompilation("") + + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim comp = CreateCompilation(source, {If(useMetadataReferences, referenceComp.ToMetadataReference(), referenceComp.EmitToImageReference())}) + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Single(types) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + End Sub + + + Public Sub GetTypesByMetadataName_MultipleInSourceNotInReferences(useMetadataReferences As Boolean, accessibility As String) + Dim referenceComp = CreateCompilation("") + + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim comp = CreateCompilation(source, {If(useMetadataReferences, referenceComp.ToMetadataReference(), referenceComp.EmitToImageReference())}) + comp.AssertTheseDiagnostics( + +BC30179: class 'C' and class 'C' conflict in namespace 'N'. + <%= accessibility %> Class C(Of T) + ~ +) + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Single(types) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + Assert.Equal(2, types(0).Locations.Length) + End Sub + + + Public Sub GetTypesByMetadataName_SingleInSourceSingleInReferences(useMetadataReference As Boolean, accessibility As String) + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim referenceComp = CreateCompilation(source) + Dim reference As MetadataReference = If(useMetadataReference, referenceComp.ToMetadataReference(), referenceComp.EmitToImageReference()) + Dim comp = CreateCompilation(source, {reference}) + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Equal(2, types.Length) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + Assert.Same(comp.Assembly, types(0).ContainingAssembly) + AssertEx.Equal("N.C(Of T)", types(1).ToTestDisplayString()) + + Dim referenceAssembly = comp.GetAssemblyOrModuleSymbol(reference) + Assert.Same(types(1).ContainingAssembly, referenceAssembly) + End Sub + + + Public Sub GetTypesByMetadataName_NotInSourceSingleInReferences(useMetadataReference As Boolean, accessibility As String) + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim referenceComp = CreateCompilation(source) + Dim reference As MetadataReference = GetReference(useMetadataReference, referenceComp) + Dim comp = CreateCompilation("", {reference}) + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Single(types) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + + Dim referenceAssembly = comp.GetAssemblyOrModuleSymbol(reference) + Assert.Same(types(0).ContainingAssembly, referenceAssembly) + End Sub + + Private Shared Function GetReference(useMetadataReference As Boolean, referenceComp As VisualBasicCompilation) As MetadataReference + Return If(useMetadataReference, referenceComp.ToMetadataReference(), referenceComp.EmitToImageReference()) + End Function + + + Public Sub GetTypesByMetadataName_NotInSourceMultipleInReferences(useMetadataReference As Boolean, accessibility As String) + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim referenceComp1 = CreateCompilation(source) + Dim referenceComp2 = CreateCompilation(source) + Dim reference1 As MetadataReference = GetReference(useMetadataReference, referenceComp1) + Dim reference2 As MetadataReference = GetReference(useMetadataReference, referenceComp2) + Dim comp = CreateCompilation("", {reference1, reference2}) + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Equal(2, types.Length) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + AssertEx.Equal("N.C(Of T)", types(1).ToTestDisplayString()) + + Dim referenceAssembly1 = comp.GetAssemblyOrModuleSymbol(reference1) + Assert.Same(types(0).ContainingAssembly, referenceAssembly1) + + Dim referenceAssembly2 = comp.GetAssemblyOrModuleSymbol(reference2) + Assert.Same(types(1).ContainingAssembly, referenceAssembly2) + + If (useMetadataReference) Then + Else + Assert.False(types(0).IsInSource()) + Assert.False(types(1).IsInSource()) + End If + End Sub + + + Public Sub GetTypesByMetadataName_SingleInSourceMultipleInReferences(useMetadataReference As Boolean, accessibility As String) + Dim source = +$"Namespace N + {accessibility} Class C(Of T) + End Class +End Namespace" + + Dim referenceComp1 = CreateCompilation(source) + Dim referenceComp2 = CreateCompilation(source) + Dim reference1 As MetadataReference = GetReference(useMetadataReference, referenceComp1) + Dim reference2 As MetadataReference = GetReference(useMetadataReference, referenceComp2) + Dim comp = CreateCompilation(source, {reference1, reference2}) + comp.AssertNoDiagnostics() + + Dim types = comp.GetTypesByMetadataName("N.C`1") + + Assert.Equal(3, types.Length) + AssertEx.Equal("N.C(Of T)", types(0).ToTestDisplayString()) + Assert.Same(comp.Assembly, types(0).ContainingAssembly) + AssertEx.Equal("N.C(Of T)", types(1).ToTestDisplayString()) + AssertEx.Equal("N.C(Of T)", types(2).ToTestDisplayString()) + + Dim referenceAssembly1 = comp.GetAssemblyOrModuleSymbol(reference1) + Assert.Same(types(1).ContainingAssembly, referenceAssembly1) + + Dim referenceAssembly2 = comp.GetAssemblyOrModuleSymbol(reference2) + Assert.Same(types(2).ContainingAssembly, referenceAssembly2) + End Sub + + + Public Sub GetTypesByMetadataName_Ordering() + Dim corlibSource = " +Namespace System + Public Class [Object] + End Class + Public Class [Void] + End Class +End Namespace +Public Class C +End Class +" + + Dim corlib = CreateEmptyCompilation(corlibSource) + Dim corlibReference = corlib.EmitToImageReference() + + Dim otherSource = " +Public Class C +End Class +" + + Dim other = CreateEmptyCompilation(otherSource, {corlibReference}) + Dim otherReference = other.EmitToImageReference() + + Dim currentSource = " +Public Class C +End Class +" + Dim current = CreateEmptyCompilation(currentSource, {otherReference, corlibReference}) + current.AssertNoDiagnostics() + + Dim types = current.GetTypesByMetadataName("C") + + AssertEx.Equal(types.Select(Function(t) t.ToTestDisplayString()), {"C", "C", "C"}) + + Assert.Same(current.Assembly, types(0).ContainingAssembly) + + Dim corlibAssembly = current.GetAssemblyOrModuleSymbol(corlibReference) + Assert.Same(types(1).ContainingAssembly, corlibAssembly) + + Dim otherAssembly = current.GetAssemblyOrModuleSymbol(otherReference) + Assert.Same(types(2).ContainingAssembly, otherAssembly) + End Sub + End Class +End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs index 649d9f7bfa377..0384afee7e1fc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static class CompilationExtensions @@ -33,42 +35,33 @@ internal static class CompilationExtensions /// The symbol to use for code analysis; otherwise, . public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName) { - // Try to get the unique type with this name, ignoring accessibility - var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName); - - // Otherwise, try to get the unique type with this name originally defined in 'compilation' - type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + INamedTypeSymbol? type = null; - // Otherwise, try to get the unique accessible type with this name from a reference - if (type is null) + foreach (var currentType in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName)) { - foreach (var module in compilation.Assembly.Modules) + if (ReferenceEquals(currentType.ContainingAssembly, compilation.Assembly)) { - foreach (var referencedAssembly in module.ReferencedAssemblySymbols) - { - var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName); - if (currentType is null) - continue; - - switch (currentType.GetResultantVisibility()) - { - case Utilities.SymbolVisibility.Public: - case Utilities.SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly): - break; + Debug.Assert(type is null); + return currentType; + } - default: - continue; - } + switch (currentType.GetResultantVisibility()) + { + case Utilities.SymbolVisibility.Public: + case Utilities.SymbolVisibility.Internal when currentType.ContainingAssembly.GivesAccessTo(compilation.Assembly): + break; - if (type is object) - { - // Multiple visible types with the same metadata name are present - return null; - } + default: + continue; + } - type = currentType; - } + if (type is object) + { + // Multiple visible types with the same metadata name are present + return null; } + + type = currentType; } return type;