csharpstandard icon indicating copy to clipboard operation
csharpstandard copied to clipboard

Property & indexer access not fully updated for ref get accessors

Open Nigel-Ecma opened this issue 8 months ago • 13 comments

In §12.2.2 we have:

  • 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) 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) 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.

Both of these do not allow for a property or indexer with a ref get accessor – in such a case a compile-time error does not occur, a value is the result, etc.

Are ref get accessors classified as something other than property/indexer accesses somewhere?

Property access comes under member_reference, indexer under element_access, do these handle ref get and correctly classify the expression (§12.2), indeed does classification handle ref valued expressions correctly?

Then there is setting a property or indexer element (§12.21.2), is ref get handled correctly in these cases?

Above questions are not claimed to be exhaustive.

The above observations come from adding support for Index and Range (#1369, draft at time of writing) which has added text such as (§12.8.12.3 Indexer access in #1369):

  • Using the best indexer determined at binding-time:
    • If the indexer access is the target of an assignment, the set accessor or ref get accessor is invoked to assign a new value (§12.21.2).
    • In all other cases, the get accessor or ref get accesor is invoked to obtain the current value (§12.2.2).

An ancient (2018 ;-)) issue was raised in csharplang regarding ref get properties, transferred here as #295 years ago (2021 ;-)), and has no milestone…

This surely needs to be sorted for v8.

Nigel-Ecma avatar Jul 14 '25 05:07 Nigel-Ecma

I do think we should add this to the C# 8 milestone, or a new pre-C#9 milestone.

BillWagner avatar Jul 30 '25 20:07 BillWagner

Both of these do not allow for a property or indexer with a ref get accessor

For my benefit, why do they not allow for a ref get accessor? Is the spec missing a mention of when a dereference happens somewhere along the way?

To me, this seems to be to be an example of "a function member invocation is performed, and the result of the invocation becomes the value of the property access expression":

ref var x = ref SomeProp;

In this scenario, the value is a reference.

jnm2 avatar Jul 30 '25 21:07 jnm2

@jnm2 – You wrote:

Both of these do not allow for a property or indexer with a ref get accessor

For my benefit, why do they not allow for a ref get accessor? Is the spec missing a mention of when a dereference happens somewhere along the way?

Just look at the quoted text from §12.2.2 “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.” This is no longer true, if there is a ref get accessor it is invoked and the resultant variable reference is used to obtain the value. There is no mention of a ref get accessor at all.

There is a similar situation when assigning to a property, a ref get accessor can be used.

There is a similar issue for indexers, as a by-product #1369 includes some text to cover it – but it is a by-product of the purpose of #1369 and there are other places needing updating, e.g. the indexer access section of §12.2.2.

Further there is a related binding time issue as well. For indexers V7 selects the best indexer at binding time but does not state that the best indexer must also have a suitable accessor. C# requires that the accessor is checked for at binding time. Text for this is included for indexers only in #1369’s §12.8.12.4 – but again this is a by-product and there may be other places that need changing for indexers and for the #1369 work properties were not even looked at.

Nigel-Ecma avatar Jul 30 '25 23:07 Nigel-Ecma

I believe a ref get accessor is a special case of a get accessor- in particular, a ref get accessor is a get accessor whose return type is a by-ref type, and which is contained within a property whose type is a by-ref type.

For assignments to a ref-returning property, something does need to specify that you first call the get accessor to obtain a reference (classified a variable I think), and secondly to assign to that variable.

jnm2 avatar Jul 31 '25 01:07 jnm2

@jnm2 – A get accessor and a ref get accessor are distinct in the current descriptions, a property/indexer has either a set and or get accessor OR it has a single ref get accessor which has different syntax and semantic descriptions – albeit of course related to those of get.

Even if you propose to unify the two for getting, for setting the use of a getter which returns a ref (rather than the current distinct ref get) still needs to be covered wherever it currently states the setter should be used. I expect in unifying the two you’ll find you’ve just moved sideways, but I’m happy to be convinced otherwise!

Nigel-Ecma avatar Jul 31 '25 04:07 Nigel-Ecma

Is the term "ref get accessor" defined?

In standard-v7, subclause 15.7.3 talks about a "get accessor for a ref-valued property":

https://github.com/dotnet/csharpstandard/blob/02d1a90122ece425cff575f47f197fb396be73d4/standard/classes.md?plain=1#L3272

There is a grammar rule for ref_get_accessor_declaration but I'm not convinced this implies a definition for "ref get accessor":

https://github.com/dotnet/csharpstandard/blob/02d1a90122ece425cff575f47f197fb396be73d4/standard/classes.md?plain=1#L3231-L3233

