Skip to content

Add parsing of StateMachine GeneratedNames to StackFrameParser #79073

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 @@ -420,7 +420,6 @@ public void TestFileInformation_InvalidDirectory()
[InlineData(@"at M.1c()")] // Invalid start character for identifier
[InlineData(@"at 1M.C()")]
[InlineData(@"at M.C(string& s)")] // "string&" represents a reference (ref, out) and is not supported yet
[InlineData(@"at StreamJsonRpc.JsonRpc.<InvokeCoreAsync>d__139`1.MoveNext()")] // Generated/Inline methods are not supported yet
[InlineData(@"at M(")] // Missing closing paren
[InlineData(@"at M)")] // MIssing open paren
[InlineData(@"at M.M[T>(T t)")] // Mismatched generic opening/close
Expand Down Expand Up @@ -519,4 +518,18 @@ public void TestLanguages(string at, string @in, string line)
line: CreateToken(StackFrameKind.NumberToken, "16", leadingTrivia: [CreateTrivia(StackFrameKind.LineTrivia, $"{line} ")]),
inTrivia: CreateTrivia(StackFrameKind.InTrivia, $" {@in} "))
);

[Fact]
public void TestStateMachineMethod()
=> Verify("Test.<MyAsyncMethod>d__610.MoveNext()",
methodDeclaration: MethodDeclaration(
QualifiedName(
Identifier("Test"),
StateMachineMethod(
GeneratedName("MyAsyncMethod", endWithDollar: false),
suffix: "610",
stateMachineMethod: "MoveNext")
),
argumentList: EmptyParams)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,11 @@ public static StackFrameLocalMethodNameNode LocalMethod(StackFrameGeneratedMetho
IdentifierToken(identifier),
PipeToken,
CreateToken(StackFrameKind.GeneratedNameSuffixToken, suffix));

public static StackFrameStateMachineMethodNameNode StateMachineMethod(StackFrameGeneratedMethodNameNode encapsulatingMethod, string suffix, string stateMachineMethod)
=> new(
encapsulatingMethod,
CreateToken(StackFrameKind.GeneratedNameSeparatorToken, "d__" + suffix),
DotToken,
IdentifierToken(stateMachineMethod));
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ internal interface IStackFrameNodeVisitor
void Visit(StackFrameGeneratedMethodNameNode stackFrameGeneratedNameNode);
void Visit(StackFrameLocalMethodNameNode stackFrameLocalMethodNameNode);
void Visit(StackFrameConstructorNode constructorNode);
void Visit(StackFrameStateMachineMethodNameNode stackFrameStateMachineMethodNameNode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal enum StackFrameKind
GenericTypeIdentifier,
GeneratedIdentifier,
LocalMethodIdentifier,
StateMachineMethodIdentifier,
TypeArgument,
TypeIdentifier,
Parameter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,10 @@ public Result<StackFrameToken> TryScanPath()

/// <summary>
/// Scans a form similar to g__, where g is a GeneratedNameKind (a single character)
/// and identifier is valid identifier characters as with <see cref="TryScanIdentifier()"/>
/// and identifier is valid identifier characters as with <see cref="TryScanIdentifier()"/>.
/// If <paramref name="scanNumericsAfter"/> is true, it will also scan for a numeric suffix after "__"
/// </summary>
public Result<StackFrameToken> TryScanRequiredGeneratedNameSeparator()
public Result<StackFrameToken> TryScanRequiredGeneratedNameSeparator(bool scanNumericsAfter = false)
{
var start = Position;
if (IsAsciiAlphaCharacter(CurrentChar))
Expand All @@ -348,6 +349,14 @@ public Result<StackFrameToken> TryScanRequiredGeneratedNameSeparator()
return Result<StackFrameToken>.Abort;
}

if (scanNumericsAfter)
{
while (IsNumber(CurrentChar))
{
Position++;
}
}

return CreateToken(StackFrameKind.GeneratedNameSeparatorToken, GetSubSequenceToCurrentPos(start));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,17 @@ protected StackFrameGeneratedNameNode(StackFrameToken identifier, StackFrameKind
}

/// <summary>
/// Generated methods follow the pattern Namespace.ClassName.&gt;MethodName$&lt;(), where
/// the "$" is optional.
/// Generated methods follow the pattern Namespace.ClassName.&gt;MethodName$&lt;Suffix(), where
/// the "$" and "Suffix" are optional.
/// </summary>
internal sealed class StackFrameGeneratedMethodNameNode : StackFrameGeneratedNameNode
{
public readonly StackFrameToken LessThanToken;
public readonly StackFrameToken GreaterThanToken;
public readonly StackFrameToken? DollarToken;
public readonly StackFrameToken? Suffix;

internal override int ChildCount => 4;
internal override int ChildCount => 5;

public StackFrameGeneratedMethodNameNode(StackFrameToken lessThanToken, StackFrameToken identifier, StackFrameToken greaterThanToken, StackFrameToken? dollarToken)
: base(identifier, StackFrameKind.GeneratedIdentifier)
Expand All @@ -238,6 +239,7 @@ internal override StackFrameNodeOrToken ChildAt(int index)
1 => Identifier,
2 => GreaterThanToken,
3 => DollarToken.HasValue ? DollarToken.Value : null,
4 => Suffix.HasValue ? Suffix.Value : null,
_ => throw new InvalidOperationException()
};
}
Expand Down Expand Up @@ -296,6 +298,44 @@ internal override StackFrameNodeOrToken ChildAt(int index)
};
}

