diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index a4af9691431ff..a5fbf6b653c21 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -2811,7 +2811,10 @@ internal override bool CompileMethods( filterOpt: filterOpt, cancellationToken: cancellationToken); - GenerateModuleInitializer(moduleBeingBuilt, methodBodyDiagnosticBag); + if (!hasDeclarationErrors && !CommonCompiler.HasUnsuppressableErrors(methodBodyDiagnosticBag)) + { + GenerateModuleInitializer(moduleBeingBuilt, methodBodyDiagnosticBag); + } bool hasMethodBodyError = !FilterAndAppendAndFreeDiagnostics(diagnostics, ref methodBodyDiagnosticBag); @@ -2832,8 +2835,7 @@ private void GenerateModuleInitializer(PEModuleBuilder moduleBeingBuilt, Diagnos { var ilBuilder = new ILBuilder(moduleBeingBuilt, new LocalSlotManager(slotAllocator: null), OptimizationLevel.Release, areLocalsZeroed: false); - // PROTOTYPE(module-initializers): require deterministic order - foreach (var method in _moduleInitializerMethods) + foreach (MethodSymbol method in _moduleInitializerMethods.OrderBy(LexicalOrderSymbolComparer.Instance)) { ilBuilder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); diff --git a/src/Compilers/CSharp/Portable/Compilation/LexicalOrderSymbolComparer.cs b/src/Compilers/CSharp/Portable/Compilation/LexicalOrderSymbolComparer.cs index 72e22335d964d..4039b9ad4c519 100644 --- a/src/Compilers/CSharp/Portable/Compilation/LexicalOrderSymbolComparer.cs +++ b/src/Compilers/CSharp/Portable/Compilation/LexicalOrderSymbolComparer.cs @@ -8,10 +8,11 @@ namespace Microsoft.CodeAnalysis.CSharp { - /// This is an implementation of a special symbol comparer, which is supposed to be used for - /// sorting original definition symbols (explicitly or explicitly declared in source within the same - /// container) in lexical order of their declarations. It will not work on anything that uses non-source locations. - /// + /// + /// This is an implementation of a special symbol comparer, which is supposed to be used for sorting + /// original definition symbols (explicitly or implicitly declared in source within the same compilation) + /// in lexical order of their declarations. It will not work on anything that uses non-source locations. + /// internal class LexicalOrderSymbolComparer : IComparer { public static readonly LexicalOrderSymbolComparer Instance = new LexicalOrderSymbolComparer(); @@ -30,6 +31,7 @@ public int Compare(Symbol x, Symbol y) var xSortKey = x.GetLexicalSortKey(); var ySortKey = y.GetLexicalSortKey(); + Debug.Assert((object)x.DeclaringCompilation == y.DeclaringCompilation); comparison = LexicalSortKey.Compare(xSortKey, ySortKey); if (comparison != 0) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ModuleInitializersTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ModuleInitializersTests.cs index 558aab4d66028..07354d26d4399 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ModuleInitializersTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ModuleInitializersTests.cs @@ -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,388 @@ 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, + 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, + expectedOutput: !ExecutionConditionUtil.IsMonoOrCoreClr ? null : "1234", + verify: !ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Skipped : Verification.Passes); + } + + [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, + expectedOutput: "123456"); + } + + [Fact] + public void StaticConstructor_Ordering() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C1 +{ + [ModuleInitializer] + internal static void Init() => Console.Write(1); +} + +class C2 +{ + static C2() => Console.Write(2); + + static void Main() + { + Console.Write(3); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify(text, parseOptions: s_parseOptions, expectedOutput: "123"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void StaticConstructor_Ordering_SameType() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C +{ + static C() => Console.Write(1); + + [ModuleInitializer] + internal static void Init() => Console.Write(2); + + static void Main() + { + Console.Write(3); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify(text, parseOptions: s_parseOptions, expectedOutput: "123"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void StaticConstructor_DefaultInitializer_SameType() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C +{ + internal static string s1 = null; + + [ModuleInitializer] + internal static void Init() + { + s1 = ""hello""; + } + + static void Main() + { + Console.Write(s1); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify( + text, + parseOptions: s_parseOptions, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + expectedOutput: "hello", + symbolValidator: validator); + verifier.VerifyDiagnostics(); + + void validator(ModuleSymbol module) + { + var cType = module.ContainingAssembly.GetTypeByMetadataName("C"); + // static constructor should be optimized out + Assert.Null(cType.GetMember(".cctor")); + + var moduleType = module.ContainingAssembly.GetTypeByMetadataName(""); + Assert.NotNull(moduleType.GetMember(".cctor")); + } + } + + [Fact] + public void StaticConstructor_EffectingInitializer_SameType() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C +{ + internal static int i = InitField(); + + internal static int InitField() + { + Console.Write(1); + return -1; + } + + [ModuleInitializer] + internal static void Init() + { + i = 2; + } + + static void Main() + { + Console.Write(i); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify( + text, + parseOptions: s_parseOptions, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + expectedOutput: "12", + symbolValidator: validator); + verifier.VerifyDiagnostics(); + + void validator(ModuleSymbol module) + { + var cType = module.ContainingAssembly.GetTypeByMetadataName("C"); + Assert.NotNull(cType.GetMember(".cctor")); + + var moduleType = module.ContainingAssembly.GetTypeByMetadataName(""); + Assert.NotNull(moduleType.GetMember(".cctor")); + } + } + + [Fact] + public void StaticConstructor_DefaultInitializer_OtherType() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C1 +{ + [ModuleInitializer] + internal static void Init() + { + C2.s1 = ""hello""; + } +} + +class C2 +{ + internal static string s1 = null; + + static void Main() + { + Console.Write(s1); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify( + text, + parseOptions: s_parseOptions, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + expectedOutput: "hello", + symbolValidator: validator); + verifier.VerifyDiagnostics(); + + void validator(ModuleSymbol module) + { + var c2Type = module.ContainingAssembly.GetTypeByMetadataName("C2"); + // static constructor should be optimized out + Assert.Null(c2Type.GetMember(".cctor")); + + var moduleType = module.ContainingAssembly.GetTypeByMetadataName(""); + Assert.NotNull(moduleType.GetMember(".cctor")); + } + } + + [Fact] + public void StaticConstructor_EffectingInitializer_OtherType() + { + const string text = @" +using System; +using System.Runtime.CompilerServices; + +class C1 +{ + [ModuleInitializer] + internal static void Init() + { + C2.i = 2; + } +} + +class C2 +{ + internal static int i = InitField(); + + static int InitField() + { + Console.Write(1); + return -1; + } + + static void Main() + { + Console.Write(i); + } +} + +namespace System.Runtime.CompilerServices { class ModuleInitializerAttribute : System.Attribute { } } +"; + var verifier = CompileAndVerify( + text, + parseOptions: s_parseOptions, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + expectedOutput: "12", + symbolValidator: validator); + verifier.VerifyDiagnostics(); + + void validator(ModuleSymbol module) + { + var c2Type = module.ContainingAssembly.GetTypeByMetadataName("C2"); + Assert.NotNull(c2Type.GetMember(".cctor")); + + var moduleType = module.ContainingAssembly.GetTypeByMetadataName(""); + Assert.NotNull(moduleType.GetMember(".cctor")); + } + } } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentSet.cs b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentSet.cs index bf23405bf7252..b8a194ba97a43 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ConcurrentSet.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ConcurrentSet.cs @@ -4,7 +4,6 @@ #nullable enable -using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -178,7 +177,13 @@ void ICollection.Add(T item) public void CopyTo(T[] array, int arrayIndex) { - throw new NotImplementedException(); + // PERF: Do not use dictionary.Keys here because that creates a snapshot + // of the collection resulting in a List allocation. + // Instead, enumerate the set and copy over the elements. + foreach (var element in this) + { + array[arrayIndex++] = element; + } } } }