From 04d11fb252870d83eed70c3d25e96b3599ab79c1 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Wed, 28 Jan 2026 11:14:07 -0500 Subject: [PATCH 1/6] support record structs --- standard/expressions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 003018736..cea1b2311 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3914,7 +3914,7 @@ An awaiter’s implementation of the interface methods `INotifyCompletion.OnComp ## 12.10 With expressions -A *with_expression* allows for ***non-destructive mutation*** by making a new record class instance that is a copy of an existing record class instance, optionally with specified properties and fields modified. +A *with_expression* allows for ***non-destructive mutation*** by making a new record class, record struct, or non-record struct instance that is a copy of an existing record class, record struct, or non-record struct instance, respectively, optionally with specified properties and fields modified. ```ANTLR with_expression @@ -3925,7 +3925,7 @@ with_expression A *with_expression* is not permitted as a statement. -The receiver type shall be non-`void` and of some record class type. +The receiver type shall be non-`void` and of some record class, record struct, or non-record struct type. *identifier* shall be an accessible instance field or property of the receiver's type. @@ -3933,7 +3933,7 @@ All non-positional properties being changed shall have both set and init accesso This expression is evaluated as follows: -- The receiver's clone method ([§15.16.3](classes.md#15163-copy-and-clone-members)) is invoked, and its result is converted to the receiver’s type. +- For a record class type, the receiver's clone method ([§15.16.3](classes.md#15163-copy-and-clone-members)) is invoked, and its result is converted to the receiver’s type. - Each `member_initializer` is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order. If *member_initializer_list* is omitted, no members are changed. From 85e5ee77f95afc81c447775b59780f15ae041ee2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Wed, 28 Jan 2026 11:17:50 -0500 Subject: [PATCH 2/6] support record structs --- standard/classes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index 0bb8ac1fb..2b7fec3ff 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -6428,7 +6428,9 @@ Expression variables declared in *argument_list* are in scope within the *argume #### 15.16.5.3 Properties -For each parameter of a positional *record_declaration* ([§15.2.1](classes.md#1521-general)) there shall be a corresponding public property member whose name and type are taken from the value parameter declaration. +For each parameter of a positional *record_declaration* ([§15.2.1](classes.md#1521-general)) that has the same name and type as an inherited or explicitly declared instance field, the remainder of this subclause does not apply. + +For each parameter of a positional *record_declaration* there shall be a corresponding public property member whose name and type are taken from the value parameter declaration. For a record class: From c42a39b8735846f16bd26a3492bcb07934a03193 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Wed, 28 Jan 2026 11:35:51 -0500 Subject: [PATCH 3/6] support record structs --- standard/structs.md | 294 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 285 insertions(+), 9 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index c14afac66..ec80b27a4 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -16,19 +16,46 @@ A *struct_declaration* is a *type_declaration* ([§14.7](namespaces.md#147-type- ```ANTLR struct_declaration + : non_record_struct_declaration + | record_struct_declaration + ; + +non_record_struct_declaration : attributes? struct_modifier* 'ref'? 'partial'? 'struct' identifier type_parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body ';'? ; + +record_struct_declaration + : attributes? struct_modifier* 'partial'? 'record' 'struct' + identifier type_parameter_list? delimited_parameter_list? struct_interfaces? + type_parameter_constraints_clause* record_struct_body + ; + +record_struct_body + : struct_body ';'? + | ';' + ; ``` -A *struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. +A *struct_declaration* is for either a ***non-record struct*** or a ***record struct***. + +A *non_record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. + +A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.6.2.1](standard/classes.md#15621-general)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. -A struct declaration shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. +A *struct_declaration* shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. -A struct declaration that supplies a *type_parameter_list* is a generic struct declaration. Additionally, any struct nested inside a generic class declaration or a generic struct declaration is itself a generic struct declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). +A *struct_declaration* that supplies a *type_parameter_list* is a generic struct declaration. Additionally, any struct nested inside a generic class declaration or a generic struct declaration is itself a generic struct declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). -A struct declaration that includes a `ref` keyword shall not have a *struct_interfaces* part. +A *non_record_struct_declaration* that includes a `ref` modifier shall not have a *struct_interfaces* part. + +A *record_struct_declaration* having a *delimited_parameter_list* declares a ***positional record struct***. + +At most only one *record_struct_declaration* containing `partial` may provide a *delimited_parameter_list*. + +The parameters in *delimited_parameter_list* shall not have `ref`, `out` or `this` modifiers; however, `in` and `params` modifiers are permitted. +For a *record_struct_declaration*, the *record_struct_body*s `{}`, `{};`, and `;` are equivalent. They all indicate that the only members are those synthesized by the compiler (§synth-members). ### 16.2.2 Struct modifiers @@ -63,7 +90,7 @@ When an instance of a readonly struct is passed to a method, its `this` is treat ### 16.2.3 Ref modifier -The `ref` modifier indicates that the *struct_declaration* declares a type whose instances are allocated on the execution stack. These types are called ***ref struct*** types. The `ref` modifier declares that instances may contain ref-like fields, and shall not be copied out of its safe-context ([§16.4.15](structs.md#16415-safe-context-constraint)). The rules for determining the safe context of a ref struct are described in [§16.4.15](structs.md#16415-safe-context-constraint). +The `ref` modifier indicates that the *non_record_struct_declaration* declares a type whose instances are allocated on the execution stack. These types are called ***ref struct*** types. The `ref` modifier declares that instances may contain ref-like fields, and shall not be copied out of its safe-context ([§16.4.15](structs.md#16415-safe-context-constraint)). The rules for determining the safe context of a ref struct are described in [§16.4.15](structs.md#16415-safe-context-constraint). It is a compile-time error if a ref struct type is used in any of the following contexts: @@ -114,9 +141,7 @@ struct_body ## 16.3 Struct members -### 16.3.1 General - -The members of a struct consist of the members introduced by its *struct_member_declaration*s and the members inherited from the type `System.ValueType`. +The members of a struct consist of the members introduced by its *struct_member_declaration*s and the members inherited from the type `System.ValueType`. For a record struct, the member set also includes the synthesized members generated by the compiler (§synth-members). ```ANTLR struct_member_declaration @@ -140,6 +165,12 @@ struct_member_declaration Except for the differences noted in [§16.4](structs.md#164-class-and-struct-differences), the descriptions of class members provided in [§15.3](classes.md#153-class-members) through [§15.12](classes.md#1512-static-constructors) apply to struct members as well. +It is an error for a member of a record struct to be named `Clone`. + +It is an error for a member of a record struct to be named `Clone`. + +It is an error for an instance field of a record struct to have an unsafe type. + ### 16.3.2 Readonly members An instance member definition or accessor of an instance property, indexer, or event that includes the `readonly` modifier has the following restrictions: @@ -174,11 +205,256 @@ An instance member definition or accessor of an instance property, indexer, or e > throw new InvalidOperationException("Messages collection is not initialized."); > } > messages.Add(message); +> The `readonly` method `AddMessage` can change the state of a message list. The `InitializeMessages` member can clear and re-initialize the list of messages. In the case of `AddMessage`, the `readonly` modifier is valid. In the case of `InitializeMessages`, adding the `readonly` modifier is invalid. *end example* + +## §synth-members Synthesized record struct members + +### §synth-members-general General + +In the case of a record struct, members are synthesized unless a member with a “matching” signature is declared in the *record_struct_body* or an accessible concrete non-virtual member with a “matching” signature is inherited. Two members are considered matching if they have the same signature or would be considered “hiding” in an inheritance scenario. (See Signatures and overloading [§7.6](basic-concepts.md#76-signatures-and-overloading).) + +The synthesized members are described in the following subclauses. + +### §rec-struct-equalmem Equality members + +The synthesized equality members are similar to those for a record class ([§15.16.2](classes.md#15162-equality-members)), except for the lack of `EqualityContract`, null checks, or inheritance. + +A record struct `R` implements `System.IEquatable` and includes a synthesized strongly-typed overload of `Equals(R other)`, which is public, as follows: + +```csharp +public readonly bool Equals(R other); +``` + +This method can be declared explicitly. However, it is an error if the explicit declaration does not match the expected signature or accessibility. + +If `Equals(R other)` is user-defined (that is, not synthesized) but `GetHashCode` is not, a warning shall be produced. + +The synthesized `Equals(R)` shall return `true` if and only if for each instance field `fieldN` in the record struct the value of `System.Collections.Generic.EqualityComparer.Default.Equals(fieldN, other.fieldN)`, where `TN` is the field type, is `true`. + +The record struct includes synthesized `==` and `!=` operators equivalent to operators declared as follows: + +```csharp +public static bool operator==(R r1, R r2) => r1.Equals(r2); +public static bool operator!=(R r1, R r2) => !(r1 == r2); +``` + +The `Equals` method called by the `==` operator is the `Equals(R other)` method specified above. The `!=` operator delegates to the `==` operator. It is an error if the operators are declared explicitly. + +The record struct includes a synthesized override equivalent to a method declared as follows: + +```csharp +public override readonly bool Equals(object? obj); +``` + +It is an error if the override is declared explicitly. The synthesized override shall return `other is R temp && Equals(temp)` where `R` is the record struct. + +The record struct includes a synthesized override equivalent to a method declared as follows: + +```csharp +public override readonly int GetHashCode(); +``` + +This method may be declared explicitly. + +A warning shall be reported if one of `Equals(R)` and `GetHashCode()` is explicitly declared but the other method is not. + +The synthesized override of `GetHashCode()` shall return an `int` result of combining the values of `System.Collections.Generic.EqualityComparer.Default.GetHashCode(fieldN)` for each instance field `fieldN` with `TN` being the type of `fieldN`. + +> *Example*: Consider the following record struct: +> +> +> ```csharp +> record struct R1(T1 P1, T2 P2); +> ``` +> +> For this, the synthesized equality members would be something like: +> +> +> ```csharp +> struct R1 : IEquatable +> { +> public T1 P1 { get; set; } +> public T2 P2 { get; set; } +> public override bool Equals(object? obj) => obj is R1 temp && Equals(temp); +> public bool Equals(R1 other) +> { +> return +> EqualityComparer.Default.Equals(P1, other.P1) && +> EqualityComparer.Default.Equals(P2, other.P2); +> } +> public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2); +> public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2); +> public override int GetHashCode() +> { +> return HashCode.Combine( +> EqualityComparer.Default.GetHashCode(P1), +> EqualityComparer.Default.GetHashCode(P2)); > } > } > ``` > -> The `readonly` method `AddMessage` can change the state of a message list. The `InitializeMessages` member can clear and re-initialize the list of messages. In the case of `AddMessage`, the `readonly` modifier is valid. In the case of `InitializeMessages`, adding the `readonly` modifier is invalid. *end example* +> *end example* + +### §rec-struct-prtmem Printing members + +A record struct includes a synthesized method equivalent to the following: + +```csharp +private bool PrintMembers(System.Text.StringBuilder builder); +``` + +This method performs the following tasks: + +1. For each of the record struct’s printable members (non-static public field and readable property members), appends that member's name followed by “` = `“ followed by the member's value separated with “`, “`, +2. Returns true if the record struct has printable members. + +For a member that has a value type, its value shall be converted to a string representation. + +If the record’s printable members do not include a readable property with a non-`readonly` `get` accessor, then the synthesized `PrintMembers` is `readonly`. There is no requirement for the record’s fields to be `readonly` for the `PrintMembers` method to be `readonly`. + +The `PrintMembers` method can be declared explicitly. However, it is an error if the explicit declaration does not match the expected signature or accessibility. + +The record struct includes a synthesized method equivalent to the following: + +```csharp +public override string ToString(); +``` + +If the record struct’s `PrintMembers` method is `readonly`, then the synthesized `ToString()` method shall be `readonly`. + +This method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility. + +This method performs the following tasks: + +1. Creates a `StringBuilder` instance, +2. Appends the record struct name to the builder, followed by “` { `“, +3. Invokes the record struct's `PrintMembers` method giving it the builder, followed by “` `” if it returned true, +4. Appends “`}`”, +5. Returns the builder’s contents with `builder.ToString()`. + +> *Example*: Consider the following record struct: +> +> +> ```csharp +> record struct R1(T1 P1, T2 P2); +> ``` +> +> For this record struct, the synthesized printing members would be something like: +> +> +> +> ```csharp +> struct R1 : IEquatable +> { +> public T1 P1 { get; set; } +> public T2 P2 { get; set; } +> +> private bool PrintMembers(StringBuilder builder) +> { +> builder.Append(nameof(P1)); +> builder.Append(" = "); +> builder.Append(this.P1); // or builder.Append(this.P1.ToString()); +> // if P1 has a value type +> builder.Append(", "); +> +> builder.Append(nameof(P2)); +> builder.Append(" = "); +> builder.Append(this.P2); // or builder.Append(this.P2.ToString()); +> // if P2 has a value type +> +> return true; +> } +> +> public override string ToString() +> { +> var builder = new StringBuilder(); +> builder.Append(nameof(R1)); +> builder.Append(" { "); +> +> if (PrintMembers(builder)) +> builder.Append(" "); +> +> builder.Append("}"); +> return builder.ToString(); +> } +> } +> ``` +> +> *end example* + +### §rec-struct-pos-mem Positional record struct members + +#### §rec-struct-pos-mem-gen General + +As well as providing the members described in the preceding subclauses, positional record structs ([§16.2.1](structs.md#1621-general)) synthesize additional members with the same conditions as the other members, as described in the following subclauses. + +#### §rec-struct-pos-mem-pricon Primary constructor + +A record struct has a public constructor whose signature corresponds to the value parameters of the type declaration. This is called the primary constructor for the type. It is an error to have a primary constructor and a constructor with the same signature already present in the struct. If the type declaration does not include a *delimited_parameter_list*, no primary constructor is generated. + +> +> ```csharp +> record struct R1 +> { +> public R1() { } // OK +> } +> +> record struct R2() +> { +> public R2() { } // error: 'R2' already defines +> // a constructor with the same parameter types +> } +> ``` + +Instance field declarations for a record struct are permitted to include variable initializers. If there is no primary constructor, the instance initializers execute as part of the parameterless constructor. Otherwise, at runtime the primary constructor executes the instance initializers appearing in the record-struct-body. + +If a record struct has a primary constructor, any user-defined constructor shall have an explicit `this` constructor initializer that calls the primary constructor or an explicitly declared constructor. + +Parameters of the primary constructor as well as members of the record struct are in scope within initializers of instance fields or properties. Instance members would be an error in these locations, but the parameters of the primary constructor would be in scope and useable and would shadow members. Static members would also be useable. + +A warning shall be produced if a parameter of the primary constructor is not read. + +The definite assignment rules for struct instance constructors apply to the primary constructor of record structs. For instance, the following is an error: + +> +> ```csharp +> record struct Pos(int X) // def assignment error in primary constructor +> { +> private int x; +> public int X { +> get { return x; } set { x = value; } +> } = X; +> } +> ``` + +#### §rec-struct-pos-mem-props Properties + +For each parameter of a *delimited_parameter_list* that has the same name and type as an explicitly declared instance field, the remainder of this subclause does not apply. + +For each record struct parameter of a *delimited_parameter_list* there is a corresponding public property member whose name and type are taken from the value parameter declaration. + +For a record struct: + +- a public `get` and `init` auto-property is created if the record struct has a `readonly` modifier, `get` and `set` otherwise. Both kinds of set accessors (`set` and `init`) are considered “matching.” So, the user may declare an init-only property in place of a synthesized mutable one. + +- An inherited `abstract` property with matching type is overridden. + +- No auto-property is created if the record struct has an instance field with expected name and type. + +- It is an error if the inherited property does not have `public` `get` and `set`/`init` accessors. + +- It is an error if the inherited property or field is hidden. + +- The auto-property is initialized to the value of the corresponding primary constructor parameter. + +- Attributes may be applied to the synthesized auto-property and its backing field by using `property:` or `field:` targets for attributes syntactically applied to the corresponding record struct parameter. + +#### §rec-struct-pos-mem-decon Deconstruct + +A positional record struct with at least one parameter synthesizes a public `void`-returning instance method called `Deconstruct` with an out parameter declaration for each parameter of the primary constructor declaration. Each parameter of `Deconstruct` has the same type as the corresponding parameter of the primary +constructor declaration. The body of the method assigns each parameter of the Deconstruct method to the value from an instance member access to a member of the same name. +If the instance members accessed in the body do not include a property with a non-`readonly` `get` accessor, then the synthesized `Deconstruct` method is `readonly`. +The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or is static. ## 16.4 Class and struct differences From 041c0194ca81f13653004096320db7408dbec242 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Wed, 28 Jan 2026 11:57:11 -0500 Subject: [PATCH 4/6] fix formatting and link --- standard/structs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index ec80b27a4..2a96b8b94 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -42,7 +42,7 @@ A *struct_declaration* is for either a ***non-record struct*** or a ***record st A *non_record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. -A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.6.2.1](standard/classes.md#15621-general)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. +A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.2.1](standard/classes.md#1521-general)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. A *struct_declaration* shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. @@ -333,7 +333,7 @@ This method performs the following tasks: 5. Returns the builder’s contents with `builder.ToString()`. > *Example*: Consider the following record struct: -> +> > > ```csharp > record struct R1(T1 P1, T2 P2); @@ -444,7 +444,7 @@ For a record struct: - It is an error if the inherited property does not have `public` `get` and `set`/`init` accessors. - It is an error if the inherited property or field is hidden. - + - The auto-property is initialized to the value of the corresponding primary constructor parameter. - Attributes may be applied to the synthesized auto-property and its backing field by using `property:` or `field:` targets for attributes syntactically applied to the corresponding record struct parameter. From 5f06e57a290bcd29f6cc9a19ba4e5a96a95063fe Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Wed, 28 Jan 2026 12:05:39 -0500 Subject: [PATCH 5/6] fix link --- standard/structs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/structs.md b/standard/structs.md index 2a96b8b94..4984e16cb 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -42,7 +42,7 @@ A *struct_declaration* is for either a ***non-record struct*** or a ***record st A *non_record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. -A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.2.1](standard/classes.md#1521-general)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. +A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.2.1](classes.md#1521-general), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. A *struct_declaration* shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. From 951daf0df3fae829aaa70f711a90fb8fa4164f4e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 10 Feb 2026 15:47:30 -0500 Subject: [PATCH 6/6] Clean up blank lines in classes.md Remove unnecessary blank lines from classes.md --- standard/classes.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 2b7fec3ff..5125cee3a 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -6485,6 +6485,3 @@ A positional record class ([§15.2.1](classes.md#1521-general)) with at least on > ``` > > *end example* - - -