Skip to content

Conversation

@jnm2
Copy link
Contributor

@jnm2 jnm2 commented Sep 4, 2025

Fixes #1371

I spread a wide net and put no effort toward deduplicating so far, after seeing the wide variety in types of update. I am open to suggestion on that.

A member lookup is the process whereby the meaning of a name in the context of a type is determined. A member lookup can occur as part of evaluating a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)) or a *member_access* ([§12.8.7](expressions.md#1287-member-access)) in an expression. If the *simple_name* or *member_access* occurs as the *primary_expression* of an *invocation_expression* ([§12.8.10.2](expressions.md#128102-method-invocations)), the member is said to be *invoked*.
If a member is a method or event, or if it is a constant, field or property of either a delegate type ([§20](delegates.md#20-delegates)) or the type `dynamic` ([§8.2.4](types.md#824-the-dynamic-type)), then the member is said to be *invocable.*
If a member is a method or event, or if it is a constant, field or property whose evaluated value is either of a delegate type ([§20](delegates.md#20-delegates)) or the type `dynamic` ([§8.2.4](types.md#824-the-dynamic-type)), then the member is said to be *invocable.*
Copy link
Contributor Author

@jnm2 jnm2 Sep 4, 2025

Choose a reason for hiding this comment

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

This change enables:

class C
{
    void M()
    {
        Prop();
    }

    private System.Action f;
    public ref System.Action Prop => ref f;
}

Since the property type is ref System.Action which is not a delegate type, but the property's evaluated value is of type System.Action.

A member initializer that specifies an expression after the equals sign is processed in the same way as an assignment ([§12.21.2](expressions.md#12212-simple-assignment)) to the target.

A member initializer that specifies an object initializer after the equals sign is a ***nested object initializer***, i.e., an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.
A member initializer that specifies an object initializer after the equals sign is a ***nested object initializer***, i.e., an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties or indexers with a value type, to ref-valued properties or indexers whose type is a reference to a value type, or to read-only fields with a value type.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Likely fixes an older spec bug which failed to mention that this rule applies to indexers as well as properties.

A member initializer that specifies an object initializer after the equals sign is a ***nested object initializer***, i.e., an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties or indexers with a value type, to ref-valued properties or indexers whose type is a reference to a value type, or to read-only fields with a value type.

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property, or indexer, the elements given in the initializer are added to the collection referenced by the target. The target shall be of a collection type that satisfies the requirements specified in [§12.8.17.2.3](expressions.md#1281723-collection-initializers).
A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property, or indexer, the elements given in the initializer are added to the collection referenced by the target. The target shall be of a collection type that satisfies the requirements specified in [§12.8.17.2.3](expressions.md#1281723-collection-initializers). Initialization of an embedded collection cannot be applied to properties or indexers with a value type, to ref-valued properties or indexers whose type is a reference to a value type, or to read-only fields with a value type.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Likely fixes an older spec bug which failed to mention that this rule applies to nested collection initializers as well as nested object initializers.

- A property that has only a get accessor is said to be a ***read-only property***. It is a compile-time error for a read-only property to be the target of an assignment.
- A property that has only a set accessor is said to be a ***write-only property***. Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression.
- A property that has only a get accessor is said to be a ***read-only property***. It is a compile-time error for a read-only property to be the target of an assignment unless the property is ref-valued and returns a writeable reference.
- A property that has only a set accessor is said to be a ***write-only property***. Except as the target of an assignment or as an argument to the `nameof` operator ([§12.8.23](expressions.md#12823-the-nameof-operator)), it is a compile-time error to reference a write-only property in an expression.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Likely fixes an older spec bug which was disallowing:

class C
{
    void M()
    {
        _ = nameof(Prop);
    }

    public int Prop { set { } }
}

@jskeet
Copy link
Contributor

jskeet commented Oct 20, 2025

@jnm2 Would you like us to review this and discuss it in the upcoming meeting after the priority PRs? If so, feel free to apply the meeting-discuss label.

@jnm2
Copy link
Contributor Author

jnm2 commented Oct 20, 2025

Sure. Is applying that label a good automatic step to take when submitting a PR that is ready for review?

@jnm2 jnm2 added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Oct 20, 2025
@jskeet
Copy link
Contributor

jskeet commented Oct 21, 2025

Sure. Is applying that label a good automatic step to take when submitting a PR that is ready for review?

If it's non-trivial, yes. For very small changes, it's fine to just get appropriate folks to review between meetings and merge.

- The best indexer of the set of candidate indexers is identified using the overload resolution rules of [§12.6.4](expressions.md#1264-overload-resolution). If a single best indexer cannot be identified, the indexer access is ambiguous, and a binding-time error occurs.
- The accessors of the best indexer are checked:
- If the indexer access is the target of an assignment then the indexer shall have a set or ref get accessor, otherwise, a binding-time error occurs;
- If the indexer access is the target of an assignment then the indexer either shall have a set accessor or shall return a non-readonly ref, otherwise, a binding-time error occurs;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixes a spec issue where it would be valid to assign to a ref readonly indexer. (That is, an indexer whose returned ref is a readonly ref. I'm not talking about a readonly ref indexer, which is an indexer which is a readonly struct member.)

Copy link
Member

Choose a reason for hiding this comment

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

Probably in classes on indexers.

Copy link
Contributor Author

@jnm2 jnm2 Nov 18, 2025

Choose a reason for hiding this comment

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

Sorry, I'm not sure what you're saying. Is there an open question on the change here or another place to update?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry. I was saying the update for ref readonly should be in the section on indexers.

@jnm2 jnm2 requested a review from Nigel-Ecma October 22, 2025 22:11
Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

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

This is good progress. I'd also like to see more xrefs to15.7.1 where "non-ref-valued" and "ref-valued" properties are defined.

- The best indexer of the set of candidate indexers is identified using the overload resolution rules of [§12.6.4](expressions.md#1264-overload-resolution). If a single best indexer cannot be identified, the indexer access is ambiguous, and a binding-time error occurs.
- The accessors of the best indexer are checked:
- If the indexer access is the target of an assignment then the indexer shall have a set or ref get accessor, otherwise, a binding-time error occurs;
- If the indexer access is the target of an assignment then the indexer either shall have a set accessor or shall return a non-readonly ref, otherwise, a binding-time error occurs;
Copy link
Member

Choose a reason for hiding this comment

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

Probably in classes on indexers.

@jnm2
Copy link
Contributor Author

jnm2 commented Nov 19, 2025

@BillWagner Thanks! The xrefs have been added.

Copy link
Contributor

@jskeet jskeet left a comment

Choose a reason for hiding this comment

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

I'm generally very much in favour of this - just a few nits.

- The value of a variable is simply the value currently stored in the storage location identified by the variable. A variable shall be considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) before its value can be obtained, or otherwise a compile-time error occurs.
- The value of a property access expression is obtained by invoking the get accessor of the property. If the property has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed, and the result of the invocation becomes the value of the property access expression.
- The value of an indexer access expression is obtained by invoking the get accessor of the indexer. If the indexer has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression.
- The value of a property access expression is obtained by invoking the get accessor of the property. If the property has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed. For non-ref-valued properties (§15.7.1), the result of the invocation becomes the value of the property access expression. For ref-valued properties, the result of the invocation is a variable and the value of the variable becomes the value of the property access expression.
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 not sure about "and the value of the variable becomes the value of the property access expression". We've classified it as a variable, not a value... Can we not just leave it as something like "and that variable is the result of evaluating the get accessor"? I may very well be wrong on this, and haven't checked other places yet.

Same applies to the line below, of course.

Copy link
Contributor

@jskeet jskeet Nov 19, 2025

Choose a reason for hiding this comment

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

Nigel: "the variable reference becomes the result of the property access expression"

Or maybe "the variable becomes" - please check for use of "variable reference" elsewhere.

Copy link
Contributor

@jskeet jskeet Nov 19, 2025

Choose a reason for hiding this comment

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

See also #324 and clause 9.5.

> <tr>
> <td><code>e.P = value</code></td>
> <td>The set accessor of the property <code>P</code> in the class, struct, or interface given by the type of <code>E</code> is invoked with the instance expression <code>e</code> and the argument list <code>(value)</code>. A binding-time error occurs if <code>P</code> is <code>static</code> or if <code>P</code> is read-only.</td>
> <td>If <code>P</code> is non-ref-valued (§15.7.1), the set accessor of the property <code>P</code> in the class, struct, or interface given by the type of <code>E</code> is invoked with the instance expression <code>e</code> and the argument list <code>(value)</code>. If <code>P</code> is ref-valued, the get accessor of the property <code>P</code> in the containing class, struct, or interface given by the type of <code>E</code> is invoked is invoked with the instance expression <code>e</code>. A binding-time error occurs if <code>P</code> is <code>static</code>, if <code>P</code> is non-ref-valued and read-only, or if <code>P</code> is ref-valued and returns a readonly reference.</td>
Copy link
Contributor

Choose a reason for hiding this comment

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

For the ref-valued case, I think we still need to say "and then an assignment is performed to the resulting variable" or something like that, don't we? Otherwise we're saying the get accessor is invoked - and that's all. (There's no reference to value.)

> <tr>
> <td><code>e[x, y] = value</code></td>
> <td>Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of <code>e</code>. The set accessor of the indexer is invoked with the instance expression <code>e</code> and the argument list <code>(x, y, value)</code>. A binding-time error occurs if the indexer is read-only.
> <td>Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of <code>e</code>. If the indexer is non-ref-valued (§15.9.1), the set accessor of the indexer is invoked with the instance expression <code>e</code> and the argument list <code>(x, y, value)</code>. If the indexer is ref-valued, the get accessor of the indexer is invoked with the instance expression <code>e</code> and the argument list <code>(x, y)</code>. A binding-time error occurs if the indexer is non-ref valued and read-only or if the indexer is ref-valued and returns a readonly reference.
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, I think.

Co-authored-by: Jon Skeet <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

meeting: discuss This issue should be discussed at the next TC49-TG2 meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Property & indexer access not fully updated for ref get accessors

4 participants