Skip to content

Commit

Permalink
Merge pull request #16303 from OmarTawfik/fix-11990-deterministic-for…
Browse files Browse the repository at this point in the history
…warded-types-emit

Emit forwarded types in a deterministic order
  • Loading branch information
OmarTawfik authored Jan 13, 2017
2 parents 20c809b + 4bc408f commit d77c3e2
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,10 @@ private static void GetForwardedTypes(
// (type, index of the parent exported type in builder, or -1 if the type is a top-level type)
var stack = ArrayBuilder<(NamedTypeSymbol type, int parentIndex)>.GetInstance();

foreach (NamedTypeSymbol forwardedType in wellKnownAttributeData.ForwardedTypes)
// Hashset enumeration is not guaranteed to be deterministic. Emitting in the order of fully qualified names.
var orderedForwardedTypes = wellKnownAttributeData.ForwardedTypes.OrderBy(t => t.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat));

foreach (NamedTypeSymbol forwardedType in orderedForwardedTypes)
{
NamedTypeSymbol originalDefinition = forwardedType.OriginalDefinition;
Debug.Assert((object)originalDefinition.ContainingType == null, "How did a nested type get forwarded?");
Expand Down
71 changes: 71 additions & 0 deletions src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,77 @@ public void TestWriteOnlyStream()
compilation.Emit(output);
}

[Fact, WorkItem(11990, "https://github.com/dotnet/roslyn/issues/11990")]
public void ForwardedTypesAreEmittedInADeterministicOrder()
{
var forwardedToCode = @"
namespace Namespace2 {
public class GenericType1<T> {}
public class GenericType3<T> {}
public class GenericType2<T> {}
}
namespace Namespace1 {
public class Type3 {}
public class Type2 {}
public class Type1 {}
}
namespace Namespace4 {
namespace Embedded {
public class Type2 {}
public class Type1 {}
}
}
namespace Namespace3 {
public class GenericType {}
public class GenericType<T> {}
public class GenericType<T, U> {}
}
";
var forwardedToCompilation = CreateCompilation(forwardedToCode);
var forwardedToReference = new CSharpCompilationReference(forwardedToCompilation);

var forwardingCode = @"
using System.Runtime.CompilerServices;
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType1<int>))]
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType3<int>))]
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType2<int>))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type3))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type2))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type1))]
[assembly: TypeForwardedTo(typeof(Namespace4.Embedded.Type2))]
[assembly: TypeForwardedTo(typeof(Namespace4.Embedded.Type1))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType<int>))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType<int, int>))]
";

var forwardingCompilation = CreateCompilationWithMscorlib(forwardingCode, new MetadataReference[] { forwardedToReference });

var sortedFullNames = new string[]
{
"Namespace1.Type1",
"Namespace1.Type2",
"Namespace1.Type3",
"Namespace2.GenericType1`1",
"Namespace2.GenericType2`1",
"Namespace2.GenericType3`1",
"Namespace3.GenericType",
"Namespace3.GenericType`1",
"Namespace3.GenericType`2",
"Namespace4.Embedded.Type1",
"Namespace4.Embedded.Type2"
};

using (var stream = forwardingCompilation.EmitToStream())
{
using (var block = ModuleMetadata.CreateFromStream(stream))
{
var metadataFullNames = MetadataValidation.GetExportedTypesFullNames(block.MetadataReader);
Assert.Equal(sortedFullNames, metadataFullNames);
}
}
}

[Fact]
public void TestPartialPartsDeterministic()
{
Expand Down
5 changes: 4 additions & 1 deletion src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit
' (type, index of the parent exported type in builder, or -1 if the type is a top-level type)
Dim stack = ArrayBuilder(Of (type As NamedTypeSymbol, parentIndex As Integer)).GetInstance()

For Each forwardedType As NamedTypeSymbol In wellKnownAttributeData.ForwardedTypes
' Hashset enumeration is not guaranteed to be deterministic. Emitting in the order of fully qualified names.
Dim orderedForwardedTypes = wellKnownAttributeData.ForwardedTypes.OrderBy(Function(t) t.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.QualifiedNameArityFormat))

For Each forwardedType As NamedTypeSymbol In orderedForwardedTypes
Dim originalDefinition As NamedTypeSymbol = forwardedType.OriginalDefinition
Debug.Assert(originalDefinition.ContainingType Is Nothing, "How did a nested type get forwarded?")

