Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[patterns] Disambiguate identifiers in the pattern grammar. #2785

Merged
merged 2 commits into from
Jan 25, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 131 additions & 72 deletions accepted/future-releases/0546-patterns/feature-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ Before introducing each pattern in detail, here is a summary with some examples:
| [Null-check][nullCheckPattern] | `subpattern?` |
| [Null-assert][nullAssertPattern] | `subpattern!` |
| [Constant][constantPattern] | `123`, `null`, `'string'`<br>`math.pi`, `SomeClass.constant`<br>`const Thing(1, 2)`, `const (1 + 2)` |
| [Variable][variablePattern] | `foo`, `var bar`, `String str`, `_`, `int _` |
| [Variable][variablePattern] | `var bar`, `String str`, `final int _` |
| [Identifier][identifierPattern] | `foo`, `_` |
| [Parenthesized][parenthesizedPattern] | `(subpattern)` |
| [List][listPattern] | `[subpattern1, subpattern2]` |
| [Map][mapPattern] | `{"key": subpattern1, someConst: subpattern2}` |
Expand All @@ -203,6 +204,7 @@ Before introducing each pattern in detail, here is a summary with some examples:
[nullAssertPattern]: #null-assert-pattern
[constantPattern]: #constant-pattern
[variablePattern]: #variable-pattern
[identifierPattern]: #identifier-pattern
[parenthesizedPattern]: #parenthesized-pattern
[listPattern]: #list-pattern
[mapPattern]: #map-pattern
Expand All @@ -226,6 +228,7 @@ unaryPattern ::= castPattern

primaryPattern ::= constantPattern
| variablePattern
| identifierPattern
| parenthesizedPattern
| listPattern
| mapPattern
Expand Down Expand Up @@ -436,7 +439,6 @@ constantPattern ::= booleanLiteral
| '-'? numericLiteral
| stringLiteral
| symbolLiteral
| identifier
| qualifiedName
| constObjectExpression
| 'const' typeArguments? '[' elements? ']'
Expand All @@ -455,14 +457,10 @@ supporting terse forms of the most common constant expressions like so:
literal `2` with a unary `-` applied to it (which is how the language
views it).

* Named constants are also allowed because they aren't ambiguous. That
includes simple identifiers like `someConstant`, prefixed constants like
`some_library.aConstant`, static constants on classes like
`SomeClass.aConstant`, and prefixed static constants like
`some_library.SomeClass.aConstant`. *Simple identifiers would be ambiguous
with variable patterns that aren't marked with `var`, `final`, or a type,
but unmarked variable patterns are only allowed in irrefutable contexts
where constant patterns are prohibited.*
* Qualified named constants are also allowed because they aren't ambiguous.
That includes prefixed constants like `some_library.aConstant`, static
constants on classes like `SomeClass.aConstant`, and prefixed static
constants like `some_library.SomeClass.aConstant`.

* List literals are ambiguous with list patterns, so we only allow list
literals explicitly marked `const`. Likewise with set and map literals
Expand All @@ -487,7 +485,7 @@ expression.
### Variable pattern

```
variablePattern ::= ( 'var' | 'final' | 'final'? type )? identifier
variablePattern ::= ( 'var' | 'final' | 'final'? type ) identifier
```

A variable pattern binds the matched value to a new variable. These usually
Expand All @@ -502,8 +500,8 @@ Here, `a` and `b` are variable patterns and end up bound to `1` and `2`,
respectively.

The pattern may have a type annotation in order to only match values of the
specified type. If the type annotation is omitted, the variable's type is
inferred and the pattern matches all values.
specified type. Otherwise, it is declared using `var` or `final` and the
variable's type is inferred such that it matches all values.

