csharpstandard
csharpstandard copied to clipboard
Variables and Variable References
This Issue replaces Issue #270, ‘Revising the Definition of “Variable Reference”.’
I’ve put each proposal in a separate Comment, so you can record a thumb-up/down for each.
Defining two kinds of variable references
The key to my proposals below is to have two flavors of variable reference: modifiable and non-modifiable, so we can adequately spec the following V7 features:
- Input parameters
- Methods/delegates with ref returns
- local ref variables
- Conditional expressions having a ref result
- ref assignment
Variable reference
10.5 Variable references says
A variable_reference is an expression that is classified as a variable. A variable_reference denotes a storage location that can be accessed both to fetch the current value and to store a new value.
variable_reference : expression ;
Note: In C and C++, a variable_reference is known as an lvalue. end note
Proposal 1: We should use the term “variable reference” (rather than “variable”) in all contexts involving an expression that denotes a variable.
Use of “lvalue”
Proposal 2: As this is the only place in the spec that we use lvalue, rather than correcting the note (because it ignores the C89-invented distinction between modifiable and non-modifiable lvalues), we should delete it.
Classified as a variable
In the current V6 draft spec, we use the phrase “is [not] classified as a variable” numerous times, all in the context of an expression. Some are declarative statements; others are qualifiers of the form “If
Proposal 3 (initial): Replace all these occurrences with “is [not] a variable_reference.” After all, that’s exactly what a variable reference is!
Variable
10 Variables|10.1 General says
Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable. C# is a type-safe language, and the C# compiler guarantees that values stored in variables are always of the appropriate type.
Operators needing write access
Proposal 4 (initial): Rewrite
12.7.10 Postfix increment and decrement operators
The operand of a postfix increment or decrement operation shall be an expression classified as a variable, a property access, or an indexer access.
as
The operand of a postfix increment or decrement operation shall be a variable_reference, a property access, or an indexer access.
Likewise, for 12.8.6 Prefix increment and decrement operators, and 12.18 Assignment operators|12.18.1 General.
My experiment
Ignoring the readonly ref
features added by V7.x, for now, I’m concentrating on the simple (existing) case of a readonly field, in the context of ++
, --
, and assignment. We know these should be rejected, but does the spec say that?
While 12.7.10 Postfix increment and decrement operators, 12.8.6 Prefix increment and decrement operators, 12.18.2 Simple assignment, and 12.18.3 Compound assignment, explicitly require property and indexer operands to have a setter, there is no stated requirement that a variable/variable_reference operand be writable! So, that needs to be addressed.
Variable reference (revisited)
I don’t see any way that the spec requires a variable to be physically write-protected. (Afterall, a readonly field can be written to by a constructor!) However, the spec can allow us to add write-protection when accessing a variable, via a variable_reference. When C89 (the first C standard) borrowed the type qualifier const
from C++, it had to revise its idea of lvalue by distinguishing between a modifiable lvalue and a non-modifiable lvalue, and I propose we do the same with variable_reference.
Proposal 5: Change 10.5 Variable references from
A variable_reference is an expression that is classified as a variable. A variable_reference denotes a storage location that can be accessed both to fetch the current value and to store a new value.
to
A variable_reference is an expression that is classified as a variable. A modifiable variable_reference denotes a storage location that can be accessed both to fetch the current value and to store a new value. A non-modifiable variable_reference denotes a storage location that can be accessed to fetch the current value, but not to store a new value.
A given storage location may be denoted by multiple modifiable and non-modifiable variable_references.
Proposal 3 (final): Re the use of “classified as a variable,” all these occurrences should be replaced with “is a [modifiable|non-modifiable] variable_reference, as appropriate.”
Operators needing write access (revisited)
Proposal 4 (final):
12.7.10 Postfix increment and decrement operators
The operand of a postfix increment or decrement operation shall be a modifiable variable_reference, a property access, or an indexer access.
12.8.6 Prefix increment and decrement operators
The operand of a prefix increment or decrement operation shall be a modifiable variable_reference, a property access, or an indexer access.
12.18 Assignment operators|12.18.1 General
The left operand of an assignment shall be a modifiable variable_reference, a property access, an indexer access, or an event access.
Readonly Fields
15.5 Fields|15.5.1 General says
The value of a field is obtained in an expression using a simple_name (§xx), a member_access (§xx) or a base_access (§xx). The value of a non-readonly field is modified using an assignment (§xx). The value of a non-readonly field can be both obtained and modified using postfix increment and decrement operators (§xx)) and prefix increment and decrement operators (§xx).
Proposal 6: Change this to
The value of a field is obtained in an expression using a simple_name (§xx), a member_access (§xx) or a base_access (§xx). A non-readonly field may be denoted by either a modifiable or a non-modifiable variable_reference. A readonly field may be denoted only by a non-modifiable variable_reference.
Other places that may need edits
Proposal 7: Change 10 Variables|10.1 General
Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable. C# is a type-safe language, and the C# compiler guarantees that values stored in variables are always of the appropriate type. The value of a variable can be changed through assignment or through use of the ++ and -- operators.
To
Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable. C# is a type-safe language, and the C# compiler guarantees that values stored in variables are always of the appropriate type. The value of a variable can be changed via a modifiable variable_reference to that variable, through assignment or through use of the ++ and -- operators.
Proposal 8: Change 10.2 Variable categories|10.2.1 General
Add here something like “Except for fields marked readonly
(§xx) all variables may be denoted by modifiable or non-modifiable variable_references (§xx).”
Still to come
I have not done an exhaustive search for all places that would be impacted by these changes, but can do that if/when we agree on these approaches.
Once we have the two flavors of variable_reference defined and use them correctly throughout the exiting spec, I can go back to the V7 feature specs and use the new terminology, as appropriate for the new ref
/readonly ref
cases.
Regarding:
Classified as a variable
In the current V6 draft spec, we use the phrase “is [not] classified as a variable” numerous times, all in the context of an expression. Some are declarative statements; others are qualifiers of the form “If is classified as a variable …”
Proposal 3 (initial): Replace all these occurrences with “is [not] a variable_reference.” After all, that’s exactly what a variable reference is!
I follow the motivation but I'm not sure this works/reads correctly. Consider from §12.7.8 This access:
- When
this
is used in a primary_expression within an instance constructor of a class, it is classified as a value. The type of the value is the instance type (§15.3.2) of the class within which the usage occurs, and the value is a reference to the object being constructed.- When
this
is used in a primary_expression within an instance method or instance accessor of a class, it is classified as a value. The type of the value is the instance type (§15.3.2) of the class within which the usage occurs, and the value is a reference to the object for which the method or accessor was invoked.- When
this
is used in a primary_expression within an instance constructor of a struct, it is classified as a variable. The type of the variable is the instance type (§15.3.2) of the struct within which the usage occurs, and the variable represents the struct being constructed.
Here we have this
being classified as a value or variable dependent on context. Does it read correctly if sometimes it is "is classified as a value" and others it "is a variable_reference"?
Both being "is classified as" (as now) or "is a" is at least more consistent.
Proposal 1: We should use the term “variable reference” (rather than “variable”) in all contexts involving an expression that denotes a variable.
The term "variable reference" is used here, in others the grammar rule variable_reference is used. I suggest the former should be a defined term (and then its read-only/writable variants) and should probably be used in many of the cases variable_reference is used below as the text is about things which are semantically variable references not the grammar rule.
Would it be possible to show a v7 example of how these changes help?
Nigel, off the top of my head, here's an example involving ?:
using the new syntax ref
:
12.15 Conditional operator
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' 'ref'? expression ':' 'ref'? expression
;
ref
shall either be present for both expressions or neither.
...
If ref
is present:
-
Both expressions shall be variable references having the same type, and that type is the type of the result.
-
If both expressions are modifiable variable references, the result is a modifiable variable reference; otherwise, the result is a non-modifiable variable reference.
On the call of Jul 2, I took an action item to draft a PR. However, after considerable deliberation, I think I’ll withdraw the issue instead. Here’s my rationale:
A. Of late, my main thrust has been to deal with the supposed hole of not prohibiting attempts to modify a readonly field. Certainly, this is not prohibited directly by words in the sections covering ++, --, and assignment (all of which do, however, directly require both get and set accessors for properties and indexers).
15.5 Fields|15.5.1 General states: The value of a field is obtained in an expression using a simple_name (§12.7.3), a member_access (§12.7.5) or a base_access (§12.7.9). The value of a non-readonly field is modified using an assignment (§12.18). The value of a non-readonly field can be both obtained and modified using postfix increment and decrement operators (§12.7.10) and prefix increment and decrement operators (§12.8.6).
But this only about non-readonly fields.
15.5.3 Readonly fields|15.5.3.1 General states: When a field_declaration includes a readonly modifier, the fields introduced by the declaration are readonly fields. Direct assignments to readonly fields can only occur as part of that declaration or in an instance constructor or static constructor in the same class. (A readonly field can be assigned to multiple times in these contexts.) Specifically, direct assignments to a readonly field are permitted only in the following contexts:
But this only talks about direct assignments, which I’m not sure includes ++/--, although one could deduce that it is intended to include them. In any event, I don’t plan on offering any word improvements here, but I do acknowledge that my initial assumption of the “problem” was wrong.
B. I’ve spec’d most of the ref-related features for V7 without using terms like modifiable variable reference and non-modifiable variable reference.
C. Looking at 12.8.6 Prefix increment and decrement operators
The operand of a prefix increment or decrement operation shall be an expression classified as a variable, a property access, or an indexer access.
Using my previous thinking, it was a small step to adjusting this to say quite explicitly
The operand of a prefix increment or decrement operation shall be an expression classified as a modifiable variable reference, a modifiable property access, or a modifiable indexer access.
Where a modifiable property access and a modifiable indexer access referred to those having setters as well as getters.
And the next step would be to consider the idea that a property or indexer access really designates a (pseudo) variable, so why not include them in the definition of variable reference? This would result in
The operand of a prefix increment or decrement operation shall be an expression classified as a modifiable variable reference.
The one problem with that is that the address-of operator cannot be applied to a property or indexer access, but it can be to a variable. But I think that would be resolvable by a constraint on &.
Bottom line, while I still think my proposal to split variable references into modifiable and non-modifiable groups has merit, at this stage of the spec’s life that seems like mostly an “issue of elegance” rather than something that solves a real problem for the V7 additions. As such, I suggest closing this issue. Then when we get to reviewing the V7 ref-like features, we can revisit this topic, if necessary.
On the June 30 call, we agreed to suspend discussion on this for now, and possibly reconsider when we get to V7. Changed the milestone to v7.
See https://github.com/dotnet/csharpstandard/pull/927#discussion_r1327562678 as relevant to some of the discussion.