diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.Parser.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.Parser.cs new file mode 100644 index 0000000000000..0f1d90abad4fc --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.Parser.cs @@ -0,0 +1,157 @@ +// 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. + +#nullable disable + +using System; +using System.Globalization; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal static partial class GeneratedNames + { + internal const char DotReplacementInTypeNames = '-'; + internal const string AnonymousNamePrefix = "<>f__AnonymousType"; + + internal static bool TryParseAnonymousTypeTemplateName(string name, out int index) + { + // No callers require anonymous types from net modules, + // so names with module id are ignored. + if (name.StartsWith(AnonymousNamePrefix, StringComparison.Ordinal)) + { + if (int.TryParse(name.Substring(AnonymousNamePrefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, out index)) + { + return true; + } + } + + index = -1; + return false; + } + + // The type of generated name. See TryParseGeneratedName. + internal static GeneratedNameKind GetKind(string name) + { + GeneratedNameKind kind; + int openBracketOffset; + int closeBracketOffset; + return TryParseGeneratedName(name, out kind, out openBracketOffset, out closeBracketOffset) ? kind : GeneratedNameKind.None; + } + + // Parse the generated name. Returns true for names of the form + // [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain + // generated names, where [middle] and [__[suffix]] are optional, + // and where c is a single character in [1-9a-z] + // (csharp\LanguageAnalysis\LIB\SpecialName.cpp). + internal static bool TryParseGeneratedName( + string name, + out GeneratedNameKind kind, + out int openBracketOffset, + out int closeBracketOffset) + { + openBracketOffset = -1; + if (name.StartsWith("CS$<", StringComparison.Ordinal)) + { + openBracketOffset = 3; + } + else if (name.StartsWith("<", StringComparison.Ordinal)) + { + openBracketOffset = 0; + } + + if (openBracketOffset >= 0) + { + closeBracketOffset = name.IndexOfBalancedParenthesis(openBracketOffset, '>'); + if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length) + { + int c = name[closeBracketOffset + 1]; + if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) // Note '0' is not special. + { + kind = (GeneratedNameKind)c; + return true; + } + } + } + + kind = GeneratedNameKind.None; + openBracketOffset = -1; + closeBracketOffset = -1; + return false; + } + + internal static int IndexOfBalancedParenthesis(this string str, int openingOffset, char closing) + { + char opening = str[openingOffset]; + + int depth = 1; + for (int i = openingOffset + 1; i < str.Length; i++) + { + var c = str[i]; + if (c == opening) + { + depth++; + } + else if (c == closing) + { + depth--; + if (depth == 0) + { + return i; + } + } + } + + return -1; + } + + internal static bool TryParseSourceMethodNameFromGeneratedName(string generatedName, GeneratedNameKind requiredKind, out string methodName) + { + int openBracketOffset; + int closeBracketOffset; + GeneratedNameKind kind; + if (!TryParseGeneratedName(generatedName, out kind, out openBracketOffset, out closeBracketOffset)) + { + methodName = null; + return false; + } + + if (requiredKind != 0 && kind != requiredKind) + { + methodName = null; + return false; + } + + methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + + if (kind.IsTypeName()) + { + methodName = methodName.Replace(DotReplacementInTypeNames, '.'); + } + + return true; + } + + // Extracts the slot index from a name of a field that stores hoisted variables or awaiters. + // Such a name ends with "__{slot index + 1}". + // Returned slot index is >= 0. + internal static bool TryParseSlotIndex(string fieldName, out int slotIndex) + { + int lastUnder = fieldName.LastIndexOf('_'); + if (lastUnder - 1 < 0 || lastUnder == fieldName.Length || fieldName[lastUnder - 1] != '_') + { + slotIndex = -1; + return false; + } + + if (int.TryParse(fieldName.Substring(lastUnder + 1), NumberStyles.None, CultureInfo.InvariantCulture, out slotIndex) && slotIndex >= 1) + { + slotIndex--; + return true; + } + + slotIndex = -1; + return false; + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs index 7f4e73a01a9db..dbd723aa1d739 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs @@ -18,7 +18,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols internal static partial class GeneratedNames { internal const string SynthesizedLocalNamePrefix = "CS$"; - internal const char DotReplacementInTypeNames = '-'; private const string SuffixSeparator = "__"; private const char IdSeparator = '_'; private const char GenerationSeparator = '#'; @@ -72,24 +71,6 @@ internal static string MakeAnonymousTypeTemplateName(int index, int submissionSl return name; } - internal const string AnonymousNamePrefix = "<>f__AnonymousType"; - - internal static bool TryParseAnonymousTypeTemplateName(string name, out int index) - { - // No callers require anonymous types from net modules, - // so names with module id are ignored. - if (name.StartsWith(AnonymousNamePrefix, StringComparison.Ordinal)) - { - if (int.TryParse(name.Substring(AnonymousNamePrefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, out index)) - { - return true; - } - } - - index = -1; - return false; - } - internal static string MakeAnonymousTypeBackingFieldName(string propertyName) { return "<" + propertyName + ">i__Field"; @@ -280,111 +261,12 @@ internal static string MakeHoistedLocalFieldName(SynthesizedLocalKind kind, int return result.ToStringAndFree(); } - // The type of generated name. See TryParseGeneratedName. - internal static GeneratedNameKind GetKind(string name) - { - GeneratedNameKind kind; - int openBracketOffset; - int closeBracketOffset; - return TryParseGeneratedName(name, out kind, out openBracketOffset, out closeBracketOffset) ? kind : GeneratedNameKind.None; - } - - // Parse the generated name. Returns true for names of the form - // [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain - // generated names, where [middle] and [__[suffix]] are optional, - // and where c is a single character in [1-9a-z] - // (csharp\LanguageAnalysis\LIB\SpecialName.cpp). - internal static bool TryParseGeneratedName( - string name, - out GeneratedNameKind kind, - out int openBracketOffset, - out int closeBracketOffset) - { - openBracketOffset = -1; - if (name.StartsWith("CS$<", StringComparison.Ordinal)) - { - openBracketOffset = 3; - } - else if (name.StartsWith("<", StringComparison.Ordinal)) - { - openBracketOffset = 0; - } - - if (openBracketOffset >= 0) - { - closeBracketOffset = name.IndexOfBalancedParenthesis(openBracketOffset, '>'); - if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length) - { - int c = name[closeBracketOffset + 1]; - if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) // Note '0' is not special. - { - kind = (GeneratedNameKind)c; - return true; - } - } - } - - kind = GeneratedNameKind.None; - openBracketOffset = -1; - closeBracketOffset = -1; - return false; - } - - internal static bool TryParseSourceMethodNameFromGeneratedName(string generatedName, GeneratedNameKind requiredKind, out string methodName) - { - int openBracketOffset; - int closeBracketOffset; - GeneratedNameKind kind; - if (!TryParseGeneratedName(generatedName, out kind, out openBracketOffset, out closeBracketOffset)) - { - methodName = null; - return false; - } - - if (requiredKind != 0 && kind != requiredKind) - { - methodName = null; - return false; - } - - methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); - - if (kind.IsTypeName()) - { - methodName = methodName.Replace(DotReplacementInTypeNames, '.'); - } - - return true; - } - internal static string AsyncAwaiterFieldName(int slotIndex) { Debug.Assert((char)GeneratedNameKind.AwaiterField == 'u'); return "<>u__" + StringExtensions.GetNumeral(slotIndex + 1); } - // Extracts the slot index from a name of a field that stores hoisted variables or awaiters. - // Such a name ends with "__{slot index + 1}". - // Returned slot index is >= 0. - internal static bool TryParseSlotIndex(string fieldName, out int slotIndex) - { - int lastUnder = fieldName.LastIndexOf('_'); - if (lastUnder - 1 < 0 || lastUnder == fieldName.Length || fieldName[lastUnder - 1] != '_') - { - slotIndex = -1; - return false; - } - - if (int.TryParse(fieldName.Substring(lastUnder + 1), NumberStyles.None, CultureInfo.InvariantCulture, out slotIndex) && slotIndex >= 1) - { - slotIndex--; - return true; - } - - slotIndex = -1; - return false; - } - internal static string MakeCachedFrameInstanceFieldName() { Debug.Assert((char)GeneratedNameKind.LambdaCacheField == '9'); diff --git a/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs index e959df94b91b2..a06753043991b 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/StringExtensions.cs @@ -226,31 +226,6 @@ internal static string Unquote(this string arg, out bool quoted) } } - internal static int IndexOfBalancedParenthesis(this string str, int openingOffset, char closing) - { - char opening = str[openingOffset]; - - int depth = 1; - for (int i = openingOffset + 1; i < str.Length; i++) - { - var c = str[i]; - if (c == opening) - { - depth++; - } - else if (c == closing) - { - depth--; - if (depth == 0) - { - return i; - } - } - } - - return -1; - } - // String isn't IEnumerable in the current Portable profile. internal static char First(this string arg) { diff --git a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/CSharpFormatter.cs b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/CSharpFormatter.cs index 70ed67b5b6bca..54bf8cc592353 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/CSharpFormatter.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/CSharpFormatter.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.ObjectModel; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.ExpressionEvaluator; using Microsoft.CodeAnalysis.PooledObjects; using Type = Microsoft.VisualStudio.Debugger.Metadata.Type; @@ -47,6 +48,21 @@ internal override string TrimAndGetFormatSpecifiers(string expression, out ReadO return RemoveLeadingAndTrailingContent(expression, 0, expression.Length, IsWhitespace, ch => ch == ';' || IsWhitespace(ch)); } + /// + /// Parses a compiler-generated name and returns the simpler user-visible name. + /// + internal override string PrettifyCompilerGeneratedName(string name) + { + if (!GeneratedNames.TryParseGeneratedName(name, out GeneratedNameKind kind, out int openBracketOffset, out int closeBracketOffset) || + kind == GeneratedNameKind.None) + { + return name; + } + + string result = name.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1); + return result; + } + private static string RemoveComments(string expression) { var pooledBuilder = PooledStringBuilder.GetInstance(); diff --git a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj index e34d41ffff1cc..daaf922106b69 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj +++ b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj @@ -28,6 +28,12 @@ Compiler\SymbolDisplay\ObjectDisplay.cs + + Compiler\Symbols\Synthesized\GeneratedNameKind.cs + + + Compiler\Symbols\Synthesized\GeneratedNames.Parser.cs + diff --git a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.CSharp.ResultProvider.csproj b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.CSharp.ResultProvider.csproj index f9faefef9e5a2..cb9948c963a42 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.CSharp.ResultProvider.csproj +++ b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/Portable/Microsoft.CodeAnalysis.CSharp.ResultProvider.csproj @@ -28,6 +28,12 @@ Compiler\SymbolDisplay\ObjectDisplay.cs + + Compiler\Symbols\Synthesized\GeneratedNameKind.cs + + + Compiler\Symbols\Synthesized\GeneratedNames.Parser.cs + diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj index 077b60fd24523..02406213b5ca3 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj @@ -27,6 +27,12 @@ Compiler\SymbolDisplay\ObjectDisplay.cs + + Compiler\Symbols\Synthesized\GeneratedNameKind.cs + + + Compiler\Symbols\Synthesized\GeneratedNames.Parser.cs + diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Formatter.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Formatter.cs index 4e065bf34ae16..e543089134245 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Formatter.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Formatter.cs @@ -137,6 +137,7 @@ string IDkmClrFullNameProvider.GetClrMemberName( bool memberIsStatic) { string qualifier; + if (memberIsStatic) { bool sawInvalidIdentifier; @@ -160,6 +161,22 @@ string IDkmClrFullNameProvider.GetClrMemberName( { qualifier = parentFullName; } + + if (qualifier.IsCompilerGenerated()) + { + qualifier = PrettifyCompilerGeneratedName(qualifier); + } + + if (memberName.IsCompilerGenerated()) + { + memberName = PrettifyCompilerGeneratedName(memberName); + } + + if (string.IsNullOrEmpty(qualifier)) + { + return memberName; + } + return $"{qualifier}.{memberName}"; } @@ -188,6 +205,8 @@ string IDkmClrFullNameProvider.GetClrExpressionForThis(DkmInspectionContext insp internal abstract bool IsWhitespace(char c); + internal abstract string PrettifyCompilerGeneratedName(string name); + // Note: We could be less conservative (e.g. "new C()"). private bool NeedsParentheses(string expr) { diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/VisualBasicFormatter.vb b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/VisualBasicFormatter.vb index 176141a0807e9..d44cfa98f9015 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/VisualBasicFormatter.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/VisualBasicFormatter.vb @@ -49,6 +49,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return RemoveLeadingAndTrailingWhitespace(expression) End Function + Friend Overrides Function PrettifyCompilerGeneratedName(expression As String) As String + Return expression + End Function + Private Shared Function RemoveComments(expression As String) As String ' Workaround for https://dev.azure.com/devdiv/DevDiv/_workitems/edit/847849 ' Do not remove any comments that might be in a string.