diff --git a/src/Compilers/CSharp/Portable/Symbols/MergedNamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MergedNamespaceSymbol.cs index 78bef38de8bef..53a81d25fd137 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MergedNamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MergedNamespaceSymbol.cs @@ -178,7 +178,7 @@ private SegmentedHashSet> SlowGetChildNames(IEqualityCompar foreach (var ns in _namespacesToMerge) { - foreach (var child in ns.GetMembersUnordered()) + foreach (var child in ns.GetMembers()) { childNames.Add(child.Name.AsMemory()); } @@ -303,12 +303,9 @@ internal override void GetExtensionMethods(ArrayBuilder methods, s // CreateRange and OfType Linq calls in MergedNamespaceSymbol.GetTypeMembersUnordered causes a full array allocation. internal sealed override void GetExtensionMembers(ArrayBuilder members, string? name, string? alternativeName, int arity, LookupOptions options, ConsList fieldsBeingBound) { - foreach (var member in GetMembersUnordered()) + foreach (var type in GetTypeMembers()) { - if (member is NamedTypeSymbol type) - { - type.GetExtensionMembers(members, name, alternativeName, arity, options, fieldsBeingBound); - } + type.GetExtensionMembers(members, name, alternativeName, arity, options, fieldsBeingBound); } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index 29b05c2966ee2..79ee45e533d5a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -360,7 +360,7 @@ internal void GetExtensionMethods(ArrayBuilder methods, string nam internal void DoGetExtensionMethods(ArrayBuilder methods, string nameOpt, int arity, LookupOptions options) { var members = nameOpt == null - ? this.GetMembersUnordered() + ? this.GetMembers() : this.GetSimpleNonTypeMembers(nameOpt); foreach (var member in members) @@ -414,7 +414,7 @@ internal void GetExtensionMembers(ArrayBuilder members, string? name, st if (!this.IsClassType() || !IsStatic || IsGenericType || !MightContainExtensionMethods) return; - foreach (NamedTypeSymbol nestedType in GetTypeMembersUnordered()) + foreach (NamedTypeSymbol nestedType in GetTypeMembers()) { if (nestedType is not { IsExtension: true, ExtensionParameter: { } extensionParameter } || !IsValidExtensionReceiverParameter(extensionParameter)) @@ -423,7 +423,7 @@ internal void GetExtensionMembers(ArrayBuilder members, string? name, st } var candidates = name is null || alternativeName is not null - ? nestedType.GetMembersUnordered() + ? nestedType.GetMembers() : nestedType.GetMembers(name); foreach (var candidate in candidates) diff --git a/src/Compilers/CSharp/Portable/Symbols/NamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamespaceSymbol.cs index 53260471a9747..9781fcb3f4e84 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamespaceSymbol.cs @@ -361,7 +361,7 @@ internal virtual void GetExtensionMethods(ArrayBuilder methods, st /// Does not perform a full viability check internal virtual void GetExtensionMembers(ArrayBuilder members, string? name, string? alternativeName, int arity, LookupOptions options, ConsList fieldsBeingBound) { - foreach (var type in this.GetTypeMembersUnordered()) + foreach (var type in this.GetTypeMembers()) { type.GetExtensionMembers(members, name, alternativeName, arity, options, fieldsBeingBound); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs index 89f1591701076..276f0f55db892 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionOperatorsTests.cs @@ -4265,14 +4265,8 @@ static void Main() Assert.Null(symbolInfo.Symbol); Assert.Equal(CandidateReason.Ambiguous, symbolInfo.CandidateReason); - -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - AssertEx.Equal("Extensions2.extension(S2).operator -(S2)", symbolInfo.CandidateSymbols[0].ToDisplayString()); - AssertEx.Equal("Extensions1.extension(S2).operator -(S2)", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#else AssertEx.Equal("Extensions1.extension(S2).operator -(S2)", symbolInfo.CandidateSymbols[0].ToDisplayString()); AssertEx.Equal("Extensions2.extension(S2).operator -(S2)", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#endif } [Theory] @@ -7707,18 +7701,9 @@ static void Main() """ + CompilerFeatureRequiredAttribute; var comp = CreateCompilation(src, options: TestOptions.DebugExe); + AssertEx.Equal(["Extensions1", "Extensions2", "C1", "Program"], + comp.GlobalNamespace.GetTypeMembers().Where(t => t.ContainingModule is SourceModuleSymbol).ToTestDisplayStrings()); -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - comp.VerifyEmitDiagnostics( - // (32,13): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions2.extension(C1).operator --()' and 'Extensions1.extension(C1).operator --()' - // _ = --c1; - Diagnostic(ErrorCode.ERR_AmbigCall, "--").WithArguments("Extensions2.extension(C1).operator --()", "Extensions1.extension(C1).operator --()").WithLocation(32, 13), - // (36,17): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions1.extension(C1).operator checked --()' and 'Extensions2.extension(C1).operator --()' - // _ = --c1; - Diagnostic(ErrorCode.ERR_AmbigCall, "--").WithArguments("Extensions1.extension(C1).operator checked --()", "Extensions2.extension(C1).operator --()").WithLocation(36, 17) - ); -#else - // https://github.com/dotnet/roslyn/issues/78968: Understand what is causing DEBUG/RELEASE behavior difference comp.VerifyEmitDiagnostics( // (32,13): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions1.extension(C1).operator --()' and 'Extensions2.extension(C1).operator --()' // _ = --c1; @@ -7727,7 +7712,6 @@ static void Main() // _ = --c1; Diagnostic(ErrorCode.ERR_AmbigCall, "--").WithArguments("Extensions1.extension(C1).operator checked --()", "Extensions2.extension(C1).operator --()").WithLocation(36, 17) ); -#endif var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); @@ -7741,6 +7725,19 @@ static void Main() AssertEx.Equal("Extensions2.extension(C1).operator --()", symbolInfo.CandidateSymbols[1].ToDisplayString()); } + [Fact] + public void GetMembers_01() + { + var src = """ +public class A; +public class B; +"""; + + var comp = CreateCompilation(src); + AssertEx.Equal(["A", "B"], + comp.GlobalNamespace.GetMembers().Where(t => t is SourceNamedTypeSymbol).ToTestDisplayStrings()); + } + [Fact] public void Increment_057_Consumption_CheckedLiftedIsWorse() { @@ -10456,19 +10453,11 @@ static void Main() var comp = CreateCompilation(src, options: TestOptions.DebugExe); -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - comp.VerifyDiagnostics( - // (26,9): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions2.extension(ref S2).operator ++()' and 'Extensions1.extension(ref S2).operator ++()' - // ++s2; - Diagnostic(ErrorCode.ERR_AmbigCall, "++").WithArguments("Extensions2.extension(ref S2).operator ++()", "Extensions1.extension(ref S2).operator ++()").WithLocation(26, 9) - ); -#else comp.VerifyDiagnostics( // (26,9): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions1.extension(ref S2).operator ++()' and 'Extensions2.extension(ref S2).operator ++()' // ++s2; Diagnostic(ErrorCode.ERR_AmbigCall, "++").WithArguments("Extensions1.extension(ref S2).operator ++()", "Extensions2.extension(ref S2).operator ++()").WithLocation(26, 9) ); -#endif var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); @@ -10478,13 +10467,8 @@ static void Main() Assert.Null(symbolInfo.Symbol); Assert.Equal(CandidateReason.OverloadResolutionFailure, symbolInfo.CandidateReason); -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - AssertEx.Equal("Extensions2.extension(ref S2).operator ++()", symbolInfo.CandidateSymbols[0].ToDisplayString()); - AssertEx.Equal("Extensions1.extension(ref S2).operator ++()", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#else AssertEx.Equal("Extensions1.extension(ref S2).operator ++()", symbolInfo.CandidateSymbols[0].ToDisplayString()); AssertEx.Equal("Extensions2.extension(ref S2).operator ++()", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#endif } [Fact] @@ -10536,14 +10520,8 @@ static void Main() Assert.Null(symbolInfo.Symbol); Assert.Equal(CandidateReason.Ambiguous, symbolInfo.CandidateReason); - -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - AssertEx.Equal("Extensions2.extension(S2).operator ++(S2)", symbolInfo.CandidateSymbols[0].ToDisplayString()); - AssertEx.Equal("Extensions1.extension(S2).operator ++(S2)", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#else AssertEx.Equal("Extensions1.extension(S2).operator ++(S2)", symbolInfo.CandidateSymbols[0].ToDisplayString()); AssertEx.Equal("Extensions2.extension(S2).operator ++(S2)", symbolInfo.CandidateSymbols[1].ToDisplayString()); -#endif } [Theory] @@ -23790,17 +23768,6 @@ static void Main() var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute], options: TestOptions.DebugExe); -#if DEBUG // Collection of extension blocks depends on GetTypeMembersUnordered for namespace, which conditionally de-orders types for DEBUG only. - comp.VerifyEmitDiagnostics( - // (35,16): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions2.extension(C1).operator -=(C1)' and 'Extensions1.extension(C1).operator -=(C1)' - // _ = c1 -= c1; - Diagnostic(ErrorCode.ERR_AmbigCall, "-=").WithArguments("Extensions2.extension(C1).operator -=(C1)", "Extensions1.extension(C1).operator -=(C1)").WithLocation(35, 16), - // (39,20): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions1.extension(C1).operator checked -=(C1)' and 'Extensions2.extension(C1).operator -=(C1)' - // _ = c1 -= c1; - Diagnostic(ErrorCode.ERR_AmbigCall, "-=").WithArguments("Extensions1.extension(C1).operator checked -=(C1)", "Extensions2.extension(C1).operator -=(C1)").WithLocation(39, 20) - ); -#else - // https://github.com/dotnet/roslyn/issues/78968: Understand what is causing DEBUG/RELEASE behavior difference comp.VerifyEmitDiagnostics( // (35,16): error CS0121: The call is ambiguous between the following methods or properties: 'Extensions1.extension(C1).operator -=(C1)' and 'Extensions2.extension(C1).operator -=(C1)' // _ = c1 -= c1; @@ -23809,7 +23776,6 @@ static void Main() // _ = c1 -= c1; Diagnostic(ErrorCode.ERR_AmbigCall, "-=").WithArguments("Extensions1.extension(C1).operator checked -=(C1)", "Extensions2.extension(C1).operator -=(C1)").WithLocation(39, 20) ); -#endif var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 5a844e37dba52..85fedfea64c5b 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -34968,5 +34968,182 @@ public static void Extension(params string[] values) { } Diagnostic(ErrorCode.WRN_NullReferenceArgument, "str").WithArguments("values", "void extension(string).Extension(params string[] values)").WithLocation(7, 18) ); } + + [Fact] + public void Ordering_01() + { + var src = """ +int.M(); + +public static class E1 +{ + extension(int) + { + public static void M() { } + } +} + +public static class E2 +{ + extension(int) + { + public static void M() { } + } +} +"""; + CreateCompilation(src).VerifyEmitDiagnostics( + // (1,5): error CS0121: The call is ambiguous between the following methods or properties: 'E1.extension(int).M()' and 'E2.extension(int).M()' + // int.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("E1.extension(int).M()", "E2.extension(int).M()").WithLocation(1, 5)); + } + + [Fact] + public void Ordering_02() + { + var src = """ +int.M(null); + +public class C1 { } +public class C2 { } + +public static class E +{ + extension(int) + { + public static void M(C1 x) { } + } + + extension(int) + { + public static void M(C2 x) { } + } +} +"""; + CreateCompilation(src).VerifyEmitDiagnostics( + // (1,5): error CS0121: The call is ambiguous between the following methods or properties: 'E.extension(int).M(C1)' and 'E.extension(int).M(C2)' + // int.M(null); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("E.extension(int).M(C1)", "E.extension(int).M(C2)").WithLocation(1, 5)); + } + + [Fact] + public void Ordering_03() + { + var src = """ +using N1; +using N2; + +int.M(); + +namespace N1 +{ + public static class E1 + { + extension(int) + { + public static void M() { } + } + } +} + +namespace N2 +{ + public static class E2 + { + extension(int) + { + public static void M() { } + } + } +} +"""; + CreateCompilation(src).VerifyEmitDiagnostics( + // (4,5): error CS0121: The call is ambiguous between the following methods or properties: 'N1.E1.extension(int).M()' and 'N2.E2.extension(int).M()' + // int.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("N1.E1.extension(int).M()", "N2.E2.extension(int).M()").WithLocation(4, 5)); + } + + [Fact] + public void Ordering_04() + { + var src = """ +using N1; + +int.M(); + +namespace N1 +{ + public static class E1 + { + extension(int) + { + public static void M() { } + } + } + + public static class E2 + { + extension(int) + { + public static void M() { } + } + } +} +"""; + CreateCompilation(src).VerifyEmitDiagnostics( + // (3,5): error CS0121: The call is ambiguous between the following methods or properties: 'N1.E1.extension(int).M()' and 'N1.E2.extension(int).M()' + // int.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("N1.E1.extension(int).M()", "N1.E2.extension(int).M()").WithLocation(3, 5)); + } + + [Fact] + public void Ordering_05() + { + var src = """ +// here + +public static class E +{ + public static void M(this object o) => throw null; + public static void M2(this object o) => throw null; +} +"""; + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var o = ((Compilation)comp).GetSpecialType(SpecialType.System_Object); + Assert.Equal(["void System.Object.M()", "void System.Object.M2()"], + model.LookupSymbols(position: 0, o, name: null, includeReducedExtensionMethods: true) + .Where(m => m is IMethodSymbol { IsExtensionMethod: true }).ToTestDisplayStrings()); + } + + [Fact] + public void Ordering_06() + { + var src = """ +// here + +public static class E +{ + extension(object o) + { + public void M() => throw null; + public void M2() => throw null; + } +} +"""; + + var comp = CreateCompilation(src); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var o = ((Compilation)comp).GetSpecialType(SpecialType.System_Object); + Assert.Equal(["void E.$C43E2675C7BBF9284AF22FB8A9BF0280.M()", "void E.$C43E2675C7BBF9284AF22FB8A9BF0280.M2()"], + model.LookupSymbols(position: 0, o, name: null, includeReducedExtensionMethods: true) + .Where(m => m is IMethodSymbol { ContainingType.IsExtension: true }).ToTestDisplayStrings()); + } } diff --git a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs index 185e75effe3b5..4e66e7ad8dd39 100644 --- a/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs +++ b/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs @@ -679,6 +679,7 @@ public static ImmutableArray NullToEmpty(this ImmutableArray? array) // In DEBUG, swap the first and last elements of a read-only array, yielding a new read only array. // This helps to avoid depending on accidentally sorted arrays. + // Use more aggressive reordering with Array.Reverse(copy) for spot-checking internal static ImmutableArray ConditionallyDeOrder(this ImmutableArray array) { #if DEBUG @@ -689,7 +690,6 @@ internal static ImmutableArray ConditionallyDeOrder(this ImmutableArray var temp = copy[0]; copy[0] = copy[last]; copy[last] = temp; - return ImmutableCollectionsMarshal.AsImmutableArray(copy); } #endif