KalleOlaviNiemitalo avatar Jul 31 '25 09:07 KalleOlaviNiemitalo

In today's meeting, we decided we liked an approach where assignments and reads of properties and indexers are defined once, handling the details of ref vs non-ref properties. Everything that involves assignments and reads of properties and indexers would then point back to that shared section. I will take a stab at this.

jnm2 avatar Sep 03 '25 20:09 jnm2

While compiling a list of references to consider, I am skipping places like the following, where the value of a ref-typed property is the ref address itself. That ref address itself is the value being read.

15.7.1 General

A property is a member that provides access to a characteristic of an object or a class. Examples of properties include the length of a string, the size of a font, the caption of a window, and the name of a customer. Properties are a natural extension of fields—both are named members with associated types, and the syntax for accessing fields and properties is the same. However, unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to be executed when their values are read or written. Properties thus provide a mechanism for associating actions with the reading and writing of an object or class’s characteristics; furthermore, they permit such characteristics to be computed.

This is strongly confirmed by the definition of ref-valued properties (which itself may deserve a clarification):

  • The second declares a ref-valued property. Its value is a variable_reference (§9.5), that may be readonly, to a variable of type type. This kind of property is only readable.

In cases where a dereference is implied, I will not skip.

jnm2 avatar Sep 03 '25 22:09 jnm2

Found a couple of English grammar issues:

https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/classes.md?plain=1#L3418

jnm2 avatar Sep 03 '25 22:09 jnm2

Edit: never mind, I see the section is split to refer to different grammar productions.

~~Side issue, I think~~

For abstract and extern non-ref-valued properties, any accessor_body for each accessor specified is simply a semicolon.

~~should be~~

For abstract and extern properties, any accessor_body for each accessor specified is simply a semicolon.

jnm2 avatar Sep 03 '25 22:09 jnm2

We already have the text defining how reads and writes work with ref-valued properties, in §15.7.3:

A get accessor for a ref-valued property corresponds to a parameterless method with a return value of a variable_reference to a variable of the property type. When such a property is referenced in an expression its get accessor is invoked to compute the variable_reference value of the property. That variable reference, like any other, is then used to read or, for non-readonly variable_references, write the referenced variable as required by the context.

jnm2 avatar Sep 03 '25 22:09 jnm2

This is the initial list of references to /propert|indexer/i that I found might need updates:

  1. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/attributes.md?plain=1#L108

    • No updates needed. The following is not allowed:
      [Attr(Prop = 42)]
      class Attr : Attribute
      {
          private int f;
          public ref int Prop => ref f;
      }
      
  2. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/attributes.md?plain=1#L482

    • Same as 1 (no updates needed)
  3. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/attributes.md?plain=1#L110

    • Same as 1 (no updates needed)
  4. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/attributes.md?plain=1#L468

    • Same as 1 (no updates needed)
  5. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/classes.md?plain=1#L1394

    • No updates needed. Ref-valued properties are defined as "read-only" even when the value that is being read is itself a writeable reference.
  6. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/classes.md?plain=1#L1459

    • Same as 5 (no updates needed)
  7. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/classes.md?plain=1#L3334

    • I'm taking this as definitive.
  8. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/classes.md?plain=1#L3474-L3480

    • Updating.
  9. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L18-L19

    • Am not sure if this needs updates, will ask.
  10. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L30

  11. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L39-L40

  12. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L362

  13. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L451-L473

  14. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L501-L508

  15. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L1718

  16. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L2296

  17. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L2429

  18. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L2437-L2443

  19. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L2560

  20. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L3629

  21. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L3637-L3641

  22. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L6566-L6569

  23. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L6598

  24. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/expressions.md?plain=1#L6748

  25. https://github.com/dotnet/csharpstandard/blob/62c498fb2a0c1a2e7bf059819a606e56f4eadb3b/standard/structs.md?plain=1#L241

jnm2 avatar Sep 03 '25 23:09 jnm2

I'm noticing usages of the term "ref get accessor" as in, "a get or ref get accessor." Something feels off about ascribing the refness to the accessor rather than to the property itself.

  1. The ref keyword is not applied to an accessor syntactically.
  2. In the phrase "a get or ref get accessor," those are not really two different kinds of accessor, in the sense that they don't perform different roles or different tasks within the property. The get accessor just returns the property's value, whether the property's value is itself a ref or not. If the property happens to be ref-valued, then the value that the ordinary getter returns is a ref. Enumerating "get or ref get" as though this was covering two different fundamental options feels wrong to me.

jnm2 avatar Oct 22 '25 21:10 jnm2