Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# 7.x: out Variables #44

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions standard/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2161,9 +2161,9 @@ In a method that takes reference parameters, it is possible for multiple names t

#### 15.6.2.4 Output parameters

A parameter declared with an `out` modifier is an output parameter. Similar to a reference parameter, an output parameter does not create a new storage location. Instead, an output parameter represents the same storage location as the variable given as the argument in the method invocation.
A parameter declared with an `out` modifier is an output parameter. An output parameter represents the same storage location as the variable given as the argument in the method invocation, except that for a discard (§9.2.8.1) argument, there is no variable, and any value assigned to that output parameter is instead discarded.

When a formal parameter is an output parameter, the corresponding argument in a method invocation shall consist of the keyword `out` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)) of the same type as the formal parameter. A variable need not be definitely assigned before it can be passed as an output parameter, but following an invocation where a variable was passed as an output parameter, the variable is considered definitely assigned.
When a formal parameter is an output parameter, the corresponding argument in a method invocation shall consist of the keyword `out`, optionally followed by *local_variable_type*, followed by *identifier* of the same type as the formal parameter, or declared `var`. A variable need not be definitely assigned before it can be passed as an output parameter, but following an invocation where a variable was passed as an output parameter, the variable is considered definitely assigned.

Within a method, just like a local variable, an output parameter is initially considered unassigned and shall be definitely assigned before its value is used.

Expand Down
4 changes: 4 additions & 0 deletions standard/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ An implicit conversion exists from a *default_literal* ([§12.8.20](expressions.

While throw expressions do not have a type, they may be implicitly converted to any type.

### §imp-typed-out-var Implicitly-typed out variables

There is a conversion from an implicitly-typed out variable ([§12.6.2](expressions.md#1262-argument-lists)) to every type.

## 10.3 Explicit conversions

### 10.3.1 General
Expand Down
74 changes: 71 additions & 3 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ argument_name
argument_value
: expression
| 'ref' variable_reference
| 'out' variable_reference
| 'out' declaration_expression? identifier
jskeet marked this conversation as resolved.
Show resolved Hide resolved
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
;
```

Expand All @@ -559,7 +559,71 @@ The *argument_value* can take one of the following forms:

- An *expression*, indicating that the argument is passed as a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)).
- The keyword `ref` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as a reference parameter ([§15.6.2.3](classes.md#15623-reference-parameters)). A variable shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before it can be passed as a reference parameter.
- The keyword `out` followed by a *variable_reference* ([§9.5](variables.md#95-variable-references)), indicating that the argument is passed as an output parameter ([§15.6.2.4](classes.md#15624-output-parameters)). A variable is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following a function member invocation in which the variable is passed as an output parameter.
- The keyword `out`, optionally followed by *local_variable_type* ([§13.6.2](statements.md#1362-local-variable-declarations)), followed by *identifier*, indicating that the argument is passed as an output parameter ([§15.6.2.4](classes.md#15624-output-parameters)). If *identifier* is not a discard, the variable it designates is considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) following a function member invocation in which the variable is passed as an output parameter. In this context, the variable designated by *identifier* is known as an ***out variable***.

*identifier* denotes a new variable, an existing variable, or a discard depending on the context. Specifically,

- If *local_variable_type* is present
- If *identifier* is `_`, it is interpreted as a typed discard
- Otherwise, *identifier* denotes a new local variable. It is a compile-time error if a variable by that name is currently in scope or if there is not an identity conversion between the corresponding parameter’s type and *local_variable_type*.

> *Example*:
>
> ```csharp
> static void M(out int x) { … }
>
> static void CallM1()
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
> {
> int v;
> M(out v); // OK: untyped existing variable
> M(out _); // OK: untyped discard
> M(out int v); // error: new variable but a variable by that name already exists
> M(out int v2); // OK: new variable
> M(out uint pu); // error: new variable but has incompatible type
> M(out int _); // OK: typed discard
> M(out var v3); // OK: typed new variable
> M(out var _); // OK: implicitly typed discard
> }
> ```
>
> *end example*

- If *local_variable_type* is absent
- If *identifier* is `_`
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
- Otherwise, if an existing variable with that name is in-scope, *identifier* denotes that variable. However, it shall not precede that variable’s declaration.
- Otherwise, *identifier* is interpreted as an untyped discard.

> *Example*:
>
> ```csharp
> static void M(out int x) { … }
> static void CallM2()
> {
> int v;
> M(out _); // error: ref to variable before it is declared
> int _ = -5; // declaration of variable _
> M(out _); // OK: existing variable
> }
> ```
>
> *end example*

It is an error to reference an implicitly-typed out variable in the same argument list that immediately contains its declaration.

> *Example*:
>
> ```csharp
> static void M(out int x, int y) { … }
> static void CallM2()
> {
> int v;
> M(out v, v); // error: ref to variable declared in the same argument list
> }
> ```
>
> *end example*

The type of an implicitly-typed out variable is the type of the corresponding parameter in the signature of the method selected by overload resolution.

The form determines the ***parameter-passing mode*** of the argument: *value*, *reference*, or *output*, respectively.

Expand Down Expand Up @@ -1033,6 +1097,8 @@ Given an implicit conversion `C₁` that converts from an expression `E` to a ty
- `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a better conversion target than `T₂` ([§12.6.4.6](expressions.md#12646-better-conversion-target))
- `E` is a method group ([§12.2](expressions.md#122-expression-classifications)), `T₁` is compatible ([§20.4](delegates.md#204-delegate-compatibility)) with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂`

The conversion from an implicitly-typed out variable declaration is not considered better than any other conversion from expression.

#### 12.6.4.5 Exactly matching expression

Given an expression `E` and a type `T`, `E` ***exactly matches*** `T` if one of the following holds:
Expand Down Expand Up @@ -6051,7 +6117,7 @@ assignment_operator

The left operand of an assignment shall be an expression classified as a variable, a property access, an indexer access, an event access or a tuple. A declaration expression is not directly permitted as a left operand, but may occur as a step in the evaluation of a deconstructing assignment.

The `=` operator is called the ***simple assignment operator***. It assigns the value or values of the right operand to the variable, property, indexer element or tuple elements given by the left operand. The left operand of the simple assignment operator shall not be an event access (except as described in [§15.8.2](classes.md#1582-field-like-events)). The simple assignment operator is described in [§12.21.2](expressions.md#12212-simple-assignment).
The `=` operator is called the ***simple assignment operator***. When the left operand is not a discard (§9.2.8.1), this operator assigns the value of the right operand to the variable, property, or indexer element given by the left operand. Otherwise, the right operand is evaluated with its value going unused. The left operand of the simple assignment operator shall not be an event access (except as described in [§15.8.2](classes.md#1582-field-like-events)). The simple assignment operator is described in [§12.21.2](expressions.md#12212-simple-assignment).
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this change and the next one intended to be in this PR? It's not really about out parameters... was it just something else that should have been in a discards PR?

Copy link
Member

Choose a reason for hiding this comment

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

That's correct. This PR in its original form included both discards and out parameters. I left this change in place because it wasn't in the updated discards and tuples PR.

Let's discuss at the meeting if it should be removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Action from 2023-04-19: @BillWagner to remove from this PR, but add it to the issue that @MadsTorgersen has already created for polishing on discards.


The assignment operators other than the `=` operator are called the ***compound assignment operators***. These operators perform the indicated operation on the two operands, and then assign the resulting value to the variable, property, or indexer element given by the left operand. The left operand of a compound assignment operator shall not be a tuple or a discard. The compound assignment operators are described in [§12.21.3](expressions.md#12213-compound-assignment).

Expand All @@ -6065,6 +6131,8 @@ The assignment operators are right-associative, meaning that operations are grou

The `=` operator is called the simple assignment operator.

If the left operand of a simple assignment is a discard (§9.2.8.1), the right operand is evaluated. Otherwise, the remainder of this subclause applies.

If the left operand of a simple assignment is of the form `E.P` or `E[Ei]` where `E` has the compile-time type `dynamic`, then the assignment is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)). In this case, the compile-time type of the assignment expression is `dynamic`, and the resolution described below will take place at run-time based on the run-time type of `E`. If the left operand is of the form `E[Ei]` where at least one element of `Ei` has the compile-time type `dynamic`, and the compile-time type of `E` is not an array, the resulting indexer access is dynamically bound, but with limited compile-time checking ([§12.6.5](expressions.md#1265-compile-time-checking-of-dynamic-member-invocation)).

A simple assignment where the left operand is classified as a tuple is also called a ***deconstructing assignment***. If any of the tuple elements of the left operand has an element name, a compile-time error occurs. If any of the tuple elements of the left operand is a *declaration_expression* and any other element is not a *declaration_expression* or a simple discard, a compile-time error occurs.
Expand Down
7 changes: 5 additions & 2 deletions standard/lexical-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ The productions for *simple_name* ([§12.8.4](expressions.md#1284-simple-names))
>
> *end example*

If a sequence of tokens can be parsed (in context) as a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)), *member_access* ([§12.8.7](expressions.md#1287-member-access)), or *pointer_member_access* ([§23.6.3](unsafe-code.md#2363-pointer-member-access)) ending with a *type_argument_list* ([§8.4.2](types.md#842-type-arguments)), the token immediately following the closing `>` token is examined, to see if it is
If a sequence of tokens can be parsed (in context) as a *simple_name* ([§12.8.4](expressions.md#1284-simple-names)), *member_access* ([§12.8.7](expressions.md#1287-member-access)), or *pointer_member_access* ([§23.6.3](unsafe-code.md#2363-pointer-member-access)) ending with a *type_argument_list* ([§8.4.2](types.md#842-type-arguments)), the token immediately following the closing `>` token is examined, to see if it is one of the following:

- One of `( ) ] } : ; , . ? == != | ^ && || & [`; or
- One of the relational operators `< > <= >= is as`; or
Expand All @@ -77,7 +77,8 @@ If the following token is among this list, or an identifier in such a context, t
> *Note*: These rules are not applied when parsing a *type_argument_list* in a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note*: In an *argument_value* of the form `out local_variable_type? variable_reference`, *local_variable_type* may be a generic type; for example: `F(out A<B> name)`. As the language grammar for the argument uses *variable_reference* (which is really *expression*), this context is subject to the disambiguation rule. In this case the closing `>` is followed by an identifier. *end note*
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

> *Example*: The statement:
>
> <!-- Incomplete$Example: {template:"standalone-lib", name:"GrammarAmbiguities2"} -->
Expand Down Expand Up @@ -123,6 +124,7 @@ If the following token is among this list, or an identifier in such a context, t
> The case label `case A<B> C:` uses a declaration pattern.
>
> *end example*
<!-- markdownlint-enable MD028 -->

A *relational_expression* ([§12.12.1](expressions.md#12121-general)) can have the form “*relational_expression* `is` *type*” or “*relational_expression* `is` *constant_pattern*,” either of which might be a valid parse of a qualified identifier. In this case, an attempt is made to bind it as a type (XREF TO 7.8.1 NAMESPACES AND TYPES); however, if that fails, it is bound as an expression, and the result must be a constant.

Expand Down Expand Up @@ -560,6 +562,7 @@ The semantics of an identifier named `_` depends on the context in which it appe
Identifiers containing two consecutive underscore characters (`U+005F`) are reserved for use by the implementation; however, no diagnostic is required if such an identifier is defined.

> *Note*: For example, an implementation might provide extended keywords that begin with two underscores. *end note*
<!-- markdownlint-disable MD028 -->

### 6.4.4 Keywords

Expand Down
2 changes: 1 addition & 1 deletion standard/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ As described in the following subclauses, variables are either ***initially assi

### 9.2.1 General

C# defines seven categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters, and local variables. The subclauses that follow describe each of these categories.
C# defines the following categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters, and local variables. The subclauses that follow describe each of these categories.

> *Example*: In the following code
>
Expand Down