From 002ab1cbfb78d1d3ccaef4ab790d7221bc8ffb3e Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:42:26 +0300 Subject: [PATCH 1/3] Fix suggestions for constant patterns --- .../SymbolCompletionProviderTests.cs | 116 ++++++++ .../CSharpRecommendationServiceRunner.cs | 279 ++++++++++-------- 2 files changed, 269 insertions(+), 126 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index 95b2a1aeba7b6..c6a4e19489cd1 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -12868,6 +12868,122 @@ public static T Method(this T s) await VerifyItemExistsAsync(markup, "Method", displayTextSuffix: "<>"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task SwitchExpressionEnumColorColor_01() + { + //lang=c#-test + const string source = """ + public sealed record OrderModel(int Id, Status Status) + { + public string StatusDisplay + { + get + { + return Status switch + { + Status.$$ + }; + } + } + } + + public enum Status + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task SwitchExpressionEnumColorColor_02() + { + //lang=c#-test + const string source = """ + public sealed record OrderModel(int Id, Status Status) + { + public string StatusDisplay + { + get + { + return this switch + { + { Status: Status.$$ } + }; + } + } + } + + public enum Status + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task ConstantPatternExpressionEnumColorColor_01() + { + //lang=c#-test + const string source = """ + public sealed record OrderModel(int Id, Status Status) + { + public string StatusDisplay + { + get + { + if (Status is Status.$$) + ; + } + } + } + + public enum Status + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task ConstantPatternExpressionEnumColorColor_02() + { + //lang=c#-test + const string source = """ + public sealed record OrderModel(int Id, Status Status) + { + public string StatusDisplay + { + get + { + if (Status is (Status.$$) + ; + } + } + } + + public enum Status + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + #region Collection expressions [Fact] diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index e50eb4f4a3c07..5b2b8bcd0088e 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Linq.Expressions; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -140,10 +142,12 @@ private RecommendedSymbols GetSymbolsOffOfContainer() // We are building a pattern expression, and thus we can only access either constants, types or namespaces. return node switch { - // x is (A. + // x is (A.$$ + // x switch { A.$$ + // x switch { { Property: A.$$ MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) memberAccess => GetSymbolsOffOfExpressionInConstantPattern(memberAccess.Expression), - // x is A. + // x is A.$$ QualifiedNameSyntax qualifiedName => GetSymbolsOffOfExpressionInConstantPattern(qualifiedName.Left), _ => default, }; @@ -526,23 +530,46 @@ private RecommendedSymbols GetSymbolsOffOfExpressionInConstantPattern(Expression if (originalExpression is null) return default; - var boundSymbol = _context.SemanticModel.GetSymbolInfo(originalExpression, _cancellationToken).Symbol; + var semanticModel = _context.SemanticModel; + var boundSymbol = semanticModel.GetSymbolInfo(originalExpression, _cancellationToken); + + if (boundSymbol.Symbol is not INamespaceOrTypeSymbol namespaceOrType) + { + // Likely a Color Color case, so we reinterpret the bound symbol into a type + if (originalExpression is IdentifierNameSyntax identifier) + { + var reinterpretedBinding = semanticModel.GetSpeculativeSymbolInfo(identifier.SpanStart, identifier, SpeculativeBindingOption.BindAsTypeOrNamespace); + var reinterpretedSymbol = reinterpretedBinding.GetAnySymbol(); + + // The reinterpretation must be a type + if (reinterpretedSymbol is not INamedTypeSymbol reinterprettedNamedType) + return default; + + var expression = originalExpression.WalkDownParentheses(); + + return GetSymbolsOffOfBoundExpressionWorker(reinterpretedBinding, originalExpression, expression, reinterprettedNamedType, false, false); + } - if (boundSymbol is not INamespaceOrTypeSymbol namespaceOrType) return default; + } var containingType = _context.SemanticModel.GetEnclosingNamedType(_context.Position, _cancellationToken); if (containingType == null) return default; - // The RHS of an `is` pattern may only include qualifications to + var symbols = namespaceOrType.GetMembers().WhereAsArray(FilterMember); + return new RecommendedSymbols(symbols); + + // A constant pattern may only include qualifications to // - namespaces (from other namespaces or aliases), // - types (from aliases, namespaces or other types), // - constant fields (from types) - // Methods, properties, events, non-constant fields etc. are excluded since they may not be present in the RHS of an `is` pattern - var symbols = namespaceOrType.GetMembers() - .WhereAsArray(s => s is INamespaceOrTypeSymbol or IFieldSymbol { IsConst: true } && s.IsAccessibleWithin(containingType)); - return new RecommendedSymbols(symbols); + // Methods, properties, events, non-constant fields etc. are excluded since they are not constant expressions + bool FilterMember(ISymbol symbol) + { + return symbol is INamespaceOrTypeSymbol or IFieldSymbol { IsConst: true } + && symbol.IsAccessibleWithin(containingType); + } } private RecommendedSymbols GetSymbolsOffOfExpression(ExpressionSyntax? originalExpression) @@ -603,11 +630,11 @@ private RecommendedSymbols GetSymbolsOffOfBoundExpression( bool isForDereference, bool allowColorColor) { - var result = GetSymbolsOffOfBoundExpressionWorker(leftHandBinding); + var result = GetSymbolsOffOfBoundExpressionWorker(leftHandBinding, originalExpression, expression, containerType, unwrapNullable, isForDereference); if (!allowColorColor || !CanAccessInstanceAndStaticMembersOffOf(out var reinterpretedBinding)) return result; - var typeMembers = GetSymbolsOffOfBoundExpressionWorker(reinterpretedBinding); + var typeMembers = GetSymbolsOffOfBoundExpressionWorker(reinterpretedBinding, originalExpression, expression, containerType, unwrapNullable, isForDereference); return new RecommendedSymbols( result.NamedSymbols.Concat(typeMembers.NamedSymbols), @@ -661,147 +688,147 @@ bool CanAccessInstanceAndStaticMembersOffOf(out SymbolInfo reinterpretedBinding) return SymbolEquivalenceComparer.Instance.Equals(instanceType, staticType); } + } - RecommendedSymbols GetSymbolsOffOfBoundExpressionWorker(SymbolInfo leftHandBinding) - { - var excludeInstance = false; - var excludeStatic = true; - var excludeBaseMethodsForRefStructs = true; - - ISymbol? containerSymbol = containerType; - - var symbol = leftHandBinding.GetAnySymbol(); - if (symbol != null) - { - // If the thing on the left is a lambda expression, we shouldn't show anything. - if (symbol is IMethodSymbol { MethodKind: MethodKind.AnonymousFunction }) - return default; - - var originalExpressionKind = originalExpression.Kind(); - - // If the thing on the left is a type, namespace or alias and the original - // expression was parenthesized, we shouldn't show anything in IntelliSense. - if (originalExpressionKind is SyntaxKind.ParenthesizedExpression && - symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.Alias) - { - return default; - } - - // If the thing on the left is a method name identifier, we shouldn't show anything. - if (symbol.Kind is SymbolKind.Method && - originalExpressionKind is SyntaxKind.IdentifierName or SyntaxKind.GenericName) - { - return default; - } - - // If the thing on the left is an event that can't be used as a field, we shouldn't show anything - if (symbol is IEventSymbol ev && - !_context.SemanticModel.IsEventUsableAsField(originalExpression.SpanStart, ev)) - { - return default; - } + private RecommendedSymbols GetSymbolsOffOfBoundExpressionWorker(SymbolInfo leftHandBinding, ExpressionSyntax originalExpression, ExpressionSyntax expression, ITypeSymbol? containerType, bool unwrapNullable, bool isForDereference) + { + var excludeInstance = false; + var excludeStatic = true; + var excludeBaseMethodsForRefStructs = true; - if (symbol is IAliasSymbol alias) - symbol = alias.Target; + ISymbol? containerSymbol = containerType; - if (symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.TypeParameter) - { - // For named typed, namespaces, and type parameters (potentially constrained to interface with statics), we flip things around. - // We only want statics and not instance members. - excludeInstance = true; - excludeStatic = false; - containerSymbol = symbol; - } + var symbol = leftHandBinding.GetAnySymbol(); + if (symbol != null) + { + // If the thing on the left is a lambda expression, we shouldn't show anything. + if (symbol is IMethodSymbol { MethodKind: MethodKind.AnonymousFunction }) + return default; - // Special case parameters. If we have a normal (non this/base) parameter, then that's what we want to - // lookup symbols off of as we have a lot of special logic for determining member symbols of lambda - // parameters. - // - // If it is a this/base parameter and we're in a static context, we shouldn't show anything - if (symbol is IParameterSymbol parameter) - { - if (parameter.IsThis && expression.IsInStaticContext()) - return default; + var originalExpressionKind = originalExpression.Kind(); - containerSymbol = symbol; - } - } - else if (containerType != null) + // If the thing on the left is a type, namespace or alias and the original + // expression was parenthesized, we shouldn't show anything in IntelliSense. + if (originalExpressionKind is SyntaxKind.ParenthesizedExpression && + symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.Alias) { - // Otherwise, if it wasn't a symbol on the left, but it was something that had a type, - // then include instance members for it. - excludeStatic = true; + return default; } - if (containerSymbol == null) + // If the thing on the left is a method name identifier, we shouldn't show anything. + if (symbol.Kind is SymbolKind.Method && + originalExpressionKind is SyntaxKind.IdentifierName or SyntaxKind.GenericName) + { return default; + } - // We don't provide any member from System.Void (which is valid only in the context of typeof operation). - // Try to bail early to avoid unnecessary work even though compiler will handle this case for us. - if (containerSymbol is INamedTypeSymbol typeSymbol && typeSymbol.IsSystemVoid()) + // If the thing on the left is an event that can't be used as a field, we shouldn't show anything + if (symbol is IEventSymbol ev && + !_context.SemanticModel.IsEventUsableAsField(originalExpression.SpanStart, ev)) + { return default; + } - Debug.Assert(!excludeInstance || !excludeStatic); + if (symbol is IAliasSymbol alias) + symbol = alias.Target; - // nameof(X.| - // Show static and instance members. - // Show base methods for "ref struct"s - if (_context.IsNameOfContext) + if (symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.TypeParameter) { - excludeInstance = false; + // For named typed, namespaces, and type parameters (potentially constrained to interface with statics), we flip things around. + // We only want statics and not instance members. + excludeInstance = true; excludeStatic = false; - excludeBaseMethodsForRefStructs = false; + containerSymbol = symbol; } - var useBaseReferenceAccessibility = symbol is IParameterSymbol { IsThis: true } p && !p.Type.Equals(containerType); - var symbols = GetMemberSymbols(containerSymbol, position: originalExpression.SpanStart, excludeInstance, useBaseReferenceAccessibility, unwrapNullable, isForDereference); + // Special case parameters. If we have a normal (non this/base) parameter, then that's what we want to + // lookup symbols off of as we have a lot of special logic for determining member symbols of lambda + // parameters. + // + // If it is a this/base parameter and we're in a static context, we shouldn't show anything + if (symbol is IParameterSymbol parameter) + { + if (parameter.IsThis && expression.IsInStaticContext()) + return default; - var namedSymbols = symbols.WhereAsArray( - static (s, a) => !IsUndesirable(s, a.containerType, a.excludeStatic, a.excludeInstance, a.excludeBaseMethodsForRefStructs), - (containerType, excludeStatic, excludeInstance, excludeBaseMethodsForRefStructs, 3)); + containerSymbol = symbol; + } + } + else if (containerType != null) + { + // Otherwise, if it wasn't a symbol on the left, but it was something that had a type, + // then include instance members for it. + excludeStatic = true; + } - // if we're dotting off an instance, then add potential operators/indexers/conversions that may be - // applicable to it as well. - var unnamedSymbols = _context.IsNameOfContext || excludeInstance - ? default - : GetUnnamedSymbols(originalExpression); + if (containerSymbol == null) + return default; - return new RecommendedSymbols(namedSymbols, unnamedSymbols); + // We don't provide any member from System.Void (which is valid only in the context of typeof operation). + // Try to bail early to avoid unnecessary work even though compiler will handle this case for us. + if (containerSymbol is INamedTypeSymbol typeSymbol && typeSymbol.IsSystemVoid()) + return default; - static bool IsUndesirable( - ISymbol symbol, - ITypeSymbol? containerType, - bool excludeStatic, - bool excludeInstance, - bool excludeBaseMethodsForRefStructs) - { - // If we're showing instance members, don't include nested types - if (excludeStatic) - { - if (symbol.IsStatic || symbol is ITypeSymbol) - return true; - } + Debug.Assert(!excludeInstance || !excludeStatic); - // If container type is "ref struct" then we should exclude methods from object and ValueType that are not - // overridden if recommendations are requested not in nameof context, because calling them produces a - // compiler error due to unallowed boxing. See https://github.com/dotnet/roslyn/issues/35178 - if (excludeBaseMethodsForRefStructs && - containerType is { IsRefLikeType: true } && - symbol.ContainingType.SpecialType is SpecialType.System_Object or SpecialType.System_ValueType) - { + // nameof(X.| + // Show static and instance members. + // Show base methods for "ref struct"s + if (_context.IsNameOfContext) + { + excludeInstance = false; + excludeStatic = false; + excludeBaseMethodsForRefStructs = false; + } + + var useBaseReferenceAccessibility = symbol is IParameterSymbol { IsThis: true } p && !p.Type.Equals(containerType); + var symbols = GetMemberSymbols(containerSymbol, position: originalExpression.SpanStart, excludeInstance, useBaseReferenceAccessibility, unwrapNullable, isForDereference); + + var namedSymbols = symbols.WhereAsArray( + static (s, a) => !IsUndesirable(s, a.containerType, a.excludeStatic, a.excludeInstance, a.excludeBaseMethodsForRefStructs), + (containerType, excludeStatic, excludeInstance, excludeBaseMethodsForRefStructs, 3)); + + // if we're dotting off an instance, then add potential operators/indexers/conversions that may be + // applicable to it as well. + var unnamedSymbols = _context.IsNameOfContext || excludeInstance + ? default + : GetUnnamedSymbols(originalExpression); + + return new RecommendedSymbols(namedSymbols, unnamedSymbols); + + static bool IsUndesirable( + ISymbol symbol, + ITypeSymbol? containerType, + bool excludeStatic, + bool excludeInstance, + bool excludeBaseMethodsForRefStructs) + { + // If we're showing instance members, don't include nested types + if (excludeStatic) + { + if (symbol.IsStatic || symbol is ITypeSymbol) return true; - } + } - // We're accessing virtual statics off of an type parameter. We cannot access normal static this - // way, so filter them out. - if (excludeInstance && containerType is ITypeParameterSymbol && symbol.IsStatic) - { - if (!(symbol.IsVirtual || symbol.IsAbstract)) - return true; - } + // If container type is "ref struct" then we should exclude methods from object and ValueType that are not + // overridden if recommendations are requested not in nameof context, because calling them produces a + // compiler error due to unallowed boxing. See https://github.com/dotnet/roslyn/issues/35178 + if (excludeBaseMethodsForRefStructs && + containerType is { IsRefLikeType: true } && + symbol.ContainingType.SpecialType is SpecialType.System_Object or SpecialType.System_ValueType) + { + return true; + } - return false; + // We're accessing virtual statics off of an type parameter. We cannot access normal static this + // way, so filter them out. + if (excludeInstance && containerType is ITypeParameterSymbol && symbol.IsStatic) + { + if (!(symbol.IsVirtual || symbol.IsAbstract)) + return true; } + + return false; } } From 85be648b84e10dd4ed1c3d0c8b51044c606b2eb5 Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:21:27 +0300 Subject: [PATCH 2/3] Add tests and support namespaces --- .../SymbolCompletionProviderTests.cs | 124 ++++++++++++++++++ .../CSharpRecommendationServiceRunner.cs | 28 ++-- 2 files changed, 141 insertions(+), 11 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index c6a4e19489cd1..c7d093a47b292 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -12928,6 +12928,70 @@ public enum Status await VerifyItemIsAbsentAsync(source, "ToString"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task SwitchExpressionEnumColorColor_03() + { + //lang=c#-test + const string source = """ + namespace Status; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + return this switch + { + { Status: Status.$$ } + }; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "StatusEn"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task SwitchExpressionEnumColorColor_04() + { + //lang=c#-test + const string source = """ + using Status = StatusEn; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + return this switch + { + { Status: Status.$$ } + }; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] public async Task ConstantPatternExpressionEnumColorColor_01() { @@ -12984,6 +13048,66 @@ public enum Status await VerifyItemIsAbsentAsync(source, "ToString"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task ConstantPatternExpressionEnumColorColor_03() + { + //lang=c#-test + const string source = """ + namespace Status; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + if (Status is (Status.$$) + ; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "StatusEn"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task ConstantPatternExpressionEnumColorColor_04() + { + //lang=c#-test + const string source = """ + using Status = StatusEn; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + if (Status is (Status.$$) + ; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + #region Collection expressions [Fact] diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index 5b2b8bcd0088e..8a4a7fc4208ec 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -540,14 +540,23 @@ private RecommendedSymbols GetSymbolsOffOfExpressionInConstantPattern(Expression { var reinterpretedBinding = semanticModel.GetSpeculativeSymbolInfo(identifier.SpanStart, identifier, SpeculativeBindingOption.BindAsTypeOrNamespace); var reinterpretedSymbol = reinterpretedBinding.GetAnySymbol(); + var container = _context.SemanticModel.GetTypeInfo(identifier, _cancellationToken).Type; - // The reinterpretation must be a type - if (reinterpretedSymbol is not INamedTypeSymbol reinterprettedNamedType) + // The reinterpretation must be a namespace or type, since we cannot have a + // constant expression out of dotting a constant value, like a x.Length + // If all we can bind to is a const local or const field, we cannot offer valid suggestions + if (reinterpretedSymbol is not INamespaceOrTypeSymbol) return default; var expression = originalExpression.WalkDownParentheses(); - return GetSymbolsOffOfBoundExpressionWorker(reinterpretedBinding, originalExpression, expression, reinterprettedNamedType, false, false); + return GetSymbolsOffOfBoundExpressionWorker( + reinterpretedBinding, + originalExpression, + expression, + container, + unwrapNullable: false, + isForDereference: false); } return default; @@ -557,19 +566,16 @@ private RecommendedSymbols GetSymbolsOffOfExpressionInConstantPattern(Expression if (containingType == null) return default; - var symbols = namespaceOrType.GetMembers().WhereAsArray(FilterMember); - return new RecommendedSymbols(symbols); - // A constant pattern may only include qualifications to // - namespaces (from other namespaces or aliases), // - types (from aliases, namespaces or other types), // - constant fields (from types) // Methods, properties, events, non-constant fields etc. are excluded since they are not constant expressions - bool FilterMember(ISymbol symbol) - { - return symbol is INamespaceOrTypeSymbol or IFieldSymbol { IsConst: true } - && symbol.IsAccessibleWithin(containingType); - } + var symbols = namespaceOrType + .GetMembers() + .WhereAsArray(symbol => symbol is INamespaceOrTypeSymbol or IFieldSymbol { IsConst: true } + && symbol.IsAccessibleWithin(containingType)); + return new RecommendedSymbols(symbols); } private RecommendedSymbols GetSymbolsOffOfExpression(ExpressionSyntax? originalExpression) From a6f0dc105e3a0891088aa29d3f8819d79bafd8e1 Mon Sep 17 00:00:00 2001 From: Rekkonnect <8298332+Rekkonnect@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:30:51 +0300 Subject: [PATCH 3/3] Add test for locals --- .../SymbolCompletionProviderTests.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index c7d093a47b292..f5cdbe0e0dcb7 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -12992,6 +12992,39 @@ public enum StatusEn await VerifyItemIsAbsentAsync(source, "ToString"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task SwitchExpressionEnumColorColor_05() + { + //lang=c#-test + const string source = """ + using Status = StatusEn; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + const StatusEn Status = StatusEn.Undisclosed; + return this switch + { + { Status: Status.$$ } + }; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] public async Task ConstantPatternExpressionEnumColorColor_01() { @@ -13108,6 +13141,37 @@ public enum StatusEn await VerifyItemIsAbsentAsync(source, "ToString"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75350")] + public async Task ConstantPatternExpressionEnumColorColor_05() + { + //lang=c#-test + const string source = """ + using Status = StatusEn; + + public sealed record OrderModel(int Id, StatusEn Status) + { + public string StatusDisplay + { + get + { + const StatusEn Status = StatusEn.Undisclosed; + if (Status is (Status.$$) + ; + } + } + } + + public enum StatusEn + { + Undisclosed, + Open, + Closed, + } + """; + await VerifyItemExistsAsync(source, "Undisclosed"); + await VerifyItemIsAbsentAsync(source, "ToString"); + } + #region Collection expressions [Fact]