internal sealed class StackFrameStateMachineMethodNameNode : StackFrameGeneratedNameNode
{
internal readonly StackFrameGeneratedMethodNameNode EncapsulatingMethod;
internal readonly StackFrameToken GeneratedNameSeparator;
internal readonly StackFrameToken DotToken;

internal override int ChildCount => 4;

public StackFrameStateMachineMethodNameNode(
StackFrameGeneratedMethodNameNode encapsulatngMethod,
StackFrameToken generatedNameSeparator,
StackFrameToken dotToken,
StackFrameToken stateMachineIdentifier)
: base(stateMachineIdentifier, StackFrameKind.StateMachineMethodIdentifier)
{
Debug.Assert(generatedNameSeparator.Kind == StackFrameKind.GeneratedNameSeparatorToken);
Debug.Assert(dotToken.Kind == StackFrameKind.DotToken);
Debug.Assert(stateMachineIdentifier.Kind == StackFrameKind.IdentifierToken);

EncapsulatingMethod = encapsulatngMethod;
GeneratedNameSeparator = generatedNameSeparator;
DotToken = dotToken;
}

public override void Accept(IStackFrameNodeVisitor visitor)
=> visitor.Visit(this);

internal override StackFrameNodeOrToken ChildAt(int index)
=> index switch
{
0 => EncapsulatingMethod,
1 => GeneratedNameSeparator,
2 => DotToken,
3 => Identifier,
_ => throw new InvalidOperationException()
};
}

/// <summary>
/// Represents an array type declaration, such as string[,][]
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ private Result<StackFrameQualifiedNameNode> TryParseQualifiedName(StackFrameName
/// ^----^--------------- "Local" is the name of the local function
/// ^---^----------- "|0_0" is suffix information such as slot
/// ^--------^- "(String s)" identifiers the method paramters
/// 3. StateMachineMethodName
/// Program.$lt;Main$gt;d__6.MoveNext()
/// ^---------------------------- Beginning of generated name
/// ^---^-------------------- Encapsulating method name
/// ^---------------- "d__6" identifies this as a state machine method
/// ^-------^--- "MoveNext" is the name of the method in the state machine
///
///
/// </code>
/// </summary>
private Result<StackFrameGeneratedNameNode> TryScanGeneratedName()
Expand Down Expand Up @@ -284,39 +292,70 @@ private Result<StackFrameGeneratedNameNode> TryScanGeneratedName()

// Check for generated name kinds we can handle
// See https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs
if (currentChar == 'g')
switch (currentChar)
{
// Local function
var encapsulatingMethod = new StackFrameGeneratedMethodNameNode(lessThanToken, identifier.Value, greaterThanToken, dollarToken: null);
var (success, generatedNameSeparator) = _lexer.TryScanRequiredGeneratedNameSeparator();
if (!success)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}
case 'g': // Local function
return TryParseLocalMethodName(lessThanToken, identifier.Value, greaterThanToken);

var generatedIdentifier = _lexer.TryScanIdentifier();
if (!generatedIdentifier.HasValue)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}
case 'd': // State Machine (such as async methods)
return TryParseStateMachineMethodName(lessThanToken, identifier.Value, greaterThanToken);

if (!_lexer.ScanCurrentCharAsTokenIfMatch(StackFrameKind.PipeToken, out var suffixSeparator))
{
default:
return Result<StackFrameGeneratedNameNode>.Abort;
}
}
}

(success, var suffix) = _lexer.TryScanRequiredGeneratedNameSuffix();
if (!success)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}
private Result<StackFrameGeneratedNameNode> TryParseStateMachineMethodName(StackFrameToken lessThanToken, StackFrameToken identifier, StackFrameToken greaterThanToken)
{
var encapsulatingMethod = new StackFrameGeneratedMethodNameNode(lessThanToken, identifier, greaterThanToken, dollarToken: null);
var (success, generatedNameSeparator) = _lexer.TryScanRequiredGeneratedNameSeparator(scanNumericsAfter: true);
if (!success)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

return new StackFrameLocalMethodNameNode(encapsulatingMethod, generatedNameSeparator, generatedIdentifier.Value, suffixSeparator, suffix);
if (!_lexer.ScanCurrentCharAsTokenIfMatch(StackFrameKind.DotToken, out var dotToken))
{
return Result<StackFrameGeneratedNameNode>.Abort;
}
else

var generatedIdentifier = _lexer.TryScanIdentifier();
if (!generatedIdentifier.HasValue)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

return new StackFrameStateMachineMethodNameNode(encapsulatingMethod, generatedNameSeparator, dotToken, generatedIdentifier.Value);
}

private Result<StackFrameGeneratedNameNode> TryParseLocalMethodName(StackFrameToken lessThanToken, StackFrameToken identifier, StackFrameToken greaterThanToken)
{

var encapsulatingMethod = new StackFrameGeneratedMethodNameNode(lessThanToken, identifier, greaterThanToken, dollarToken: null);
var (success, generatedNameSeparator) = _lexer.TryScanRequiredGeneratedNameSeparator();
if (!success)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

var generatedIdentifier = _lexer.TryScanIdentifier();
if (!generatedIdentifier.HasValue)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

if (!_lexer.ScanCurrentCharAsTokenIfMatch(StackFrameKind.PipeToken, out var suffixSeparator))
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

(success, var suffix) = _lexer.TryScanRequiredGeneratedNameSuffix();
if (!success)
{
return Result<StackFrameGeneratedNameNode>.Abort;
}

return new StackFrameLocalMethodNameNode(encapsulatingMethod, generatedNameSeparator, generatedIdentifier.Value, suffixSeparator, suffix);
}

/// <summary>
Expand Down
Loading