diff --git a/src/EditorFeatures/CSharpTest/SignatureHelp/AbstractCSharpSignatureHelpProviderTests.cs b/src/EditorFeatures/CSharpTest/SignatureHelp/AbstractCSharpSignatureHelpProviderTests.cs index 0fee9ebb3303b..44247c09e6abc 100644 --- a/src/EditorFeatures/CSharpTest/SignatureHelp/AbstractCSharpSignatureHelpProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/SignatureHelp/AbstractCSharpSignatureHelpProviderTests.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editor.UnitTests.SignatureHelp; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; @@ -11,7 +14,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SignatureHelp; public abstract class AbstractCSharpSignatureHelpProviderTests : AbstractSignatureHelpProviderTests { protected override ParseOptions CreateExperimentalParseOptions() + => new CSharpParseOptions().WithFeatures([]); // no experimental features to enable + + protected override Task TestAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup, + IEnumerable? expectedOrderedItemsOrNull = null, + bool usePreviousCharAsTrigger = false, + SourceCodeKind? sourceCodeKind = null, + bool experimental = false) { - return new CSharpParseOptions().WithFeatures([]); // no experimental features to enable + return base.TestAsync(markup, expectedOrderedItemsOrNull, usePreviousCharAsTrigger, sourceCodeKind, experimental); } } diff --git a/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs b/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameFullyWrittenSignatureHelpProviderTests.cs similarity index 96% rename from src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs rename to src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameFullyWrittenSignatureHelpProviderTests.cs index dedfddaf80d47..3f48441f2e27d 100644 --- a/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameFullyWrittenSignatureHelpProviderTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -19,10 +17,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SignatureHelp; [Trait(Traits.Feature, Traits.Features.SignatureHelp)] -public sealed class GenericNameSignatureHelpProviderTests : AbstractCSharpSignatureHelpProviderTests +public sealed class GenericNameFullyWrittenSignatureHelpProviderTests : AbstractCSharpSignatureHelpProviderTests { internal override Type GetSignatureHelpProviderType() - => typeof(GenericNameSignatureHelpProvider); + => typeof(GenericNameFullyWrittenSignatureHelpProvider); #region "Declaring generic type objects" @@ -952,4 +950,33 @@ void Goo() } """, expectedOrderedItems); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80233")] + public Task TestModernGenericExtensionMethodOnGenericType() + => TestAsync(""" + public class My + { + } + + public static class MyExtensions + { + extension(My self) + { + public void GenericMethod() + { + } + } + } + + class User + { + static void Main() + { + My target = new(); + target.GenericMethod<$$>(); + } + } + """, + [new("void MyExtensions.extension(My).GenericMethod()")], + sourceCodeKind: SourceCodeKind.Regular); } diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider.cs similarity index 79% rename from src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs rename to src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider.cs index 2a478ebc0578f..de6a1bb133478 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,64 +13,28 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DocumentationComments; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SignatureHelp; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; -[ExportSignatureHelpProvider("GenericNameSignatureHelpProvider", LanguageNames.CSharp), Shared] -internal partial class GenericNameSignatureHelpProvider : AbstractCSharpSignatureHelpProvider +internal abstract partial class AbstractGenericNameSignatureHelpProvider : AbstractCSharpSignatureHelpProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public GenericNameSignatureHelpProvider() - { - } - public override ImmutableArray TriggerCharacters => ['<', ',']; public override ImmutableArray RetriggerCharacters => ['>']; - protected virtual bool TryGetGenericIdentifier( + protected abstract TextSpan GetTextSpan(SyntaxToken genericIdentifier, SyntaxToken lessThanToken); + + protected abstract bool TryGetGenericIdentifier( SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, out SyntaxToken genericIdentifier, - out SyntaxToken lessThanToken) - { - if (CommonSignatureHelpUtilities.TryGetSyntax( - root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out GenericNameSyntax? name)) - { - genericIdentifier = name.Identifier; - lessThanToken = name.TypeArgumentList.LessThanToken; - return true; - } - - genericIdentifier = default; - lessThanToken = default; - return false; - } - - private bool IsTriggerToken(SyntaxToken token) - { - return !token.IsKind(SyntaxKind.None) && - token.ValueText.Length == 1 && - TriggerCharacters.Contains(token.ValueText[0]) && - token.Parent is TypeArgumentListSyntax && - token.Parent.Parent is GenericNameSyntax; - } - - private bool IsArgumentListToken(GenericNameSyntax node, SyntaxToken token) - { - return node.TypeArgumentList != null && - node.TypeArgumentList.Span.Contains(token.SpanStart) && - token != node.TypeArgumentList.GreaterThanToken; - } + out SyntaxToken lessThanToken); protected override async Task GetItemsWorkerAsync(Document document, int position, SignatureHelpTriggerInfo triggerInfo, MemberDisplayOptions options, CancellationToken cancellationToken) { @@ -116,16 +79,13 @@ private bool IsArgumentListToken(GenericNameSyntax node, SyntaxToken token) return null; } - var accessibleSymbols = - symbols.WhereAsArray(s => s.GetArity() > 0) - .WhereAsArray(s => s is INamedTypeSymbol or IMethodSymbol) - .FilterToVisibleAndBrowsableSymbols(options.HideAdvancedMembers, semanticModel.Compilation, inclusionFilter: static s => true) - .Sort(semanticModel, genericIdentifier.SpanStart); + var accessibleSymbols = symbols + .WhereAsArray(s => s.GetArity() > 0) + .FilterToVisibleAndBrowsableSymbols(options.HideAdvancedMembers, semanticModel.Compilation, inclusionFilter: static s => true) + .Sort(semanticModel, genericIdentifier.SpanStart); if (!accessibleSymbols.Any()) - { return null; - } var structuralTypeDisplayService = document.GetRequiredLanguageService(); var documentationCommentFormattingService = document.GetRequiredLanguageService(); @@ -158,12 +118,6 @@ private bool IsArgumentListToken(GenericNameSyntax node, SyntaxToken token) return null; } - protected virtual TextSpan GetTextSpan(SyntaxToken genericIdentifier, SyntaxToken lessThanToken) - { - Contract.ThrowIfFalse(lessThanToken.Parent is TypeArgumentListSyntax && lessThanToken.Parent.Parent is GenericNameSyntax); - return SignatureHelpUtilities.GetSignatureHelpSpan(((GenericNameSyntax)lessThanToken.Parent.Parent).TypeArgumentList); - } - private static SignatureHelpItem Convert( ISymbol symbol, SyntaxToken lessThanToken, @@ -173,34 +127,54 @@ private static SignatureHelpItem Convert( { var position = lessThanToken.SpanStart; - SignatureHelpItem item; if (symbol is INamedTypeSymbol namedType) { - item = CreateItem( + return CreateItem( symbol, semanticModel, position, structuralTypeDisplayService, - false, + isVariadic: false, symbol.GetDocumentationPartsFactory(semanticModel, position, documentationCommentFormattingService), GetPreambleParts(namedType, semanticModel, position), GetSeparatorParts(), GetPostambleParts(), [.. namedType.TypeParameters.Select(p => Convert(p, semanticModel, position, documentationCommentFormattingService))]); } - else + else if (symbol is IMethodSymbol method) { - var method = (IMethodSymbol)symbol; - item = CreateItem( + return CreateItem( symbol, semanticModel, position, structuralTypeDisplayService, - false, - c => symbol.GetDocumentationParts(semanticModel, position, documentationCommentFormattingService, c), + isVariadic: false, + symbol.GetDocumentationPartsFactory(semanticModel, position, documentationCommentFormattingService), GetPreambleParts(method, semanticModel, position), GetSeparatorParts(), GetPostambleParts(method, semanticModel, position), - [.. method.TypeParameters.Select(p => Convert(p, semanticModel, position, documentationCommentFormattingService))]); + GetTypeArguments(method)); + } + else + { + throw ExceptionUtilities.UnexpectedValue(symbol); } - return item; + IList GetTypeArguments(IMethodSymbol method) + { + var result = new List(); + + // Signature help for generic modern extensions must include the generic type *arguments* for the containing + // extension as well. These are fixed given the receiver, and need to be repeated in the method type argument + // list. + if (method.ContainingType.IsExtension) + { + result.AddRange(method.ContainingType.TypeArguments.Select(t => new SignatureHelpSymbolParameter( + name: null, isOptional: false, + t.GetDocumentationPartsFactory(semanticModel, position, documentationCommentFormattingService), + t.ToMinimalDisplayParts(semanticModel, position)))); + } + + result.AddRange(method.TypeParameters.Select(p => Convert(p, semanticModel, position, documentationCommentFormattingService))); + + return result; + } } private static readonly SymbolDisplayFormat s_minimallyQualifiedFormat = diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_Method.cs b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_Method.cs similarity index 98% rename from src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_Method.cs rename to src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_Method.cs index c1072cfa05d42..38690a5a7def4 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_Method.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_Method.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; -internal partial class GenericNameSignatureHelpProvider +internal partial class AbstractGenericNameSignatureHelpProvider { private static IList GetPreambleParts( IMethodSymbol method, diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_NamedType.cs similarity index 92% rename from src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs rename to src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_NamedType.cs index d3546f93092f4..834a77dd75536 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider_NamedType.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/AbstractGenericNameSignatureHelpProvider_NamedType.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; -internal partial class GenericNameSignatureHelpProvider +internal partial class AbstractGenericNameSignatureHelpProvider { private static IList GetPreambleParts( INamedTypeSymbol namedType, diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameFullyWrittenSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/GenericNameFullyWrittenSignatureHelpProvider.cs new file mode 100644 index 0000000000000..f6a66e0567f48 --- /dev/null +++ b/src/Features/CSharp/Portable/SignatureHelp/GenericNameFullyWrittenSignatureHelpProvider.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.SignatureHelp; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; + +[ExportSignatureHelpProvider(nameof(GenericNameFullyWrittenSignatureHelpProvider), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GenericNameFullyWrittenSignatureHelpProvider() : AbstractGenericNameSignatureHelpProvider +{ + protected override bool TryGetGenericIdentifier( + SyntaxNode root, int position, + ISyntaxFactsService syntaxFacts, + SignatureHelpTriggerReason triggerReason, + CancellationToken cancellationToken, + out SyntaxToken genericIdentifier, + out SyntaxToken lessThanToken) + { + if (CommonSignatureHelpUtilities.TryGetSyntax( + root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out GenericNameSyntax? name)) + { + genericIdentifier = name.Identifier; + lessThanToken = name.TypeArgumentList.LessThanToken; + return true; + } + + genericIdentifier = default; + lessThanToken = default; + return false; + } + + private bool IsTriggerToken(SyntaxToken token) + { + return !token.IsKind(SyntaxKind.None) && + token.ValueText.Length == 1 && + TriggerCharacters.Contains(token.ValueText[0]) && + token.Parent is TypeArgumentListSyntax && + token.Parent.Parent is GenericNameSyntax; + } + + private bool IsArgumentListToken(GenericNameSyntax node, SyntaxToken token) + { + return node.TypeArgumentList != null && + node.TypeArgumentList.Span.Contains(token.SpanStart) && + token != node.TypeArgumentList.GreaterThanToken; + } + + protected override TextSpan GetTextSpan(SyntaxToken genericIdentifier, SyntaxToken lessThanToken) + { + Contract.ThrowIfFalse(lessThanToken.Parent is TypeArgumentListSyntax && lessThanToken.Parent.Parent is GenericNameSyntax); + return SignatureHelpUtilities.GetSignatureHelpSpan(((GenericNameSyntax)lessThanToken.Parent.Parent).TypeArgumentList); + } +} diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNamePartiallyWrittenSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/GenericNamePartiallyWrittenSignatureHelpProvider.cs index 6a73d1fe258da..a20e59f15dc1b 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNamePartiallyWrittenSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/GenericNamePartiallyWrittenSignatureHelpProvider.cs @@ -13,15 +13,11 @@ namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; -[ExportSignatureHelpProvider("GenericNamePartiallyWrittenSignatureHelpProvider", LanguageNames.CSharp), Shared] -internal sealed class GenericNamePartiallyWrittenSignatureHelpProvider : GenericNameSignatureHelpProvider +[ExportSignatureHelpProvider(nameof(GenericNamePartiallyWrittenSignatureHelpProvider), LanguageNames.CSharp), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class GenericNamePartiallyWrittenSignatureHelpProvider() : AbstractGenericNameSignatureHelpProvider { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public GenericNamePartiallyWrittenSignatureHelpProvider() - { - } - protected override bool TryGetGenericIdentifier(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, out SyntaxToken genericIdentifier, out SyntaxToken lessThanToken) => root.SyntaxTree.IsInPartiallyWrittenGeneric(position, cancellationToken, out genericIdentifier, out lessThanToken); diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs index b51083fab8f2c..76547cc878ef0 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_2.cs @@ -231,7 +231,7 @@ public static DocumentationComment GetAppropriateDocumentationComment(this ISymb public static Func> GetDocumentationPartsFactory( this ISymbol symbol, SemanticModel semanticModel, int position, IDocumentationCommentFormattingService formatter) - => c => symbol.GetDocumentationParts(semanticModel, position, formatter, cancellationToken: c); + => cancellationToken => symbol.GetDocumentationParts(semanticModel, position, formatter, cancellationToken); public static readonly SymbolDisplayFormat CrefFormat = new( diff --git a/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs b/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs index 94250b831e95f..408f15cf50de5 100644 --- a/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs +++ b/src/Features/Core/Portable/SignatureHelp/SignatureHelpParameter.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.SignatureHelp; /// point to TaggedText parts. /// internal sealed class SignatureHelpSymbolParameter( - string name, + string? name, bool isOptional, Func>? documentationFactory, IEnumerable displayParts, @@ -86,7 +86,7 @@ public static explicit operator SignatureHelpParameter(SignatureHelpSymbolParame } internal sealed class SignatureHelpParameter( - string name, + string? name, bool isOptional, Func>? documentationFactory, IEnumerable displayParts,