@@ -513,6 +513,7 @@ binder
513
513
| wildcardBinder
514
514
| variableBinder
515
515
| castBinder
516
+ | nullAssertBinder
516
517
517
518
binders ::= binder ( ',' binder )* ','?
518
519
```
@@ -681,6 +682,30 @@ pattern:
681
682
var (i as int, s as String) = record;
682
683
```
683
684
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
+
684
709
### Refutable patterns ("matchers")
685
710
686
711
Refutable patterns determine if the value in question matches or meets some
@@ -699,6 +724,7 @@ matcher ::=
699
724
| variableMatcher
700
725
| declarationMatcher
701
726
| extractMatcher
727
+ | nullCheckMatcher
702
728
```
703
729
704
730
#### Literal matcher
@@ -846,6 +872,9 @@ By using variable matchers as subpatterns of a larger matched pattern, a single
846
872
composite pattern can validate some condition and then bind one or more
847
873
variables only when that condition holds.
848
874
875
+ A variable pattern can also have a type annotation in order to only match values
876
+ of the specified type.
877
+
849
878
#### Declaration matcher
850
879
851
880
A 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
930
959
value. It is a compile-time error if a type argument list is present and does
931
960
not match the arity of the type of ` extractName ` .
932
961
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
+ ```
934
987
935
988
## Static semantics
936
989
@@ -1036,12 +1089,19 @@ The context type schema for a pattern `p` is:
1036
1089
be used to downcast from any other type.*
1037
1090
* Else it is ` ? ` .
1038
1091
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? ` .
1041
1094
1042
1095
** TODO: Should type arguments on an extractor create a type argument
1043
1096
constraint?**
1044
1097
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
+
1045
1105
* **Literal matcher** or **constant matcher**: The context type schema is the
1046
1106
static type of the pattern's constant value expression.
1047
1107
@@ -1218,6 +1278,22 @@ The static type of a pattern `p` being matched against a value of type `M` is:
1218
1278
The static type of `p` is `Object?`. *Wildcards accept all types. Casts and
1219
1279
extractors exist to check types at runtime, so statically accept all types.*
1220
1280
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
+
1221
1297
* **Literal matcher** or **constant matcher**: The static type of `p` is the
1222
1298
static type of the pattern's value expression.
1223
1299
@@ -1265,6 +1341,9 @@ patterns binds depend on what kind of pattern it is:
1265
1341
declaration or declaration matcher has a `final` modifier. The variable is
1266
1342
late if it is inside a pattern variable declaration marked `late`.
1267
1343
1344
+ * **Null-assert binder** or **null-check matcher**: Introduces all of the
1345
+ variables of its subpattern.
1346
+
1268
1347
* **Declaration matcher**: The `final` or `var` keyword establishes whether
1269
1348
the binders nested inside this create final or assignable variables and
1270
1349
then introduces those variables.
@@ -1550,6 +1629,14 @@ To match a pattern `p` against a value `v`:
1550
1629
2 . Otherwise, bind the variable's identifier to ` v ` . The match always
1551
1630
succeeds (if it didn't throw).
1552
1631
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
+
1553
1640
* ** Literal matcher** or ** constant matcher** : The pattern matches if ` o == v `
1554
1641
evaluates to ` true ` where ` o ` is the pattern's value.
1555
1642
@@ -1571,6 +1658,12 @@ To match a pattern `p` against a value `v`:
1571
1658
3 . Otherwise, match ` v ` against the subpatterns of ` p ` as if it were a
1572
1659
record pattern.
1573
1660
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
+
1574
1667
** TODO: Update to specify that the result of operations can be cached across
1575
1668
cases. See: https://github.com/dart-lang/language/issues/2107 **
1576
1669
@@ -1611,6 +1704,8 @@ main() {
1611
1704
1612
1705
- Allow extractor patterns to match enum values.
1613
1706
1707
+ - Add null-assert binder ` ! ` and null-check ` ? ` matcher patterns.
1708
+
1614
1709
### 1.1
1615
1710
1616
1711
- Copy editing and clean up.
0 commit comments