Expand Down
88 changes: 88 additions & 0 deletions src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,94 @@ Partial.c = 3
Next
End Sub

<Fact>
<WorkItem(11990, "https://github.com/dotnet/roslyn/issues/11990")>
Public Sub ForwardedTypesAreEmittedInADeterministicOrder()
' VBC doesn't recognize type forwards in VB source code. Because of that,
' this test generates types as a C# library (1), then generates a C# netmodule (2) that
' contains the type forwards that forwards to reference (1), then generates an empty VB
' library (3) that contains (2) and references (1).

Dim forwardedToCode = "
namespace Namespace2 {
public class GenericType1<T> {}
public class GenericType3<T> {}
public class GenericType2<T> {}
}
namespace Namespace1 {
public class Type3 {}
public class Type2 {}
public class Type1 {}
}
namespace Namespace4 {
namespace Embedded {
public class Type2 {}
public class Type1 {}
}
}
namespace Namespace3 {
public class GenericType {}
public class GenericType<T> {}
public class GenericType<T, U> {}
}
"
Dim forwardedToCompilation = CreateCSharpCompilation(
forwardedToCode,
compilationOptions:=New CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))

Dim forwardedToReference = forwardedToCompilation.EmitToImageReference()

Dim forwardingCode = "
using System.Runtime.CompilerServices;
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType1<int>))]
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType3<int>))]
[assembly: TypeForwardedTo(typeof(Namespace2.GenericType2<int>))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type3))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type2))]
[assembly: TypeForwardedTo(typeof(Namespace1.Type1))]
[assembly: TypeForwardedTo(typeof(Namespace4.Embedded.Type2))]
[assembly: TypeForwardedTo(typeof(Namespace4.Embedded.Type1))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType<int>))]
[assembly: TypeForwardedTo(typeof(Namespace3.GenericType<int, int>))]
"
Dim forwardingNetModule = CreateCSharpCompilation(
"ForwardingAssembly",
forwardingCode,
compilationOptions:=New CSharp.CSharpCompilationOptions(OutputKind.NetModule),
referencedAssemblies:={MscorlibRef, SystemRef, forwardedToReference})

Dim forwardingNetModuleReference = forwardingNetModule.EmitToImageReference()

Dim forwardingCompilation = CreateCompilationWithMscorlib(
assemblyName:="ForwardingAssembly",
source:=String.Empty,
options:=New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
references:={forwardingNetModuleReference, forwardedToReference})

Dim sortedFullNames =
{
"Namespace1.Type1",
"Namespace1.Type2",
"Namespace1.Type3",
"Namespace2.GenericType1`1",
"Namespace2.GenericType2`1",
"Namespace2.GenericType3`1",
"Namespace3.GenericType",
"Namespace3.GenericType`1",
"Namespace3.GenericType`2",
"Namespace4.Embedded.Type1",
"Namespace4.Embedded.Type2"
}

Using stream = forwardingCompilation.EmitToStream()
Using block = ModuleMetadata.CreateFromStream(stream)
Dim metadataFullNames = MetadataValidation.GetExportedTypesFullNames(block.MetadataReader)
Assert.Equal(sortedFullNames, metadataFullNames)
End Using
End Using
End Sub

<Fact>
Public Sub TestInterfacesPartialClassDeterminism()
' When we have parts of a class in different trees,
Expand Down
12 changes: 12 additions & 0 deletions src/Test/Utilities/Portable/Metadata/MetadataValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,17 @@ internal static IEnumerable<string> GetFullTypeNames(MetadataReader metadataRead
yield return (ns.Length == 0) ? name : (ns + "." + name);
}
}

internal static IEnumerable<string> GetExportedTypesFullNames(MetadataReader metadataReader)
{
foreach (var typeDefHandle in metadataReader.ExportedTypes)
{
var typeDef = metadataReader.GetExportedType(typeDefHandle);
var ns = metadataReader.GetString(typeDef.Namespace);
var name = metadataReader.GetString(typeDef.Name);

yield return (ns.Length == 0) ? name : (ns + "." + name);
}
}
}
}

0 comments on commit d77c3e2

Please sign in to comment.