-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Module initializers deterministic ordering #43964
Changes from 2 commits
4c804d2
d78f276
0d9bdd9
488e63d
2c82cf8
3892e4b
0078cf2
6d8582a
4dd65de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; | ||
using Microsoft.CodeAnalysis.CSharp.Test.Utilities; | ||
using Microsoft.CodeAnalysis.Test.Utilities; | ||
using Roslyn.Test.Utilities; | ||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols | ||
|
@@ -137,5 +138,194 @@ namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : S | |
C.M | ||
Program.Main"); | ||
} | ||
|
||
[Fact] | ||
public void MultipleInitializers_SingleFile() | ||
{ | ||
string source = @" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
class C1 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M1() => Console.Write(1); | ||
|
||
internal class C2 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M2() => Console.Write(2); | ||
} | ||
|
||
[ModuleInitializer] | ||
internal static void M3() => Console.Write(3); | ||
} | ||
|
||
class Program | ||
{ | ||
static void Main() => Console.Write(4); | ||
} | ||
|
||
namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } | ||
"; | ||
|
||
CompileAndVerify( | ||
source, | ||
parseOptions: s_parseOptions, | ||
options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), | ||
symbolValidator: module => | ||
{ | ||
var rootModuleType = (TypeSymbol)module.GlobalNamespace.GetMember("<Module>"); | ||
var staticConstructor = (PEMethodSymbol)rootModuleType.GetMember(".cctor"); | ||
|
||
Assert.NotNull(staticConstructor); | ||
Assert.Equal(MethodKind.StaticConstructor, staticConstructor.MethodKind); | ||
|
||
var expectedFlags = | ||
MethodAttributes.Private | ||
| MethodAttributes.Static | ||
| MethodAttributes.SpecialName | ||
| MethodAttributes.RTSpecialName | ||
| MethodAttributes.HideBySig; | ||
|
||
Assert.Equal(expectedFlags, staticConstructor.Flags); | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be repeated? I'm not repeating it in any of the new tests in the diagnostics PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (This includes the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems fine to extract the validator method and reuse it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Are you referring to assert for flags? I think it is fine to test that once, but it is your call In reply to: 420305655 [](ancestors = 420305655) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not just the flags but the symbol validation itself, since verification is being run. Though I see now that it only runs when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There is no conditional logic in how we emit metadata for the initializer. I would say testing symbol properties once is sufficient, but, if you'd like, feel free to test in more scenarios. In reply to: 420323727 [](ancestors = 420323727) |
||
expectedOutput: "1234"); | ||
} | ||
|
||
[Fact] | ||
public void MultipleInitializers_DifferentContainingTypeKinds() | ||
{ | ||
string source = @" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
class C1 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M1() => Console.Write(1); | ||
} | ||
|
||
struct S1 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M2() => Console.Write(2); | ||
} | ||
|
||
interface I1 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M3() => Console.Write(3); | ||
} | ||
|
||
class Program | ||
{ | ||
static void Main() => Console.Write(4); | ||
} | ||
|
||
namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } | ||
"; | ||
|
||
CompileAndVerify( | ||
source, | ||
parseOptions: s_parseOptions, | ||
targetFramework: TargetFramework.NetStandardLatest, | ||
options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe these WithMetadataImportOptions lines serve a purpose unless a |
||
symbolValidator: module => | ||
{ | ||
var rootModuleType = (TypeSymbol)module.GlobalNamespace.GetMember("<Module>"); | ||
var staticConstructor = (PEMethodSymbol)rootModuleType.GetMember(".cctor"); | ||
|
||
Assert.NotNull(staticConstructor); | ||
Assert.Equal(MethodKind.StaticConstructor, staticConstructor.MethodKind); | ||
|
||
var expectedFlags = | ||
MethodAttributes.Private | ||
| MethodAttributes.Static | ||
| MethodAttributes.SpecialName | ||
| MethodAttributes.RTSpecialName | ||
| MethodAttributes.HideBySig; | ||
|
||
Assert.Equal(expectedFlags, staticConstructor.Flags); | ||
}, | ||
expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : "1234"); | ||
} | ||
|
||
[Fact] | ||
public void MultipleInitializers_MultipleFiles() | ||
{ | ||
string source1 = @" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
class C1 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M1() => Console.Write(1); | ||
[ModuleInitializer] | ||
internal static void M2() => Console.Write(2); | ||
} | ||
|
||
namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } | ||
"; | ||
string source2 = @" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
class C2 | ||
{ | ||
internal class C3 | ||
{ | ||
[ModuleInitializer] | ||
internal static void M3() => Console.Write(3); | ||
} | ||
|
||
[ModuleInitializer] | ||
internal static void M4() => Console.Write(4); | ||
} | ||
|
||
class Program | ||
{ | ||
static void Main() => Console.Write(6); | ||
} | ||
"; | ||
|
||
string source3 = @" | ||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
class C4 | ||
{ | ||
// shouldn't be called | ||
internal static void M() => Console.Write(0); | ||
|
||
[ModuleInitializer] | ||
internal static void M5() => Console.Write(5); | ||
} | ||
"; | ||
|
||
CompileAndVerify( | ||
new[] { source1, source2, source3 }, | ||
parseOptions: s_parseOptions, | ||
options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), | ||
symbolValidator: module => | ||
{ | ||
var rootModuleType = (TypeSymbol)module.GlobalNamespace.GetMember("<Module>"); | ||
var staticConstructor = (PEMethodSymbol)rootModuleType.GetMember(".cctor"); | ||
|
||
Assert.NotNull(staticConstructor); | ||
Assert.Equal(MethodKind.StaticConstructor, staticConstructor.MethodKind); | ||
|
||
var expectedFlags = | ||
MethodAttributes.Private | ||
| MethodAttributes.Static | ||
| MethodAttributes.SpecialName | ||
| MethodAttributes.RTSpecialName | ||
| MethodAttributes.HideBySig; | ||
|
||
Assert.Equal(expectedFlags, staticConstructor.Flags); | ||
}, | ||
expectedOutput: "123456"); | ||
} | ||
} | ||
} |
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.
I am not sure why did we end up with Symbol after OrderBy. It looks like the start with a collection of MethodSymbols,
LexicalOrderSymbolComparer
implementsIComparer<Symbol>
, which is variant and for our case is equivalent`IComparer<MethodSymbol>
. I am not suggesting to replace explicit type withvar
, in fact I prefer explicit types. I am just want to make sure that the right API is used. WhatOrderBy
are we calling here? #ClosedThere 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.
I found it a little surprising as well but didn't look closely. Now I see this overload actually comes from Roslyn. We call this method:
roslyn/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs
Line 345 in f8009ac
Maybe we should change this to two type parameters which are constrained to have an inheritance relationship so we support an
IComparer<in T>
as expected.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.
I am seeing some surprising behavior when I change this code to either:
keySelector
:.OrderBy(m => m, LexicalOrderSymbolComparer.Instance)
.OrderBy<MethodSymbol>(LexicalOrderSymbolComparer.Instance)
Both cases cause several tests to crash:
Is there something about the type arguments or conversions in use that is making linq decide to perform the sort in a different way?
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.
It looks like we need to properly implement the API that throws, it is our code that throws. Linq might attempt to apply some optimizations and the types involved might affect that and affect what code path is taken.
In reply to: 420311709 [](ancestors = 420311709)
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.
There is only one type parameter and the inference result is probably expected, we infer from two interfaces with different variance. It isn't quite obvious what direction should be taken.
In reply to: 420304803 [](ancestors = 420304803)