Skip to content

Fix parsing of delegate declarations with tuple return types at top-level#80889

Merged
CyrusNajmabadi merged 16 commits intomainfrom
copilot/fix-top-level-delegate-errors
Nov 1, 2025
Merged

Fix parsing of delegate declarations with tuple return types at top-level#80889
CyrusNajmabadi merged 16 commits intomainfrom
copilot/fix-top-level-delegate-errors

Conversation

Copy link
Contributor

Copilot AI commented Oct 24, 2025

  • Understand the issue: delegate declarations with tuple return types fail to parse at top-level
  • Locate the problematic code in LanguageParser.cs (lines 2579-2586)
  • Create comprehensive test cases for the fix
  • Implement fix in LanguageParser.cs to properly distinguish delegate declarations from anonymous delegate expressions
  • Address code review feedback: Use ScanTupleType instead of manual token scanning for consistency with Roslyn's speculative parsing patterns
  • Add WorkItem attribute to tests and add test for anonymous delegates at top-level
  • Fix logic to handle non-tuple parameter lists (e.g., delegate (ref int i) { })
  • Simplify logic to just return true after checking for tuple+identifier case
  • Fix whitespace formatting issues
  • Add comprehensive tests for all anonymous delegate cases
  • Build and test the changes - all tests passing (70 top-level statement tests, 9731 syntax tests total)
  • Run CodeQL security check - no issues found

Summary

Fixed a parser bug where delegate (int, int) I(); at the top level was incorrectly parsed as an anonymous delegate expression instead of a delegate type declaration with a tuple return type.

Solution: Implemented IsAnonymousDelegateExpression() that uses ScanTupleType() to properly distinguish:

  • If delegate ( is followed by a valid tuple type and an identifier → delegate declaration (return false)
  • Otherwise → anonymous delegate expression (return true)

Testing:

  • ✅ All 9,731 syntax parsing tests pass
  • ✅ All 820 delegate semantic tests pass
  • ✅ Test DelegateWithTupleReturnType_TopLevel validates the fix with WorkItem attribute
  • ✅ Test AnonymousDelegateAtTopLevel validates anonymous delegates with variable assignment
  • ✅ Test AnonymousDelegateWithRefParameter_TopLevel validates ref parameter case
  • ✅ Test AnonymousDelegateStandaloneExpression_TopLevel validates standalone anonymous delegate with parameters
  • ✅ Test AnonymousDelegateNoParameters_TopLevel validates anonymous delegate without parameters
  • ✅ CodeQL security check passed
  • ✅ All formatting issues resolved

Fixes #67311

Original prompt

This section details on the original issue you should resolve

<issue_title>Re: Why can't we declare a top-level delegate returning a value-tuple?</issue_title>
<issue_description>Version Used: #error version reports Compiler version: '4.5.0-2.22527.10 (401447a2)'. Language version: 10.0.

Steps to Reproduce:

  1. See discussion: Why can't we declare a top-level delegate returning a value-tuple?

In the following Program.cs listing, only the second to last delegate, I() gives off compile errors.

System.Console.WriteLine("Hello");

delegate int F(int x);
delegate int G((int, int) x);
delegate System.ValueTuple<int, int> H(int x);
delegate (int, int) I(int x);
class _ {
   delegate (int, int) J(int x);
}

As if to compensate for its uniqueness, it gives off tons of errors, actually:

 1: error CS8803: Top-level statements must precede namespace and type declarations.
14: error CS1001: Identifier expected
19: error CS1001: Identifier expected
21: error CS1514: { expected
21: error CS1002: ; expected
23: error CS1525: Invalid expression term 'int'
27: error CS1003: Syntax error, ',' expected

... But not one that would help me understand how I angered the compiler.

Just wondering. 😗

  1. The above using sharplab.

Diagnostic Id: No IDE... diagnostic to show.
Expected Behavior: We can declare value-tuple-returning delegates at top-level.
Actual Behavior: We can't.
</issue_description>

<agent_instructions>Copilot, there seems to be a problem in the parser when determining if something is a legal delegate declaration. Putting delegate (int, int) I(); inside a method, or inside a type is fine. It's only at the top level that there is an issue. This is likely in LanguageParser.cs</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@jcouv This is indeed a bug. Thanks for reporting.

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits October 24, 2025 15:00
…evel

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix compile errors for top-level delegate returning value-tuple Fix parsing of delegate declarations with tuple return types at top-level Oct 24, 2025
Copilot AI requested a review from CyrusNajmabadi October 24, 2025 15:12

// Scan to the matching ')'
int depth = 1;
while (depth > 0 && this.CurrentToken.Kind != SyntaxKind.EndOfFileToken)
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 24, 2025

Choose a reason for hiding this comment

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

@copilot i don't like this part. it's not like how we generally speculatively parse ahead. Can you instead literally just try to use ParseTupleType(), and then see if what follows is an identifier or not?
#Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to use ScanTupleType() instead of manual token scanning. This is more consistent with Roslyn's speculative parsing patterns. See commit 5afa2b3.

EOF();
}

[Fact]
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 24, 2025

Choose a reason for hiding this comment

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

@copilot add WorkItem("https://github.com/dotnet/roslyn/issues/67311") attributes to any tests you add. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added [WorkItem("https://github.com/dotnet/roslyn/issues/67311")] attribute to both tests. See commit 847999a.

delegate int G((int, int) x);
delegate System.ValueTuple<int, int> H();
delegate (int, int) I();
""";
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 24, 2025

Choose a reason for hiding this comment

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

@copilot add test where it is an anonymous delegate at the top level. Also, remove the System.Console.WriteLine from the test. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added test AnonymousDelegateAtTopLevel and removed System.Console.WriteLine from the original test. See commit 847999a.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
return false;
}

// Check if what follows is an open brace (anonymous delegate body)
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 24, 2025

Choose a reason for hiding this comment

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

@copilot i think at this point we should assume we're a delegate-expression, since we didn't have a valid tuple type.

We also need a test for something like delegate (ref int i) { }. With your current logic, this would break (since we wouldn't have a tuple type, but we also wouldn't see the { since we'd stop parsing early. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the logic to return true (anonymous delegate) when ScanTupleType() doesn't succeed, since delegate declarations must have a valid return type. Added test AnonymousDelegateWithRefParameter_TopLevel for the delegate (ref int i) { } case. See commit 96b398c.

…iteLine

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Comment on lines +8700 to +8715
// If we didn't scan a valid tuple type, assume it's an anonymous delegate expression
// since delegate declarations must have a valid return type.
// This handles cases like: delegate (ref int i) { }
if (scanResult != ScanTypeFlags.TupleType)
{
return true;
}

// We have a tuple type but no identifier following - check for open brace
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
return true;
}

// Default to delegate type declaration for other cases
return false;
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 24, 2025

Choose a reason for hiding this comment

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

@copilot why not just simplify from 8700-8715 with just return true; ? I'm saying: once you determine it's not a tuple-followed-by-identifier, just assume it's an anonymous-delegate at that point. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplified to just return true; after the tuple+identifier check. See commit a1fa782.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
delegate (int x) { };
""";

UsingTree(test);
Copy link
Member

Choose a reason for hiding this comment

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

nit: consider verify diagnostics.
And for a scenario that now successfully parsed, consider also verifying diagnostics

@jcouv jcouv self-assigned this Oct 27, 2025
Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (commit 11) with some nits to consider

}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67311")]
public void DelegateWithTupleReturnType_TopLevel()
Copy link
Contributor

