Skip to content

Reorganize how record classes (and structs) are spec'd #1683

Closed

Description

@RexJaeschke

During the 2026-05-06 TG2 call, we had a lengthy discussion about whether record is a modifier on a class/struct, or a different type entirely. We ended up with the following position:

  1. We'll treat record as a modifier. (This is how it is currently treated in Draft PR #1458.)
  2. Add a new subclause to the end of 15, “Classes” that describes the diffs between a record class and a non-record class, and remove from the current Draft PR mentions of “record [class]” in earlier subclauses in that clause. This allows all information about a record class to be found in one subclause rather than it being spread around multiple earlier subclauses. A reader of the earlier subclauses not interested in record classes will no longer be distracted by text on that topic.
  3. [Repeat for structs] Add a new subclause to the end of 16, “Structs” that describes the diffs between a record struct and a non-record struct, and remove from the current Draft PR mentions of “record struct” in earlier subclauses in that clause. This allows all information about a record struct to be found in one subclause rather than it being spread around multiple earlier subclauses. A reader of the earlier subclauses not interested in record structs will no longer be distracted by text on that topic.

The reorganization needs to accommodate the following:

  1. Addition of record classes in V9.
  2. Addition of record structs in V10.
  3. Allowance of primary constructors in non-record classes/structs in V12.

Activity

  1. self-assigned this
    on May 9, 2026
  2. added this to the C# 9.0 milestone on May 9, 2026
  3. added
    status: in-progressThe assignee has actively started working on this; consult with them before taking other actions
    on May 9, 2026
  4. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    This coming week, I plan to start changing PR #1458 so that record-related content is almost entirely removed from the existing Clause 15 subclauses, and is placed into a new 15.x subclause that is record-class-specific. (Below, I will mention why we should leave minimal hooks to records in at least 15.2.1, “General.”)

    Here’s my current thinking of the corresponding (new) subclause organization for record classes:

    • 15.x Record classes (see Step 3 below)
      • 15.x.1 General (see Step 3 below)
      • 15.x.2 Class base specification (see Step 5 below)
      • 15.x.3 Record class body (see Step 6 below)
      • 15.x.4 Class members (see Step 7 below)
      • 15.x.5 Instance constructors (see Step 8 below)
      • 15.x.6 Copy constructors (see Step 9 below)
      • 15.x.7 Synthesized record class members (see Step 10 below)
      • 15.x.7.1 …

    Below is the series of Steps I plan to follow with each one shown as a separate comment. Please indicate your thumbs-up/down reaction for each Step, and, as necessary, add a related comment.

    Once all these Steps have been executed, only two existing subclauses will mention records, and those mentions will each be in a grammar rule and there will be a related forward pointer to the corresponding record class subclauses.

  5. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 1:

    In 15.2.1, “General,” I’ll apply the grammar changes I proposed here. As that comment states, this removes the need to have constraints in the narrative. It also splits the top-level grammar into two: non-record classes and record classes, allowing these two to be spec’d in separate subclauses. I’ll also modify the related narrative to reflect this new grammar.

    Note that Step 2 considers alternate names for the new top-level grammar rules.

    (Members may well want to tweak this grammar later, but such content change is not relevant here; I’m just looking at repartitioning the content.)

  6. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 2:

    Re my choice of grammar rule name prefixes, “non-record” and “record.” For a reader starting at 15.1, which says nothing about records, you might think we should not use the word “record” here. I’m OK with that. As a strawman, how about we call the two class flavors “plain class” and “record class?” (I think “class” and “record class” is not a clear enough distinction, as “class” might mean any class or just a non-record class.)

    I currently use the two-favor distinction in new definitions, as follows:

    A non_record_class_declaration declares a non-record class, while a record_class_declaration declares a record class.

    This might then become:

    A plain_class_declaration declares a plain class, while a record_class_declaration declares a record class.

    I’m thinking that all existing uses of “class” will apply to both flavors, so no distinction will be needed for them.

    For now, I’ll use “non-record” and “record.”

  7. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 3:

    At the end of Clause 15, “Classes,” I’ll create a new subclause 15.x, “Record classes” having the following subclause, 15.x.1, “General.”

    I’ll move the record-class-specific grammar rules record_class_declaration and record_class_body and their related semantics from 15.2.1, “General,” to 15.x.1, “General.”

    This will leave the following mention of records in (the non-record classes subclause) 15.2.1, “General:”

    class_declaration
        : non_record_class_declaration   // NAME MIGHT BE CHANGED IN STEP 2
        | record_class_declaration
        ;

    But that’s OK, and desirable. Why? If you think back to V5 (and earlier) when we made no mention of unsafe in the grammar in the core part of the spec, we then “augmented“ that grammar in the Unsafe clause. However, that lead to duplicates for quite a few grammar rules (core and augmented), and our grammar-extraction tooling isn’t set up to handle that. So what we did was to admit in the core grammar that unsafe could exist and pointed to where that was spec’d separately. I propose we do the same with records in the very few cases this will be needed.

  8. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 4:

    I’ll review each existing reference to grammar rule class_declaration (there are only a handful) and take the following action:

    1. If it is applicable to both record and non-record classes, no action is needed.
    2. If it is applicable to non-record classes only, change that reference to non_record_class_declaration (15.x.1, “General”).
  9. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 5:

    Revised 15.2.4 Class base specification|15.2.4.1 General contains the following:

    class_base
        : ':' class_type base_argument_list?
        | ':' interface_type_list
        | ':' class_type base_argument_list? ',' interface_type_list
        ;
    
    base_argument_list
        : '(' argument_list? ')'
        ;

    Although base_argument_list only applies to record classes, I don’t see any way around mentioning it here in the class_base rule, as we can’t augment its subrules later (see discussion of unsafe in Step 3). Of course, we can move the rule base_argument_list (and related narrative) to the new records subclause.

  10. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 6:

    I revert all proposed changes to 15.2.6, “Class body” and move the record-specific narrative to the new records subclause where record_class_body is defined.

  11. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 7:

    I revert all proposed changes to 15.3 |15.3.1 “General” and move the record-specific narrative to the new records subclause.

  12. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 8:

    I revert all proposed changes to 15.11 “Instance constructors”|15.11.1 “General” and move the record-specific narrative to the new records subclause.

  13. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 9:

    I move new clause 15.11.(5+) “Copy constructors” to the new records subclause.

  14. RexJaeschke commented on May 9, 2026

    @RexJaeschke
    ContributorAuthor

    Reorg Step 10:

    I move new clause 15.(15+) “Synthesized record class members” and all its subclauses to the new records subclause.

  15. RexJaeschke commented on May 11, 2026

    @RexJaeschke
    ContributorAuthor

    @Nigel-Ecma Please take a look at this sometime this week. If there is little or no pushback to my proposed approach, I'll start revising the V9 Draft PR to reflect that approach, but I will not begin doing that until at least Monday May 18, my time.

  16. RexJaeschke commented on May 19, 2026

    @RexJaeschke
    ContributorAuthor

    Applied all the steps and moved Step 2 to a comment ion the Draft PR.

  17. removed
    status: in-progressThe assignee has actively started working on this; consult with them before taking other actions
    on May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions