Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,7 +14,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SignatureHelp;
public abstract class AbstractCSharpSignatureHelpProviderTests : AbstractSignatureHelpProviderTests<CSharpTestWorkspaceFixture>
{
protected override ParseOptions CreateExperimentalParseOptions()
=> new CSharpParseOptions().WithFeatures([]); // no experimental features to enable

protected override Task TestAsync(
[StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup,
IEnumerable<SignatureHelpTestItem>? 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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"

Expand Down Expand Up @@ -952,4 +950,33 @@ void Goo()
}
""", expectedOrderedItems);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80233")]
public Task TestModernGenericExtensionMethodOnGenericType()
=> TestAsync("""
public class My<T>
{
}

public static class MyExtensions
{
extension<TBase>(My<TBase> self)
{
public void GenericMethod<TDerived>()
{
}
}
}

class User
{
static void Main()
{
My<object> target = new();
target.GenericMethod<$$>();
}
}
""",
[new("void MyExtensions.extension<object>(My<object>).GenericMethod<object, TDerived>()")],
sourceCodeKind: SourceCodeKind.Regular);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<char> TriggerCharacters => ['<', ','];

public override ImmutableArray<char> 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<SignatureHelpItems?> GetItemsWorkerAsync(Document document, int position, SignatureHelpTriggerInfo triggerInfo, MemberDisplayOptions options, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -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<IStructuralTypeDisplayService>();
var documentationCommentFormattingService = document.GetRequiredLanguageService<IDocumentationCommentFormattingService>();
Expand Down Expand Up @@ -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,
Expand All @@ -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<SignatureHelpSymbolParameter> GetTypeArguments(IMethodSymbol method)
{
var result = new List<SignatureHelpSymbolParameter>();

// 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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp;

internal partial class GenericNameSignatureHelpProvider
internal partial class AbstractGenericNameSignatureHelpProvider
{
private static IList<SymbolDisplayPart> GetPreambleParts(
IMethodSymbol method,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp;

internal partial class GenericNameSignatureHelpProvider
internal partial class AbstractGenericNameSignatureHelpProvider
{
private static IList<SymbolDisplayPart> GetPreambleParts(
INamedTypeSymbol namedType,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public static DocumentationComment GetAppropriateDocumentationComment(this ISymb

public static Func<CancellationToken, IEnumerable<TaggedText>> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.SignatureHelp;
/// point to TaggedText parts.
/// </summary>
internal sealed class SignatureHelpSymbolParameter(
string name,
string? name,
bool isOptional,
Func<CancellationToken, IEnumerable<TaggedText>>? documentationFactory,
IEnumerable<SymbolDisplayPart> displayParts,
Expand Down Expand Up @@ -86,7 +86,7 @@ public static explicit operator SignatureHelpParameter(SignatureHelpSymbolParame
}

internal sealed class SignatureHelpParameter(
string name,
string? name,
bool isOptional,
Func<CancellationToken, IEnumerable<TaggedText>>? documentationFactory,
IEnumerable<TaggedText> displayParts,
Expand Down
Loading