Choose a reason for hiding this comment

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

DelegateWithTupleReturnType_TopLevel

It looks like these tests belong in DeclarationParsingTests.cs. This file is for top level statements feature.

Copy link
Contributor

Choose a reason for hiding this comment

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

delegate int F(int x);
delegate int G((int, int) x);
delegate System.ValueTuple<int, int> H();
delegate (int, int) I();
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2025

Choose a reason for hiding this comment

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

delegate (int, int) I();

Consider splitting this test, a test per declaration.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with this.

public void AnonymousDelegateAtTopLevel()
{
var test = """
var f1 = delegate { return 42; };
Copy link
Contributor

Choose a reason for hiding this comment

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

var f1 = delegate { return 42; };

Consider splitting this test

}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67311")]
public void DelegateWithTupleReturnType_TopLevel()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please test scenarios when these declarations are nested into a namespace, nested into a type.

Copy link
Contributor

Choose a reason for hiding this comment

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

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 11)

var test = """
class C
{
delegate (int, int) I();
Copy link
Member

Choose a reason for hiding this comment

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

Please add some versions with tuple names.

Copy link
Member

Choose a reason for hiding this comment

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

It would also be good to have more complicated nested types. Things that ScanType doesn't return with definitely a type, like simple names or qualified names.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Oct 30, 2025

Choose a reason for hiding this comment

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

@CyrusNajmabadi CyrusNajmabadi force-pushed the copilot/fix-top-level-delegate-errors branch from 956c6a1 to c96f7fa Compare October 30, 2025 20:57
@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (squash) October 31, 2025 00:47
@CyrusNajmabadi CyrusNajmabadi merged commit 1ab5f51 into main Nov 1, 2025
23 of 24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 1, 2025
@CyrusNajmabadi CyrusNajmabadi deleted the copilot/fix-top-level-delegate-errors branch November 1, 2025 03:18
@davidwengier davidwengier modified the milestones: Next, 18.3 Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re: Why can't we declare a top-level delegate returning a value-tuple?

6 participants