Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EE: Support compact name in IDkmLanguageInstructionDecoder.GetMethodName() implementation #75764

Merged
merged 6 commits into from
Nov 15, 2024
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
4 changes: 2 additions & 2 deletions eng/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@
VS Debugger
-->
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Contracts" Version="17.13.0-beta.24561.1" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Engine-implementation" Version="17.13.1100701-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Metadata-implementation" Version="17.13.1100701-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Engine-implementation" Version="17.13.1110101-preview" />
<PackageVersion Include="Microsoft.VisualStudio.Debugger.Metadata-implementation" Version="17.13.1110101-preview" />

<!--
VS .NET Runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ private CSharpInstructionDecoder()
AddMemberOptions(SymbolDisplayMemberOptions.IncludeParameters).
WithParameterOptions(SymbolDisplayParameterOptions.IncludeType);

private static readonly SymbolDisplayFormat s_indexerCompactNameFormat = CompactNameFormat.
WithMemberOptions(SymbolDisplayMemberOptions.IncludeParameters).
WithParameterOptions(SymbolDisplayParameterOptions.None);

internal override string GetCompactName(MethodSymbol method)
{
var symbol = method.AssociatedSymbol ?? method;
var format = symbol is PropertySymbol { IsIndexer: true } ?
s_indexerCompactNameFormat :
CompactNameFormat;
return symbol.ToDisplayString(format);
}

internal override void AppendFullName(StringBuilder builder, MethodSymbol method)
{
var displayFormat = method.MethodKind is MethodKind.PropertyGet or MethodKind.PropertySet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.UnitTests;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.PooledObjects;
Expand Down Expand Up @@ -402,6 +402,144 @@ class C
Assert.Equal("System.Action<System.Func<object>>", GetReturnTypeName(source, "C.M1", [typeof(object)]));
}

