@@ -513,6 +513,7 @@ binder
513513| wildcardBinder
514514| variableBinder
515515| castBinder
516+ | nullAssertBinder
516517
517518binders ::= binder ( ',' binder )* ','?
518519```
@@ -681,6 +682,30 @@ pattern:
681682var (i as int, s as String) = record;
682683```
683684
685+ #### Null-assert binder
686+
687+ ```
688+ nullAssertBinder ::= binder '!'
689+ ```
690+
691+ When the type being matched or destructured is nullable and you want to assert
692+ that the value shouldn't be null, you can use a cast pattern, but that can be
693+ verbose if the underlying type name is long:
694+
695+ ``` dart
696+ (String, Map<String, List<DateTime>?>) data = ...
697+ var (name, timeStamps as Map<String, List<DateTime>>) = data;
698+ ```
699+
700+ To make that easier, similar to the null-assert expression, a null-assert binder
701+ pattern forcibly casts the matched value to its non-nullable type. If the value
702+ is null, a runtime exception is thrown:
703+
704+ ``` dart
705+ (String, Map<String, List<DateTime>?>) data = ...
706+ var (name, timeStamps!) = data;
707+ ```
708+
684709### Refutable patterns ("matchers")
685710
686711Refutable patterns determine if the value in question matches or meets some
@@ -699,6 +724,7 @@ matcher ::=
699724 | variableMatcher
700725 | declarationMatcher
701726 | extractMatcher
727+ | nullCheckMatcher
702728```
703729
704730#### Literal matcher
@@ -846,6 +872,9 @@ By using variable matchers as subpatterns of a larger matched pattern, a single
846872composite pattern can validate some condition and then bind one or more
847873variables only when that condition holds.
848874
875+ A variable pattern can also have a type annotation in order to only match values
876+ of the specified type.
877+
849878#### Declaration matcher
850879
851880A declaration matcher enables embedding an entire declaration binding pattern
@@ -930,7 +959,31 @@ It is a compile-time error if `extractName` does not refer to a type or enum
930959value. It is a compile-time error if a type argument list is present and does
931960not match the arity of the type of ` extractName ` .
932961
933- ** TODO: Some kind of terse null-check pattern that matches a non-null value?**
962+ #### Null-check matcher
963+
964+ Similar to the null-assert binder, a null-check matcher provides a nicer syntax
965+ for working with nullable values. Where a null-assert binder * throws* if the
966+ matched value is null, a null-check matcher simply fails the match. To highlight
967+ the difference, it uses a gentler ` ? ` syntax, like the [ similar feature in
968+ Swift] [ swift null check ] :
969+
970+ [ swift null check ] : https://docs.swift.org/swift-book/ReferenceManual/Patterns.html#ID520
971+
972+ ```
973+ nullCheckMatcher ::= matcher '?'
974+ ```
975+
976+ A null-check pattern matches if the value is not null, and then matches the
977+ inner pattern against that same value. Because of how type inference flows
978+ through patterns, this also provides a terse way to bind a variable whose type
979+ is the non-nullable base type of the nullable value being matched:
980+
981+ ``` dart
982+ String? maybeString = ...
983+ if (case var s? = maybeString) {
984+ // s has type String here.
985+ }
986+ ```
934987
935988## Static semantics
936989
@@ -1036,12 +1089,19 @@ The context type schema for a pattern `p` is:
10361089 be used to downcast from any other type.*
10371090 * Else it is ` ? ` .
10381091
1039- * ** Cast binder** , ** wildcard matcher** , or ** extractor matcher** : The
1040- context type schema is ` Object? ` .
1092+ * ** Cast binder** , ** wildcard matcher** , or ** extractor matcher** : The context
1093+ type schema is ` Object? ` .
10411094
10421095 ** TODO: Should type arguments on an extractor create a type argument
10431096 constraint?**
10441097
1098+ * ** Null-assert binder** or ** null-check matcher** : A type schema ` E? ` where
1099+ ` E ` is the type schema of the inner pattern. * For example:*
1100+
1101+ ``` dart
1102+ var [[int x]!] = [[]]; // Infers List<List<int>?> for the list literal.
1103+ ```
1104+
10451105* **Literal matcher** or **constant matcher**: The context type schema is the
10461106 static type of the pattern's constant value expression.
10471107
@@ -1218,6 +1278,22 @@ The static type of a pattern `p` being matched against a value of type `M` is:
12181278 The static type of `p` is `Object?`. *Wildcards accept all types. Casts and
12191279 extractors exist to check types at runtime, so statically accept all types.*
12201280
1281+ * **Null-assert binder** or **null-check matcher**:
1282+
1283+ 1. If `M` is `N?` for some type `N` then calculate the static type `q` of
1284+ the inner pattern using `N` as the matched value type. Otherwise,
1285+ calculate `q` using `M` as the matched value type. *A null-assert or
1286+ null-check pattern removes the nullability of the type it matches
1287+ against.*
1288+
1289+ ```dart
1290+ var [x!] = <int?>[]; // x is int.
1291+ ```
1292+
1293+ 2. The static type of `p` is `q?`. *The intent of `!` and `?` is only to
1294+ remove nullability and not cast from an arbitrary type, so they accept a
1295+ value of its nullable base type, and not simply `Object?`.*
1296+
12211297* **Literal matcher** or **constant matcher**: The static type of `p` is the
12221298 static type of the pattern's value expression.
12231299
@@ -1265,6 +1341,9 @@ patterns binds depend on what kind of pattern it is:
12651341 declaration or declaration matcher has a `final` modifier. The variable is
12661342 late if it is inside a pattern variable declaration marked `late`.
12671343
1344+ * **Null-assert binder** or **null-check matcher**: Introduces all of the
1345+ variables of its subpattern.
1346+
12681347* **Declaration matcher**: The `final` or `var` keyword establishes whether
12691348 the binders nested inside this create final or assignable variables and
12701349 then introduces those variables.
@@ -1550,6 +1629,14 @@ To match a pattern `p` against a value `v`:
15501629 2 . Otherwise, bind the variable's identifier to ` v ` . The match always
15511630 succeeds (if it didn't throw).
15521631
1632+ * ** Null-assert binder** :
1633+
1634+ 1 . If ` v ` is null then throw a runtime exception. * Note that we throw even
1635+ if this appears in a refutable context. The intent of this pattern is to
1636+ assert that a value * must* not be null.*
1637+
1638+ 2 . Otherwise, match the inner pattern against ` v ` .
1639+
15531640* ** Literal matcher** or ** constant matcher** : The pattern matches if ` o == v `
15541641 evaluates to ` true ` where ` o ` is the pattern's value.
15551642
@@ -1571,6 +1658,12 @@ To match a pattern `p` against a value `v`:
15711658 3 . Otherwise, match ` v ` against the subpatterns of ` p ` as if it were a
15721659 record pattern.
15731660
1661+ * ** Null-check matcher** :
1662+
1663+ 1 . If ` v ` is null then the match fails.
1664+
1665+ 2 . Otherwise, match the inner pattern against ` v ` .
1666+
15741667** TODO: Update to specify that the result of operations can be cached across
15751668cases. See: https://github.com/dart-lang/language/issues/2107 **
15761669
@@ -1611,6 +1704,8 @@ main() {
16111704
16121705- Allow extractor patterns to match enum values.
16131706
1707+ - Add null-assert binder ` ! ` and null-check ` ? ` matcher patterns.
1708+
16141709### 1.1
16151710
16161711- Copy editing and clean up.
0 commit comments