|
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal static class GeneratedNames
{
internal static bool IsGeneratedMemberName(string memberName)
{
return memberName.Length > 0 && memberName[0] == '<';
}
internal static string MakeBackingFieldName(string propertyName)
{
Debug.Assert((char)GeneratedNameKind.AutoPropertyBackingField == 'k');
return "<" + propertyName + ">k__BackingField";
}
internal static string MakePrimaryConstructorParameterFieldName(string parameterName)
{
Debug.Assert((char)GeneratedNameKind.PrimaryConstructorParameter == 'P');
return "<" + parameterName + ">P";
}
internal static string MakeIteratorFinallyMethodName(StateMachineState finalizeState)
{
Debug.Assert((int)finalizeState < -2);
// It is important that the name is only derived from the finalizeState, so that when
// editing method during EnC the Finally methods corresponding to matching states have matching names.
Debug.Assert((char)GeneratedNameKind.IteratorFinallyMethod == 'm');
return "<>m__Finally" + StringExtensions.GetNumeral(-((int)finalizeState + 2));
}
internal static string MakeStaticLambdaDisplayClassName(int methodOrdinal, int generation)
{
return MakeMethodScopedSynthesizedName(GeneratedNameKind.LambdaDisplayClass, methodOrdinal, generation);
}
internal static string MakeLambdaDisplayClassName(int methodOrdinal, int generation, int closureOrdinal, int closureGeneration)
{
// -1 for singleton static lambdas
Debug.Assert(closureOrdinal >= -1);
Debug.Assert(methodOrdinal >= 0);
return MakeMethodScopedSynthesizedName(GeneratedNameKind.LambdaDisplayClass, methodOrdinal, generation, suffix: "DisplayClass", entityOrdinal: closureOrdinal, entityGeneration: closureGeneration);
}
internal static string MakeAnonymousTypeOrDelegateTemplateName(int index, int submissionSlotIndex, string moduleId, bool isDelegate)
{
var name = "<" + moduleId + (isDelegate ? ">f__AnonymousDelegate" : ">f__AnonymousType") + StringExtensions.GetNumeral(index);
if (submissionSlotIndex >= 0)
{
name += "#" + StringExtensions.GetNumeral(submissionSlotIndex);
}
return name;
}
internal static string MakeAnonymousTypeBackingFieldName(string propertyName)
{
return "<" + propertyName + ">i__Field";
}
internal static string MakeAnonymousTypeParameterName(string propertyName)
{
return "<" + propertyName + ">j__TPar";
}
internal static string MakeStateMachineTypeName(string methodName, int methodOrdinal, int generation)
{
Debug.Assert(generation >= 0);
Debug.Assert(methodOrdinal >= -1);
return MakeMethodScopedSynthesizedName(GeneratedNameKind.StateMachineType, methodOrdinal, generation, methodName);
}
internal static string MakeBaseMethodWrapperName(int uniqueId)
{
Debug.Assert((char)GeneratedNameKind.BaseMethodWrapper == 'n');
return "<>n__" + StringExtensions.GetNumeral(uniqueId);
}
internal static string MakeLambdaMethodName(string methodName, int methodOrdinal, int methodGeneration, int lambdaOrdinal, int lambdaGeneration)
{
Debug.Assert(methodOrdinal >= -1);
Debug.Assert(methodGeneration >= 0);
Debug.Assert(lambdaOrdinal >= 0);
Debug.Assert(lambdaGeneration >= 0);
// The EE displays the containing method name and unique id in the stack trace,
// and uses it to find the original binding context.
return MakeMethodScopedSynthesizedName(GeneratedNameKind.LambdaMethod, methodOrdinal, methodGeneration, methodName, entityOrdinal: lambdaOrdinal, entityGeneration: lambdaGeneration);
}
internal static string MakeLambdaCacheFieldName(int methodOrdinal, int generation, int lambdaOrdinal, int lambdaGeneration)
{
Debug.Assert(methodOrdinal >= -1);
Debug.Assert(lambdaOrdinal >= 0);
return MakeMethodScopedSynthesizedName(GeneratedNameKind.LambdaCacheField, methodOrdinal, generation, entityOrdinal: lambdaOrdinal, entityGeneration: lambdaGeneration);
}
internal static string MakeLocalFunctionName(string methodName, string localFunctionName, int methodOrdinal, int methodGeneration, int lambdaOrdinal, int lambdaGeneration)
{
Debug.Assert(methodOrdinal >= -1);
Debug.Assert(methodGeneration >= 0);
Debug.Assert(lambdaOrdinal >= 0);
Debug.Assert(lambdaGeneration >= 0);
return MakeMethodScopedSynthesizedName(GeneratedNameKind.LocalFunction, methodOrdinal, methodGeneration, methodName, localFunctionName, GeneratedNameConstants.LocalFunctionNameTerminator, lambdaOrdinal, lambdaGeneration);
}
private static string MakeMethodScopedSynthesizedName(
GeneratedNameKind kind,
int methodOrdinal,
int methodGeneration,
string? methodName = null,
string? suffix = null,
char suffixTerminator = default,
int entityOrdinal = -1,
int entityGeneration = -1)
{
Debug.Assert(methodOrdinal >= -1);
Debug.Assert(methodGeneration >= 0 || methodGeneration == -1 && methodOrdinal == -1);
Debug.Assert(entityOrdinal >= -1);
Debug.Assert(entityGeneration >= 0 || entityGeneration == -1 && entityOrdinal == -1);
Debug.Assert(entityGeneration == -1 || entityGeneration >= methodGeneration);
Debug.Assert(suffixTerminator != GeneratedNameConstants.IdSeparator);
var result = PooledStringBuilder.GetInstance();
var builder = result.Builder;
builder.Append('<');
if (methodName != null)
{
builder.Append(methodName);
// CLR generally allows names with dots, however some APIs like IMetaDataImport
// can only return full type names combined with namespaces.
// see: http://msdn.microsoft.com/en-us/library/ms230143.aspx (IMetaDataImport::GetTypeDefProps)
// When working with such APIs, names with dots become ambiguous since metadata
// consumer cannot figure where namespace ends and actual type name starts.
// Therefore it is a good practice to avoid type names with dots.
// As a replacement use a character not allowed in C# identifier to avoid conflicts.
if (kind.IsTypeName())
{
builder.Replace('.', GeneratedNameConstants.DotReplacementInTypeNames);
}
}
builder.Append('>');
builder.Append((char)kind);
if (suffix != null || methodOrdinal >= 0 || entityOrdinal >= 0)
{
builder.Append(GeneratedNameConstants.SuffixSeparator);
builder.Append(suffix);
if (suffixTerminator != default)
{
builder.Append(suffixTerminator);
}
if (methodOrdinal >= 0)
{
builder.Append(methodOrdinal);
AppendOptionalGeneration(builder, methodGeneration);
}
if (entityOrdinal >= 0)
{
if (methodOrdinal >= 0)
{
builder.Append(GeneratedNameConstants.IdSeparator);
}
builder.Append(entityOrdinal);
AppendOptionalGeneration(builder, entityGeneration);
}
}
return result.ToStringAndFree();
}
private static void AppendOptionalGeneration(StringBuilder builder, int generation)
{
if (generation > 0)
{
builder.Append(GeneratedNameConstants.GenerationSeparator);
builder.Append(generation);
}
}
internal static string MakeHoistedLocalFieldName(SynthesizedLocalKind kind, int slotIndex, string? localName = null)
{
Debug.Assert((localName != null) == (kind == SynthesizedLocalKind.UserDefined));
Debug.Assert(slotIndex >= 0);
Debug.Assert(kind.IsLongLived());
// Lambda display class local follows a different naming pattern.
// EE depends on the name format.
// There's logic in the EE to recognize locals that have been captured by a lambda
// and would have been hoisted for the state machine. Basically, we just hoist the local containing
// the instance of the lambda display class and retain its original name (rather than using an
// iterator local name). See FUNCBRECEE::ImportIteratorMethodInheritedLocals.
var result = PooledStringBuilder.GetInstance();
var builder = result.Builder;
builder.Append('<');
if (localName != null)
{
Debug.Assert(localName.IndexOf('.') == -1);
builder.Append(localName);
}
builder.Append('>');
if (kind == SynthesizedLocalKind.LambdaDisplayClass)
{
builder.Append((char)GeneratedNameKind.DisplayClassLocalOrField);
}
else if (kind == SynthesizedLocalKind.UserDefined)
{
builder.Append((char)GeneratedNameKind.HoistedLocalField);
}
else
{
builder.Append((char)GeneratedNameKind.HoistedSynthesizedLocalField);
}
builder.Append("__");
builder.Append(slotIndex + 1);
return result.ToStringAndFree();
}
internal static string AsyncAwaiterFieldName(int slotIndex)
{
Debug.Assert((char)GeneratedNameKind.AwaiterField == 'u');
return "<>u__" + StringExtensions.GetNumeral(slotIndex + 1);
}
internal static string MakeCachedFrameInstanceFieldName()
{
Debug.Assert((char)GeneratedNameKind.LambdaCacheField == '9');
return "<>9";
}
internal static string? MakeSynthesizedLocalName(SynthesizedLocalKind kind, ref int uniqueId)
{
Debug.Assert(kind.IsLongLived());
// Lambda display class local has to be named. EE depends on the name format.
if (kind == SynthesizedLocalKind.LambdaDisplayClass)
{
return MakeLambdaDisplayLocalName(uniqueId++);
}
return null;
}
internal static string MakeSynthesizedInstrumentationPayloadLocalFieldName(int uniqueId)
{
return GeneratedNameConstants.SynthesizedLocalNamePrefix + "InstrumentationPayload" + StringExtensions.GetNumeral(uniqueId);
}
internal static string MakeLambdaDisplayLocalName(int uniqueId)
{
Debug.Assert((char)GeneratedNameKind.DisplayClassLocalOrField == '8');
return GeneratedNameConstants.SynthesizedLocalNamePrefix + "<>8__locals" + StringExtensions.GetNumeral(uniqueId);
}
internal static string MakeFixedFieldImplementationName(string fieldName)
{
// the native compiler adds numeric digits at the end. Roslyn does not.
Debug.Assert((char)GeneratedNameKind.FixedBufferField == 'e');
return "<" + fieldName + ">e__FixedBuffer";
}
internal static string MakeStateMachineStateFieldName()
{
// Microsoft.VisualStudio.VIL.VisualStudioHost.AsyncReturnStackFrame depends on this name.
Debug.Assert((char)GeneratedNameKind.StateMachineStateField == '1');
return "<>1__state";
}
internal static string MakeAsyncIteratorPromiseOfValueOrEndFieldName()
{
Debug.Assert((char)GeneratedNameKind.AsyncIteratorPromiseOfValueOrEndBackingField == 'v');
return "<>v__promiseOfValueOrEnd";
}
internal static string MakeAsyncIteratorCombinedTokensFieldName()
{
Debug.Assert((char)GeneratedNameKind.CombinedTokensField == 'x');
return "<>x__combinedTokens";
}
internal static string MakeIteratorCurrentFieldName()
{
Debug.Assert((char)GeneratedNameKind.IteratorCurrentBackingField == '2');
return "<>2__current";
}
internal static string MakeDisposeModeFieldName()
{
Debug.Assert((char)GeneratedNameKind.DisposeModeField == 'w');
return "<>w__disposeMode";
}
internal static string MakeIteratorCurrentThreadIdFieldName()
{
Debug.Assert((char)GeneratedNameKind.IteratorCurrentThreadIdField == 'l');
return "<>l__initialThreadId";
}
internal static string MakeStateMachineStateIdFieldName()
{
Debug.Assert((char)GeneratedNameKind.StateMachineStateIdField == 'I');
return "<>I";
}
internal static string ThisProxyFieldName()
{
Debug.Assert((char)GeneratedNameKind.ThisProxyField == '4');
return "<>4__this";
}
internal static string StateMachineThisParameterProxyName()
{
return StateMachineParameterProxyFieldName(ThisProxyFieldName());
}
internal static string StateMachineParameterProxyFieldName(string parameterName)
{
Debug.Assert((char)GeneratedNameKind.StateMachineParameterProxyField == '3');
return "<>3__" + parameterName;
}
internal static string MakeDynamicCallSiteContainerName(int methodOrdinal, int localFunctionOrdinal, int generation)
{
return MakeMethodScopedSynthesizedName(GeneratedNameKind.DynamicCallSiteContainerType, methodOrdinal, generation,
suffix: localFunctionOrdinal != -1 ? localFunctionOrdinal.ToString() : null,
suffixTerminator: localFunctionOrdinal != -1 ? '|' : default);
}
internal static string MakeDynamicCallSiteFieldName(int uniqueId)
{
Debug.Assert((char)GeneratedNameKind.DynamicCallSiteField == 'p');
return "<>p__" + StringExtensions.GetNumeral(uniqueId);
}
internal const string AnonymousTypeNameWithoutModulePrefix = "<>f__AnonymousType";
internal const string AnonymousDelegateNameWithoutModulePrefix = "<>f__AnonymousDelegate";
internal const string ActionDelegateNamePrefix = "<>A";
internal const string FuncDelegateNamePrefix = "<>F";
private const int DelegateNamePrefixLength = 3;
private const int DelegateNamePrefixLengthWithOpenBrace = 4;
/// <summary>
/// Produces name of the synthesized delegate symbol that encodes the parameter byref-ness and return type of the delegate.
/// The arity is appended via `N suffix in MetadataName calculation since the delegate is generic.
/// </summary>
/// <remarks>
/// Logic here should match <see cref="TryParseSynthesizedDelegateName" />.
/// </remarks>
internal static string MakeSynthesizedDelegateName(RefKindVector byRefs, bool returnsVoid, int generation)
{
var pooledBuilder = PooledStringBuilder.GetInstance();
var builder = pooledBuilder.Builder;
builder.Append(returnsVoid ? ActionDelegateNamePrefix : FuncDelegateNamePrefix);
if (!byRefs.IsNull)
{
builder.Append(byRefs.ToRefKindString());
}
AppendOptionalGeneration(builder, generation);
return pooledBuilder.ToStringAndFree();
}
/// <summary>
/// Parses the name of a synthesized delegate out into the things it represents.
/// </summary>
/// <remarks>
/// Logic here should match <see cref="MakeSynthesizedDelegateName" />.
/// </remarks>
internal static bool TryParseSynthesizedDelegateName(string name, out RefKindVector byRefs, out bool returnsVoid, out int generation, out int parameterCount)
{
byRefs = default;
parameterCount = 0;
generation = 0;
name = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(name, out var arity);
returnsVoid = name.StartsWith(ActionDelegateNamePrefix);
if (!returnsVoid && !name.StartsWith(FuncDelegateNamePrefix))
{
return false;
}
parameterCount = arity - (returnsVoid ? 0 : 1);
// If there are no ref kinds encoded
// (and therefore no braces), use the end of the prefix instead.
var nameEndIndex = name.LastIndexOf('}');
if (nameEndIndex < 0)
{
nameEndIndex = DelegateNamePrefixLength - 1;
}
else
{
// There should be a character after the prefix, and it should be an open brace
if (name.Length <= DelegateNamePrefixLength || name[DelegateNamePrefixLength] != '{')
{
return false;
}
// If there are braces, then the ref kind string is encoded between them
var refKindString = name[DelegateNamePrefixLengthWithOpenBrace..nameEndIndex];
if (!RefKindVector.TryParse(refKindString, arity, out byRefs))
{
return false;
}
}
// If there is a generation index it will be directly after the brace, otherwise the brace
// is the last character
if (nameEndIndex < name.Length - 1)
{
// Format is a '#' followed by the generation number
if (name[nameEndIndex + 1] != GeneratedNameConstants.GenerationSeparator)
{
return false;
}
if (!int.TryParse(name[(nameEndIndex + 2)..], out generation))
{
return false;
}
}
Debug.Assert(name == MakeSynthesizedDelegateName(byRefs, returnsVoid, generation));
return true;
}
internal static string MakeSynthesizedInlineArrayName(int arrayLength, int generation)
{
Debug.Assert((char)GeneratedNameKind.InlineArrayType == 'y');
var name = "<>y__InlineArray" + arrayLength;
// Synthesized inline arrays need to have unique name across generations because they are not reused.
return (generation > 0) ? name + GeneratedNameConstants.GenerationSeparator + generation : name;
}
internal static string MakeSynthesizedReadOnlyListName(SynthesizedReadOnlyListKind kind, int generation)
{
Debug.Assert((char)GeneratedNameKind.ReadOnlyListType == 'z');
string name = kind switch
{
SynthesizedReadOnlyListKind.Array => "<>z__ReadOnlyArray",
SynthesizedReadOnlyListKind.List => "<>z__ReadOnlyList",
SynthesizedReadOnlyListKind.SingleElement => "<>z__ReadOnlySingleElementList",
var v => throw ExceptionUtilities.UnexpectedValue(v)
};
// Synthesized list types need to have unique name across generations because they are not reused.
return (generation > 0) ? name + CommonGeneratedNames.GenerationSeparator + generation : name;
}
internal static string AsyncBuilderFieldName()
{
// Microsoft.VisualStudio.VIL.VisualStudioHost.AsyncReturnStackFrame depends on this name.
Debug.Assert((char)GeneratedNameKind.AsyncBuilderField == 't');
return "<>t__builder";
}
internal static string DelegateCacheContainerType(int generation, string? methodName = null, int methodOrdinal = -1, int ownerUniqueId = -1)
{
const char NameKind = (char)GeneratedNameKind.DelegateCacheContainerType;
var result = PooledStringBuilder.GetInstance();
var builder = result.Builder;
builder.Append('<').Append(methodName).Append('>').Append(NameKind);
if (methodOrdinal > -1)
{
builder.Append(GeneratedNameConstants.SuffixSeparator).Append(methodOrdinal);
}
if (ownerUniqueId > -1)
{
builder.Append(GeneratedNameConstants.IdSeparator).Append(ownerUniqueId);
}
AppendOptionalGeneration(builder, generation);
return result.ToStringAndFree();
}
internal static string DelegateCacheContainerFieldName(int id, string targetMethod)
{
var result = PooledStringBuilder.GetInstance();
var builder = result.Builder;
builder.Append('<').Append(id).Append(">__").Append(targetMethod);
return result.ToStringAndFree();
}
internal static string ReusableHoistedLocalFieldName(int number)
{
Debug.Assert((char)GeneratedNameKind.ReusableHoistedLocalField == '7');
return "<>7__wrap" + StringExtensions.GetNumeral(number);
}
internal static string LambdaCopyParameterName(int ordinal)
{
return "<p" + StringExtensions.GetNumeral(ordinal) + ">";
}
internal static string AnonymousDelegateParameterName(int index, int parameterCount)
{
// SPEC: parameter names arg1, ..., argn or arg if a single parameter
if (parameterCount == 1)
{
return "arg";
}
return "arg" + StringExtensions.GetNumeral(index + 1);
}
internal static string MakeFileTypeMetadataNamePrefix(string filePath, ImmutableArray<byte> checksumOpt)
{
var pooledBuilder = PooledStringBuilder.GetInstance();
var sb = pooledBuilder.Builder;
sb.Append('<');
AppendFileName(filePath, sb);
sb.Append('>');
sb.Append((char)GeneratedNameKind.FileType);
if (checksumOpt.IsDefault)
{
// Note: this is an error condition.
// This is only included for clarity for users inspecting the value of 'MetadataName'.
sb.Append("<no checksum>");
}
else
{
foreach (var b in checksumOpt)
{
sb.AppendFormat("{0:X2}", b);
}
}
sb.Append("__");
return pooledBuilder.ToStringAndFree();
}
internal static string GetDisplayFilePath(string filePath)
{
var pooledBuilder = PooledStringBuilder.GetInstance();
AppendFileName(filePath, pooledBuilder.Builder);
return pooledBuilder.ToStringAndFree();
}
private static void AppendFileName(string filePath, StringBuilder sb)
{
var fileName = FileNameUtilities.GetFileName(filePath, includeExtension: false);
if (fileName is null)
{
return;
}
foreach (var ch in fileName)
{
sb.Append(ch switch
{
>= 'a' and <= 'z' => ch,
>= 'A' and <= 'Z' => ch,
>= '0' and <= '9' => ch,
_ => '_'
});
}
}
}
}
|