[Fact]
public void GetCompactName_Members()
{
var source = """
using System;
namespace System.Runtime.CompilerServices
{
public class IsExternalInit { }
}
class C
{
static C() { }
C(int x) { }
~C() { }
object F() => null;
object P1 { get; }
object P2 { set { } }
object P3 { init { } }
object this[int i] { get { return null; } set { } }
event EventHandler E;
public static C operator+(C c) => c;
public static implicit operator int(C c) => 0;
public static explicit operator string(C c) => "";
}
static class E
{
static void M(this string x) { }
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("C");
VerifyMethodName(compilation, containingType.GetMethod("F"), "C.F()", "F");
VerifyMethodName(compilation, containingType.GetMethod("get_P1"), "C.P1.get()", "P1");
VerifyMethodName(compilation, containingType.GetMethod("set_P2"), "C.P2.set(value)", "P2");
VerifyMethodName(compilation, containingType.GetMethod("set_P3"), "C.P3.init(value)", "P3");
VerifyMethodName(compilation, containingType.GetMethod("get_Item"), "C.this[int].get(i)", "this[]");
VerifyMethodName(compilation, containingType.GetMethod("set_Item"), "C.this[int].set(i, value)", "this[]");
VerifyMethodName(compilation, containingType.GetMethod("add_E"), "C.E.add(value)", "E");
VerifyMethodName(compilation, containingType.GetMethod("remove_E"), "C.E.remove(value)", "E");
VerifyMethodName(compilation, compilation.GlobalNamespace.GetTypeMember("E").GetMethod("M"), "E.M(x)", "M");
VerifyMethodName(compilation, containingType.GetMethod(".cctor"), "C.C()", "C");
VerifyMethodName(compilation, containingType.GetMethod(".ctor"), "C.C(x)", "C");
VerifyMethodName(compilation, containingType.GetMethod("Finalize"), "C.~C()", "~C");
VerifyMethodName(compilation, containingType.GetMethod("op_UnaryPlus"), "C.operator +(c)", "operator +");
VerifyMethodName(compilation, containingType.GetMethod("op_Implicit"), "C.implicit operator int(c)", "implicit operator int");
jcouv marked this conversation as resolved.
Show resolved Hide resolved
VerifyMethodName(compilation, containingType.GetMethod("op_Explicit"), "C.explicit operator string(c)", "explicit operator string");
}

[Fact]
public void GetCompactName_GenericMethod()
{
var source = """
using System;
class A<T>
{
public struct B<U>
{
static void M<V>(T t, U u, V v) { }
}
}
""";
var compilation = CreateCompilation(source);
var instructionDecoder = CSharpInstructionDecoder.Instance;
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)compilation.GlobalNamespace.GetTypeMember("A").GetTypeMember("B").GetMethod("M"),
["object", "int", "string"],
instructionDecoder);
var actualName = instructionDecoder.GetName(method, includeParameterTypes: false, includeParameterNames: true);
var actualCompactName = instructionDecoder.GetCompactName(method);
Assert.Equal("A<object>.B<int>.M<string>(t, u, v)", actualName);
Assert.Equal("M", actualCompactName);
}

[Fact]
public void GetCompactName_ExplicitImplementation()
{
var source = """
using System;
interface I
{
object F();
object P { get; }
object this[int i] { get; }
event EventHandler E;
}
class C : I
{
object I.F() => null;
object I.P => null;
object I.this[int i] => null;
event EventHandler I.E { add { } remove { } }
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("C");
VerifyMethodName(compilation, containingType.GetMethod("I.F"), "C.I.F()", "I.F");
VerifyMethodName(compilation, containingType.GetMethod("I.get_P"), "C.I.P.get()", "I.P");
VerifyMethodName(compilation, containingType.GetMethod("I.get_Item"), "C.I.get_Item(i)", "I.get_Item");
VerifyMethodName(compilation, containingType.GetMethod("I.add_E"), "C.I.E.add(value)", "I.E");
}

[Fact]
public void GetCompactName_NestedFunctions()
{
var source = """
using System;
class Program
{
static void Main(string[] args)
{
Func<string> f1 = () => args[0];
string f2() => args[1];
_ = f1();
_ = f2();
}
}
""";
var compilation = CreateCompilation(source);
var containingType = compilation.GlobalNamespace.GetTypeMember("Program").GetTypeMember("<>c__DisplayClass0_0");
VerifyMethodName(compilation, containingType.GetMethod("<Main>b__0"), "Program.Main.AnonymousMethod__0()", "<Main>b__0");
VerifyMethodName(compilation, containingType.GetMethod("<Main>g__f2|1"), "Program.Main.__f2|1()", "<Main>g__f2|1");
}

private void VerifyMethodName(CSharpCompilation compilation, MethodSymbol method, string expectedName, string expectedCompactName)
{
var instructionDecoder = CSharpInstructionDecoder.Instance;
method = GetConstructedMethod(
compilation,
(PEMethodSymbol)method,
null,
instructionDecoder);
var actualName = instructionDecoder.GetName(method, includeParameterTypes: false, includeParameterNames: true);
var actualCompactName = instructionDecoder.GetCompactName(method);
Assert.Equal(expectedName, actualName);
Assert.Equal(expectedCompactName, actualCompactName);
}

private string GetName(string source, string methodName, DkmVariableInfoFlags argumentFlags, Type[] typeArguments = null, string[] argumentValues = null)
{
var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
Expand All @@ -414,7 +552,12 @@ private string GetName(string source, string methodName, DkmVariableInfoFlags ar
"Unexpected argumentFlags", "argumentFlags = {0}", argumentFlags);

var instructionDecoder = CSharpInstructionDecoder.Instance;
var method = GetConstructedMethod(source, methodName, typeArguments, instructionDecoder);
var compilation = CreateCompilation(source);
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName),
typeArguments,
instructionDecoder);

