Skip to content

Commit

Permalink
Have inline hints display aliases if appropriate
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi committed Dec 19, 2024
1 parent 395d298 commit b99876e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 31 deletions.
49 changes: 47 additions & 2 deletions src/EditorFeatures/Test2/InlineHints/CSharpInlineTypeHintsTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints
<Trait(Traits.Feature, Traits.Features.InlineHints)>
Public Class CSharpInlineTypeHintsTests
Public NotInheritable Class CSharpInlineTypeHintsTests
Inherits AbstractInlineHintsTests

<WpfFact>
Expand Down Expand Up @@ -766,7 +766,7 @@ class A
<Document>
class A
{
void M(System.Threading.CancellationToken ct = new CancellationToken()) { }
void M(System.Threading.CancellationToken ct = new System.Threading.CancellationToken()) { }
}
</Document>
</Project>
Expand Down Expand Up @@ -889,5 +889,50 @@ class A

Await VerifyTypeHints(input, output)
End Function

<WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/72219")>
Public Async Function TestAlias() As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System.Collections.Generic;
using TestFile = (string Path, string Content);

class C
{
void M()
{
var {|List&lt;TestFile&gt; :|}testFiles = GetTestFiles();
}

List&lt;TestFile&gt; GetTestFiles() => default;
}
</Document>
</Project>
</Workspace>

Dim output =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using System.Collections.Generic;
using TestFile = (string Path, string Content);

class C
{
void M()
{
List&lt;TestFile&gt; testFiles = GetTestFiles();
}

List&lt;TestFile&gt; GetTestFiles() => default;
}
</Document>
</Project>
</Workspace>

Await VerifyTypeHints(input, output)
End Function
End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@
namespace Microsoft.CodeAnalysis.CSharp.InlineHints;

[ExportLanguageService(typeof(IInlineTypeHintsService), LanguageNames.CSharp), Shared]
internal sealed class CSharpInlineTypeHintsService : AbstractInlineTypeHintsService
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class CSharpInlineTypeHintsService() : AbstractInlineTypeHintsService
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpInlineTypeHintsService()
{
}

protected override TypeHint? TryGetTypeHint(
SemanticModel semanticModel,
SyntaxNode node,
Expand All @@ -41,7 +37,7 @@ public CSharpInlineTypeHintsService()
{
var type = semanticModel.GetTypeInfo(variableDeclaration.Type, cancellationToken).Type;
if (IsValidType(type))
return CreateTypeHint(type, displayAllOverride, forImplicitVariableTypes, variableDeclaration.Type, variableDeclaration.Variables[0].Identifier);
return CreateTypeHint(type, variableDeclaration.Type, variableDeclaration.Variables[0].Identifier);
}

// We handle individual variables of ParenthesizedVariableDesignationSyntax separately.
Expand All @@ -51,7 +47,7 @@ public CSharpInlineTypeHintsService()
{
var type = semanticModel.GetTypeInfo(declarationExpression.Type, cancellationToken).Type;
if (IsValidType(type))
return CreateTypeHint(type, displayAllOverride, forImplicitVariableTypes, declarationExpression.Type, declarationExpression.Designation);
return CreateTypeHint(type, declarationExpression.Type, declarationExpression.Designation);
}
else if (node is SingleVariableDesignationSyntax { Parent: not DeclarationPatternSyntax and not DeclarationExpressionSyntax } variableDesignation)
{
Expand All @@ -60,7 +56,7 @@ public CSharpInlineTypeHintsService()
if (IsValidType(type))
{
return node.Parent is VarPatternSyntax varPattern
? CreateTypeHint(type, displayAllOverride, forImplicitVariableTypes, varPattern.VarKeyword, variableDesignation.Identifier)
? CreateTypeHint(type, varPattern.VarKeyword, variableDesignation.Identifier)
: new(type, new TextSpan(variableDesignation.Identifier.SpanStart, 0), textChange: null, trailingSpace: true);
}
}
Expand All @@ -69,7 +65,7 @@ public CSharpInlineTypeHintsService()
var info = semanticModel.GetForEachStatementInfo(forEachStatement);
var type = info.ElementType;
if (IsValidType(type))
return CreateTypeHint(type, displayAllOverride, forImplicitVariableTypes, forEachStatement.Type, forEachStatement.Identifier);
return CreateTypeHint(type, forEachStatement.Type, forEachStatement.Identifier);
}
}