```dart
switch (record) {
Expand All @@ -517,17 +515,9 @@ They are specified later in the "Pattern context" section.*

#### Wildcards

If the variable's name is `_`, it doesn't bind any variable. This "wildcard"
name is useful as a placeholder in places where you need a subpattern in order
to destructure later positional values:

```dart
var list = [1, 2, 3];
var [_, two, _] = list;
```

The `_` identifier can also be used with a type annotation when you want to test
a value's type but not bind the value to a name:
If the variable's name is `_`, it doesn't bind any variable. A "wildcard" name
with a type annotation is useful when you want to test a value's type but not
bind the value to a name:

```dart
switch (record) {
Expand All @@ -536,6 +526,32 @@ switch (record) {
}
```

### Identifier pattern

```
identifierPattern ::= identifier
```

A bare identifier in a pattern is semantically ambiguous. A user might expect it
to match if the value is equal to a constant with that name (as it currently
does in switches). Or the user could expect it to bind or assign to a variable
with that name.

The answer is it's both. Depending on the context where it appears, a bare
identifier pattern may behave like a constant pattern or like a variable
pattern. The section on pattern context below lays out the precise rules.

#### Wildcards

As with variable patterns, an identifier pattern named `_` is a wildcard that
doesn't bind or assign to any variable. It's useful as a placeholder in places
where you need a subpattern in order to destructure later positional values:

```dart
var list = [1, 2, 3];
var [_, two, _] = list;
```

### Parenthesized pattern

```
Expand Down Expand Up @@ -782,8 +798,8 @@ In both record patterns and object patterns, a field subpattern's name may be
elided when it can be inferred from the field's value subpattern. The inferred
field name for a pattern `p`, if one exists, is defined as:

* If `p` is a variable pattern which binds a variable `v`, and `v` is not `_`,
then the inferred name is `v`.
* If `p` is a variable or identifier pattern with identifier `v`, and `v` is
not `_`, then the inferred name is `v`.

* If `p` is `q?` then the inferred name of `p` (if any) is the inferred name
of `q`.
Expand Down Expand Up @@ -912,15 +928,15 @@ patternAssignment ::= outerPattern '=' expression
assignments, but does not allow patterns to the left of a compound assignment
operator.*

In a pattern assignment, all variable patterns are interpreted as referring to
In a pattern assignment, all identifier patterns are interpreted as referring to
existing variables. You can't declare any new variables. *Disallowing new
variables allows pattern assignment expressions to appear anywhere expressions
are allowed while avoiding confusion about the scope of new variables.*

It is a compile-time error if:

* An identifier in a variable pattern does not resolve to an assignable local
variable or formal parameter. A variable is assignable if it is any of:
* An identifier pattern does not resolve to an assignable local variable or
formal parameter. A variable is assignable if it is any of:

* Non-final
* Final and definitely unassigned
Expand Down Expand Up @@ -952,12 +968,12 @@ It is a compile-time error if:
to local variables, which are also the only kind of variables that can be
declared by patterns.*

* The matched value type for a variable pattern is not assignable to the
* The matched value type for an identifier pattern is not assignable to the
corresponding variable's type.

* The same variable is assigned more than once. *In other words, a pattern
assignment can't have multiple variable subpatterns with the same name. This
prohibits code like:*
assignment can't have multiple identifier subpatterns with the same name.
munificent marked this conversation as resolved.
Show resolved Hide resolved
This prohibits code like:*

```dart
var a = 1;
Expand Down Expand Up @@ -1452,8 +1468,8 @@ categorize into three contexts:
We refer to declaration and assignment contexts as *irrefutable contexts*.

While most patterns look and act the same regardless of where they appear in the
language, context places some restrictions on which kinds of patterns are
allowed and what their syntax is. The rules are:
language, context determines what identifier patterns mean, and places some
restrictions on which other kinds of patterns are allowed. The rules are:

* It is a compile-time error if any of the following *refutable patterns*
appear in an irrefutable context:
Expand Down Expand Up @@ -1481,6 +1497,14 @@ allowed and what their syntax is. The rules are:
the matched value isn't assignable to their required type. This error is
specified under type checking.*

* In a declaration context, an identifier pattern declares a new variable with
that name. *A pattern declaration statement begins with `var` or `final`, so
within that, new variables can be introduced just using simple identifiers:*

```dart
var (a, b) = (1, 2);
```

* It is a compile-time error if a variable pattern in a declaration context is
marked with `var` or `final`. *A pattern declaration statement is already
preceded by `var` or `final`, so allowing those on the variable patterns
Expand All @@ -1492,18 +1516,12 @@ allowed and what their syntax is. The rules are:
final [var y] = [2];
```

*To declare variables in a declaration context, use a simple identifer:*

```dart
// OK:
var [x] = [1];
final [y] = [2];
```
*Variable patterns are allowed in declaration contexts but must have type
annotations. This can be useful to upcast the declared variable.*

* It is a compile-time error if a variable pattern in an assignment context is
marked with `var`, `final`, or a type annotation (or both `final` and a type
annotation). *Patterns in assignments can only assign to existing variables,
not declare new ones.*
* It is a compile-time error if a variable pattern appears in an assignment
context. *Patterns in assignments can only assign to existing variables
using identifier patterns, not declare new ones.*

```dart
var a = 1;
Expand All @@ -1516,16 +1534,13 @@ allowed and what their syntax is. The rules are:
(a, b) = (3, 4);
```

* A simple identifier in a matching context is treated as a named constant
pattern unless its name is `_`. *A bare identifier is ambiguous and could
be either a named constant or a variable pattern without any `var`, `final`,
or type annotation marker. We prefer the constant interpretation for
backwards compatibility and to make variable declarations more explicit in
cases. To declare variables in a matching context, use `var`, `final`, or a
type before the name.*

*There is no ambiguity with bare identifiers in irrefutable contexts since
constant patterns are disallowed there.*
* An identifier pattern in a matching context is treated as a named constant
pattern unless its name is `_`. *A bare identifier is ambiguous and could be
either a named constant or a variable pattern without any `var`, `final`, or
type annotation marker. We prefer the constant interpretation for backwards
compatibility and to make variable declarations more explicit in cases. To
declare variables in a matching context, use a variable pattern with `var`,
`final`, or a type before the name.*

```dart
const c = 1;
Expand All @@ -1537,9 +1552,13 @@ allowed and what their syntax is. The rules are:

*This program prints "no match" and not "match 2".*

* A simple identifier in any context named `_` is treated as a wildcard
variable pattern. *A bare `_` is always treated as a wildcard regardless of
context, even though other variables in matching contexts require a marker.*
*There is no ambiguity with bare identifiers in irrefutable contexts since
constant patterns are disallowed there.*

* An identifier pattern named `_` in any context is treated as a wildcard that
matches any value and discards it. *A bare `_` is always treated as a
wildcard regardless of context, even though other variables in matching
contexts require a marker.*

```dart
// OK:
Expand All @@ -1553,10 +1572,10 @@ allowed and what their syntax is. The rules are:
forbid it, but doing so is discouraged.*

*In short, you can't use refutable patterns in places that don't do control
flow. Use simple identifiers (optionally with type annotations) to declare
variables in pattern declarations. Use simple identifiers to assign to variables
in pattern assignments. Use explicitly marked identifiers to declare variables
in `case` patterns. Use `_` anywhere for a wildcard.*
flow. Use identifier patterns or type annotated variable patterns to declare
variables in pattern declarations. Use identifier patterns to assign to
variables in pattern assignments. Use variable patterns to declare variables in
`case` patterns. Use `_` anywhere for a wildcard.*

## Static semantics

Expand Down Expand Up @@ -1766,6 +1785,10 @@ The context type schema for a pattern `p` is:
// ^- Infers List<int>.
```

* **Identifier**: Context type schemas are only used in irrefutable contexts,
so an identifier pattern is always a variable and is handled like a
variable pattern, as above.

* **Cast**: The context type schema is `_`.

* **Parenthesized**: The context type schema of the inner subpattern.
Expand Down Expand Up @@ -1926,13 +1949,10 @@ To type check a pattern `p` being matched against a value of type `M`:

* **Variable**:

1. In an assignment context, the required type of `p` is the (unpromoted)
static type of the variable that `p` resolves to.

2. Else if the variable has a type annotation, the required type of `p` is
that type, as is the static type of the variable introduced by `p`.
1. If the variable has a type annotation, the required type of `p` is that
type, as is the static type of the variable introduced by `p`.

3. Else the required type of `p` is `M`, as is the static type of the
2. Else the required type of `p` is `M`, as is the static type of the
variable introduced by `p`. *This means that an untyped variable pattern
can have its type indirectly inferred from the type of a superpattern:*

Expand All @@ -1945,6 +1965,17 @@ To type check a pattern `p` being matched against a value of type `M`:
That inferred type is then destructured and used to infer `num` for `a`
and `Object` for `b`.*

* **Identifier**:

1. In an assignment context, the required type of `p` is the (unpromoted)
static type of the variable that `p` resolves to.

2. In a matching context, the name refers to a constant. Type check
the constant identifier expression in context type `M`.

3. In a declaration context, the required type of `p` is `M`, as is the
static type of the variable introduced by `p`.

* **Parenthesized**: Type-check the inner subpattern using `M` as the matched
value type.

Expand Down Expand Up @@ -2051,10 +2082,11 @@ To type check a pattern `p` being matched against a value of type `M`:

If `p` with required type `T` is in an irrefutable context:

* It is a compile-time error if `M` is not assignable to `T`. *Destructuring
and variable patterns can only be used in declarations and assignments if we
can statically tell that the destructuring and variable binding won't fail
to match.*
* It is a compile-time error if `M` is not assignable to `T`. *Destructuring,
variable, and identifier patterns can only be used in declarations and
assignments if we can statically tell that the destructuring and variable
binding won't fail to match (though it may still throw at runtime if the
matched value type is `dynamic`).*

* Else if `M` is not a subtype of `T` then an implicit coercion or cast is
inserted before the pattern binds the value, tests the value's type,
Expand Down Expand Up @@ -2161,6 +2193,16 @@ pattern is:
matching context, the variable is final if the variable pattern is
marked `final` and is not otherwise.

* **Identifier**:

1. In a matching context, the empty set. *The identifier is a constant
reference.*

2. Else a set containing a single variable whose name is the identifier and
whose type is the pattern's required type (which may have been
inferred). The variable is final if and only if the surrounding
`patternVariableDeclaration` has a `final` modifier.

#### Scope

The variables defined by a pattern and its subpatterns (its pattern variable
Expand Down Expand Up @@ -2745,6 +2787,13 @@ To match a pattern `p` against a value `v`:

3. Otherwise, store `v` in `p`'s variable and the match succeeds.

* **Identifier**:

1. In a matching context, the same as a constant pattern whose constant
expression is the identifier.

2. Else, the same as a variable pattern with the same identifier.

* **Parenthesized**: Match the subpattern against `v` and succeed if it
matches.

Expand Down Expand Up @@ -3168,6 +3217,13 @@ To bind invocation keys in a pattern `p` using parent invocation `i`:

1. Nothing to do.

* **Identifier**:

1. In a matching context, the same as a constant pattern whose constant
expression is the identifier.

2. Else, nothing to do.

* **List**:

1. Bind `i : ("length", [])` to the `length` getter invocation.
Expand Down Expand Up @@ -3356,6 +3412,9 @@ Here is one way it could be broken down into separate pieces:

- Handle negative length lists and maps (#2701).

- Disambiguate the grammar around bare identifiers (#2714). The overall syntax
and semantics are unchanged, but the pattern grammar is now unambiguous.

- Allow promoted types and type variables with bounds to be always-exhaustive
(#2765).

Expand Down