var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);
Expand All @@ -435,20 +578,27 @@ private string GetReturnTypeName(string source, string methodName, Type[] typeAr
{
var instructionDecoder = CSharpInstructionDecoder.Instance;
var serializedTypeArgumentNames = typeArguments?.Select(t => t?.AssemblyQualifiedName).ToArray();
var method = GetConstructedMethod(source, methodName, serializedTypeArgumentNames, instructionDecoder);
var compilation = CreateCompilation(source);
var method = GetConstructedMethod(
compilation,
(PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName),
serializedTypeArgumentNames,
instructionDecoder);

return instructionDecoder.GetReturnTypeName(method);
}

private MethodSymbol GetConstructedMethod(string source, string methodName, string[] serializedTypeArgumentNames, CSharpInstructionDecoder instructionDecoder)
private CSharpCompilation CreateCompilation(string source)
{
var compilation = CreateCompilationWithMscorlib461(source, options: TestOptions.DebugDll, assemblyName: nameof(InstructionDecoderTests));
var runtime = CreateRuntimeInstance(compilation);
var moduleInstances = runtime.Modules;
var blocks = moduleInstances.SelectAsArray(m => m.MetadataBlock);
compilation = blocks.ToCompilation(default(Guid), MakeAssemblyReferencesKind.AllAssemblies);
var frame = (PEMethodSymbol)GetMethodOrTypeBySignature(compilation, methodName);
return blocks.ToCompilation(default(Guid), MakeAssemblyReferencesKind.AllAssemblies);
}

private MethodSymbol GetConstructedMethod(CSharpCompilation compilation, PEMethodSymbol frame, string[] serializedTypeArgumentNames, CSharpInstructionDecoder instructionDecoder)
{
// Once we have the method token, we want to look up the method (again)
// using the same helper as the product code. This helper will also map
// async/iterator "MoveNext" methods to the original source method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ internal abstract class InstructionDecoder<TCompilation, TMethodSymbol, TModuleS
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType | SymbolDisplayMemberOptions.IncludeExplicitInterface,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

internal static readonly SymbolDisplayFormat CompactNameFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
genericsOptions: SymbolDisplayGenericsOptions.None,
memberOptions: SymbolDisplayMemberOptions.IncludeExplicitInterface,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

private readonly bool _useReferencedAssembliesOnly;

internal InstructionDecoder()
Expand All @@ -36,6 +42,8 @@ internal MakeAssemblyReferencesKind GetMakeAssemblyReferencesKind()
return _useReferencedAssembliesOnly ? MakeAssemblyReferencesKind.AllReferences : MakeAssemblyReferencesKind.AllAssemblies;
}

internal abstract string GetCompactName(TMethodSymbol method);

internal abstract void AppendFullName(StringBuilder builder, TMethodSymbol method);

internal virtual void AppendParameterTypeName(StringBuilder builder, IParameterSymbol parameter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@ string IDkmLanguageInstructionDecoder.GetMethodName(DkmLanguageInstructionAddres
// but it was ignored. Furthermore, it's not clear what FullNames would mean with respect
// to argument names in C# or Visual Basic. For consistency with the old behavior, we'll
// just ignore the flag as well.
Debug.Assert((argumentFlags & (DkmVariableInfoFlags.FullNames | DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types)) == argumentFlags,
Debug.Assert((argumentFlags & (DkmVariableInfoFlags.FullNames | DkmVariableInfoFlags.Names | DkmVariableInfoFlags.Types | DkmVariableInfoFlags.CompactName)) == argumentFlags,
Copy link
Member

@jjonescz jjonescz Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious where will the CompactName be used exactly in VS/EE UI? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBD.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjonescz FWIW, here's the doc for the new flag:

  // Summary:
  //     If specified, the expression evaluator will create a very compact name for the
  //     frame. This is intended for UI presentation in cases where screen space is very
  //     limited. The suggested implementation is to return only the method name, with
  //     no namespace, class name, or generic arguments.

jcouv marked this conversation as resolved.
Show resolved Hide resolved
$"Unexpected argumentFlags '{argumentFlags}'");

var instructionAddress = (DkmClrInstructionAddress)languageInstructionAddress.Address;
var compilation = _instructionDecoder.GetCompilation(instructionAddress.ModuleInstance);
var method = _instructionDecoder.GetMethod(compilation, instructionAddress);
var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);

return _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames);
if (argumentFlags.Includes(DkmVariableInfoFlags.CompactName))
{
return _instructionDecoder.GetCompactName(method);
}
else
{
var includeParameterTypes = argumentFlags.Includes(DkmVariableInfoFlags.Types);
var includeParameterNames = argumentFlags.Includes(DkmVariableInfoFlags.Names);

return _instructionDecoder.GetName(method, includeParameterTypes, includeParameterNames);
}
}
catch (NotImplementedMetadataException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Imports System.Reflection.Metadata.Ecma335
Imports Microsoft.CodeAnalysis.ExpressionEvaluator
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE
Imports Microsoft.VisualStudio.Debugger
Imports Microsoft.VisualStudio.Debugger.Clr
Imports System.Text

Expand All @@ -29,6 +28,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Private Sub New()
End Sub

Friend Overrides Function GetCompactName(method As MethodSymbol) As String
Dim symbol = If(method.AssociatedSymbol, method)
Return symbol.ToDisplayString(CompactNameFormat)
End Function

Friend Overrides Sub AppendFullName(builder As StringBuilder, method As MethodSymbol)
Dim parts = method.ToDisplayParts(DisplayFormat)
Dim numParts = parts.Length
Expand Down
Loading
Loading