Expand All @@ -83,7 +79,7 @@ public CSharpInlineTypeHintsService()
IsValidType(parameter?.Type))
{
return parameterNode.Parent?.Parent?.Kind() is SyntaxKind.ParenthesizedLambdaExpression
? new TypeHint(parameter.Type, span, textChange: new TextChange(span, parameter.Type.ToDisplayString(s_minimalTypeStyle) + " "), trailingSpace: true)
? new TypeHint(parameter.Type, span, textChange: new TextChange(span, GetTypeDisplayString(parameter.Type) + " "), trailingSpace: true)
: new TypeHint(parameter.Type, span, textChange: null, trailingSpace: true);
}
}
Expand All @@ -97,7 +93,7 @@ public CSharpInlineTypeHintsService()
if (IsValidType(type))
{
var span = new TextSpan(implicitNew.NewKeyword.Span.End, 0);
return new(type, span, new TextChange(span, " " + type.ToDisplayString(s_minimalTypeStyle)), leadingSpace: true);
return new(type, span, new TextChange(span, " " + GetTypeDisplayString(type)), leadingSpace: true);
}
}
}
Expand All @@ -110,26 +106,27 @@ public CSharpInlineTypeHintsService()
if (IsValidType(type))
{
var span = new TextSpan(collectionExpression.OpenBracketToken.SpanStart, 0);
return new(type, span, new TextChange(span, type.ToDisplayString(s_minimalTypeStyle)), leadingSpace: true);
return new(type, span, new TextChange(span, GetTypeDisplayString(type)), leadingSpace: true);
}
}
}

return null;
}

private static TypeHint CreateTypeHint(
ITypeSymbol type,
bool displayAllOverride,
bool normalOption,
SyntaxNodeOrToken displayAllSpan,
SyntaxNodeOrToken normalSpan)
{
var span = GetSpan(displayAllOverride, normalOption, displayAllSpan, normalSpan);
// if this is a hint that is placed in-situ (i.e. it's not overwriting text like 'var'), then place
// a space after it to make things feel less cramped.
var trailingSpace = span.Length == 0;
return new TypeHint(type, span, new TextChange(displayAllSpan.Span, type.ToDisplayString(s_minimalTypeStyle)), trailingSpace: trailingSpace);
string GetTypeDisplayString(ITypeSymbol type)
=> type.ToMinimalDisplayString(semanticModel, node.SpanStart, s_minimalTypeStyle);

TypeHint CreateTypeHint(
ITypeSymbol type,
SyntaxNodeOrToken displayAllSpan,
SyntaxNodeOrToken normalSpan)
{
var span = GetSpan(displayAllOverride, forImplicitVariableTypes, displayAllSpan, normalSpan);
// if this is a hint that is placed in-situ (i.e. it's not overwriting text like 'var'), then place
// a space after it to make things feel less cramped.
var trailingSpace = span.Length == 0;
return new TypeHint(type, span, new TextChange(displayAllSpan.Span, GetTypeDisplayString(type)), trailingSpace: trailingSpace);
}
}

private static TextSpan GetSpan(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,13 @@ public async Task<ImmutableArray<InlineHint>> GetInlineHintsAsync(
using var _2 = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var finalParts);
finalParts.AddRange(prefix);

var parts = type.ToDisplayParts(s_minimalTypeStyle);
AddParts(anonymousTypeService, finalParts, parts, semanticModel, spanStart);
// Try to get the minimal display string for the type. Try to use it if it's actually shorter (it may not
// be as we've setup ToDisplayParts to only show the type name, while ToMinimalDisplayParts may show the
// full name of the type if the short name doesn't bind. This will also help us use aliases if present.
var minimalDisplayParts = type.ToMinimalDisplayParts(semanticModel, spanStart, s_minimalTypeStyle);
var displayParts = type.ToDisplayParts(s_minimalTypeStyle);
var preferredParts = minimalDisplayParts.Length <= displayParts.Length ? minimalDisplayParts : displayParts;
AddParts(anonymousTypeService, finalParts, preferredParts, semanticModel, spanStart);

// If we have nothing to show, then don't bother adding this hint.
if (finalParts.All(p => string.IsNullOrWhiteSpace(p.ToString())))
Expand Down

0 comments on commit b99876e

Please sign in to comment.