Dealing with var, parts 1 and 2 - #1696
Conversation
|
Just to set expectations, I'm out for the next week or so - will review all of these pending PRs when I get back. |
Nigel-Ecma
left a comment
There was a problem hiding this comment.
Unfortunately poking these issues brings out more :-(
If I am reading this correctly the Note on line 89 contains normative information. I think the cases maybe:
When there is no declaration of var:
- Then
var x; wherexis an discard, identifier, tuple; is a var_pattern
When a type declaration for var is in scope:
- Then
var xis neither a var_pattern or declaration_pattern - However
@var x; and other ugly ways to write “var”; is a declaration_pattern
When var is declared in scope as something other than a type:
- I’ve no idea offhand
I don’t think this is clear in the text of either §11.2.2 or §11.2.4
Would it be better if these two clauses were adjacent? Indeed maybe investigating a “Declaration patterns” clause with subclauses General (includes the disambig stuff), Explicitly typed (declaration_pattern), and Implicitly type (var_pattern), could be considered?
Tuples: consider the ways to match a pair of ints:
(int, int) x– declaration_pattern- `var (x, y) – var_pattern
- (int x, int y) - positional_pattern
(int, int) (x, y)– syntactically invalid
Should this be made clear somewhere?
dynamic: The syntax of declaration_pattern permits dynamic but this is I understand invalid as the semantics are defined in terms of is (line 91) which disallows dynamic. This should be covered in §11.2.2
|
2nd commit addresses #1696 (review). I knew this looked to easy. First, moved "Constant Pattern" before "Declaration Pattern". Now, declaration_pattern and var_pattern are adjacent. That should make it easier to determine if we want to combine them. The flow seems reasonable. Next, handle Finally, add a note about how tuple literals are parsed as different patterns depending on their form. |
|
@Nigel-Ecma I believe I've addressed your review comments. Can you take another look? |
jskeet
left a comment
There was a problem hiding this comment.
Will look again once the constant pattern movement has been sorted out, to avoid missing something.
|
|
||
| The order of evaluation of operations and side effects during pattern-matching (calls to `Deconstruct`, property accesses, and invocations of methods in `System.ITuple`) is not specified. | ||
|
|
||
| The *identifier* `var` appearing in any *pattern* is bound using the normal identifier-resolution rules. The *identifier* `var` may appear in patterns only where the entity to which it resolves is valid in that position. When no declaration of the *identifier* `var` is in scope, the form `var` *designation* is recognised as a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)); when any declaration of the *identifier* `var` is in scope, a *var_pattern* cannot be used. The verbatim identifier `@var` and Unicode-escaped equivalents are distinct identifiers from the contextual keyword `var` and are bound and used in patterns according to the entities they denote, like any other identifier. |
There was a problem hiding this comment.
Not sure about the verbatim identifier part - that would suggest this should compile:
using System;
class @var {}
class Test
{
static void Main()
{
object x = "";
if (x is var y)
{
Console.WriteLine("yes");
}
}
}
... but it doesn't. I think you're trying to say that using @var in a pattern isn't equivalent to using var, but if that's the case, we should be more explicit about it.
| > | ||
| > - If `var` denotes a type, then `var` may be the *type* of a *declaration_pattern* ([§11.2.2](patterns.md#1122-declaration-pattern)). | ||
| > - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). | ||
| > - Otherwise (for example, when `var` denotes a field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid in pattern position. |
There was a problem hiding this comment.
I'm not sure what "in pattern position" means here. (I suspect just adding a couple of words will be fine.)
There was a problem hiding this comment.
Whatever we decide to do, we should apply to line 191 as well.
There was a problem hiding this comment.
@jskeet – I think the intention is “is not valid as the first token of a pattern”
Nigel-Ecma
left a comment
There was a problem hiding this comment.
A step in the right direction, but describing this cleanly is a challenge… :-(
|
|
||
| > *Note*: When a declaration of the *identifier* `var` is in scope, the consequences of the rule above are: | ||
| > | ||
| > - If `var` denotes a type, then `var` may be the *type* of a *declaration_pattern* ([§11.2.2](patterns.md#1122-declaration-pattern)). |
There was a problem hiding this comment.
I believe this is incorrect, once var is declared as a type then it cannot be used as the type in a declaration_pattern – you must go ugly (@var et al) – but it can be used in is *type*[*].
If var is declared as a constant then both is var – constant_pattern and is var y – var_pattern are valid. However is @var y is invalid as that references the constant…
Or something like that!
[*] This is testing for v8, for v9 is *type* vs is *pattern* where pattern is type_pattern throws a spanner, and the rest of the toolbox, into the works as both in theory exist and have different semantics yet are syntactically indistinguishable – at least that’s what the source material says…
| > | ||
| > - If `var` denotes a type, then `var` may be the *type* of a *declaration_pattern* ([§11.2.2](patterns.md#1122-declaration-pattern)). | ||
| > - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). | ||
| > - Otherwise (for example, when `var` denotes a field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid in pattern position. |
There was a problem hiding this comment.
@jskeet – I think the intention is “is not valid as the first token of a pattern”
|
@jskeet is taking an action to create as exhaustive a list of test cases as feasible. |
|
Starting point of test cases: #if VAR_IS_NAMESPACE
namespace var
{
#endif
public class VarTests
{
#if VAR_IS_CONSTANT
private const int var = 5;
#elif VAR_IS_TYPE
private class @var { }
#endif
static void Tests()
{
object o = new object();
bool t1 = o is var;
bool t2 = o is @var;
bool t3 = o is (var);
bool t4 = o is (@var);
if (o is var t5) { }
if (o is @var t6) { }
if (o is (var t7a, var t7b)) { }
if (o is (@var t8a, @var t8b)) { }
}
}
#if VAR_IS_NAMESPACE
}
#endifThe Note: when Without defining anything:
With
With
With
|
|
@jskeet I removed the meeting discuss label on this one. Go ahead and add it back if you want to discuss the tests you've added. |
7d8d627 to
7b5a0f0
Compare
1. A *var_pattern* can't be used if an identifier `var` is in scope. 2. If `var` is a type in scope, it can't be used as a *declaration_pattern*. However, other "spellings" of `var` are allowed as a type.
I knew this looked to easy. First, moved "Constant Pattern" before "Declaration Pattern". Now, *declaration_pattern* and *var_pattern* are adjacent. That should make it easier to determine if we want to combine them. The flow seems reasonable. Next, handle `var` in general. I moved these rules into the "general" section, because they apply across all patterns. If `var` is an identifier, it binds to that element. If `var` is a constant, it's a constant pattern. If it's a type, it's a type pattern. If it's a variable, it's an invalid pattern. (Similarly if its a method group or namespace). Finally, add a note about how tuple literals are parsed as different patterns depending on their form.
Address reviewer feedback on the `var`/`@var` disambiguation in the
pattern-form clauses:
- Clarify that the verbatim identifier `@var` and Unicode-escaped
spellings of `var` are never recognised as the contextual keyword of a
`var_pattern`. Such a pattern is interpreted as a `declaration_pattern`,
`constant_pattern`, or other form according to the entity the identifier
denotes.
- Promote the §11.2.1 note describing the consequences of an in-scope
`var` declaration to normative body text, stating the three cases
(type, constant, and otherwise) precisely.
- State that when `var` denotes a type, the plain token `var` cannot be
used as the *type* of a `declaration_pattern`; the type must be named
another way, such as `@var` (§11.2.1 and §11.2.2).
- Replace the vague phrase "pattern position" with "the first token of a
*pattern*" for clarity and consistency (§11.2.1 and §11.2.4).
- Narrow the `var_pattern` restriction: a `var_pattern` is unavailable
only when the plain token `var` would refer to an in-scope type named
`var`, not when `var` names a constant or namespace. The previous
wording ("any declaration of `var`") was too broad; the refined rule
matches observed compiler behaviour.
- Record the C# 9 boundary change for a future draft-v9 update: a
parenthesised `is (var)` / `is (@var)` binds as a type pattern in C# 9
while remaining unavailable in C# 8, and bare `is T` and pattern
`is (T)` are distinct grammar and binding paths.
4b4159b to
e4efe2b
Compare
|
I've addressed all unresolved comments and validated the language against the tests Jon added. |
jskeet
left a comment
There was a problem hiding this comment.
LGTM, thanks for the clarifications.
|
Punted in the meeting due to @Nigel-Ecma's absence - we'll see if we can discuss and address offline between meetings. |
|
The base branch |
Nigel-Ecma
left a comment
There was a problem hiding this comment.
I think the description of var handling “in a pattern” is unintentionally misleading as the handling follows that of other contextual keywords, the description lacks any reference to §6.4.
I also think a simpler/clearer approach is to follow the usual one of dealing with contextual keyword vs. identifier resolution in context at the source.
So I’ve made suggestions which delete some of the changes and rephrase others. There are a few other related odds'n'ends as well.
|
|
||
| When a declaration of the *identifier* `var` in scope resolves to a constant, `var` may appear as the *constant_expression* of a *constant_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). |
There was a problem hiding this comment.
The introduction of contextual keywords and discards to C# while also maintaining the ability to use the same identifiers in user-defined ways allows for potential confusion but is generally not remarked upon unless rules, such as disambiguation, are required. So why remark on it here?
In this particular situation if var is defined as a constant then the input var can itself be a pattern which might to some be potentially confusing with the var x pattern… Is this more confusing than the general cases caused by contextual keywords and so worthy of note?
If it is thought not then this addition becomes redundant:
| When a declaration of the *identifier* `var` in scope resolves to a constant, `var` may appear as the *constant_expression* of a *constant_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). |
If it is deemed worthy of explanation then this addition should become a Note explaining the potential confusion:
| When a declaration of the *identifier* `var` in scope resolves to a constant, `var` may appear as the *constant_expression* of a *constant_pattern*; the general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). | |
| > *Note*: Come up with a short clear wording stating that using `var` as a constant in a context where patterns are used could be confusing – this is left as an exercise… *end note* |
Take your pick (or neither of course!)
| ``` | ||
|
|
||
| Let *n* be the number of *subpattern*s appearing between the parentheses. The matching strategy is selected at compile time by applying the following cases in order; the first case whose conditions are satisfied is used, and the remaining cases are not considered. Once a case is selected, that strategy is committed: any compile-time error stated within that case is reported, and matching does not fall through to a subsequent case. | ||
| Given a match of an input value to the pattern *type* `(` *subpatterns* `)`, a method is selected by searching in *type* for accessible declarations of `Deconstruct` and selecting one among them using the same rules as for the deconstruction declaration. |
There was a problem hiding this comment.
As far as I can see there is no such thing as a “deconstruction declaration”, also found on line 261. Are these two uses accidental (untranslated?) copies from some source material or am I missing something?
| The token `var` in a *pattern* is recognised as the contextual keyword of a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)) only in the form `var` *designation*. The verbatim identifier `@var` and Unicode-escaped spellings of `var` are never recognised as the contextual keyword of a *var_pattern*; they are ordinary identifiers denoting whatever entity is in scope. A pattern that uses such a spelling is therefore interpreted as a *declaration_pattern*, *constant_pattern*, or other pattern form according to the entity that the identifier denotes. | ||
|
|
||
| When a declaration of the *identifier* `var` is in scope, the following rules apply: | ||
|
|
||
| - If `var` denotes a type, then the plain token `var` cannot be used as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. | ||
| - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). | ||
| - Otherwise (for example, when `var` denotes a namespace, field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid as the first token of a *pattern* unless it is used in the form `var` *designation*. | ||
|
|
There was a problem hiding this comment.
This reads, along with references in the changes below back to here stating (emphasis added):
the general rule for the identifier
varin patterns is specified in §11.2.1
as though the recognition of var in this context is somehow special.
However var is recognised according to the token rules of §6.4 and the pattern grammar rules; just as uses of contextual keywords elsewhere are. What may differ in any location where a contextual keyword is used are the disambiguation rules that apply if there is a definition in scope with the same name (§6.4.4).
Those disambiguation rules are usually stated close to the source of the ambiguity, which in this case would be in the subclauses for constant, declaration and var patterns; in this case they have been split between here and the relevant subclauses. I don’t see that this gains anything.
Rewording to address these issues could be considered, e.g. by including references back to §6.4 and making it clear the process follows the usual model.
However instead I suggest simply deleting the text here and handling var on a case-by-case basis in the following clauses.
| The token `var` in a *pattern* is recognised as the contextual keyword of a *var_pattern* ([§11.2.4](patterns.md#1124-var-pattern)) only in the form `var` *designation*. The verbatim identifier `@var` and Unicode-escaped spellings of `var` are never recognised as the contextual keyword of a *var_pattern*; they are ordinary identifiers denoting whatever entity is in scope. A pattern that uses such a spelling is therefore interpreted as a *declaration_pattern*, *constant_pattern*, or other pattern form according to the entity that the identifier denotes. | |
| When a declaration of the *identifier* `var` is in scope, the following rules apply: | |
| - If `var` denotes a type, then the plain token `var` cannot be used as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. | |
| - If `var` denotes a constant, then `var` may be the *constant_expression* of a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)). | |
| - Otherwise (for example, when `var` denotes a namespace, field, property, local variable, parameter, or any other entity that is neither a type nor a constant), `var` is not valid as the first token of a *pattern* unless it is used in the form `var` *designation*. |
| <!-- C# 9 update marker: the committee's empirical testing shows that in C# 8, when `var` names a type, `o is (var)` and `o is (@var)` fail CS8400 because type patterns are not available; in C# 9 they bind as *type_pattern*s and are valid. Draft-v9 should update this boundary for type patterns and parenthesized patterns, while preserving that bare `is T` and pattern `is (T)` are distinct grammar/binding paths. --> | ||
|
|
There was a problem hiding this comment.
I don't think stating that the definition of C# is determined by “empirical testing” is how formal language semantics are decided – even if replacing formal definition with empirical observation might be becoming de rigueur for AI software specification 😉
So I will again delete this text as the simplest option, if is not then at minimum it needs rephrasing.
| <!-- C# 9 update marker: the committee's empirical testing shows that in C# 8, when `var` names a type, `o is (var)` and `o is (@var)` fail CS8400 because type patterns are not available; in C# 9 they bind as *type_pattern*s and are valid. Draft-v9 should update this boundary for type patterns and parenthesized patterns, while preserving that bare `is T` and pattern `is (T)` are distinct grammar/binding paths. --> |
|
|
||
| > *Note*: ANTLR makes the specified choice automatically due to the ordering of the alternatives of *simple_designation*. *end note* | ||
|
|
||
| When a declaration of the *identifier* `var` in scope resolves to a type, the plain token `var` cannot appear as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. The general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). |
There was a problem hiding this comment.
This is a curious case, why does the declaration pattern differ in its handling of var compared to the declaration statement? I.e. if var is, say, a user-defined class type; then var x declares x to be of type var when the tokens appear in the context of a declaration statement, and the token sequence is invalid if they appear in the context of a declaration pattern – why the difference?
Assuming this difference is intentional we fortunately we are not required to provide the rationale, we just need to state the rule:
| When a declaration of the *identifier* `var` in scope resolves to a type, the plain token `var` cannot appear as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. The general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). | |
| A *declaration_pattern* cannot be used to test that a value has a type named `var` unless that type is referenced using an identifier containing a unicode character escape sequence ([§6.4.2](lexical-structure.md#642-unicode-character-escape-sequences)) or represented by an *Escaped_Identifier* ([§6.4.3](lexical-structure.md#643-identifiers)). |
An example could be included which, say, declares a class var and then uses @var and/or v\u0061r in declaration patterns. Here is one which is not included in the above suggestion as it feels far too long, I expect others will do better:
Example: Given the definition of a class
varand a variablexof that type:class var { ... }Then testing whether a variable
xis of typevarusing the is-type operator (§12.14.12.1):if (x is var) ...is valid. However a similar test using the is-pattern operator (§12.14.12.2):
if (x is var y) ...is a compile-time error. However the following:
if (x is v\u0061r y) ... if (x is @var z) ...are both valid uses of the is-pattern operator with a declaration_pattern.
end example
|
|
||
| When a declaration of the *identifier* `var` in scope resolves to a type, the plain token `var` cannot appear as the *type* of a *declaration_pattern*; that type shall be named another way, such as by the verbatim identifier `@var`. The general rule for the *identifier* `var` in patterns is specified in [§11.2.1](patterns.md#1121-general). | ||
|
|
||
| The *type* of a *declaration_pattern* cannot be `dynamic`, because the runtime type test is defined in terms of the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)), which does not permit `dynamic`. |
There was a problem hiding this comment.
The “because…” is surely wrong as nothing has the runtime type of dynamic? I suggest just:
| The *type* of a *declaration_pattern* cannot be `dynamic`, because the runtime type test is defined in terms of the is-type operator ([§12.14.12.1](expressions.md#1214121-the-is-type-operator)), which does not permit `dynamic`. | |
| The *type* of a *declaration_pattern* cannot be `dynamic`. |
and avoid getting into why.
|
|
||
| A *var_pattern* is *applicable to* every type. | ||
|
|
||
| A *var_pattern* cannot be used when the plain token `var` would refer to an in-scope type named `var`. See [§11.2.1](patterns.md#1121-general) for the interpretation of `var` when it is the first token of a *pattern* in that case. |
There was a problem hiding this comment.
Following the previous suggested changes we need to delete the x-ref as it now goes nowhere, and a slight rewording suggestion:
| A *var_pattern* cannot be used when the plain token `var` would refer to an in-scope type named `var`. See [§11.2.1](patterns.md#1121-general) for the interpretation of `var` when it is the first token of a *pattern* in that case. | |
| A *var_pattern* cannot be used when there is an in-scope type named `var`. |
Is it worth adding a Note that there is/is not no easy workaround in such as case?
|
|
||
| Let *n* be the number of *subpattern*s appearing between the parentheses. The matching strategy is selected at compile time by applying the following cases in order; the first case whose conditions are satisfied is used, and the remaining cases are not considered. Once a case is selected, that strategy is committed: any compile-time error stated within that case is reported, and matching does not fall through to a subsequent case. | ||
| Given a match of an input value to the pattern *type* `(` *subpatterns* `)`, a method is selected by searching in *type* for accessible declarations of `Deconstruct` and selecting one among them using the same rules as for the deconstruction declaration. | ||
| It is an error if a *positional_pattern* omits the type, has a single *subpattern* without an *identifier*, has no *property_subpattern* and has no *simple_designation*. This disambiguates between a *constant_pattern* that is parenthesized and a *positional_pattern*. |
There was a problem hiding this comment.
This is just a repeat of line 33, repeating it is harmless but it should be a true repeat:
| It is an error if a *positional_pattern* omits the type, has a single *subpattern* without an *identifier*, has no *property_subpattern* and has no *simple_designation*. This disambiguates between a *constant_pattern* that is parenthesized and a *positional_pattern*. | |
| If the input can be syntactically recognised as both a *constant_pattern* and a *positional_pattern* then the *constant_pattern* shall be chosen. |
Note that neither location contains an ANTLR comment as some other similar cases do, in this case it would be:
Note: ANTLR makes the specified choice automatically due to the ordering of the alternatives of pattern. end note
Adding it to line 33 could be done, I wouldn't add it at both locations.
Fixes #1663
Fixes #1665
Because these are related, and small in scope (even if combined) put them both in one PR:
varis in scope.varis a type in scope, it can't be used as a declaration_pattern. However, other "spellings" ofvarare allowed as a type.