From b51c0cf21a52289ce0ed9102f6cd214f9a718177 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 1 Nov 2022 21:10:03 -0700 Subject: [PATCH 01/41] Rough cut at discards, tuples and out variables --- standard/conversions.md | 16 +++++++- standard/expressions.md | 84 +++++++++++++++++++++++++++++++++++++++-- standard/types.md | 17 +++++++++ standard/variables.md | 10 +++-- 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 8f8feea86..d0afd364e 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -60,6 +60,7 @@ The following conversions are classified as implicit conversions: - Method group conversions - Null literal conversions - Implicit nullable conversions +- Implicit tuple conversions - Lifted user-defined implicit conversions - Implicit throw conversion @@ -77,7 +78,11 @@ However, dynamic conversions ([§10.2.10](conversions.md#10210-implicit-dynamic- An identity conversion converts from any type to the same type. One reason this conversion exists is so that a type T or an expression of type T can be said to be convertible to T itself. -Because `object` and `dynamic` are considered equivalent there is an identity conversion between `object` and `dynamic`, and between constructed types that are the same when replacing all occurrences of `dynamic` with `object`. +In some cases there is an identity conversion between types that are not exactly the same, but are considered equivalent. Such identity conversions exist: + +- between `object` and `dynamic`. +- between tuple types with the same arity, when an identity conversion exists between each pair of corresponding element types. +- between constructed types that are the same when replacing constituent types with identity convertible ones. In most cases, an identity conversion has no effect at runtime. However, since floating point operations may be performed at higher precision than prescribed by their type ([§8.3.7](types.md#837-floating-point-types)), assignment of their results may result in a loss of precision, and explicit casts are guaranteed to reduce precision to what is prescribed by the type ([§11.8.7](expressions.md#1187-cast-expressions)). @@ -301,6 +306,10 @@ The following further implicit conversions exist for a given type parameter `T` In all cases, the rules ensure that a conversion is executed as a boxing conversion if and only if at run-time the conversion is from a value type to a reference type. +### §implicit-tuple-conversions Implicit tuple conversions + +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of the `T`'s corresonding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. + ### 10.2.13 User-defined implicit conversions A user-defined implicit conversion consists of an optional standard implicit conversion, followed by execution of a user-defined implicit conversion operator, followed by another optional standard implicit conversion. The exact rules for evaluating user-defined implicit conversions are described in [§10.5.4](conversions.md#1054-user-defined-implicit-conversions). @@ -323,6 +332,7 @@ The following conversions are classified as explicit conversions: - Explicit numeric conversions - Explicit enumeration conversions - Explicit nullable conversions +- Explicit tuple conversions - Explicit reference conversions - Explicit interface conversions - Unboxing conversions @@ -429,6 +439,10 @@ For an explicit reference conversion to succeed at run-time, the value of the so > *Note*: Reference conversions, implicit or explicit, never change the value of the reference itself ([§8.2.1](types.md#821-general)), only its type; neither does it change the type or value of the object being referenced. *end note* +### §explicit-tuple-conversions Explicit tuple conversions + +An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresonding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. + ### 10.3.6 Unboxing conversions An unboxing conversion permits a *reference_type* to be explicitly converted to a *value_type*. The following unboxing conversions exist: diff --git a/standard/expressions.md b/standard/expressions.md index a7f242cc5..4d28bb532 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -14,6 +14,7 @@ The result of an expression is classified as one of the following: - A variable. Every variable has an associated type, namely the declared type of the variable. - A null literal. An expression with this classification can be implicitly converted to a reference type or nullable value type. - An anonymous function. An expression with this classification can be implicitly converted to a compatible delegate type or expression tree type. +- A tuple. Every tuple has a fixed number of elements, each with an expression and an optional tuple element name. - A property access. Every property access has an associated type, namely the type of the property. Furthermore, a property access may have an associated instance expression. When an accessor of an instance property access is invoked, the result of evaluating the instance expression becomes the instance represented by `this` ([§11.7.12](expressions.md#11712-this-access)). - An indexer access. Every indexer access has an associated type, namely the element type of the indexer. Furthermore, an indexer access has an associated instance expression and an associated argument list. When an accessor of an indexer access is invoked, the result of evaluating the instance expression becomes the instance represented by `this` ([§11.7.12](expressions.md#11712-this-access)), and the result of evaluating the argument list becomes the parameter list of the invocation. - Nothing. This occurs when the expression is an invocation of a method with a return type of `void`. An expression classified as nothing is only valid in the context of a *statement_expression* ([§12.7](statements.md#127-expression-statements)) or as the body of a *lambda_expression* ([§11.17](expressions.md#1117-anonymous-function-expressions)). @@ -37,6 +38,7 @@ Most of the constructs that involve an expression ultimately require the express - 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 ([§11.6.6](expressions.md#1166-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 ([§11.6.6](expressions.md#1166-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 tuple is obtained by applying an implicit tuple conversion to a receiving tuple type, if one exists, or to the type of the tuple expression itself if not. ## 11.3 Static and Dynamic Binding @@ -1447,6 +1449,11 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain exactly one type having name `I` and `e` type parameters, then the *simple_name* refers to that type constructed with the given type arguments. - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* +- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard () and its type shall be inferred from the syntactic context: + - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. + - If the discard occurs as the left-hand side of an assignment expression, its type shall be the type of the right-hand side + - If the discard occurs as a *tuple_element* on the left hand side of a deconstructing assignment, its type shall be the type of the corresponding tuple element on the right-hand side. + - If the discard occurs in a different syntactic context, or a type cannot be inferred, a compile time error occurs. - Otherwise, the *simple_name* is undefined and a compile-time error occurs. ### 11.7.5 Parenthesized expressions @@ -1461,6 +1468,21 @@ parenthesized_expression A *parenthesized_expression* is evaluated by evaluating the *expression* within the parentheses. If the *expression* within the parentheses denotes a namespace or type, a compile-time error occurs. Otherwise, the result of the *parenthesized_expression* is the result of the evaluation of the contained *expression*. +### Tuple expressions + +A *tuple_expression* consists of two or more comma-separated and optionally-named *expression*s enclosed in parentheses. + +``` ANTLR +tuple_expression + : `(` tuple_element (',' tuple_element)+ ')' +tuple_element + : (identifier ':')? expression +``` + +A tuple expression is classified as a tuple. It has a type if all the element expressions have a type. In that case, the type of a tuple expression is the tuple type of the same arity, where each type element has the type of the corresponding element expression and the same identifier as the corresponding tuple element if it has one, or none if it does not. + +A tuple expression can be the target of a deconstructing assignment (). + ### 11.7.6 Member access #### 11.7.6.1 General @@ -4300,6 +4322,28 @@ A *throw expression* shall only occur in the following syntactic contexts: - As the second operand of a null coalescing operator (`??`). - As the body of an expression-bodied lambda or member. +## §declaration-expressions-new-clause Declaration expressions + +A declaration expression declares a local variable. + +``` antlr +declaration_expression + : local_variable_type identifier +``` + +A declaration expression shall only occur in the following syntactic contexts: + +- As an `out` *argument_value* in an *argument_list*. +- As a *tuple_element* in a *tuple_expression* that occurs on the left-hand side of a deconstructing assignment. + +The *local_variable_type* of a *local_variable_declaration* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: + +- In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. +- In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. + +A declaration expression with the identifier `_` is a discard (), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space (). + + ## 11.16 Conditional operator The `?:` operator is called the conditional operator. It is at times also called the ternary operator. @@ -5757,11 +5801,13 @@ assignment_operator ; ``` -The left operand of an assignment shall be an expression classified as a variable, a property access, an indexer access, or an event access. +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. + +The `=` operator is called the ***simple assignment operator***. It assigns the value(s) 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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). -The `=` operator is called the ***simple assignment operator***. It assigns the value of the right operand to the variable, property, or indexer element given by the left operand. The left operand of the simple assignment operator shall not be an event access (except as described in [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). +The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in [§11.9.new](). -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 compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). +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. The compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). The `+=` and `-=` operators with an event access expression as the left operand are called the ***event assignment operators***. No other assignment operator is valid with an event access as the left operand. The event assignment operators are described in [§11.19.4](expressions.md#11194-event-assignment). @@ -5895,6 +5941,38 @@ When a property or indexer declared in a *struct_type* is the target of an assig > > *end example* +### §deconstructing-assignment-new-clause Deconstructing assignment + +If the left operand of a `=` operator is classified as a tuple, the right hand side is ***deconstructed*** into individual expressions, which are assigned to each of the elements of the left hand side tuple. + +If any of the tuple elements of the left operand have an identifier, 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 discard, a compile-time error occurs. + +A list of deconstructed expressions for the right hand side is determined as follows: + +- If the right operand is a tuple expression, then the list shall consist of the element expressions of the right operand, in order. +- Otherwise, if the right operand is a expression of a tuple type, then a variable of that type shall be declared, and the list shall consist of the element accesses on that variable of each of the tuple type's elements, in order. +- Otherwise, if an instance method lookup on the right operand finds a method of the name `Deconstruct` with a number of parameters corresponding to the arity of the left operand, and each of those parameters is an `out` parameter, then the list shall consist of a newly declared local variable for each of those parameters, and with that parameter's type. +- Otherwise a compile-time error occurs. + +If the list of expression has different length than the arity of the left operand then a compile-time error occurs. + +For each of the tuple elements of the left operand: + +- If the element is itself a tuple, then a deconstructing assignment shall recursively be valid to it from the corresponding expression. +- Otherwise if the element is an implicitly typed declaration expression or discard, the corresponding expression shall have a type. That type shall be the inferred type of the element. +- Otherwise an implicit conversion shall exist from the corresponding expression. + +A deconstructing assignment is evaluated as follows: + +- Each of the elements of the left operand are evaluated in order, recursively evaluating elements that are themselves tuples. +- If the right operand is a tuple, then each element of the right operand is evaluated in order. +- Otherwise, if the right operand is of a tuple type then it is evaluated, and each of its elements is accessed with a member access. +- Otherwise, the right operand is evaluated, and a call of a `Deconstruct` method is performed on it, with a list of variables of the appropriate type as out arguments, and with the values of that list as the resulting values. +- The resulting list of values is converted to the type of the corresponding left element and assigned to it. + +The type of the deconstructing assignment is the tuple type with the types of the left elements and no element names. The value is a tuple constructed from the assigned and converted values. + + ### 11.19.3 Compound assignment If the left operand of a compound assignment is of the form `E.P` or `E[Ei]` where `E` has the compile-time type `dynamic`, then the assignment is dynamically bound ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). diff --git a/standard/types.md b/standard/types.md index a69ee8f8f..ab44d83be 100644 --- a/standard/types.md +++ b/standard/types.md @@ -153,6 +153,7 @@ non_nullable_value_type struct_type : type_name | simple_type + | tuple_type ; simple_type @@ -183,6 +184,14 @@ floating_point_type | 'double' ; +tuple_type + : '(' tuple_type_element (',' tuple_type_element)+ ')' + ; + +tuple_type_element + : type identifier? + ; + enum_type : type_name ; @@ -378,6 +387,14 @@ No standard conversions exist between `bool` and other value types. In particula An enumeration type is a distinct type with named constants. Every enumeration type has an underlying type, which shall be `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long` or `ulong`. The set of values of the enumeration type is the same as the set of values of the underlying type. Values of the enumeration type are not restricted to the values of the named constants. Enumeration types are defined through enumeration declarations ([§18.2](enums.md#182-enum-declarations)). +### §tuple-types-new_clause Tuple types + +A tuple type represents an ordered fixed-length sequence of values with optional names and individual types. A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional. This syntax is shorthand for `System.ValueTuple` (or an equivalent type for long tuple types), which is a generic struct type available for each "arity" (length) of tuple type, with unconstrained type parameters for each tuple element type. + +The `ValueTuple<...>` types do not represent the optional element names, which are thus not part of the runtime representation of the tuple value, but impact how the tuple elements can be accessed. There is an identy conversion between all tuple types with the same arity and element types, and with the corresponding constructed `ValueTuple<...>` type. + +Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access. Additionally, if the tuple type has a name for a given element, that name can be used to access it. + ### 8.3.11 Nullable value types A nullable value type can represent all values of its underlying type plus an additional null value. A nullable value type is written `T?`, where `T` is the underlying type. This syntax is shorthand for `System.Nullable`, and the two forms can be used interchangeably. diff --git a/standard/variables.md b/standard/variables.md index bf98249c9..905736571 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -113,9 +113,9 @@ Within an instance constructor of a struct type, the `this` keyword behaves exac ### 9.2.8 Local variables -A ***local variable*** is declared by a *local_variable_declaration*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. +A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *tuple_deconstruction*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. -A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. +A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element*. A *tuple_deconstruction* can occur as the left hand side of an assignment or as a *tuple_element*. The lifetime of a local variable is the portion of program execution during which storage is guaranteed to be reserved for it. This lifetime extends from entry into the scope with which it is associated, at least until execution of that scope ends in some way. (Entering an enclosed *block*, calling a method, or yielding a value from an iterator block suspends, but does not end, execution of the current scope.) If the local variable is captured by an anonymous function ([§11.17.6.2](expressions.md#111762-captured-outer-variables)), its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection. If the parent scope is entered recursively or iteratively, a new instance of the local variable is created each time, and its *local_variable_initializer*, if any, is evaluated each time. @@ -133,7 +133,7 @@ The lifetime of a local variable is the portion of program execution during whic > > *end note* -A local variable introduced by a *local_variable_declaration* is not automatically initialized and thus has no default value. Such a local variable is considered initially unassigned. +A local variable introduced by a *local_variable_declaration*, *declaration_expression* or *tuple_deconstruction* is not automatically initialized and thus has no default value. Such a local variable is considered initially unassigned. > *Note*: A *local_variable_declaration* that includes a *local_variable_initializer* is still initially unassigned. Execution of the declaration behaves exactly like an assignment to the variable ([§9.4.4.5](variables.md#9445-declaration-statements)). It is possible to use a variable without executing its *local_variable_initializer*; e.g., within the initializer expression itself or by using a *goto_statement* to bypass the initialization: > @@ -150,6 +150,10 @@ A local variable introduced by a *local_variable_declaration* is not automatical > > *end note* +#### 9.2.8.1 Discards + +A ***discard*** is a local variable that has no name, and can thus only be used once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its the value. A discard is introduced by a *declaration_expression* or *tuple_deconstruction* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not yield a result (). + ## 9.3 Default values The following categories of variables are automatically initialized to their default values: From 0e9bcdaeecc1ba7e4893e156eb999453f53ed72b Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 2 Nov 2022 08:21:12 -0700 Subject: [PATCH 02/41] Fix typos --- standard/conversions.md | 4 ++-- standard/types.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index d0afd364e..49307236f 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -308,7 +308,7 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### §implicit-tuple-conversions Implicit tuple conversions -An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of the `T`'s corresonding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of the `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. ### 10.2.13 User-defined implicit conversions @@ -441,7 +441,7 @@ For an explicit reference conversion to succeed at run-time, the value of the so ### §explicit-tuple-conversions Explicit tuple conversions -An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresonding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. +An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. ### 10.3.6 Unboxing conversions diff --git a/standard/types.md b/standard/types.md index ab44d83be..92c73384b 100644 --- a/standard/types.md +++ b/standard/types.md @@ -391,7 +391,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t A tuple type represents an ordered fixed-length sequence of values with optional names and individual types. A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional. This syntax is shorthand for `System.ValueTuple` (or an equivalent type for long tuple types), which is a generic struct type available for each "arity" (length) of tuple type, with unconstrained type parameters for each tuple element type. -The `ValueTuple<...>` types do not represent the optional element names, which are thus not part of the runtime representation of the tuple value, but impact how the tuple elements can be accessed. There is an identy conversion between all tuple types with the same arity and element types, and with the corresponding constructed `ValueTuple<...>` type. +The `ValueTuple<...>` types do not represent the optional element names, which are thus not part of the runtime representation of the tuple value, but impact how the tuple elements can be accessed. There is an identity conversion between all tuple types with the same arity and element types, and with the corresponding constructed `ValueTuple<...>` type. Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access. Additionally, if the tuple type has a name for a given element, that name can be used to access it. From c6a30bc0b7a135207e8d2200ab8022431365524e Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 2 Nov 2022 08:38:52 -0700 Subject: [PATCH 03/41] Fix indentation and whitespace --- standard/expressions.md | 16 +++++++--------- standard/variables.md | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 4d28bb532..8c3734e59 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -38,7 +38,7 @@ Most of the constructs that involve an expression ultimately require the express - 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 ([§11.6.6](expressions.md#1166-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 ([§11.6.6](expressions.md#1166-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 tuple is obtained by applying an implicit tuple conversion to a receiving tuple type, if one exists, or to the type of the tuple expression itself if not. +- The value of a tuple is obtained by applying an implicit tuple conversion to a receiving tuple type, if one exists, or to the type of the tuple expression itself if not. ## 11.3 Static and Dynamic Binding @@ -1450,10 +1450,10 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* - Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard () and its type shall be inferred from the syntactic context: - - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. - - If the discard occurs as the left-hand side of an assignment expression, its type shall be the type of the right-hand side - - If the discard occurs as a *tuple_element* on the left hand side of a deconstructing assignment, its type shall be the type of the corresponding tuple element on the right-hand side. - - If the discard occurs in a different syntactic context, or a type cannot be inferred, a compile time error occurs. + - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. + - If the discard occurs as the left-hand side of an assignment expression, its type shall be the type of the right-hand side + - If the discard occurs as a *tuple_element* on the left hand side of a deconstructing assignment, its type shall be the type of the corresponding tuple element on the right-hand side. + - If the discard occurs in a different syntactic context, or a type cannot be inferred, a compile time error occurs. - Otherwise, the *simple_name* is undefined and a compile-time error occurs. ### 11.7.5 Parenthesized expressions @@ -4333,7 +4333,7 @@ declaration_expression A declaration expression shall only occur in the following syntactic contexts: -- As an `out` *argument_value* in an *argument_list*. +- As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left-hand side of a deconstructing assignment. The *local_variable_type* of a *local_variable_declaration* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: @@ -4343,7 +4343,6 @@ The *local_variable_type* of a *local_variable_declaration* either directly spec A declaration expression with the identifier `_` is a discard (), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space (). - ## 11.16 Conditional operator The `?:` operator is called the conditional operator. It is at times also called the ternary operator. @@ -5805,7 +5804,7 @@ The left operand of an assignment shall be an expression classified as a variabl The `=` operator is called the ***simple assignment operator***. It assigns the value(s) 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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). -The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in [§11.9.new](). +The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in §11.9.new. 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. The compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). @@ -5972,7 +5971,6 @@ A deconstructing assignment is evaluated as follows: The type of the deconstructing assignment is the tuple type with the types of the left elements and no element names. The value is a tuple constructed from the assigned and converted values. - ### 11.19.3 Compound assignment If the left operand of a compound assignment is of the form `E.P` or `E[Ei]` where `E` has the compile-time type `dynamic`, then the assignment is dynamically bound ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). diff --git a/standard/variables.md b/standard/variables.md index 905736571..c39f831c1 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -152,7 +152,7 @@ A local variable introduced by a *local_variable_declaration*, *declaration_expr #### 9.2.8.1 Discards -A ***discard*** is a local variable that has no name, and can thus only be used once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its the value. A discard is introduced by a *declaration_expression* or *tuple_deconstruction* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not yield a result (). +A ***discard*** is a local variable that has no name, and can thus only be used once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* or *tuple_deconstruction* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not yield a result (). ## 9.3 Default values From 4bea7032fa5a29c2343c3369235730373cfc3664 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 2 Nov 2022 12:54:53 -0700 Subject: [PATCH 04/41] Fix up tuples --- standard/conversions.md | 2 +- standard/expressions.md | 4 ++-- standard/types.md | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 49307236f..39b4758fa 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -306,7 +306,7 @@ The following further implicit conversions exist for a given type parameter `T` In all cases, the rules ensure that a conversion is executed as a boxing conversion if and only if at run-time the conversion is from a value type to a reference type. -### §implicit-tuple-conversions Implicit tuple conversions +### §implicit-tuple-conversions-new-clause Implicit tuple conversions An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of the `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. diff --git a/standard/expressions.md b/standard/expressions.md index 8c3734e59..d101ef487 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1468,7 +1468,7 @@ parenthesized_expression A *parenthesized_expression* is evaluated by evaluating the *expression* within the parentheses. If the *expression* within the parentheses denotes a namespace or type, a compile-time error occurs. Otherwise, the result of the *parenthesized_expression* is the result of the evaluation of the contained *expression*. -### Tuple expressions +### §tuple-expressions-new-clause Tuple expressions A *tuple_expression* consists of two or more comma-separated and optionally-named *expression*s enclosed in parentheses. @@ -2086,7 +2086,7 @@ object_or_collection_initializer ; ``` -The *type* of an *object_creation_expression* shall be a *class_type*, a *value_type*, or a *type_parameter*. The *type* cannot be an abstract or static *class_type*. +The *type* of an *object_creation_expression* shall be a *class_type*, a *value_type*, or a *type_parameter*. The *type* cannot be a *tuple_type* or an abstract or static *class_type*. The optional *argument_list* ([§11.6.2](expressions.md#1162-argument-lists)) is permitted only if the *type* is a *class_type* or a *struct_type*. diff --git a/standard/types.md b/standard/types.md index 92c73384b..4f6a4ee76 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,11 +389,15 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new_clause Tuple types -A tuple type represents an ordered fixed-length sequence of values with optional names and individual types. A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional. This syntax is shorthand for `System.ValueTuple` (or an equivalent type for long tuple types), which is a generic struct type available for each "arity" (length) of tuple type, with unconstrained type parameters for each tuple element type. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. -The `ValueTuple<...>` types do not represent the optional element names, which are thus not part of the runtime representation of the tuple value, but impact how the tuple elements can be accessed. There is an identity conversion between all tuple types with the same arity and element types, and with the corresponding constructed `ValueTuple<...>` type. +Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. -Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access. Additionally, if the tuple type has a name for a given element, that name can be used to access it. +The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and element types, as well as with the corresponding constructed `ValueTuple<...>` type. + +The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied directly to a tuple type. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. + +Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value. Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. ### 8.3.11 Nullable value types From c9152a79b112790090bd93fe1ae4430e134f2a36 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 2 Nov 2022 15:52:44 -0700 Subject: [PATCH 05/41] Fix section and word converter errors --- standard/expressions.md | 4 ++-- standard/types.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index d101ef487..7aa18912f 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4326,7 +4326,7 @@ A *throw expression* shall only occur in the following syntactic contexts: A declaration expression declares a local variable. -``` antlr +``` ANTLR declaration_expression : local_variable_type identifier ``` @@ -5804,7 +5804,7 @@ The left operand of an assignment shall be an expression classified as a variabl The `=` operator is called the ***simple assignment operator***. It assigns the value(s) 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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). -The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in §11.9.new. +The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in §deconstructing-assignment-new-clause. 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. The compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). diff --git a/standard/types.md b/standard/types.md index 4f6a4ee76..9eea8c90e 100644 --- a/standard/types.md +++ b/standard/types.md @@ -387,7 +387,7 @@ No standard conversions exist between `bool` and other value types. In particula An enumeration type is a distinct type with named constants. Every enumeration type has an underlying type, which shall be `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long` or `ulong`. The set of values of the enumeration type is the same as the set of values of the underlying type. Values of the enumeration type are not restricted to the values of the named constants. Enumeration types are defined through enumeration declarations ([§18.2](enums.md#182-enum-declarations)). -### §tuple-types-new_clause Tuple types +### §tuple-types-new-clause Tuple types A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. From 7908b9468ad1aec3f14b920bb23a954eb75366cf Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 2 Nov 2022 16:02:27 -0700 Subject: [PATCH 06/41] Fix typos and lint errors --- standard/expressions.md | 2 +- standard/types.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 7aa18912f..94a28f540 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4336,7 +4336,7 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left-hand side of a deconstructing assignment. -The *local_variable_type* of a *local_variable_declaration* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: +The *local_variable_type* of a *declaration_expression* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. - In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. diff --git a/standard/types.md b/standard/types.md index 9eea8c90e..45b1e10ae 100644 --- a/standard/types.md +++ b/standard/types.md @@ -397,7 +397,7 @@ The optional element names are not represented in the `ValueTuple<...>` types, a The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied directly to a tuple type. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. -Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value. Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. +Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value. Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. ### 8.3.11 Nullable value types From fc50f9cdb67c910233fbe5bd1a467c1c2bfa3228 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 29 Nov 2022 16:09:13 -0800 Subject: [PATCH 07/41] Crisp up deconstruction Make sure evaluation order is described precisely in deconstructing assignments. --- standard/conversions.md | 2 +- standard/expressions.md | 39 +++++++++++++++------------------------ standard/types.md | 2 +- standard/variables.md | 2 +- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 39b4758fa..4ed403374 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -308,7 +308,7 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### §implicit-tuple-conversions-new-clause Implicit tuple conversions -An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of the `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. ### 10.2.13 User-defined implicit conversions diff --git a/standard/expressions.md b/standard/expressions.md index 72ca5e8e2..05ed73cb3 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -38,7 +38,7 @@ Most of the constructs that involve an expression ultimately require the express - 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 ([§11.6.6](expressions.md#1166-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 ([§11.6.6](expressions.md#1166-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 tuple is obtained by applying an implicit tuple conversion to a receiving tuple type, if one exists, or to the type of the tuple expression itself if not. +- The value of a tuple expression is obtained by applying an implicit tuple conversion (§implicit-tuple-conversions-new-clause) to the type of the tuple expression itself. It is an error to obtain the value of a tuple expression that does not have a type. ## 11.3 Static and Dynamic Binding @@ -1490,7 +1490,9 @@ tuple_element A tuple expression is classified as a tuple. It has a type if all the element expressions have a type. In that case, the type of a tuple expression is the tuple type of the same arity, where each type element has the type of the corresponding element expression and the same identifier as the corresponding tuple element if it has one, or none if it does not. -A tuple expression can be the target of a deconstructing assignment (). +A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. + +A tuple expression can be the target of a deconstructing assignment (§deconstructing-assignment-new-clause). ### 11.7.6 Member access @@ -4347,7 +4349,7 @@ The *local_variable_type* of a *declaration_expression* either directly specifie - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. - In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. -A declaration expression with the identifier `_` is a discard (), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space (). +A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space. ## 11.16 Conditional operator @@ -5952,30 +5954,19 @@ If the left operand of a `=` operator is classified as a tuple, the right hand s If any of the tuple elements of the left operand have an identifier, 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 discard, a compile-time error occurs. -A list of deconstructed expressions for the right hand side is determined as follows: - -- If the right operand is a tuple expression, then the list shall consist of the element expressions of the right operand, in order. -- Otherwise, if the right operand is a expression of a tuple type, then a variable of that type shall be declared, and the list shall consist of the element accesses on that variable of each of the tuple type's elements, in order. -- Otherwise, if an instance method lookup on the right operand finds a method of the name `Deconstruct` with a number of parameters corresponding to the arity of the left operand, and each of those parameters is an `out` parameter, then the list shall consist of a newly declared local variable for each of those parameters, and with that parameter's type. -- Otherwise a compile-time error occurs. - -If the list of expression has different length than the arity of the left operand then a compile-time error occurs. - -For each of the tuple elements of the left operand: - -- If the element is itself a tuple, then a deconstructing assignment shall recursively be valid to it from the corresponding expression. -- Otherwise if the element is an implicitly typed declaration expression or discard, the corresponding expression shall have a type. That type shall be the inferred type of the element. -- Otherwise an implicit conversion shall exist from the corresponding expression. +The evaluation of a deconstructing assignment `(R1, ..., Rn) = E` proceeds as follows: -A deconstructing assignment is evaluated as follows: +- First the left hand side tuple is evaluated, meaning that each of the element expressions `R1, ..., Rn` are evaluated in order. +- Then the right hand side `E` is deconstructed into a list of expressions `E1, E2, ...` as follows: + - If `E` is a tuple expression with the same arity as the left-hand side, then `E1, E2, ...` shall be the element expressions of `E`. + - Otherwise, if `E` has a tuple type (§tuple-types-new-clause) with the same arity as the left-hand side, then `E1, E2, ...` shall be the member access expressions `E.Item1, E.Item2, ...`, except that `E` shall be evaluated only once. + - Otherwise, if `E.Deconstruct(out var v1, out var v2, ...)` with the number of arguments corresponding to the arity of the left hand side is a valid instance method invocation, then that invocation shall be performed and `E1, E2, ...` shall be the simple name expressions `v1, v2, ...`. + - Otherwise `E` cannot be deconstructed, and a compile time error occurs. +- Finally, the operation is evaluated as `(R1 = E1, R2 = E2, ...)`, except that `R1, R2, ...` have already been evaluated and are not evaluated again. This means that individual element-wise assignments are recursively performed, and a resulting tuple value is created. -- Each of the elements of the left operand are evaluated in order, recursively evaluating elements that are themselves tuples. -- If the right operand is a tuple, then each element of the right operand is evaluated in order. -- Otherwise, if the right operand is of a tuple type then it is evaluated, and each of its elements is accessed with a member access. -- Otherwise, the right operand is evaluated, and a call of a `Deconstruct` method is performed on it, with a list of variables of the appropriate type as out arguments, and with the values of that list as the resulting values. -- The resulting list of values is converted to the type of the corresponding left element and assigned to it. +Note that even though the assignment operation "evaluated as" a tuple expression containing element-wise assignment expressions, this does prevent the deconstructing assignment from occurring as a statement expression, nor the elements on the left-hand side from being declaration expressions. -The type of the deconstructing assignment is the tuple type with the types of the left elements and no element names. The value is a tuple constructed from the assigned and converted values. +Note that the resulting tuple value of a deconstructing assignment always has a type, since each of its elements is an assignment and thus has a type. ### 11.19.3 Compound assignment diff --git a/standard/types.md b/standard/types.md index 45b1e10ae..b7c96a6c3 100644 --- a/standard/types.md +++ b/standard/types.md @@ -393,7 +393,7 @@ A tuple type represents an ordered, fixed-length sequence of values with optiona Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. -The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and element types, as well as with the corresponding constructed `ValueTuple<...>` type. +The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and element types, as well as to and from the corresponding constructed `ValueTuple<...>` type. The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied directly to a tuple type. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. diff --git a/standard/variables.md b/standard/variables.md index bf9b2dff3..2bcbca361 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -150,7 +150,7 @@ A local variable introduced by a *local_variable_declaration*, *declaration_expr > > *end note* -#### 9.2.8.1 Discards +#### §discards-new-clause Discards A ***discard*** is a local variable that has no name, and can thus only be used once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* or *tuple_deconstruction* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not yield a result (). From 5b1c9059d462f1a953bdb65b1a7d80eb804d25ed Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Fri, 27 Jan 2023 12:47:23 -0800 Subject: [PATCH 08/41] Apply suggestions from code review Co-authored-by: Bill Wagner --- standard/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index 10379c11d..cdcad46fb 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,7 +389,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***. A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. From 05821a9d050466dd2a987d8c97bdb621d74ffec8 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Fri, 27 Jan 2023 12:49:22 -0800 Subject: [PATCH 09/41] Add examples and section references, fix issues from review comments --- standard/conversions.md | 6 +++--- standard/expressions.md | 15 ++++++++------- standard/types.md | 25 +++++++++++++++++++++++-- standard/variables.md | 8 ++++---- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index f27bea663..41b5e98de 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -83,7 +83,7 @@ In some cases there is an identity conversion between types that are not exactly - between `object` and `dynamic`. - between tuple types with the same arity, when an identity conversion exists between each pair of corresponding element types. -- between constructed types that are the same when replacing constituent types with identity convertible ones. +- between types constructed from the same generic type where there exists an identity conversion between each corresponding type argument. In most cases, an identity conversion has no effect at runtime. However, since floating point operations may be performed at higher precision than prescribed by their type ([§8.3.7](types.md#837-floating-point-types)), assignment of their results may result in a loss of precision, and explicit casts are guaranteed to reduce precision to what is prescribed by the type ([§11.8.7](expressions.md#1187-cast-expressions)). @@ -317,7 +317,7 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### §implicit-tuple-conversions-new-clause Implicit tuple conversions -An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. ### 10.2.13 User-defined implicit conversions @@ -454,7 +454,7 @@ For an explicit reference conversion to succeed at run-time, the value of the so ### §explicit-tuple-conversions Explicit tuple conversions -An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, evaluating each tuple element expression of `E` in order, converting it to the corresponding element type, and initializing each corresponding field of the tuple value with the result. +An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the explicit conversion found, and initializing the field with the result. ### 10.3.6 Unboxing conversions diff --git a/standard/expressions.md b/standard/expressions.md index f30d6f69f..97a86cc04 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1468,7 +1468,7 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain exactly one type having name `I` and `e` type parameters, then the *simple_name* refers to that type constructed with the given type arguments. - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* -- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard () and its type shall be inferred from the syntactic context: +- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a fresh discard (§discards-new-clause) and its type shall be inferred from the syntactic context: - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. - If the discard occurs as the left-hand side of an assignment expression, its type shall be the type of the right-hand side - If the discard occurs as a *tuple_element* on the left hand side of a deconstructing assignment, its type shall be the type of the corresponding tuple element on the right-hand side. @@ -1498,7 +1498,7 @@ tuple_element : (identifier ':')? expression ``` -A tuple expression is classified as a tuple. It has a type if all the element expressions have a type. In that case, the type of a tuple expression is the tuple type of the same arity, where each type element has the type of the corresponding element expression and the same identifier as the corresponding tuple element if it has one, or none if it does not. +A tuple expression is classified as a tuple. It has a type if all the element expressions have a type. In that case, the type of a tuple expression is the tuple type of the same arity, where each type element has the type - and name, where given - of the corresponding element expression. A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. @@ -4382,7 +4382,7 @@ The *local_variable_type* of a *declaration_expression* either directly specifie - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. - In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. -A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space. +A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space ([§11.7.19]()). ## 11.16 Conditional operator @@ -5892,9 +5892,9 @@ 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. -The `=` operator is called the ***simple assignment operator***. It assigns the value(s) 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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). -The `=` operator with a tuple as the left operand is called the ***deconstructing assignment operator***. It assigns elements of the right operand to each of the tuple elements of the left operand. The deconstructing assignment operator is described in §deconstructing-assignment-new-clause. +A simple assignment with a tuple as the left operand is called a ***deconstructing assignment***. It assigns elements of the right operand to each of the tuple elements of the left operand. Deconstructing assignment is described in §deconstructing-assignment-new-clause. 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. The compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). @@ -6045,9 +6045,10 @@ The evaluation of a deconstructing assignment `(R1, ..., Rn) = E` proceeds as fo - Otherwise, if `E` has a tuple type (§tuple-types-new-clause) with the same arity as the left-hand side, then `E1, E2, ...` shall be the member access expressions `E.Item1, E.Item2, ...`, except that `E` shall be evaluated only once. - Otherwise, if `E.Deconstruct(out var v1, out var v2, ...)` with the number of arguments corresponding to the arity of the left hand side is a valid instance method invocation, then that invocation shall be performed and `E1, E2, ...` shall be the simple name expressions `v1, v2, ...`. - Otherwise `E` cannot be deconstructed, and a compile time error occurs. -- Finally, the operation is evaluated as `(R1 = E1, R2 = E2, ...)`, except that `R1, R2, ...` have already been evaluated and are not evaluated again. This means that individual element-wise assignments are recursively performed, and a resulting tuple value is created. +- Then, each of the expressions `E1, E2, ...` in order is evaluated and converted to the type of the corresponding `R1, R2, ...` of the left hand tuple. +- Finally, the operation is evaluated as `(R1 = E1, R2 = E2, ...)`, except that `R1, R2, ...` and `E1, E2, ...` have already been evaluated and are not evaluated again. This means that individual element-wise assignments are recursively performed, and a resulting tuple value is created. -Note that even though the assignment operation "evaluated as" a tuple expression containing element-wise assignment expressions, this does prevent the deconstructing assignment from occurring as a statement expression, nor the elements on the left-hand side from being declaration expressions. +Note that even though the assignment operation is "evaluated as" a tuple expression containing element-wise assignment expressions, this does not prevent the deconstructing assignment from occurring as a statement expression, nor the elements on the left-hand side from being declaration expressions. Note that the resulting tuple value of a deconstructing assignment always has a type, since each of its elements is an assignment and thus has a type. diff --git a/standard/types.md b/standard/types.md index 10379c11d..2de1d649e 100644 --- a/standard/types.md +++ b/standard/types.md @@ -393,11 +393,32 @@ A tuple type represents an ordered, fixed-length sequence of values with optiona Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. -The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and element types, as well as to and from the corresponding constructed `ValueTuple<...>` type. +The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and the same sequence of element types, as well as to and from the corresponding constructed `ValueTuple<...>` type. The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied directly to a tuple type. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. -Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value. Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. +Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value ([§11.7.6](expressions.md#1176-member-access). Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. + +Given the following examples: + +``` c# +(int, string) pair1 = (1, "One"); +(int, string word) pair2 = (2, "Two"); +(int number, string word) pair3 = (3, "Three"); +(int Item1, string Item2) pair4 = (4, "Four"); +(int Item2, string Item123) pair5 = (5, "Five"); // Error: "Item" names do not match their position +(int, string) pair6 = new ValueTuple(6, "Six"); +ValueTuple pair7 = (7, "Seven"); +Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}"); +``` + +The tuple types for `pair1`, `pair2`, and `pair3` are all legal, with names for no, some or all of the tuple type elements. + +The tuple type for `pair4` is legal because the names `Item1` and `Item2` match their positions, whereas the tuple type for `pair5` is disallowed, because the names `Item2` and `Item123` do not. + +The declarations for `pair6` and `pair7` demonstrate that tuple types are interchangeable with constructed types of the form `ValueTuple<...>`, and that the `new` operator is allowed with the latter syntax. + +The last line shows that tuple elements can be accessed by the `Item` name corresponding to their position, as well as by the corresponding tuple element name, if present in the type. ### 8.3.11 Nullable value types diff --git a/standard/variables.md b/standard/variables.md index a8cb626e0..dd87c2534 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -113,9 +113,9 @@ Within an instance constructor of a struct type, the `this` keyword behaves exac ### 9.2.8 Local variables -A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *tuple_deconstruction*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. +A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. -A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element*. A *tuple_deconstruction* can occur as the left hand side of an assignment or as a *tuple_element*. +A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element* that is the target of a deconstructing assignment (§deconstructing-assignment-new-clause). The lifetime of a local variable is the portion of program execution during which storage is guaranteed to be reserved for it. This lifetime extends from entry into the scope with which it is associated, at least until execution of that scope ends in some way. (Entering an enclosed *block*, calling a method, or yielding a value from an iterator block suspends, but does not end, execution of the current scope.) If the local variable is captured by an anonymous function ([§11.17.6.2](expressions.md#111762-captured-outer-variables)), its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection. If the parent scope is entered recursively or iteratively, a new instance of the local variable is created each time, and its *local_variable_initializer*, if any, is evaluated each time. @@ -133,7 +133,7 @@ The lifetime of a local variable is the portion of program execution during whic > > *end note* -A local variable introduced by a *local_variable_declaration*, *declaration_expression* or *tuple_deconstruction* is not automatically initialized and thus has no default value. Such a local variable is considered initially unassigned. +A local variable introduced by a *local_variable_declaration* or *declaration_expression* is not automatically initialized and thus has no default value. Such a local variable is considered initially unassigned. > *Note*: A *local_variable_declaration* that includes a *local_variable_initializer* is still initially unassigned. Execution of the declaration behaves exactly like an assignment to the variable ([§9.4.4.5](variables.md#9445-declaration-statements)). It is possible to use a variable without executing its *local_variable_initializer*; e.g., within the initializer expression itself or by using a *goto_statement* to bypass the initialization: > @@ -152,7 +152,7 @@ A local variable introduced by a *local_variable_declaration*, *declaration_expr #### §discards-new-clause Discards -A ***discard*** is a local variable that has no name, and can thus only be used once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* or *tuple_deconstruction* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not yield a result (). +A ***discard*** is a local variable that has no name, and can thus only be referred to once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). ## 9.3 Default values From f67f61dcf870c201d0276f4db0fa5b90a9da11aa Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Fri, 27 Jan 2023 12:55:04 -0800 Subject: [PATCH 10/41] Fix lint issues --- standard/expressions.md | 2 +- standard/types.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 97a86cc04..f4de4bebb 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4382,7 +4382,7 @@ The *local_variable_type* of a *declaration_expression* either directly specifie - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. - In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. -A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space ([§11.7.19]()). +A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). ## 11.16 Conditional operator diff --git a/standard/types.md b/standard/types.md index 1a53a19d6..ef6068df7 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,7 +389,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***. A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. @@ -418,7 +418,7 @@ The tuple type for `pair4` is legal because the names `Item1` and `Item2` match The declarations for `pair6` and `pair7` demonstrate that tuple types are interchangeable with constructed types of the form `ValueTuple<...>`, and that the `new` operator is allowed with the latter syntax. -The last line shows that tuple elements can be accessed by the `Item` name corresponding to their position, as well as by the corresponding tuple element name, if present in the type. +The last line shows that tuple elements can be accessed by the `Item` name corresponding to their position, as well as by the corresponding tuple element name, if present in the type. ### 8.3.11 Nullable value types From 71145668ce046b5a083b58a0abbbe14c5bacff6d Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Fri, 27 Jan 2023 16:10:16 -0800 Subject: [PATCH 11/41] Add examples --- standard/expressions.md | 27 +++++++++++++++++++++++++++ standard/variables.md | 15 ++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index f4de4bebb..7f53a9be9 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4377,6 +4377,8 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left-hand side of a deconstructing assignment. +It is an error for a variable declared with a *declaration_expression* to be referenced within the *argument_list* or left-hand side of a deconstructing assignment where it occurs. + The *local_variable_type* of a *declaration_expression* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. @@ -4384,6 +4386,31 @@ The *local_variable_type* of a *declaration_expression* either directly specifie A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). +*Example:* + +``` c# +string M(out int i, string s, out bool b) { ... } + +var s1 = M(out int i1, "One", out var b1); +Console.WriteLine($"{i1}, {b1}, {s1}"); +var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); // Error: i2 referenced within declaring argument list +var s3 = M(out int _, "Three", out var _); +``` + +The declaration of `s1` shows both explicitly and implicitly typed declaration expressions. The inferred type of `b1` is `bool` because that is the type of the corresponding out parameter in `M1`. The subsequent `WriteLine` is able to access `i1` and `b1`, which have been introduced to the enclosing scope. + +The declaration of `s2` shows an attempt to use `i2` in the nested call to `M`, which is disallowed, because the reference occurs within the argument list where `i2` was declared. On the other hand the reference to `b2` in the final argument is allowed, because it occurs after the end of the nested argument list where `b2` was declared. + +The declaration of `s3` shows the use of both implicitly and explicitly typed declaration expressions that are discards. Because discards do not declare a named variable, the multiple occurrences of the identifier `_` are allowed. + +*Example:* + +``` c# +(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5); +``` + +This example shows the use of implicitly and explicitly typed declaration expressions for both variables and discards in a deconstructing assignment. + ## 11.16 Conditional operator The `?:` operator is called the conditional operator. It is at times also called the ternary operator. diff --git a/standard/variables.md b/standard/variables.md index dd87c2534..72ee0c4a3 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -152,7 +152,20 @@ A local variable introduced by a *local_variable_declaration* or *declaration_ex #### §discards-new-clause Discards -A ***discard*** is a local variable that has no name, and can thus only be referred to once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). +A ***discard*** is a local variable that has no name and can thus only be referred to once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). + +*Example:* + +``` c# +_ = "Hello".Length; +(int, int, int) M(out int i1, out int i2, out int i3) { ... } +(int _, var _, _) = M(out int _, out var _, out _); +``` + +The example assumes that there is no declaration of the name `_` in scope. + +The assignment to `_` shows a simple pattern for ignoring the result of an expression. +The call of `M` shows the different forms of discards available in tuples and as out parameters. ## 9.3 Default values From 4d75674334b44ef2e285d1f8740fae81400fadee Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Fri, 27 Jan 2023 16:11:21 -0800 Subject: [PATCH 12/41] Update variables.md --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index 72ee0c4a3..a194d7aa0 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -162,7 +162,7 @@ _ = "Hello".Length; (int _, var _, _) = M(out int _, out var _, out _); ``` -The example assumes that there is no declaration of the name `_` in scope. +The example assumes that there is no declaration of the name `_` in scope. The assignment to `_` shows a simple pattern for ignoring the result of an expression. The call of `M` shows the different forms of discards available in tuples and as out parameters. From 204888107617b8fe9f645c9803523890698917dd Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 12:03:57 -0800 Subject: [PATCH 13/41] Add examples --- standard/conversions.md | 14 ++++++++++++++ standard/expressions.md | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/standard/conversions.md b/standard/conversions.md index 41b5e98de..371e30937 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -319,6 +319,20 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. +If an element name in the tuple expression does not match a corresponding element name in the tuple type, a warning shall be issued. + +*Example:* + +``` c# +(int, string) t1 = (1, "One"); +(byte, string) t2 = (2, null); +(int, string) t3 = (null, null); // Error: No conversion +(int i, string s) t4 = (i: 4, "Four"); +(int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored +``` + +The declarations of `t1`, `t2`, `t4` and `t5` are all valid, since implicit conversions exist from the element expressions to the corresponding element types. The declaration of `t3` is invalid, because there is no conversion from `null` to `int`. The declaration of `t5` causes a warning because the element names in the tuple expression differs from those in the tuple type. + ### 10.2.13 User-defined implicit conversions A user-defined implicit conversion consists of an optional standard implicit conversion, followed by execution of a user-defined implicit conversion operator, followed by another optional standard implicit conversion. The exact rules for evaluating user-defined implicit conversions are described in [§10.5.4](conversions.md#1054-user-defined-implicit-conversions). diff --git a/standard/expressions.md b/standard/expressions.md index 7f53a9be9..ee32d301c 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1502,7 +1502,18 @@ A tuple expression is classified as a tuple. It has a type if all the element ex A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. -A tuple expression can be the target of a deconstructing assignment (§deconstructing-assignment-new-clause). +A tuple value can be obtained from a tuple expression by converting it to a tuple type (§implicit-tuple-conversions-new-clause), by reclassifying it as a value ([§11.2.2](expressions.md#1122-values-of-expressions))) or by making it the target of a deconstructing assignment (§deconstructing-assignment-new-clause). + +*Example:* + +``` c# +(int i, string) t1 = (i: 1, "One"); +(int i, string) t2 = (i: 2, null); +var t3 = (i: 3, "Three"); // (int i, string) +var t4 = (i: 4, null); // Error: no type +``` + +In this example, all four tuple expressions are valid. The first two, `t1` and `t2`, do not use the type of the tuple expression, but instead apply an implicit tuple conversion. The third tuple expression has a type `(int i, string)`, and can therefore be reclassified as a value of that type. The declaration of `t4`, on the other hand, is an error: The tuple expression has no type because its second element has no type. ### 11.7.6 Member access From 7896208f1ebac8eef7ff110d6d7500ec7f1f1c31 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 12:16:24 -0800 Subject: [PATCH 14/41] Fix editorial comments --- standard/lexical-structure.md | 5 +++++ standard/types.md | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index 4c1215a4f..e0533cedc 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -536,6 +536,11 @@ Two identifiers are considered the same if they are identical after the followin - Each *Unicode_Escape_Sequence* is transformed into its corresponding Unicode character. - Any *Formatting_Character*s are removed. +The semantics of an identifier named `_` depends on the context in which it appears: + +- It can denote a named program element, such as a variable, class, or method, or +- It can denote a discard (§discards-new-clause). + 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* diff --git a/standard/types.md b/standard/types.md index ef6068df7..2a9a518cd 100644 --- a/standard/types.md +++ b/standard/types.md @@ -412,9 +412,9 @@ ValueTuple pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}"); ``` -The tuple types for `pair1`, `pair2`, and `pair3` are all legal, with names for no, some or all of the tuple type elements. +The tuple types for `pair1`, `pair2`, and `pair3` are all valid, with names for no, some or all of the tuple type elements. -The tuple type for `pair4` is legal because the names `Item1` and `Item2` match their positions, whereas the tuple type for `pair5` is disallowed, because the names `Item2` and `Item123` do not. +The tuple type for `pair4` is valid because the names `Item1` and `Item2` match their positions, whereas the tuple type for `pair5` is disallowed, because the names `Item2` and `Item123` do not. The declarations for `pair6` and `pair7` demonstrate that tuple types are interchangeable with constructed types of the form `ValueTuple<...>`, and that the `new` operator is allowed with the latter syntax. From eeec248d67595622fc7868e1466c51b508ada6b9 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 16:07:45 -0800 Subject: [PATCH 15/41] Fix declaration spaces and simple name lookup --- standard/basic-concepts.md | 12 ++++++++---- standard/expressions.md | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 21322f096..10ac7bb01 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -78,12 +78,16 @@ There are several different types of declaration spaces, as described in the fol - Within all compilation units of a program, *namespace_member_declaration*s with no enclosing *namespace_declaration* are members of a single combined declaration space called the ***global declaration space***. - Within all compilation units of a program, *namespace_member_declaration*s within *namespace_declaration*s that have the same fully qualified namespace name are members of a single combined declaration space. - Each *compilation_unit* and *namespace_body* has an ***alias declaration space***. Each *extern_alias_directive* and *using_alias_directive* of the *compilation_unit* or *namespace_body* contributes a member to the alias declaration space ([§13.5.2](namespaces.md#1352-using-alias-directives)). -- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§15.2.3](structs.md#1523-partial-modifier)).Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. +- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§15.2.3](structs.md#1523-partial-modifier)). Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. -- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. It is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. -- Each *block* or *switch_block*, as well as a `for`, `foreach`, and `using` statement, creates a local variable declaration space for local variables and local constants. Names are introduced into this declaration space through *local_variable_declaration*s and *local_constant_declaration*s. Note that blocks that occur as or within the body of a function member, anonymous function, or local function are nested within the local variable declaration space declared by those functions for their parameters. Thus, it is an error to have, for example, a method with a local variable and a parameter of the same name. - +- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. +- Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *declaration_expression*s, *local_variable_declaration*s and *local_constant_declaration*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a parameter, type parameter, local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: + - Each *variable_initializer* introduces its own local variable declaration space, that is not nested within any other local variable declaration space. + - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function's local variable declaration space. + - Each *constructor_initializer* creates a local variable declaration space nested within the instance constructor declaration. The local variable declaration space for the constructor body is in turn nested within this local variable declaration space. + - Each *block*, *switch_block*, *iteration_statement* and *using_statement* creates a nested local variable declaration space. + - Each *embedded_statement* that is not directly part of a *statement_list* creates a nested local variable declaration space. - Each *block* or *switch_block* creates a separate declaration space for labels. Names are introduced into this declaration space through *labeled_statement*s, and the names are referenced through *goto_statement*s. The ***label declaration space*** of a block includes any nested blocks. Thus, within a nested block it is not possible to declare a label with the same name as a label in an enclosing block. The textual order in which names are declared is generally of no significance. In particular, textual order is not significant for the declaration and use of namespaces, constants, methods, properties, events, indexers, operators, instance constructors, finalizers, static constructors, and types. Declaration order is significant in the following ways: diff --git a/standard/expressions.md b/standard/expressions.md index ee32d301c..066cdd54a 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1448,7 +1448,7 @@ simple_name A *simple_name* is either of the form `I` or of the form `I`, where `I` is a single identifier and `I` is an optional *type_argument_list*. When no *type_argument_list* is specified, consider `e` to be zero. The *simple_name* is evaluated and classified as follows: -- If `e` is zero and the *simple_name* appears within a *block* and if the *block*’s (or an enclosing *block*’s) local variable declaration space ([§7.3](basic-concepts.md#73-declarations)) contains a local variable, parameter or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. +- If `e` is zero and the *simple_name* appears within a local variable declaration space ([§7.3](basic-concepts.md#73-declarations)) that contains a local variable, parameter or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. - If `e` is zero and the *simple_name* appears within a generic method declaration but outside the *attributes* of its *method_header,* and if that declaration includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. - Otherwise, for each instance type `T` ([§14.3.2](classes.md#1432-the-instance-type)), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any): - If `e` is zero and the declaration of `T` includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. @@ -6081,7 +6081,7 @@ The evaluation of a deconstructing assignment `(R1, ..., Rn) = E` proceeds as fo - Then the right hand side `E` is deconstructed into a list of expressions `E1, E2, ...` as follows: - If `E` is a tuple expression with the same arity as the left-hand side, then `E1, E2, ...` shall be the element expressions of `E`. - Otherwise, if `E` has a tuple type (§tuple-types-new-clause) with the same arity as the left-hand side, then `E1, E2, ...` shall be the member access expressions `E.Item1, E.Item2, ...`, except that `E` shall be evaluated only once. - - Otherwise, if `E.Deconstruct(out var v1, out var v2, ...)` with the number of arguments corresponding to the arity of the left hand side is a valid instance method invocation, then that invocation shall be performed and `E1, E2, ...` shall be the simple name expressions `v1, v2, ...`. + - Otherwise, if `E.Deconstruct(out var v1, out var v2, ...)` with the number of arguments corresponding to the arity of the left hand side is a valid instance or extension method invocation, then that invocation shall be performed and `E1, E2, ...` shall be the simple name expressions `v1, v2, ...`. - Otherwise `E` cannot be deconstructed, and a compile time error occurs. - Then, each of the expressions `E1, E2, ...` in order is evaluated and converted to the type of the corresponding `R1, R2, ...` of the left hand tuple. - Finally, the operation is evaluated as `(R1 = E1, R2 = E2, ...)`, except that `R1, R2, ...` and `E1, E2, ...` have already been evaluated and are not evaluated again. This means that individual element-wise assignments are recursively performed, and a resulting tuple value is created. From 231a77ee8df19c1a099ec764246ed4ed049097e9 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 16:09:09 -0800 Subject: [PATCH 16/41] Update basic-concepts.md --- standard/basic-concepts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 10ac7bb01..f8e6175f7 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -81,10 +81,10 @@ There are several different types of declaration spaces, as described in the fol - Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§15.2.3](structs.md#1523-partial-modifier)). Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. -- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. +- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. - Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *declaration_expression*s, *local_variable_declaration*s and *local_constant_declaration*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a parameter, type parameter, local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: - Each *variable_initializer* introduces its own local variable declaration space, that is not nested within any other local variable declaration space. - - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function's local variable declaration space. + - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function's local variable declaration space. - Each *constructor_initializer* creates a local variable declaration space nested within the instance constructor declaration. The local variable declaration space for the constructor body is in turn nested within this local variable declaration space. - Each *block*, *switch_block*, *iteration_statement* and *using_statement* creates a nested local variable declaration space. - Each *embedded_statement* that is not directly part of a *statement_list* creates a nested local variable declaration space. From 25d39d02a7a219d0b96624f1f37f1536914a866b Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 16:23:51 -0800 Subject: [PATCH 17/41] Fix local declarations --- standard/basic-concepts.md | 2 +- standard/expressions.md | 2 +- standard/statements.md | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index f8e6175f7..4f9177dec 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -82,7 +82,7 @@ There are several different types of declaration spaces, as described in the fol - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. - Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. -- Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *declaration_expression*s, *local_variable_declaration*s and *local_constant_declaration*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a parameter, type parameter, local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: +- Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *declaration_expression*s and *declaration_statement*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a parameter, type parameter, local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: - Each *variable_initializer* introduces its own local variable declaration space, that is not nested within any other local variable declaration space. - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function's local variable declaration space. - Each *constructor_initializer* creates a local variable declaration space nested within the instance constructor declaration. The local variable declaration space for the constructor body is in turn nested within this local variable declaration space. diff --git a/standard/expressions.md b/standard/expressions.md index 066cdd54a..71bbb2dde 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4395,7 +4395,7 @@ The *local_variable_type* of a *declaration_expression* either directly specifie - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. - In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. -A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). +A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the nearest enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). *Example:* diff --git a/standard/statements.md b/standard/statements.md index 40decb7f5..b97647927 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -278,18 +278,20 @@ In addition to the reachability provided by normal flow of control, a labeled st ### 12.6.1 General -A *declaration_statement* declares a local variable, local constant, or local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. +A *declaration_statement* declares one or more local variables, a local constant, or a local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. ```ANTLR declaration_statement : local_variable_declaration ';' | local_constant_declaration ';' - | local_function_declaration + | local_function_declaration ; ``` A local variable is declared using a *local_variable_declaration* ([§12.6.2](statements.md#1262-local-variable-declarations)). A local constant is declared using a *local_constant_declaration* ([§12.6.3](statements.md#1263-local-constant-declarations)). A local function is declared using a *local_function_declaration* ([§12.6.4](statements.md#1264-local-function-declarations)). +The declared names are introduced into the nearest enclosing declaration space ([§7.3](basic-concepts.md#73-declarations)). + ### 12.6.2 Local variable declarations A *local_variable_declaration* declares one or more local variables. From 0fb99c1096fa287c8671fd136eb32a337e193d9c Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 31 Jan 2023 16:24:56 -0800 Subject: [PATCH 18/41] Update statements.md --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index b97647927..08217ec59 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -290,7 +290,7 @@ declaration_statement A local variable is declared using a *local_variable_declaration* ([§12.6.2](statements.md#1262-local-variable-declarations)). A local constant is declared using a *local_constant_declaration* ([§12.6.3](statements.md#1263-local-constant-declarations)). A local function is declared using a *local_function_declaration* ([§12.6.4](statements.md#1264-local-function-declarations)). -The declared names are introduced into the nearest enclosing declaration space ([§7.3](basic-concepts.md#73-declarations)). +The declared names are introduced into the nearest enclosing declaration space ([§7.3](basic-concepts.md#73-declarations)). ### 12.6.2 Local variable declarations From 26d24a3afa9d530417e20ad6ea1f38fed1c0c15c Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 14:06:09 -0800 Subject: [PATCH 19/41] Fix deconstructing assignment Deconstructing assignment is now fully integrated into simple assignment, and doesn't have its own section. Deconstruction itself has been broken into an independent subsection of Expressions in anticipation of more deconstruction to come in future releases. The evaluation order of a deconstructing assignment is now elegant and recursive. However, it still does not match the compiler behavior 100%. The evaluation order of the expressions directly occurring in the assignment expression is correct (except for what seems to be a compiler bug in a very specific case). However, in the case of nested left side tuples, some conversions and deconstructions may occur slightly later than my text implies. I've chosen to live with this difference since it is small, unobservable when the conversions and deconstructions are side-effect free (as they should be), and frankly beyond my capabilities to adequately overcome. --- standard/expressions.md | 89 ++++++++++++++++++++--------------------- standard/variables.md | 2 +- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 71bbb2dde..29c9ab951 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1166,6 +1166,20 @@ In these situations, the boxed instance is considered to contain a variable of t > *Note*: In particular, this means that when a function member is invoked on a boxed instance, it is possible for the function member to modify the value contained in the boxed instance. *end note* +## §deconstruction-new-clause Deconstruction + +Deconstruction is a process whereby an expression gets turned into a tuple of individual expressions. Deconstruction is used when the target of a simple assignment is a tuple expression, in order to obtain values to assign to each of that tuple's elements. + +An expression `E` is ***deconstructed*** to a tuple expression with `n` elements in the following way: + +- If `E` is a tuple expression with `n` elements, the result of deconstruction is the expression `E` itself. +- Otherwise, if `E` has a tuple type `(T1, ..., Tn)` with `n` elements, then `E` is evaluated into a temporary variable `__v`, and the result of deconstruction is the expression `(__v.Item1, ..., __v.Itemn)`. +- Otherwise, if the expression `E.Deconstruct(out var __v1, ..., out var __vn)` resolves at compile-time to a unique instance or extension method, that expression is evaluated, and the result of deconstruction is the expression `(__v1, ..., __vn)`. Such a method is referred to as a ***deconstructor***. +- Otherwise, `E` cannot be deconstructed. + +Here, `__v` and `__v1, ..., __vn` refer to otherwise invisible and inaccessible temporary variables. +> *Note*: An expression of type `dynamic` cannot be deconstructed. *end note* + ## 11.7 Primary expressions ### 11.7.1 General @@ -1470,8 +1484,8 @@ A *simple_name* is either of the form `I` or of the form `I`, > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* - Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a fresh discard (§discards-new-clause) and its type shall be inferred from the syntactic context: - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. - - If the discard occurs as the left-hand side of an assignment expression, its type shall be the type of the right-hand side - - If the discard occurs as a *tuple_element* on the left hand side of a deconstructing assignment, its type shall be the type of the corresponding tuple element on the right-hand side. + - If the discard occurs as the left side of an assignment expression, its type shall be the type of the right side + - If the discard occurs as a *tuple_element* on the left side of a deconstructing assignment, its type shall be the type of the corresponding tuple element (after deconstruction) on the right side. - If the discard occurs in a different syntactic context, or a type cannot be inferred, a compile time error occurs. - Otherwise, the *simple_name* is undefined and a compile-time error occurs. @@ -1502,7 +1516,7 @@ A tuple expression is classified as a tuple. It has a type if all the element ex A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. -A tuple value can be obtained from a tuple expression by converting it to a tuple type (§implicit-tuple-conversions-new-clause), by reclassifying it as a value ([§11.2.2](expressions.md#1122-values-of-expressions))) or by making it the target of a deconstructing assignment (§deconstructing-assignment-new-clause). +A tuple value can be obtained from a tuple expression by converting it to a tuple type (§implicit-tuple-conversions-new-clause), by reclassifying it as a value ([§11.2.2](expressions.md#1122-values-of-expressions))) or by making it the target of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). *Example:* @@ -4386,14 +4400,14 @@ declaration_expression A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. -- As a *tuple_element* in a *tuple_expression* that occurs on the left-hand side of a deconstructing assignment. +- As a *tuple_element* in a *tuple_expression* that occurs on the left side of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -It is an error for a variable declared with a *declaration_expression* to be referenced within the *argument_list* or left-hand side of a deconstructing assignment where it occurs. +It is an error for a variable declared with a *declaration_expression* to be referenced within the *argument_list* or deconstructing assignment where it occurs. The *local_variable_type* of a *declaration_expression* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: - In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. -- In a *tuple_expression* on the left hand side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right hand side of the assignment. +- In a *tuple_expression* on the left side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the nearest enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). @@ -5928,13 +5942,11 @@ 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. +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 [§14.8.2](classes.md#1482-field-like-events)). The simple assignment operator is described in [§11.19.2](expressions.md#11192-simple-assignment). -A simple assignment with a tuple as the left operand is called a ***deconstructing assignment***. It assigns elements of the right operand to each of the tuple elements of the left operand. Deconstructing assignment is described in §deconstructing-assignment-new-clause. - -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. The compound assignment operators are described in [§11.19.3](expressions.md#11193-compound-assignment). +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 [§11.19.3](expressions.md#11193-compound-assignment). The `+=` and `-=` operators with an event access expression as the left operand are called the ***event assignment operators***. No other assignment operator is valid with an event access as the left operand. The event assignment operators are described in [§11.19.4](expressions.md#11194-event-assignment). @@ -5948,24 +5960,31 @@ The `=` operator is called the simple assignment operator. 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 ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). -In a simple assignment, the right operand shall be an expression that is implicitly convertible to the type of the left operand. The operation assigns the value of the right operand to the variable, property, or indexer element given by the left operand. - -The result of a simple assignment expression is the value assigned to the left operand. The result has the same type as the left operand, and is always classified as a value. +A simple assignment with a tuple as the left operand is also called a ***deconstructing assignment***. If any of the tuple elements of the left operand have an identifier, 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 discard, a compile-time error occurs. -If the left operand is a property or indexer access, the property or indexer shall have an accessible set accessor. If this is not the case, a binding-time error occurs. +The type of a simple assignment `x = y` is determined as follows: -The run-time processing of a simple assignment of the form `x` = `y` consists of the following steps: +- If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements (§deconstruction-new-clause), and an assignment of each `xi` from `yi` is valid and has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. +- Otherwise, if `x` is classified as a variable, the variable is not `readonly`, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. +- Otherwise, if `x` is an implicitly typed variable (i.e. a discard or an implicitly type declaration expression) and `y` has a type `T`, then the inferred type of `x` is `T`, and the assignment has the type `T`. +- Otherwise, if `x` is classified as a property or indexer access, the property or indexer has an accessible set accessor, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. +- Otherwise the assignment is not valid and a binding-time error occurs. -- If `x` is classified as a variable: - - `x` is evaluated to produce the variable. - - `y` is evaluated and, if required, converted to the type of `x` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). +The run-time processing of a simple assignment of the form `x = y` with type `T` consists of the following steps: +- `x` is evaluated if it wasn't already. +- If `x` is classified as a variable, `y` is evaluated and, if required, converted to `T` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). - If the variable given by `x` is an array element of a *reference_type*, a run-time check is performed to ensure that the value computed for `y` is compatible with the array instance of which `x` is an element. The check succeeds if `y` is `null`, or if an implicit reference conversion ([§10.2.8](conversions.md#1028-implicit-reference-conversions)) exists from the type of the instance referenced by `y` to the actual element type of the array instance containing `x`. Otherwise, a `System.ArrayTypeMismatchException` is thrown. - - The value resulting from the evaluation and conversion of `y` is stored into the location given by the evaluation of `x`. + - The value resulting from the evaluation and conversion of `y` is stored into the location given by the evaluation of `x`, and is yielded as a result of the assignment. - If `x` is classified as a property or indexer access: - - The instance expression (if `x` is not `static`) and the argument list (if `x` is an indexer access) associated with `x` are evaluated, and the results are used in the subsequent set accessor invocation. - - `y` is evaluated and, if required, converted to the type of `x` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). - - The set accessor of `x` is invoked with the value computed for `y` as its value argument. - + - `y` is evaluated and, if required, converted to `T` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). + - The set accessor of `x` is invoked with the value resulting from the evaluation and conversion of `y` as its value argument. + - The value resulting from the evaluation and conversion of `y` is yielded as the result of the assignment. +- If `x` is classified as a tuple `(x1, ..., xn)` with arity `n`: + - `y` is deconstructed with `n` elements to a tuple expression `e`. + - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. + - for each `xi` in order left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. + - `t` is yielded as the result of the assignment. + > *Note*: if the compile time type of `x` is `dynamic` and there is an implicit conversion from the compile time type of `y` to `dynamic`, no runtime resolution is required. *end note* @@ -5985,8 +6004,7 @@ The run-time processing of a simple assignment of the form `x` = `y` consists > > *end note* -When a property or indexer declared in a *struct_type* is the target of an assignment, the instance expression associated with the property or indexer access shall be classified as a variable. If the instance expression is classified as a value, a binding-time error occurs. - +When a property or indexer declared in a *struct_type* is the target of an assignment, the instance expression associated with the property or indexer access shall be classified as a variable. If the instance expression is classified as a value, a binding-time error occurs. > *Note*: Because of [§11.7.6](expressions.md#1176-member-access), the same rule also applies to fields. *end note* @@ -6069,27 +6087,6 @@ When a property or indexer declared in a *struct_type* is the target of an assig > > *end example* -### §deconstructing-assignment-new-clause Deconstructing assignment - -If the left operand of a `=` operator is classified as a tuple, the right hand side is ***deconstructed*** into individual expressions, which are assigned to each of the elements of the left hand side tuple. - -If any of the tuple elements of the left operand have an identifier, 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 discard, a compile-time error occurs. - -The evaluation of a deconstructing assignment `(R1, ..., Rn) = E` proceeds as follows: - -- First the left hand side tuple is evaluated, meaning that each of the element expressions `R1, ..., Rn` are evaluated in order. -- Then the right hand side `E` is deconstructed into a list of expressions `E1, E2, ...` as follows: - - If `E` is a tuple expression with the same arity as the left-hand side, then `E1, E2, ...` shall be the element expressions of `E`. - - Otherwise, if `E` has a tuple type (§tuple-types-new-clause) with the same arity as the left-hand side, then `E1, E2, ...` shall be the member access expressions `E.Item1, E.Item2, ...`, except that `E` shall be evaluated only once. - - Otherwise, if `E.Deconstruct(out var v1, out var v2, ...)` with the number of arguments corresponding to the arity of the left hand side is a valid instance or extension method invocation, then that invocation shall be performed and `E1, E2, ...` shall be the simple name expressions `v1, v2, ...`. - - Otherwise `E` cannot be deconstructed, and a compile time error occurs. -- Then, each of the expressions `E1, E2, ...` in order is evaluated and converted to the type of the corresponding `R1, R2, ...` of the left hand tuple. -- Finally, the operation is evaluated as `(R1 = E1, R2 = E2, ...)`, except that `R1, R2, ...` and `E1, E2, ...` have already been evaluated and are not evaluated again. This means that individual element-wise assignments are recursively performed, and a resulting tuple value is created. - -Note that even though the assignment operation is "evaluated as" a tuple expression containing element-wise assignment expressions, this does not prevent the deconstructing assignment from occurring as a statement expression, nor the elements on the left-hand side from being declaration expressions. - -Note that the resulting tuple value of a deconstructing assignment always has a type, since each of its elements is an assignment and thus has a type. - ### 11.19.3 Compound assignment If the left operand of a compound assignment is of the form `E.P` or `E[Ei]` where `E` has the compile-time type `dynamic`, then the assignment is dynamically bound ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). diff --git a/standard/variables.md b/standard/variables.md index a194d7aa0..c773fd987 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -115,7 +115,7 @@ Within an instance constructor of a struct type, the `this` keyword behaves exac A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. -A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element* that is the target of a deconstructing assignment (§deconstructing-assignment-new-clause). +A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element* that is the target of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). The lifetime of a local variable is the portion of program execution during which storage is guaranteed to be reserved for it. This lifetime extends from entry into the scope with which it is associated, at least until execution of that scope ends in some way. (Entering an enclosed *block*, calling a method, or yielding a value from an iterator block suspends, but does not end, execution of the current scope.) If the local variable is captured by an anonymous function ([§11.17.6.2](expressions.md#111762-captured-outer-variables)), its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection. If the parent scope is entered recursively or iteratively, a new instance of the local variable is created each time, and its *local_variable_initializer*, if any, is evaluated each time. From 1107b68cc51181a584c1d7193b17fb6174bfd004 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 14:09:13 -0800 Subject: [PATCH 20/41] Fix linting issues --- standard/expressions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 29c9ab951..b1b665677 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -5971,6 +5971,7 @@ The type of a simple assignment `x = y` is determined as follows: - Otherwise the assignment is not valid and a binding-time error occurs. The run-time processing of a simple assignment of the form `x = y` with type `T` consists of the following steps: + - `x` is evaluated if it wasn't already. - If `x` is classified as a variable, `y` is evaluated and, if required, converted to `T` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). - If the variable given by `x` is an array element of a *reference_type*, a run-time check is performed to ensure that the value computed for `y` is compatible with the array instance of which `x` is an element. The check succeeds if `y` is `null`, or if an implicit reference conversion ([§10.2.8](conversions.md#1028-implicit-reference-conversions)) exists from the type of the instance referenced by `y` to the actual element type of the array instance containing `x`. Otherwise, a `System.ArrayTypeMismatchException` is thrown. @@ -5980,10 +5981,10 @@ The run-time processing of a simple assignment of the form `x = y` with type `T` - The set accessor of `x` is invoked with the value resulting from the evaluation and conversion of `y` as its value argument. - The value resulting from the evaluation and conversion of `y` is yielded as the result of the assignment. - If `x` is classified as a tuple `(x1, ..., xn)` with arity `n`: - - `y` is deconstructed with `n` elements to a tuple expression `e`. - - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. - - for each `xi` in order left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. - - `t` is yielded as the result of the assignment. + - `y` is deconstructed with `n` elements to a tuple expression `e`. + - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. + - for each `xi` in order left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. + - `t` is yielded as the result of the assignment. > *Note*: if the compile time type of `x` is `dynamic` and there is an implicit conversion from the compile time type of `y` to `dynamic`, no runtime resolution is required. *end note* From d180df7a6056cb9789d20807368f04fcb653c8cd Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 14:10:28 -0800 Subject: [PATCH 21/41] Fix more linting issues --- standard/expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index b1b665677..c5ed561f7 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -5985,7 +5985,7 @@ The run-time processing of a simple assignment of the form `x = y` with type `T` - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. - for each `xi` in order left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. - `t` is yielded as the result of the assignment. - + > *Note*: if the compile time type of `x` is `dynamic` and there is an implicit conversion from the compile time type of `y` to `dynamic`, no runtime resolution is required. *end note* From 39a3c1a6dd45d34d409ebddeb16c97f5f6ab9ce2 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 16:36:07 -0800 Subject: [PATCH 22/41] Fix issues from comments --- standard/basic-concepts.md | 2 +- standard/conversions.md | 28 +++++----- standard/expressions.md | 104 +++++++++++++++++++++-------------- standard/standard-library.md | 69 +++++++++++++++++++++++ standard/statements.md | 2 +- standard/types.md | 41 +++++++------- standard/variables.md | 31 +++++++---- 7 files changed, 190 insertions(+), 87 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 4f9177dec..08b174929 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -83,7 +83,7 @@ There are several different types of declaration spaces, as described in the fol - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. - Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. - Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *declaration_expression*s and *declaration_statement*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a parameter, type parameter, local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: - - Each *variable_initializer* introduces its own local variable declaration space, that is not nested within any other local variable declaration space. + - Each *variable_initializer* in a field and property declaration introduces its own local variable declaration space, that is not nested within any other local variable declaration space. - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function's local variable declaration space. - Each *constructor_initializer* creates a local variable declaration space nested within the instance constructor declaration. The local variable declaration space for the constructor body is in turn nested within this local variable declaration space. - Each *block*, *switch_block*, *iteration_statement* and *using_statement* creates a nested local variable declaration space. diff --git a/standard/conversions.md b/standard/conversions.md index 371e30937..842cd6a6b 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -317,21 +317,23 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### §implicit-tuple-conversions-new-clause Implicit tuple conversions -An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. If an element name in the tuple expression does not match a corresponding element name in the tuple type, a warning shall be issued. -*Example:* - -``` c# -(int, string) t1 = (1, "One"); -(byte, string) t2 = (2, null); -(int, string) t3 = (null, null); // Error: No conversion -(int i, string s) t4 = (i: 4, "Four"); -(int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored -``` - -The declarations of `t1`, `t2`, `t4` and `t5` are all valid, since implicit conversions exist from the element expressions to the corresponding element types. The declaration of `t3` is invalid, because there is no conversion from `null` to `int`. The declaration of `t5` causes a warning because the element names in the tuple expression differs from those in the tuple type. +> *Example*: +> +> ```csharp +> (int, string) t1 = (1, "One"); +> (byte, string) t2 = (2, null); +> (int, string) t3 = (null, null); // Error: No conversion +> (int i, string s) t4 = (i: 4, "Four"); +> (int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored +> ``` +> +> The declarations of `t1`, `t2`, `t4` and `t5` are all valid, since implicit conversions exist from the element expressions to the corresponding element types. The declaration of `t3` is invalid, because there is no conversion from `null` to `int`. The declaration of `t5` causes a warning because the element names in the tuple expression differs from those in the tuple type. +> +> *end example* ### 10.2.13 User-defined implicit conversions @@ -468,7 +470,7 @@ For an explicit reference conversion to succeed at run-time, the value of the so ### §explicit-tuple-conversions Explicit tuple conversions -An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the explicit conversion found, and initializing the field with the result. +An explicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit or explicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`'s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the explicit conversion found, and initializing the field with the result. ### 10.3.6 Unboxing conversions diff --git a/standard/expressions.md b/standard/expressions.md index c5ed561f7..de5c7adf2 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -38,7 +38,7 @@ Most of the constructs that involve an expression ultimately require the express - 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 ([§11.6.6](expressions.md#1166-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 ([§11.6.6](expressions.md#1166-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 tuple expression is obtained by applying an implicit tuple conversion (§implicit-tuple-conversions-new-clause) to the type of the tuple expression itself. It is an error to obtain the value of a tuple expression that does not have a type. +- The value of a tuple expression is obtained by applying an implicit tuple conversion (§implicit-tuple-conversions-new-clause) to the type of the tuple expression. It is an error to obtain the value of a tuple expression that does not have a type. ## 11.3 Static and Dynamic Binding @@ -1462,7 +1462,7 @@ simple_name A *simple_name* is either of the form `I` or of the form `I`, where `I` is a single identifier and `I` is an optional *type_argument_list*. When no *type_argument_list* is specified, consider `e` to be zero. The *simple_name* is evaluated and classified as follows: -- If `e` is zero and the *simple_name* appears within a local variable declaration space ([§7.3](basic-concepts.md#73-declarations)) that contains a local variable, parameter or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. +- If `e` is zero and the *simple_name* appears within a local variable declaration space ([§7.3](basic-concepts.md#73-declarations)) that directly contains a local variable, parameter or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. - If `e` is zero and the *simple_name* appears within a generic method declaration but outside the *attributes* of its *method_header,* and if that declaration includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. - Otherwise, for each instance type `T` ([§14.3.2](classes.md#1432-the-instance-type)), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any): - If `e` is zero and the declaration of `T` includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. @@ -1482,7 +1482,7 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain exactly one type having name `I` and `e` type parameters, then the *simple_name* refers to that type constructed with the given type arguments. - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* -- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a fresh discard (§discards-new-clause) and its type shall be inferred from the syntactic context: +- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard (§discards-new-clause) and its type shall be inferred from the syntactic context: - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. - If the discard occurs as the left side of an assignment expression, its type shall be the type of the right side - If the discard occurs as a *tuple_element* on the left side of a deconstructing assignment, its type shall be the type of the corresponding tuple element (after deconstruction) on the right side. @@ -1518,16 +1518,24 @@ A tuple expression is evaluated by evaluating each of its element expressions in A tuple value can be obtained from a tuple expression by converting it to a tuple type (§implicit-tuple-conversions-new-clause), by reclassifying it as a value ([§11.2.2](expressions.md#1122-values-of-expressions))) or by making it the target of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -*Example:* - -``` c# -(int i, string) t1 = (i: 1, "One"); -(int i, string) t2 = (i: 2, null); -var t3 = (i: 3, "Three"); // (int i, string) -var t4 = (i: 4, null); // Error: no type -``` - -In this example, all four tuple expressions are valid. The first two, `t1` and `t2`, do not use the type of the tuple expression, but instead apply an implicit tuple conversion. The third tuple expression has a type `(int i, string)`, and can therefore be reclassified as a value of that type. The declaration of `t4`, on the other hand, is an error: The tuple expression has no type because its second element has no type. +> *Example*: +> +> ```csharp +> (int i, string) t1 = (i: 1, "One"); +> (long l, string) t2 = (l: 2, null); +> var t3 = (i: 3, "Three"); // (int i, string) +> var t4 = (i: 4, null); // Error: no type +> ``` +> +> In this example, all four tuple expressions are valid. The first two, `t1` and `t2`, do not use the type of the tuple expression, but instead apply an implicit tuple conversion. In the case of `t2`, the implicit tuple conversion relies on the implicit conversions from `2` to `long` and from `null` to `string`. The third tuple expression has a type `(int i, string)`, and can therefore be reclassified as a value of that type. The declaration of `t4`, on the other hand, is an error: The tuple expression has no type because its second element has no type. +> +> ```csharp +> if ((x, y). Equals((1, 2))) { ... }; +> ``` +> +> This example shows that tuples can sometimes lead to multiple layers of parentheses, especially when the tuple expression is the sole argument to a method invocation. +> +> *end example* ### 11.7.6 Member access @@ -2350,7 +2358,7 @@ A collection initializer consists of a sequence of element initializers, enclose > > *end example* -The collection object to which a collection initializer is applied shall be of a type that implements `System.Collections.IEnumerable` or a compile-time error occurs. For each specified element in order, normal member lookup is applied to find a member named `Add`. If the result of the member lookup is not a method group, a compile-time error occurs. Otherwise, overload resolution is applied with the expression list of the element initializer as the argument list, and the collection initializer invokes the resulting method. Thus, the collection object shall contain an applicable instance or extension method with the name `Add` for each element initializer. +The collection object to which a collection initializer is applied shall be of a type that implements `System.Collections.IEnumerable` or a compile-time error occurs. For each specified element in order from left to right, normal member lookup is applied to find a member named `Add`. If the result of the member lookup is not a method group, a compile-time error occurs. Otherwise, overload resolution is applied with the expression list of the element initializer as the argument list, and the collection initializer invokes the resulting method. Thus, the collection object shall contain an applicable instance or extension method with the name `Add` for each element initializer. > *Example*:The following shows a class that represents a contact with a name and a list of phone numbers, and the creation and initialization of a `List`: > @@ -4402,7 +4410,9 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left side of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -It is an error for a variable declared with a *declaration_expression* to be referenced within the *argument_list* or deconstructing assignment where it occurs. +It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it occurs. + +It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. The *local_variable_type* of a *declaration_expression* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: @@ -4411,30 +4421,42 @@ The *local_variable_type* of a *declaration_expression* either directly specifie A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the nearest enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). -*Example:* - -``` c# -string M(out int i, string s, out bool b) { ... } - -var s1 = M(out int i1, "One", out var b1); -Console.WriteLine($"{i1}, {b1}, {s1}"); -var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); // Error: i2 referenced within declaring argument list -var s3 = M(out int _, "Three", out var _); -``` - -The declaration of `s1` shows both explicitly and implicitly typed declaration expressions. The inferred type of `b1` is `bool` because that is the type of the corresponding out parameter in `M1`. The subsequent `WriteLine` is able to access `i1` and `b1`, which have been introduced to the enclosing scope. - -The declaration of `s2` shows an attempt to use `i2` in the nested call to `M`, which is disallowed, because the reference occurs within the argument list where `i2` was declared. On the other hand the reference to `b2` in the final argument is allowed, because it occurs after the end of the nested argument list where `b2` was declared. - -The declaration of `s3` shows the use of both implicitly and explicitly typed declaration expressions that are discards. Because discards do not declare a named variable, the multiple occurrences of the identifier `_` are allowed. - -*Example:* - -``` c# -(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5); -``` - -This example shows the use of implicitly and explicitly typed declaration expressions for both variables and discards in a deconstructing assignment. +> *Example*: +> +> ```csharp +> string M(out int i, string s, out bool b) { ... } +> +> var s1 = M(out int i1, "One", out var b1); +> Console.WriteLine($"{i1}, {b1}, {s1}"); +> var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); // Error: i2 referenced within declaring argument list +> var s3 = M(out int _, "Three", out var _); +> ``` +> +> The declaration of `s1` shows both explicitly and implicitly typed declaration expressions. The inferred type of `b1` is `bool` because that is the type of the corresponding out parameter in `M1`. The subsequent `WriteLine` is able to access `i1` and `b1`, which have been introduced to the enclosing scope. +> +> The declaration of `s2` shows an attempt to use `i2` in the nested call to `M`, which is disallowed, because the reference occurs within the argument list where `i2` was declared. On the other hand the reference to `b2` in the final argument is allowed, because it occurs after the end of the nested argument list where `b2` was declared. +> +> The declaration of `s3` shows the use of both implicitly and explicitly typed declaration expressions that are discards. Because discards do not declare a named variable, the multiple occurrences of the identifier `_` are allowed. +> +> ```csharp +> (int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5); +> ``` +> +> This example shows the use of implicitly and explicitly typed declaration expressions for both variables and discards in a deconstructing assignment. +> +> ```csharp +> void M1(out int i) { ... } +> +> void M2(string _) +> { +> M1(out _); // Error: `_` is a string +> M1(out var _); // +> } +> ``` +> +> This examples shows the use of `var _` to provide an implicitly typed discard when `_` is not available, because it designates a variable in the enclosing scope. +> +> *end example* ## 11.16 Conditional operator @@ -5960,7 +5982,7 @@ The `=` operator is called the simple assignment operator. 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 ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). -A simple assignment with a tuple as the left operand is also called a ***deconstructing assignment***. If any of the tuple elements of the left operand have an identifier, 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 discard, a compile-time error occurs. +A simple assignment with a tuple as the left operand is also called a ***deconstructing assignment***. If any of the tuple elements of the left operand has an identifier, 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 discard, a compile-time error occurs. The type of a simple assignment `x = y` is determined as follows: @@ -5983,7 +6005,7 @@ The run-time processing of a simple assignment of the form `x = y` with type `T` - If `x` is classified as a tuple `(x1, ..., xn)` with arity `n`: - `y` is deconstructed with `n` elements to a tuple expression `e`. - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. - - for each `xi` in order left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. + - for each `xi` in order from left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. - `t` is yielded as the result of the assignment. > *Note*: if the compile time type of `x` is `dynamic` and there is an implicit conversion from the compile time type of `y` to `dynamic`, no runtime resolution is required. *end note* diff --git a/standard/standard-library.md b/standard/standard-library.md index 8e94f52fa..1a2a212ac 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -245,6 +245,75 @@ namespace System public struct UInt64 { } public struct UIntPtr { } + public struct ValueTuple + { + public T1 Item1; + public ValueTuple(T1 item1); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public ValueTuple(T1 item1, T2 item2, T3 item3); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public T7 Item7; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7); + } + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public T3 Item3; + public T4 Item4; + public T5 Item5; + public T6 Item6; + public T7 Item7; + public TRest Rest; + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest); + } + public abstract class ValueType { protected ValueType(); diff --git a/standard/statements.md b/standard/statements.md index 08217ec59..1b024e001 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -278,7 +278,7 @@ In addition to the reachability provided by normal flow of control, a labeled st ### 12.6.1 General -A *declaration_statement* declares one or more local variables, a local constant, or a local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. +A *declaration_statement* declares one or more local variables, one or more local constants, or a local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. ```ANTLR declaration_statement diff --git a/standard/types.md b/standard/types.md index 2a9a518cd..10e7a5b00 100644 --- a/standard/types.md +++ b/standard/types.md @@ -399,26 +399,27 @@ The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressio Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value ([§11.7.6](expressions.md#1176-member-access). Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. -Given the following examples: - -``` c# -(int, string) pair1 = (1, "One"); -(int, string word) pair2 = (2, "Two"); -(int number, string word) pair3 = (3, "Three"); -(int Item1, string Item2) pair4 = (4, "Four"); -(int Item2, string Item123) pair5 = (5, "Five"); // Error: "Item" names do not match their position -(int, string) pair6 = new ValueTuple(6, "Six"); -ValueTuple pair7 = (7, "Seven"); -Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}"); -``` - -The tuple types for `pair1`, `pair2`, and `pair3` are all valid, with names for no, some or all of the tuple type elements. - -The tuple type for `pair4` is valid because the names `Item1` and `Item2` match their positions, whereas the tuple type for `pair5` is disallowed, because the names `Item2` and `Item123` do not. - -The declarations for `pair6` and `pair7` demonstrate that tuple types are interchangeable with constructed types of the form `ValueTuple<...>`, and that the `new` operator is allowed with the latter syntax. - -The last line shows that tuple elements can be accessed by the `Item` name corresponding to their position, as well as by the corresponding tuple element name, if present in the type. +> *Example*: Given the following examples: +> +> ```csharp +> (int, string) pair1 = (1, "One"); +> (int, string word) pair2 = (2, "Two"); +> (int number, string word) pair3 = (3, "Three"); +> (int Item1, string Item2) pair4 = (4, "Four"); +> (int Item2, string Item123) pair5 = (5, "Five"); // Error: "Item" names do not match their position +> (int, string) pair6 = new ValueTuple(6, "Six"); +> ValueTuple pair7 = (7, "Seven"); +> Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}"); +> ``` +> +> The tuple types for `pair1`, `pair2`, and `pair3` are all valid, with names for no, some or all of the tuple type elements. +> +> The tuple type for `pair4` is valid because the names `Item1` and `Item2` match their positions, whereas the tuple type for `pair5` is disallowed, because the names `Item2` and `Item123` do not. +> +> The declarations for `pair6` and `pair7` demonstrate that tuple types are interchangeable with constructed types of the form `ValueTuple<...>`, and that the `new` operator is allowed with the latter syntax. +> +>The last line shows that tuple elements can be accessed by the `Item` name corresponding to their position, as well as by the corresponding tuple element name, if present in the type. +> *end example* ### 8.3.11 Nullable value types diff --git a/standard/variables.md b/standard/variables.md index c773fd987..aee7735fe 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -115,7 +115,7 @@ Within an instance constructor of a struct type, the `this` keyword behaves exac A ***local variable*** is declared by a *local_variable_declaration*, *declaration_expression*, *foreach_statement*, or *specific_catch_clause* of a *try_statement*. For a *foreach_statement*, the local variable is an iteration variable ([§12.9.5](statements.md#1295-the-foreach-statement)). For a *specific_catch_clause*, the local variable is an exception variable ([§12.11](statements.md#1211-the-try-statement)). A local variable declared by a *foreach_statement* or *specific_catch_clause* is considered initially assigned. -A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and in a *tuple_element* that is the target of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). +A *local_variable_declaration* can occur in a *block*, a *for_statement*, a *switch_block*, or a *using_statement*. A *declaration_expression* can occur as an `out` *argument_value*, and as a *tuple_element* that is the target of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). The lifetime of a local variable is the portion of program execution during which storage is guaranteed to be reserved for it. This lifetime extends from entry into the scope with which it is associated, at least until execution of that scope ends in some way. (Entering an enclosed *block*, calling a method, or yielding a value from an iterator block suspends, but does not end, execution of the current scope.) If the local variable is captured by an anonymous function ([§11.17.6.2](expressions.md#111762-captured-outer-variables)), its lifetime extends at least until the delegate or expression tree created from the anonymous function, along with any other objects that come to reference the captured variable, are eligible for garbage collection. If the parent scope is entered recursively or iteratively, a new instance of the local variable is created each time, and its *local_variable_initializer*, if any, is evaluated each time. @@ -152,20 +152,29 @@ A local variable introduced by a *local_variable_declaration* or *declaration_ex #### §discards-new-clause Discards -A ***discard*** is a local variable that has no name and can thus only be referred to once, at the point where it is introduced. A discard is not initially assigned, so it is always an error to access its value. A discard is introduced by a *declaration_expression* with the identifier `_`, and by use of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). +A ***discard*** is a local variable that has no name. A discard is introduced by either of the following expressions: -*Example:* +- A *declaration_expression* with the identifier `_`, or +- An occurrence of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). -``` c# -_ = "Hello".Length; -(int, int, int) M(out int i1, out int i2, out int i3) { ... } -(int _, var _, _) = M(out int _, out var _, out _); -``` +Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. A discard is not initially assigned, so it is always an error to access its value. -The example assumes that there is no declaration of the name `_` in scope. +> *Note*: `_` is a valid identifier in many forms of declarations. *end note* -The assignment to `_` shows a simple pattern for ignoring the result of an expression. -The call of `M` shows the different forms of discards available in tuples and as out parameters. +> *Example*: +> +> ```csharp +> _ = "Hello".Length; +> (int, int, int) M(out int i1, out int i2, out int i3) { ... } +> (int _, var _, _) = M(out int _, out var _, out _); +> ``` +> +> The example assumes that there is no declaration of the name `_` in scope. +> +> The assignment to `_` shows a simple pattern for ignoring the result of an expression. +> The call of `M` shows the different forms of discards available in tuples and as out parameters. +> +> *end example* ## 9.3 Default values From 02b0dc7c5e643f3a1fd7673f17e56a69a6e583cf Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 16:38:08 -0800 Subject: [PATCH 23/41] Try to fix linting issue --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index aee7735fe..246326615 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -160,7 +160,7 @@ A ***discard*** is a local variable that has no name. A discard is introduced by Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. A discard is not initially assigned, so it is always an error to access its value. > *Note*: `_` is a valid identifier in many forms of declarations. *end note* - + > *Example*: > > ```csharp From 2b6778180a8fff57bb23e68497ee69e3d6801a47 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 9 Feb 2023 16:40:53 -0800 Subject: [PATCH 24/41] Fix linting issue --- standard/variables.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 246326615..09ed0d3c5 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -157,10 +157,10 @@ A ***discard*** is a local variable that has no name. A discard is introduced by - A *declaration_expression* with the identifier `_`, or - An occurrence of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). -Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. A discard is not initially assigned, so it is always an error to access its value. - > *Note*: `_` is a valid identifier in many forms of declarations. *end note* +Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. A discard is not initially assigned, so it is always an error to access its value. + > *Example*: > > ```csharp From 3231f8133032f0f734c9ad06504760d2a12c79f7 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Mon, 20 Mar 2023 12:31:18 -0700 Subject: [PATCH 25/41] Fix several comments from gafter --- standard/expressions.md | 4 ++-- standard/statements.md | 2 +- standard/types.md | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index de5c7adf2..cc399b38c 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4410,7 +4410,7 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left side of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it occurs. +It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. @@ -5982,7 +5982,7 @@ The `=` operator is called the simple assignment operator. 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 ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-compile-time-checking-of-dynamic-member-invocation)). -A simple assignment with a tuple as the left operand is also called a ***deconstructing assignment***. If any of the tuple elements of the left operand has an identifier, 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 discard, a compile-time error occurs. +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 identifier, 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 discard, a compile-time error occurs. The type of a simple assignment `x = y` is determined as follows: diff --git a/standard/statements.md b/standard/statements.md index 1b024e001..77983da7a 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -278,7 +278,7 @@ In addition to the reachability provided by normal flow of control, a labeled st ### 12.6.1 General -A *declaration_statement* declares one or more local variables, one or more local constants, or a local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. +A *declaration_statement* declares one or more local variables, one or more local constants, or a local function. Declaration statements are permitted in blocks and switch blocks, but are not permitted as embedded statements. ```ANTLR declaration_statement diff --git a/standard/types.md b/standard/types.md index 10e7a5b00..8f8cf834c 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,16 +389,21 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing every arity of tuple types. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. -Element names within a tuple type shall be distinct. It is an error for an explicit tuple element name to be of the form `ItemX` where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, except as the name for that actual element. +> *Note*: There does not need to exist a `System.ValueTuple<...>` declaration that directly matches the arity of any tuple type with a corresponding number of type parameters. Instead, larger tuples can be represented with a special overload `System.ValueTuple<..., TRest>` that in addition to tuple elements has a `Rest` field containing a nested tuple value of the remaining elements. Such nesting may be observable in various ways, e.g. via the presence of a `Rest` field. *end note* -The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and the same sequence of element types, as well as to and from the corresponding constructed `ValueTuple<...>` type. +Element names within a tuple type shall be distinct. A tuple element name of the form `ItemX`, where `X` is any sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, is only permitted at the position denoted by `X`. -The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied directly to a tuple type. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. +The optional element names are not represented in the `ValueTuple<...>` types, and are not stored in the runtime representation of a tuple value. There is an identity conversion between all tuple types with the same arity and identity-convertible sequences of element types, as well as to and from the corresponding constructed `ValueTuple<...>` type. + +The `new` operator [§11.7.15.2](expressions.md#117152-object-creation-expressions) cannot be applied with the tuple type syntax `new (T1, ..., Tn)`. Tuple values can be created from tuple expressions (§tuple-expressions-new-clause), or by applying the `new` operator directly to a type constructed from `ValueTuple<...>`. Tuple elements are public fields with the names `Item1`, `Item2`, etc., and can be accessed via a member access on a tuple value ([§11.7.6](expressions.md#1176-member-access). Additionally, if the tuple type has a name for a given element, that name can be used to access the element in question. +> *Note*: Even when large tuples are represented with nested `System.ValueTuple<...>` values, each tuple element can still be accessed directly with the `Item...` name corresponding to its position. *end note* + + > *Example*: Given the following examples: > > ```csharp From eb94c3fda9a4fd3d0a98c80d9d4c613da2c713ec Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Mon, 20 Mar 2023 14:30:46 -0700 Subject: [PATCH 26/41] Fix "projection" tuple element names --- standard/expressions.md | 9 ++++++++- standard/types.md | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index cc399b38c..ed221576b 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1512,7 +1512,14 @@ tuple_element : (identifier ':')? expression ``` -A tuple expression is classified as a tuple. It has a type if all the element expressions have a type. In that case, the type of a tuple expression is the tuple type of the same arity, where each type element has the type - and name, where given - of the corresponding element expression. +A tuple expression is classified as a tuple. A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: + +- If the tuple element in the corresponding position has a name `Ii`, then the tuple type element shall be `Ti Ii`. +- Otherwise, if `Ei` is of the form `Ii` or `E.Ii` or `E?.Ii` then the tuple type element shall be `Ti Ii`, *unless* any of the following holds: + - Another element of the tuple expression has the name `Ii`, + - Another tuple element without a name has a tuple element expression of the form `Ii` or `E.Ii` or `E?.Ii`, or + - `Ii` is of the form `ItemX`, where `X` is a sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, and `X` does not represent the position of the element. +- Otherwise, the tuple type element shall be `Ti`. A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. diff --git a/standard/types.md b/standard/types.md index 8f8cf834c..f72ea0a52 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,7 +389,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two tuple type elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. > *Note*: There does not need to exist a `System.ValueTuple<...>` declaration that directly matches the arity of any tuple type with a corresponding number of type parameters. Instead, larger tuples can be represented with a special overload `System.ValueTuple<..., TRest>` that in addition to tuple elements has a `Rest` field containing a nested tuple value of the remaining elements. Such nesting may be observable in various ways, e.g. via the presence of a `Rest` field. *end note* From 97379cb9ab716f066d54bde613198e55729d1f8b Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Mon, 20 Mar 2023 15:45:06 -0700 Subject: [PATCH 27/41] Fix overload resolution with out vars --- standard/conversions.md | 4 ++++ standard/expressions.md | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 842cd6a6b..770e29163 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -351,6 +351,10 @@ An implicit conversion exists from a *default_literal* ([§11.7.19](expressions. While throw expressions do not have a type, they may be implicitly converted to any type. +### §imp-typed-out-var Implicitly-typed variable conversions + +There is a conversion from an implicitly-typed declaration expression (§declaration-expressions-new-clause) to every type. + ## 10.3 Explicit conversions ### 10.3.1 General diff --git a/standard/expressions.md b/standard/expressions.md index ed221576b..4f74d50d8 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -979,14 +979,14 @@ A function member is said to be an ***applicable function member*** with respect - Each argument in `A` corresponds to a parameter in the function member declaration as described in [§11.6.2.2](expressions.md#11622-corresponding-parameters), at most one argument corresponds to each parameter, and any parameter to which no argument corresponds is an optional parameter. - For each argument in `A`, the parameter-passing mode of the argument is identical to the parameter-passing mode of the corresponding parameter, and - for a value parameter or a parameter array, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) exists from the argument expression to the type of the corresponding parameter, or - - for a `ref` or `out` parameter, there is an identity conversion between the type of the argument expression and the type of the corresponding parameter + - for a `ref` or `out` parameter, there is an identity conversion between the type of the argument expression (if any) and the type of the corresponding parameter For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its ***normal form***. If a function member that includes a parameter array is not applicable in its normal form, the function member might instead be applicable in its ***expanded form***: - The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable. - Otherwise, the expanded form is applicable if for each argument in `A` the parameter-passing mode of the argument is identical to the parameter-passing mode of the corresponding parameter, and - for a fixed value parameter or a value parameter created by the expansion, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) exists from the argument expression to the type of the corresponding parameter, or - - for a `ref` or `out` parameter, the type of the argument expression is identical to the type of the corresponding parameter. + - for a `ref` or `out` parameter, the type of the argument expression is either implicit or identical to the type of the corresponding parameter. Additional rules determine whether a method is applicable or not based on the context of the expression: @@ -1482,11 +1482,7 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain exactly one type having name `I` and `e` type parameters, then the *simple_name* refers to that type constructed with the given type arguments. - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* -- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard (§discards-new-clause) and its type shall be inferred from the syntactic context: - - If the discard occurs as an `out` *argument_value*, its type shall be the type of the corresponding parameter. - - If the discard occurs as the left side of an assignment expression, its type shall be the type of the right side - - If the discard occurs as a *tuple_element* on the left side of a deconstructing assignment, its type shall be the type of the corresponding tuple element (after deconstruction) on the right side. - - If the discard occurs in a different syntactic context, or a type cannot be inferred, a compile time error occurs. +- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard (§discards-new-clause) and is equivalent to the declaration expression `var _` (§declaration-expressions-new-clause). - Otherwise, the *simple_name* is undefined and a compile-time error occurs. ### 11.7.5 Parenthesized expressions @@ -4417,14 +4413,18 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a *tuple_element* in a *tuple_expression* that occurs on the left side of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). +In addition, a *simple_name* ([§11.7.4](expressions.md#1174-simple-names) that resolves to a *discard* is equivalent to the declaration expression `var _`, and is additionally permitted to occur as the left-hand side of an assignment. + It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. -The *local_variable_type* of a *declaration_expression* either directly specifies the type of the variable introduced by the declaration, or indicates with the identifier `var` that the type is implicit and should be inferred based on the syntactic context as follows: +A declaration expression where the *local_variable_type* is the identifier `var` is *implicitly typed*. The expression has no type, and the type of the local variable is inferred based on the syntactic context as follows: + +- In an *argument_list* the inferred type of the variable is the declared type of the corresponding parameter. +- In a *tuple_expression* on the left side of an assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. -- In an *argument_list* the inferred type of a declaration expression is the declared type of the corresponding parameter. -- In a *tuple_expression* on the left side of an assignment, the inferred type of a declaration expression is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. +Otherwise, the declaration expression is *explicitly typed*, and the type of the expression as well as the declared variable shall be that given by the *local_variable_type*. A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the nearest enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). @@ -4449,7 +4449,7 @@ A declaration expression with the identifier `_` is a discard (§discards-new-cl > (int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5); > ``` > -> This example shows the use of implicitly and explicitly typed declaration expressions for both variables and discards in a deconstructing assignment. +> This example shows the use of implicitly and explicitly typed declaration expressions for both variables and discards in a deconstructing assignment. The *simple_name* `_` is equivalent to `var _` when no declaration of `_` is found. > > ```csharp > void M1(out int i) { ... } From d48514a7bc3b312231104553591df29360b628e6 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 21 Mar 2023 11:34:30 -0700 Subject: [PATCH 28/41] Fix declaration expressions and overload resolution I've further unified `_` discards and declaration expressions. Furthermore I've changed the approach to overload resolution. The change is small but radical, leaving out all `out` and `ref` parameters entirely from parameter-wise betterness! I believe this is sound, since out and ref arguments are always required to match precisely, so one is never better than the other. However, the desired effect was just to ensure that declaration expressions do not influence betterness. If we don't like this fix we can reduce its scope to a bigger change with smaller consequences. ;-) --- standard/conversions.md | 4 ---- standard/expressions.md | 40 ++++++++++++++++++++++------------------ standard/types.md | 2 +- standard/variables.md | 5 +---- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index 770e29163..842cd6a6b 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -351,10 +351,6 @@ An implicit conversion exists from a *default_literal* ([§11.7.19](expressions. While throw expressions do not have a type, they may be implicitly converted to any type. -### §imp-typed-out-var Implicitly-typed variable conversions - -There is a conversion from an implicitly-typed declaration expression (§declaration-expressions-new-clause) to every type. - ## 10.3 Explicit conversions ### 10.3.1 General diff --git a/standard/expressions.md b/standard/expressions.md index 4f74d50d8..afd8a9050 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -11,7 +11,7 @@ An expression is a sequence of operators and operands. This clause defines the s The result of an expression is classified as one of the following: - A value. Every value has an associated type. -- A variable. Every variable has an associated type, namely the declared type of the variable. +- A variable. Unless otherwise specified, a variable is explicitly typed and has an associated type, namely the declared type of the variable. An implicitly typed variable has no associated type. - A null literal. An expression with this classification can be implicitly converted to a reference type or nullable value type. - An anonymous function. An expression with this classification can be implicitly converted to a compatible delegate type or expression tree type. - A tuple. Every tuple has a fixed number of elements, each with an expression and an optional tuple element name. @@ -986,7 +986,7 @@ For a function member that includes a parameter array, if the function member is - The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable. - Otherwise, the expanded form is applicable if for each argument in `A` the parameter-passing mode of the argument is identical to the parameter-passing mode of the corresponding parameter, and - for a fixed value parameter or a value parameter created by the expansion, an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)) exists from the argument expression to the type of the corresponding parameter, or - - for a `ref` or `out` parameter, the type of the argument expression is either implicit or identical to the type of the corresponding parameter. + - for a `ref` or `out` parameter, there is an identity conversion between the type of the argument expression (if any) and the type of the corresponding parameter. Additional rules determine whether a method is applicable or not based on the context of the expression: @@ -999,12 +999,13 @@ Additional rules determine whether a method is applicable or not based on the co #### 11.6.4.3 Better function member -For the purposes of determining the better function member, a stripped-down argument list `A` is constructed containing just the argument expressions themselves in the order they appear in the original argument list. +For the purposes of determining the better function member, a stripped-down argument list `A` is constructed containing just the argument expressions themselves in the order they appear in the original argument list, and leaving out any `out` or `ref` arguments. Parameter lists for each of the candidate function members are constructed in the following way: - The expanded form is used if the function member was applicable only in the expanded form. - Optional parameters with no corresponding arguments are removed from the parameter list +- `ref` and `out` parameters are removed from the parameter list - The parameters are reordered so that they occur at the same position as the corresponding argument in the argument list. Given an argument list `A` with a set of argument expressions `{E₁, E₂, ..., Eᵥ}` and two applicable function members `Mᵥ` and `Mₓ` with parameter types `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂, ..., Qᵥ}`, `Mᵥ` is defined to be a ***better function member*** than `Mₓ` if @@ -1482,7 +1483,7 @@ A *simple_name* is either of the form `I` or of the form `I`, - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain exactly one type having name `I` and `e` type parameters, then the *simple_name* refers to that type constructed with the given type arguments. - Otherwise, if the namespaces imported by the *using_namespace_directive*s of the namespace declaration contain more than one type having name `I` and `e` type parameters, then the *simple_name* is ambiguous and a compile-time error occurs. > *Note*: This entire step is exactly parallel to the corresponding step in the processing of a *namespace_or_type_name* ([§7.8](basic-concepts.md#78-namespace-and-type-names)). *end note* -- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a discard (§discards-new-clause) and is equivalent to the declaration expression `var _` (§declaration-expressions-new-clause). +- Otherwise, if `e` is zero and `I` is the identifier `_`, the *simple_name* is a *simple discard*, which is a form of declaration expression (§declaration-expressions-new-clause). - Otherwise, the *simple_name* is undefined and a compile-time error occurs. ### 11.7.5 Parenthesized expressions @@ -1510,11 +1511,11 @@ tuple_element A tuple expression is classified as a tuple. A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: -- If the tuple element in the corresponding position has a name `Ii`, then the tuple type element shall be `Ti Ii`. -- Otherwise, if `Ei` is of the form `Ii` or `E.Ii` or `E?.Ii` then the tuple type element shall be `Ti Ii`, *unless* any of the following holds: - - Another element of the tuple expression has the name `Ii`, - - Another tuple element without a name has a tuple element expression of the form `Ii` or `E.Ii` or `E?.Ii`, or - - `Ii` is of the form `ItemX`, where `X` is a sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, and `X` does not represent the position of the element. +- If the tuple element in the corresponding position has a name `Ni`, then the tuple type element shall be `Ti Ni`. +- Otherwise, if `Ei` is of the form `Ni` or `E.Ni` or `E?.Ni` then the tuple type element shall be `Ti Ni`, *unless* any of the following holds: + - Another element of the tuple expression has the name `Ni`, or + - Another tuple element without a name has a tuple element expression of the form `Ni` or `E.Ni` or `E?.Ni`, or + - `Ni` is of the form `ItemX`, where `X` is a sequence of non-`0`-initiated decimal digits that could represent the position of a tuple element, and `X` does not represent the position of the element. - Otherwise, the tuple type element shall be `Ti`. A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. @@ -4406,25 +4407,28 @@ A declaration expression declares a local variable. ``` ANTLR declaration_expression : local_variable_type identifier + | `_` ``` +The identifier `_` is only considered a declaration expression if simple name lookup did not find an associated declaration ([§11.7.4](expressions.md#1174-simple-names)). When used as a declaration expression, `_` is called a *simple discard*. It is semantically equivalent to `var _`, but is permitted in more places. + A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. -- As a *tuple_element* in a *tuple_expression* that occurs on the left side of a deconstructing assignment ([§11.19.2](expressions.md#11192-simple-assignment)). - -In addition, a *simple_name* ([§11.7.4](expressions.md#1174-simple-names) that resolves to a *discard* is equivalent to the declaration expression `var _`, and is additionally permitted to occur as the left-hand side of an assignment. +- As a simple discard `_` comprising the left side of a simple assignment ([§11.19.2](expressions.md#11192-simple-assignment)). +- As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. -A declaration expression where the *local_variable_type* is the identifier `var` is *implicitly typed*. The expression has no type, and the type of the local variable is inferred based on the syntactic context as follows: +A declaration expression that is a simple discard or where the *local_variable_type* is the identifier `var` is classified as an *implicitly typed* variable. The expression has no type, and the type of the local variable is inferred based on the syntactic context as follows: - In an *argument_list* the inferred type of the variable is the declared type of the corresponding parameter. -- In a *tuple_expression* on the left side of an assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. +- As the left side of a simple assignment, the inferred type of the variable is the type of the right side of the assignment. +- In a *tuple_expression* on the left side of a simple assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. -Otherwise, the declaration expression is *explicitly typed*, and the type of the expression as well as the declared variable shall be that given by the *local_variable_type*. +Otherwise, the declaration expression is classified as an *explicitly typed* variable, and the type of the expression as well as the declared variable shall be that given by the *local_variable_type*. A declaration expression with the identifier `_` is a discard (§discards-new-clause), and does not introduce a name for the variable. A declaration expression with an identifier other than `_` introduces that name into the nearest enclosing local variable declaration space ([§7.3](basic-concepts.md#73-declarations)). @@ -5989,13 +5993,13 @@ The `=` operator is called the simple assignment operator. 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 ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-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 identifier, 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 discard, a compile-time error occurs. +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 which is not a simple discard `_`, and any other element is not a declaration expression, a compile-time error occurs. The type of a simple assignment `x = y` is determined as follows: -- If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements (§deconstruction-new-clause), and an assignment of each `xi` from `yi` is valid and has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. +- If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements (§deconstruction-new-clause), and each simple assignment `xi = yi` has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. - Otherwise, if `x` is classified as a variable, the variable is not `readonly`, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. -- Otherwise, if `x` is an implicitly typed variable (i.e. a discard or an implicitly type declaration expression) and `y` has a type `T`, then the inferred type of `x` is `T`, and the assignment has the type `T`. +- Otherwise, if `x` is classified as an implicitly typed variable (i.e. an implicitly typed declaration expression) and `y` has a type `T`, then the inferred type of the variable is `T`, and the assignment has the type `T`. - Otherwise, if `x` is classified as a property or indexer access, the property or indexer has an accessible set accessor, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. - Otherwise the assignment is not valid and a binding-time error occurs. diff --git a/standard/types.md b/standard/types.md index f72ea0a52..a2e418105 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,7 +389,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***.A tuple type is written `(T1 I1, ..., Tn In)` with at least two tuple type elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***. A tuple type is written `(T1 I1, ..., Tn In)` with at least two tuple type elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. > *Note*: There does not need to exist a `System.ValueTuple<...>` declaration that directly matches the arity of any tuple type with a corresponding number of type parameters. Instead, larger tuples can be represented with a special overload `System.ValueTuple<..., TRest>` that in addition to tuple elements has a `Rest` field containing a nested tuple value of the remaining elements. Such nesting may be observable in various ways, e.g. via the presence of a `Rest` field. *end note* diff --git a/standard/variables.md b/standard/variables.md index 09ed0d3c5..6505f3e79 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -152,10 +152,7 @@ A local variable introduced by a *local_variable_declaration* or *declaration_ex #### §discards-new-clause Discards -A ***discard*** is a local variable that has no name. A discard is introduced by either of the following expressions: - -- A *declaration_expression* with the identifier `_`, or -- An occurrence of the *simple_name* `_` when lookup of that name does not find a declaration ([§11.7.4](expressions.md#1174-simple-names)). +A ***discard*** is a local variable that has no name. A discard is introduced by a declaration expression (§declaration-expressions-new-clause) with the identifier `_`; and is either implicitly typed (`_` or `var _`) or explicitly typed (`T _`). > *Note*: `_` is a valid identifier in many forms of declarations. *end note* From 8737fc4281548ee415197e99e48364cc5adfa85f Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Tue, 21 Mar 2023 13:18:11 -0700 Subject: [PATCH 29/41] Root declaration_expression Root declaration_expression as a non_assignment_expression and adjust verbiage accordingly in various places. --- standard/expressions.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index afd8a9050..12e60da81 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1534,7 +1534,7 @@ A tuple value can be obtained from a tuple expression by converting it to a tupl > In this example, all four tuple expressions are valid. The first two, `t1` and `t2`, do not use the type of the tuple expression, but instead apply an implicit tuple conversion. In the case of `t2`, the implicit tuple conversion relies on the implicit conversions from `2` to `long` and from `null` to `string`. The third tuple expression has a type `(int i, string)`, and can therefore be reclassified as a value of that type. The declaration of `t4`, on the other hand, is an error: The tuple expression has no type because its second element has no type. > > ```csharp -> if ((x, y). Equals((1, 2))) { ... }; +> if ((x, y).Equals((1, 2))) { ... }; > ``` > > This example shows that tuples can sometimes lead to multiple layers of parentheses, especially when the tuple expression is the sole argument to a method invocation. @@ -4407,10 +4407,9 @@ A declaration expression declares a local variable. ``` ANTLR declaration_expression : local_variable_type identifier - | `_` ``` -The identifier `_` is only considered a declaration expression if simple name lookup did not find an associated declaration ([§11.7.4](expressions.md#1174-simple-names)). When used as a declaration expression, `_` is called a *simple discard*. It is semantically equivalent to `var _`, but is permitted in more places. +The *simple_name* `_` is also considered a declaration expression if simple name lookup did not find an associated declaration ([§11.7.4](expressions.md#1174-simple-names)). When used as a declaration expression, `_` is called a *simple discard*. It is semantically equivalent to `var _`, but is permitted in more places. A declaration expression shall only occur in the following syntactic contexts: @@ -4418,6 +4417,8 @@ A declaration expression shall only occur in the following syntactic contexts: - As a simple discard `_` comprising the left side of a simple assignment ([§11.19.2](expressions.md#11192-simple-assignment)). - As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. +> *Note:* This means that a declaration expression cannot be parenthesized. *end note* + It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. @@ -5993,17 +5994,17 @@ The `=` operator is called the simple assignment operator. 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 ([§11.3.3](expressions.md#1133-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 ([§11.6.5](expressions.md#1165-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 which is not a simple discard `_`, and any other element is not a declaration expression, a compile-time error occurs. +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. -The type of a simple assignment `x = y` is determined as follows: +The type of a simple assignment `x = y` is the type of an assignment to `x` of `y`, which is recursively determined as follows: -- If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements (§deconstruction-new-clause), and each simple assignment `xi = yi` has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. +- If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements (§deconstruction-new-clause), and each assignment to `xi` of `yi` has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. - Otherwise, if `x` is classified as a variable, the variable is not `readonly`, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. - Otherwise, if `x` is classified as an implicitly typed variable (i.e. an implicitly typed declaration expression) and `y` has a type `T`, then the inferred type of the variable is `T`, and the assignment has the type `T`. - Otherwise, if `x` is classified as a property or indexer access, the property or indexer has an accessible set accessor, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. - Otherwise the assignment is not valid and a binding-time error occurs. -The run-time processing of a simple assignment of the form `x = y` with type `T` consists of the following steps: +The run-time processing of a simple assignment of the form `x = y` with type `T` is performed as an assignment to `x` of `y` with type `T`, which consists of the following recursive steps: - `x` is evaluated if it wasn't already. - If `x` is classified as a variable, `y` is evaluated and, if required, converted to `T` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). @@ -6016,7 +6017,7 @@ The run-time processing of a simple assignment of the form `x = y` with type `T` - If `x` is classified as a tuple `(x1, ..., xn)` with arity `n`: - `y` is deconstructed with `n` elements to a tuple expression `e`. - a result tuple `t` is created by converting `e` to `T` using an implicit tuple conversion. - - for each `xi` in order from left to right, a simple assignment `xi = t.Itemi` is performed, except that the `xi` are not evaluated again. + - for each `xi` in order from left to right, an assignment to `xi` of `t.Itemi` is performed, except that the `xi` are not evaluated again. - `t` is yielded as the result of the assignment. > *Note*: if the compile time type of `x` is `dynamic` and there is an implicit conversion from the compile time type of `y` to `dynamic`, no runtime resolution is required. *end note* @@ -6185,7 +6186,8 @@ expression ; non_assignment_expression - : conditional_expression + : declaration_expression + | conditional_expression | lambda_expression | query_expression ; From 5f62fd21081e069275b148baad1e970faee090ae Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 10:00:46 -0700 Subject: [PATCH 30/41] Add tuple equality --- standard/expressions.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index 12e60da81..3b3fa6992 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4092,6 +4092,40 @@ x == null null == x x != null null != x where `x` is an expression of a nullable value type, if operator overload resolution ([§11.4.5](expressions.md#1145-binary-operator-overload-resolution)) fails to find an applicable operator, the result is instead computed from the `HasValue` property of `x`. Specifically, the first two forms are translated into `!x.HasValue`, and the last two forms are translated into `x.HasValue`. +### §tuple-equality-operators-new-clause Tuple equality operators + +The tuple equality operators are applied pairwise to the elements of the tuple operands in lexical order. + +If each operand `x` and `y` of a `==` or `!=` operator classified either as a tuple or as a value with a tuple type (§tuple-types-new-clause), the operator is a *tuple equality operator*. + +If an operand `e` is classified as a tuple, the elements `e1...en` shall be the results of evaluating the element expressions of the tuple expression. Otherwise if `e` is a value of a tuple type, the elements shall be `t.Item1...t.Itemn` where `t` is the result of evaluating `e`. + +The operands `x` and `y` of a tuple equality operator shall have the same arity, or a compile time error occurs. For each pair of elements `xi` and `yi`, the same equality operator must apply, and must yield a result of type `bool`, `dynamic`, a type that has an implicit conversion to `bool`, or a type that defines the `true` and `false` operators. + +The tuple equality operator `x == y` is evaluated as follows: +- The left side operand `x` is evaluated. +- The right side operand `y` is evaluated. +- For each pair of elements `xi` and `yi` in lexical order: + - The operator `xi == yi` is evaluated, and a result of type `bool` is obtained in the following way: + - If the comparison yielded a `bool` then that is the result. + - Otherwise if the comparison yielded a `dynamic` then the operator `false` is dynamically invoked on it, and the resulting `bool` value is negated with the `!` operator. + - Otherwise, if the type of the comparison has an implicit conversion to `bool`, that conversion is applied. + - Otherwise, if the type of the comparison has an operator `false`, that operator is invoked and the resulting `bool` value is negated with the `!` operator. + - If the resulting `bool` is `false`, then no further evaluation occurs, and the result of the tuple equality operator is `false`. +- If all element comparisons yielded `true`, the result of the tuple equality operator is `true`. + +The tuple equality operator `x != y` is evaluated as follows: +- The left side operand `x` is evaluated. +- The right side operand `y` is evaluated. +- For each pair of elements `xi` and `yi` in lexical order: + - The operator `xi != yi` is evaluated, and a result of type `bool` is obtained in the following way: + - If the comparison yielded a `bool` then that is the result. + - Otherwise if the comparison yielded a `dynamic` then the operator `true` is dynamically invoked on it, and the resulting `bool` value is the result. + - Otherwise, if the type of the comparison has an implicit conversion to `bool`, that conversion is applied. + - Otherwise, if the type of the comparison has an operator `true`, that operator is invoked and the resulting `bool` value is the result. + - If the resulting `bool` is `true`, then no further evaluation occurs, and the result of the tuple equality operator is `true`. +- If all element comparisons yielded `false`, the result of the tuple equality operator is `false`. + ### 11.11.11 The is operator The `is` operator is used to check if the run-time type of an object is compatible with a given type. The check is performed at runtime. The result of the operation `E is T`, where `E` is an expression and `T` is a type other than `dynamic`, is a Boolean value indicating whether `E` is non-null and can successfully be converted to type `T` by a reference conversion, a boxing conversion, an unboxing conversion, a wrapping conversion, or an unwrapping conversion. @@ -4404,9 +4438,10 @@ A *throw expression* shall only occur in the following syntactic contexts: A declaration expression declares a local variable. -``` ANTLR +```ANTLR declaration_expression : local_variable_type identifier + ; ``` The *simple_name* `_` is also considered a declaration expression if simple name lookup did not find an associated declaration ([§11.7.4](expressions.md#1174-simple-names)). When used as a declaration expression, `_` is called a *simple discard*. It is semantically equivalent to `var _`, but is permitted in more places. From 218921f58d17425d7afe2e8f468461b1e75a7a0f Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 10:09:00 -0700 Subject: [PATCH 31/41] Fixing linter errors --- standard/expressions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index 3b3fa6992..eba592290 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -4100,9 +4100,10 @@ If each operand `x` and `y` of a `==` or `!=` operator classified either as a t If an operand `e` is classified as a tuple, the elements `e1...en` shall be the results of evaluating the element expressions of the tuple expression. Otherwise if `e` is a value of a tuple type, the elements shall be `t.Item1...t.Itemn` where `t` is the result of evaluating `e`. -The operands `x` and `y` of a tuple equality operator shall have the same arity, or a compile time error occurs. For each pair of elements `xi` and `yi`, the same equality operator must apply, and must yield a result of type `bool`, `dynamic`, a type that has an implicit conversion to `bool`, or a type that defines the `true` and `false` operators. +The operands `x` and `y` of a tuple equality operator shall have the same arity, or a compile time error occurs. For each pair of elements `xi` and `yi`, the same equality operator must apply, and must yield a result of type `bool`, `dynamic`, a type that has an implicit conversion to `bool`, or a type that defines the `true` and `false` operators. The tuple equality operator `x == y` is evaluated as follows: + - The left side operand `x` is evaluated. - The right side operand `y` is evaluated. - For each pair of elements `xi` and `yi` in lexical order: @@ -4115,6 +4116,7 @@ The tuple equality operator `x == y` is evaluated as follows: - If all element comparisons yielded `true`, the result of the tuple equality operator is `true`. The tuple equality operator `x != y` is evaluated as follows: + - The left side operand `x` is evaluated. - The right side operand `y` is evaluated. - For each pair of elements `xi` and `yi` in lexical order: From 205760f4b23fc9a58d1e3b8fb4957c27c54f529d Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 10:41:58 -0700 Subject: [PATCH 32/41] Add deconstruction expressions --- standard/expressions.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index eba592290..e3b82b615 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1500,16 +1500,36 @@ A *parenthesized_expression* is evaluated by evaluating the *expression* within ### §tuple-expressions-new-clause Tuple expressions -A *tuple_expression* consists of two or more comma-separated and optionally-named *expression*s enclosed in parentheses. +A *tuple_expression* represents a tuple, and consists of two or more comma-separated and optionally-named *expression*s enclosed in parentheses. A *deconstruction_expression* is a shorthand syntax for a tuple containing implicitly typed declaration expressions. -``` ANTLR +```ANTLR tuple_expression - : `(` tuple_element (',' tuple_element)+ ')' + : '(' tuple_element (',' tuple_element)+ ')' + ; + tuple_element : (identifier ':')? expression + ; + +deconstruction_expression + : 'var' deconstruction_tuple + ; + +deconstruction_tuple + : '(' deconstruction_element (',' deconstruction_element)+ ')' + ; + +deconstruction_element + : deconstruction_tuple + | identifier + ; ``` -A tuple expression is classified as a tuple. A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: +Each of *tuple_expression* and *deconstruction_expression* is a tuple expression, and is classified as a tuple. + +A *deconstruction_expression* `var (e1, ..., en)` is semantically equivalent to the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. A *deconstruction_expression* can only occur on the left side of a simple assignment, since the identifiers nested within it represent declaration expressions (§declaration-expressions-new-clause). + +A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: - If the tuple element in the corresponding position has a name `Ni`, then the tuple type element shall be `Ti Ni`. - Otherwise, if `Ei` is of the form `Ni` or `E.Ni` or `E?.Ni` then the tuple type element shall be `Ti Ni`, *unless* any of the following holds: @@ -4096,7 +4116,7 @@ where `x` is an expression of a nullable value type, if operator overload resolu The tuple equality operators are applied pairwise to the elements of the tuple operands in lexical order. -If each operand `x` and `y` of a `==` or `!=` operator classified either as a tuple or as a value with a tuple type (§tuple-types-new-clause), the operator is a *tuple equality operator*. +If each operand `x` and `y` of a `==` or `!=` operator is classified either as a tuple or as a value with a tuple type (§tuple-types-new-clause), the operator is a *tuple equality operator*. If an operand `e` is classified as a tuple, the elements `e1...en` shall be the results of evaluating the element expressions of the tuple expression. Otherwise if `e` is a value of a tuple type, the elements shall be `t.Item1...t.Itemn` where `t` is the result of evaluating `e`. @@ -4452,10 +4472,11 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a simple discard `_` comprising the left side of a simple assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -- As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. +- As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. A *deconstruction_expression* also gives rise to declaration expressions in this position. > *Note:* This means that a declaration expression cannot be parenthesized. *end note* + It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. @@ -4464,7 +4485,7 @@ A declaration expression that is a simple discard or where the *local_variable_t - In an *argument_list* the inferred type of the variable is the declared type of the corresponding parameter. - As the left side of a simple assignment, the inferred type of the variable is the type of the right side of the assignment. -- In a *tuple_expression* on the left side of a simple assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. +- In a *tuple_expression* or *deconstruction_expression* on the left side of a simple assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. Otherwise, the declaration expression is classified as an *explicitly typed* variable, and the type of the expression as well as the declared variable shall be that given by the *local_variable_type*. @@ -6224,6 +6245,7 @@ expression non_assignment_expression : declaration_expression + | deconstruction_expression | conditional_expression | lambda_expression | query_expression From 978b3dc83391c6129dce06bdf05af5903de13f35 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 10:44:52 -0700 Subject: [PATCH 33/41] Fix lint errors --- standard/expressions.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index e3b82b615..f38f3883e 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1525,7 +1525,7 @@ deconstruction_element ; ``` -Each of *tuple_expression* and *deconstruction_expression* is a tuple expression, and is classified as a tuple. +Each of *tuple_expression* and *deconstruction_expression* is a tuple expression, and is classified as a tuple. A *deconstruction_expression* `var (e1, ..., en)` is semantically equivalent to the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. A *deconstruction_expression* can only occur on the left side of a simple assignment, since the identifiers nested within it represent declaration expressions (§declaration-expressions-new-clause). @@ -4476,7 +4476,6 @@ A declaration expression shall only occur in the following syntactic contexts: > *Note:* This means that a declaration expression cannot be parenthesized. *end note* - It is an error for an implicitly typed variable declared with a *declaration_expression* to be referenced within the *argument_list* where it is declared. It is an error for a variable declared with a *declaration_expression* to be referenced within the deconstructing assignment where it occurs. From c36dc0b464ec6175f91594426b079459d0e887bb Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 10:55:26 -0700 Subject: [PATCH 34/41] Fix wording --- standard/expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index f38f3883e..e2b6a84c9 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1527,7 +1527,7 @@ deconstruction_element Each of *tuple_expression* and *deconstruction_expression* is a tuple expression, and is classified as a tuple. -A *deconstruction_expression* `var (e1, ..., en)` is semantically equivalent to the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. A *deconstruction_expression* can only occur on the left side of a simple assignment, since the identifiers nested within it represent declaration expressions (§declaration-expressions-new-clause). +A *deconstruction_expression* `var (e1, ..., en)` is semantically equivalent to the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. Each identifier nested within a *deconstruction_expression* thus introduces a declaration expression (§declaration-expressions-new-clause). As a result, a *deconstruction_expression* can only occur on the left side of a simple assignment. A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: From f4cfcc8fe520d9f496bc348c1bb53988e2766097 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 11:36:13 -0700 Subject: [PATCH 35/41] Root tuple_expression --- standard/expressions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/standard/expressions.md b/standard/expressions.md index e2b6a84c9..16c9b1efd 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1198,6 +1198,7 @@ primary_no_array_creation_expression | interpolated_string_expression | simple_name | parenthesized_expression + | tuple_expression | member_access | null_conditional_member_access | invocation_expression From 1316315c140758cea48cea57b789755d2a40f118 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 11:56:28 -0700 Subject: [PATCH 36/41] Add definite assignment rules for new expression forms --- standard/variables.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 6505f3e79..73cb4b2e4 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -584,13 +584,13 @@ For all other constant expressions, the definite-assignment state of *v* after t #### 9.4.4.22 General rules for simple expressions -The following rule applies to these kinds of expressions: literals ([§11.7.2](expressions.md#1172-literals)), simple names ([§11.7.4](expressions.md#1174-simple-names)), member access expressions ([§11.7.6](expressions.md#1176-member-access)), non-indexed base access expressions ([§11.7.13](expressions.md#11713-base-access)), `typeof` expressions ([§11.7.16](expressions.md#11716-the-typeof-operator)), default value expressions ([§11.7.19](expressions.md#11719-default-value-expressions)), and `nameof` expressions ([§11.7.20](expressions.md#11720-nameof-expressions)). +The following rule applies to these kinds of expressions: literals ([§11.7.2](expressions.md#1172-literals)), simple names ([§11.7.4](expressions.md#1174-simple-names)), member access expressions ([§11.7.6](expressions.md#1176-member-access)), non-indexed base access expressions ([§11.7.13](expressions.md#11713-base-access)), `typeof` expressions ([§11.7.16](expressions.md#11716-the-typeof-operator)), default value expressions ([§11.7.19](expressions.md#11719-default-value-expressions)), `nameof` expressions ([§11.7.20](expressions.md#11720-nameof-expressions)), and declaration expressions (§declaration-expressions-new-clause). - The definite-assignment state of *v* at the end of such an expression is the same as the definite-assignment state of *v* at the beginning of the expression. #### 9.4.4.23 General rules for expressions with embedded expressions -The following rules apply to these kinds of expressions: parenthesized expressions ([§11.7.5](expressions.md#1175-parenthesized-expressions)), element access expressions ([§11.7.10](expressions.md#11710-element-access)), base access expressions with indexing ([§11.7.13](expressions.md#11713-base-access)), increment and decrement expressions ([§11.7.14](expressions.md#11714-postfix-increment-and-decrement-operators), [§11.8.6](expressions.md#1186-prefix-increment-and-decrement-operators)), cast expressions ([§11.8.7](expressions.md#1187-cast-expressions)), unary `+`, `-`, `~`, `*` expressions, binary `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `is`, `as`, `&`, `|`, `^` expressions ([§11.9](expressions.md#119-arithmetic-operators), [§11.10](expressions.md#1110-shift-operators), [§11.11](expressions.md#1111-relational-and-type-testing-operators), [§11.12](expressions.md#1112-logical-operators)), compound assignment expressions ([§11.19.3](expressions.md#11193-compound-assignment)), `checked` and `unchecked` expressions ([§11.7.18](expressions.md#11718-the-checked-and-unchecked-operators)), array and delegate creation expressions ([§11.7.15](expressions.md#11715-the-new-operator)) , and `await` expressions ([§11.8.8](expressions.md#1188-await-expressions)). +The following rules apply to these kinds of expressions: parenthesized expressions ([§11.7.5](expressions.md#1175-parenthesized-expressions)), tuple expressions (§tuple-expressions-new-clause), element access expressions ([§11.7.10](expressions.md#11710-element-access)), base access expressions with indexing ([§11.7.13](expressions.md#11713-base-access)), increment and decrement expressions ([§11.7.14](expressions.md#11714-postfix-increment-and-decrement-operators), [§11.8.6](expressions.md#1186-prefix-increment-and-decrement-operators)), cast expressions ([§11.8.7](expressions.md#1187-cast-expressions)), unary `+`, `-`, `~`, `*` expressions, binary `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `is`, `as`, `&`, `|`, `^` expressions ([§11.9](expressions.md#119-arithmetic-operators), [§11.10](expressions.md#1110-shift-operators), [§11.11](expressions.md#1111-relational-and-type-testing-operators), [§11.12](expressions.md#1112-logical-operators)), compound assignment expressions ([§11.19.3](expressions.md#11193-compound-assignment)), `checked` and `unchecked` expressions ([§11.7.18](expressions.md#11718-the-checked-and-unchecked-operators)), array and delegate creation expressions ([§11.7.15](expressions.md#11715-the-new-operator)) , and `await` expressions ([§11.8.8](expressions.md#1188-await-expressions)). Each of these expressions has one or more subexpressions that are unconditionally evaluated in a fixed order. @@ -631,12 +631,16 @@ new «type» ( «arg₁», «arg₂», … , «argₓ» ) For an expression *expr* of the form: ```csharp -«w» = «expr_rhs» +«expr_lhs» = «expr_rhs» ``` -- The definite-assignment state of *v* before *w* is the same as the definite-assignment state of *v* before *expr*. -- The definite-assignment state of *v* before *expr_rhs* is the same as the definite-assignment state of *v* after *w*. -- If *w* is the same variable as *v*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, if the assignment occurs within the instance constructor of a struct type, and *w* is a property access designating an automatically implemented property *P* on the instance being constructed and *v* is the hidden backing field of *P*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, the definite-assignment state of *v* after *expr* is the same as the definite-assignment state of *v* after *expr_rhs*. +Let the set of *assignment targets* of *e* be defined as follows: +- If *e* is a tuple expression, then the assignment targets of *e* is the union of the assignment targets of the elements of *e*. +- Otherwise, the assignment targets of *e* is *e*. + +- The definite-assignment state of *v* before *expr_lhs* is the same as the definite-assignment state of *v* before *expr*. +- The definite-assignment state of *v* before *expr_rhs* is the same as the definite-assignment state of *v* after *expr_lhs*. +- If *v* is an assignment target of *expr_lhs*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, if the assignment occurs within the instance constructor of a struct type, and *v* is the hidden backing field of an automatically implemented property *P* on the instance being constructed, and a property access designating *P* is an assigment target of *expr_lhs*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, the definite-assignment state of *v* after *expr* is the same as the definite-assignment state of *v* after *expr_rhs*. > *Example*: In the following code > From 797c301189762ce433b2f3e18f0fa2570662620a Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 13:05:54 -0700 Subject: [PATCH 37/41] Fix lint errors --- standard/variables.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 73cb4b2e4..641397d46 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -628,16 +628,17 @@ new «type» ( «arg₁», «arg₂», … , «argₓ» ) #### 9.4.4.25 Simple assignment expressions +Let the set of *assignment targets* in an expression *e* be defined as follows: + +- If *e* is a tuple expression, then the assignment targets in *e* are the union of the assignment targets of the elements of *e*. +- Otherwise, the assignment targets in *e* are *e*. + For an expression *expr* of the form: ```csharp «expr_lhs» = «expr_rhs» ``` -Let the set of *assignment targets* of *e* be defined as follows: -- If *e* is a tuple expression, then the assignment targets of *e* is the union of the assignment targets of the elements of *e*. -- Otherwise, the assignment targets of *e* is *e*. - - The definite-assignment state of *v* before *expr_lhs* is the same as the definite-assignment state of *v* before *expr*. - The definite-assignment state of *v* before *expr_rhs* is the same as the definite-assignment state of *v* after *expr_lhs*. - If *v* is an assignment target of *expr_lhs*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, if the assignment occurs within the instance constructor of a struct type, and *v* is the hidden backing field of an automatically implemented property *P* on the instance being constructed, and a property access designating *P* is an assigment target of *expr_lhs*, then the definite-assignment state of *v* after *expr* is definitely assigned. Otherwise, the definite-assignment state of *v* after *expr* is the same as the definite-assignment state of *v* after *expr_rhs*. From 938205b2d5d954e8c8c706c58bfefe6dac08251d Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 13:15:08 -0700 Subject: [PATCH 38/41] wordsmith Co-authored-by: Nigel-Ecma --- standard/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index a2e418105..2be527eae 100644 --- a/standard/types.md +++ b/standard/types.md @@ -389,7 +389,7 @@ An enumeration type is a distinct type with named constants. Every enumeration t ### §tuple-types-new-clause Tuple types -A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***. A tuple type is written `(T1 I1, ..., Tn In)` with at least two tuple type elements, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. +A tuple type represents an ordered, fixed-length sequence of values with optional names and individual types. The number of elements in a tuple type is referred to as its ***arity***. A tuple type is written `(T1 I1, ..., Tn In)` with n ≥ 2, where the identifiers `I1...In` are optional ***tuple element names***. This syntax is shorthand for a type constructed with the types `T1...Tn` from `System.ValueTuple<...>`, which shall be a set of generic struct types capable of expressing tuple types of any arity greater than one. > *Note*: There does not need to exist a `System.ValueTuple<...>` declaration that directly matches the arity of any tuple type with a corresponding number of type parameters. Instead, larger tuples can be represented with a special overload `System.ValueTuple<..., TRest>` that in addition to tuple elements has a `Rest` field containing a nested tuple value of the remaining elements. Such nesting may be observable in various ways, e.g. via the presence of a `Rest` field. *end note* From 8e94899883143aedc3c20faaed34447afd121ac7 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Wed, 22 Mar 2023 13:57:05 -0700 Subject: [PATCH 39/41] Unify deconstruction expressions with tuple expresseions --- standard/expressions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 16c9b1efd..b683b2032 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -1506,6 +1506,7 @@ A *tuple_expression* represents a tuple, and consists of two or more comma-separ ```ANTLR tuple_expression : '(' tuple_element (',' tuple_element)+ ')' + | deconstruction_expression ; tuple_element @@ -1526,9 +1527,9 @@ deconstruction_element ; ``` -Each of *tuple_expression* and *deconstruction_expression* is a tuple expression, and is classified as a tuple. +A *tuple_expression* is classified as a tuple. -A *deconstruction_expression* `var (e1, ..., en)` is semantically equivalent to the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. Each identifier nested within a *deconstruction_expression* thus introduces a declaration expression (§declaration-expressions-new-clause). As a result, a *deconstruction_expression* can only occur on the left side of a simple assignment. +A *deconstruction_expression* `var (e1, ..., en)` is shorthand for the *tuple_expression* `(var e1, ..., var en)` and follows the same behavior. This applies recursively to any nested *deconstruction_tuple*s in the *deconstruction_expression*. Each identifier nested within a *deconstruction_expression* thus introduces a declaration expression (§declaration-expressions-new-clause). As a result, a *deconstruction_expression* can only occur on the left side of a simple assignment. A tuple expression has a type if and only if each of its element expressions `Ei` has a type `Ti`. The type shall be a tuple type of the same arity as the tuple expression, where each element is given by the following: @@ -4473,7 +4474,7 @@ A declaration expression shall only occur in the following syntactic contexts: - As an `out` *argument_value* in an *argument_list*. - As a simple discard `_` comprising the left side of a simple assignment ([§11.19.2](expressions.md#11192-simple-assignment)). -- As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. A *deconstruction_expression* also gives rise to declaration expressions in this position. +- As a *tuple_element* in one or more recursively nested *tuple_expression*s, the outermost of which comprises the left side of a deconstructing assignment. A *deconstruction_expression* gives rise to declaration expressions in this position, even though the declaration expressions are not syntactically present. > *Note:* This means that a declaration expression cannot be parenthesized. *end note* @@ -4485,7 +4486,7 @@ A declaration expression that is a simple discard or where the *local_variable_t - In an *argument_list* the inferred type of the variable is the declared type of the corresponding parameter. - As the left side of a simple assignment, the inferred type of the variable is the type of the right side of the assignment. -- In a *tuple_expression* or *deconstruction_expression* on the left side of a simple assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. +- In a *tuple_expression* on the left side of a simple assignment, the inferred type of the variable is the type of the corresponding tuple element on the right side (after deconstruction) of the assignment. Otherwise, the declaration expression is classified as an *explicitly typed* variable, and the type of the expression as well as the declared variable shall be that given by the *local_variable_type*. @@ -6245,7 +6246,6 @@ expression non_assignment_expression : declaration_expression - | deconstruction_expression | conditional_expression | lambda_expression | query_expression From b0de44ff595d45747e53b2fa1a4e4380455a4099 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 6 Apr 2023 12:59:28 -0700 Subject: [PATCH 40/41] Add note about discards as out arguments --- standard/variables.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index 641397d46..b7cededbb 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -156,7 +156,11 @@ A ***discard*** is a local variable that has no name. A discard is introduced by > *Note*: `_` is a valid identifier in many forms of declarations. *end note* -Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. A discard is not initially assigned, so it is always an error to access its value. +Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. + +> *Note*: A discard can however be passed as an out argument, allowing the out parameter to denote its associated storage location. *end note* + +A discard is not initially assigned, so it is always an error to access its value. > *Example*: > From 5bfcb4515dd172936604bfbdb825eefbab1024e8 Mon Sep 17 00:00:00 2001 From: Mads Torgersen Date: Thu, 6 Apr 2023 13:06:11 -0700 Subject: [PATCH 41/41] Remove space --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index b7cededbb..d4922f2dd 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -156,7 +156,7 @@ A ***discard*** is a local variable that has no name. A discard is introduced by > *Note*: `_` is a valid identifier in many forms of declarations. *end note* -Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. +Because a discard has no name, the only reference to the variable it represents is the expression that introduces it. > *Note*: A discard can however be passed as an out argument, allowing the out parameter to denote its associated storage location. *end note*