Skip to content

Initial support for union declarations#82500

Merged
AlekseyTs merged 6 commits into
dotnet:features/Unionsfrom
AlekseyTs:Unions_20
Feb 25, 2026
Merged

Initial support for union declarations#82500
AlekseyTs merged 6 commits into
dotnet:features/Unionsfrom
AlekseyTs:Unions_20

Conversation

@AlekseyTs

@AlekseyTs AlekseyTs commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Supporting:

  • union <name> ...

Supporting:
- ```union <name> ... ```
- ```record union <name> ... ```
@AlekseyTs AlekseyTs requested review from a team as code owners February 23, 2026 19:05
@dotnet-policy-service dotnet-policy-service Bot added the Needs API Review Needs to be reviewed by the API review council label Feb 23, 2026
@AlekseyTs

Copy link
Copy Markdown
Contributor Author

Working group decided to not support record union <name> ... . I will update the PR shortly

@AlekseyTs

Copy link
Copy Markdown
Contributor Author

@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review

// union S1(int);
Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "S1").WithArguments("System.Object").WithLocation(100, 7)
);
}

@jcouv jcouv Feb 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if this already planned for a later PR, consider testing the various modifiers ref union, abstract union, readonly union, unsafe union, ... #Resolved

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also various parameter modifiers for the case types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This PR is meant to enable the scenario, but doesn't include complete test coverage

comp.VerifyEmitDiagnostics(
// (100,10): error CS1031: Type expected
// union S1();
Diagnostic(ErrorCode.ERR_TypeExpected, ")").WithLocation(100, 10)

@333fred 333fred Feb 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this error reported and not CS9401? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why is this error reported and not CS9401?

Grammar requires at least one type, parser reports accordingly. The CS9401 is for cases when the types list is missing in all partial declarations

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is Ok to omit the list, but if the list is not omitted, there must be at least one type

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It feels like we should still be reporting 9401 here. It's odd that the better error text is reported in the scenario where the user typed less.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It feels like we should still be reporting 9401 here.

I am not necessarily sharing your opinion, but I will capture it in a PROTOTYPE comment. I think that the current behavior is sufficiently good for preview

";
var comp = CreateCompilation([src, UnionAttributeSource]);
comp.VerifyEmitDiagnostics(
// (100,15): error CS0111: Type 'S1' already defines a member called 'S1' with the same parameter types

@333fred 333fred Feb 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we perhaps report a better error here? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could we perhaps report a better error here?

Probably, but it is not a priority at the moment.

// union S1(int);
Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "S1").WithArguments("System.Object").WithLocation(100, 7)
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also various parameter modifiers for the case types.

@AlekseyTs AlekseyTs requested review from a team and 333fred February 24, 2026 21:38
Assert.True(members[1].IsImplicitlyDeclared);
Assert.False(members[2].IsStatic);
Assert.True(members[2].IsImplicitlyDeclared);
Assert.False(members[^3].IsStatic);

@RikkiGibson RikkiGibson Feb 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a particular reason for accessing "from start" sometimes and "from end" other times here? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is there a particular reason for accessing "from start" sometimes and "from end" other times here?

This is in artifact of support for record unions. The test was a theory covering both declaration kinds and for records there were some record specific members in the middle


struct_declaration
: attribute_list* modifier* 'struct' identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'?
: attribute_list* modifier* ('struct' | 'union') identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'?

@jcouv jcouv Feb 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Passing on feedback from @CyrusNajmabadi in #82526 (comment)

This is very weird that this is part of struct_declaration when it changes that keyword. I would expect a different type-decl here.

FWIW, I had the same reaction. I found it strange to use the parameter list for the union's case list.
#Resolved

@AlekseyTs AlekseyTs Feb 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The syntax model is a public API and, therefore will go though the standard review process. In this PR, I really don't focus on this aspect. The goal is to enable an ability to use a union declaration in code.

// For extension declarations, there must be a parameter list
var paramList = CurrentToken.Kind == SyntaxKind.OpenParenToken || isExtension
? ParseParenthesizedParameterList(forExtension: isExtension) : null;
? ParseParenthesizedParameterList(forExtensionOrUnion: isExtension || isUnion) : null;

@jcouv jcouv Feb 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Passing on feedback from @CyrusNajmabadi from #82526 (comment)

Imo,this is very difficult to understand precedence here. I would parenthesize for clarity. #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

very difficult to understand precedence here. I would parenthesize for clarity.

The modified condition is already parenthesized:

(forExtensionOrUnion: isExtension || isUnion)

@AlekseyTs AlekseyTs enabled auto-merge (squash) February 25, 2026 18:44
@AlekseyTs AlekseyTs merged commit 49a4215 into dotnet:features/Unions Feb 25, 2026
27 of 28 checks passed
@CyrusNajmabadi

Copy link
Copy Markdown
Contributor

Fwiw, I only noticed this in julien' ide pr, but reusing StructDecl here with a different keyword/kind doesn't fit how I think TypeDecl is intended to be used. Like with RecordDecl, I think this should be it's own TypeDecl subclass. So keep the new UnionDecl syntaxkind, but pair withe a UnionDecl subclass.

If I can be included in the API review, that would be appreciated. Thanks.

@333fred fyi, tnx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Unions Needs API Review Needs to be reviewed by the API review council

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants