-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Emit forwarded types in a deterministic order #16303
Emit forwarded types in a deterministic order #16303
Conversation
@dotnet/roslyn-compiler |
Please add a test if possible. |
@@ -557,7 +557,10 @@ private void ReportExportedTypeNameCollisions(ImmutableArray<Cci.ExportedType> e | |||
// (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 guarenteed to be deterministic. Emitting in the order of fully qualified names. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: guaranteed
@@ -557,7 +557,10 @@ private void ReportExportedTypeNameCollisions(ImmutableArray<Cci.ExportedType> e | |||
// (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 guarenteed to be deterministic. Emitting in the order of fully qualified names. | |||
var orderedForwardedTypes = wellKnownAttributeData.ForwardedTypes.OrderBy(t => t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) [](start = 95, length = 59)
t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) [](start = 95, length = 59)
We probably should get display string from OriginalDefinition and use metadata format, for example "...Dictionary`2" vs. "...Dictionary<K, V>". #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlekseyTs you mean SymbolDisplayFormat.QualifiedNameArityFormat
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean SymbolDisplayFormat.QualifiedNameArityFormat?
Yes. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this unnecessarily building display strings? Don't we have any other symbol comparer?
Similar issue should be addressed for VB. There should be test that verifies the order. #Closed |
Emit forwarded types in a deterministic order
@cston @AlekseyTs comments addressed. |
@@ -184,6 +185,54 @@ public void TestWriteOnlyStream() | |||
compilation.Emit(output); | |||
} | |||
|
|||
[Fact, WorkItem(11990, "https://github.com/dotnet/roslyn/issues/11990")] | |||
public void ForwardedTypesAreEmmittedInADeterministicOrder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: Emitted
for (var i = 1; i <= generatedTypes; i++) | ||
{ | ||
forwardingCode.AppendLine($"[assembly: TypeForwardedTo(typeof(ForwardedToNamespace.T{i}))]"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forwardingCode
can be created once, outside of local function.
Assert.Equal(generatedTypes, baseline.Length); | ||
|
||
var reference = compileAndGetExportedTypes().ToArray(); | ||
Assert.Equal(baseline, reference); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert.Equal(baseline, reference); [](start = 12, length = 34)
It feels like the test would be better if TypeForwardedTo attributes would not be in sorted order in source and we simply verify that ExportedTypes are sorted in the way we expect. #Closed
forwardedToCode.AppendLine("namespace ForwardedToNamespace {"); | ||
for (var i = 1; i <= generatedTypes; i++) | ||
{ | ||
forwardedToCode.AppendLine($"public class T{i} {{}}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
T{i} [](start = 58, length = 4)
Consider incorporating generic types into the test. #Closed
const int generatedTypes = 100; | ||
|
||
var forwardedToCode = new StringBuilder(); | ||
forwardedToCode.AppendLine("namespace ForwardedToNamespace {"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ForwardedToNamespace [](start = 50, length = 20)
It would be good to add variations in namespace name into the mix. #Closed
assemblyName:="ForwardingAssembly", | ||
source:=String.Empty, | ||
options:=New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary), | ||
references:={forwardingNetModule.EmitToImageReference(), forwardedToCompilation.EmitToImageReference()}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forwardedToCompilation.EmitToImageReference() [](start = 77, length = 45)
Doing this twice, consider caching result and reusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert.Equal(generatedTypes, baseline.Length) | ||
|
||
Dim reference = compileAndGetExportedTypes().ToArray() | ||
Assert.Equal(baseline, reference) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert.Equal(baseline, reference) [](start = 12, length = 33)
Same comments as for C# test. #Closed
@@ -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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lowercase for Is Not
.
@AlekseyTs @cston tests updated to include multiple namespaces and generics. Typos fixed. |
@@ -12,6 +12,7 @@ | |||
using Roslyn.Test.Utilities; | |||
using Microsoft.CodeAnalysis.Emit; | |||
using System.Diagnostics; | |||
using System.Text; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this namespace used? Same question for VB.
var namespaceName = block.MetadataReader.GetString(type.Namespace); | ||
|
||
metadataFullNames.Add($"{namespaceName}.{typeName}"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor point: Consider extracting a helper method shared with VB. See MetadataValidation.GetFullTypeNames()
for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cston done.
LGTM |
public class Type1 {} | ||
} | ||
namespace Namespace3 { | ||
public class GenericType2<T, U> {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public class GenericType2<T, U> {} [](start = 4, length = 34)
public class GenericType2<T, U> {} [](start = 4, length = 34)
It would be good to have types that differ only by generic arity. #Closed
@AlekseyTs do I've a sign off? |
LGTM |
@MattGertz @natidea can I get approval for RTM? |
@OmarTawfik Template is missing. |
@MattGertz done. |
RTW. |
@jaredpar I see the milestone for this bug changed. Should I rebase to |
@OmarTawfik no this needs to target master / RTW |
Fixes #11990
Customer and Scenario Info
Who is impacted by this bug: customers trying to use the deterministic output feature of compilers
What is the customer scenario and impact of the bug: two identical builds might not produce the same bits.
What is the workaround: none.
How was the bug found: Manual code inspection.
If this fix is for a regression - what had regressed, when was the regression introduced, and why was the regression originally missed: No.
What is a Customer Ready description of change should we need to document this: builds are now one step closer to be 100% deterministic (see #372).
Bug Fix Details
Overview of the fix: Enumerating a hashset in an ordered way when emitting forwarded types in an assembly.
Which VS products, SKUs, or components need the fix: all SKUs shipping Roslyn compilers.
What branch does the fix go into: lab/ml.
What feature areas are impacted by the fix: Roslyn compilers.
What is the risk of the code change: none.
Does this change have impact on anything else (e.g. setup, perf, stress, ux, visual freeze, go live, breaking change, samples, etc): no.
How is this fix rolling forward in the next major release / sprintly train (e.g. Dev15): RTM.
Validation
What testing/validation was done on the fix, and what were the results: manual testing an unit tests were included in the PR.
Which engineering partner teams have signed off on the fix: @dotnet/roslyn-compiler
Who code reviewed the fix: @AlekseyTs @cston
Additional information during Divisional Escrow Mode
Which Engineering DIRECTOR is accountable for the code change and quality: @MattGertz
What are you doing to ensure that this bug does not happen again: not a regression.
Which part of the D15 Escrow Bar does this fall under?: RTM ASK MODE