git:20260909.998370c to git:20261001.6c38d68

136 added, 35 removed. Audit A to A.

---
name: dart-use-pattern-matching
- description: Use switch expressions and pattern matching where appropriate
- metadata:
- model: models/gemini-3.1-pro-preview
- last_modified: Fri, 24 Apr 2026 15:08:55 GMT
+ description: 在合适处使用 switch 表达式与模式匹配。
+ en_description: Use switch expressions and pattern matching where appropriate
+ zh_displayName: Dart 模式匹配
+ category: 移动端开发
+ en_category: Mobile Dev
+ metadata:
---
# Implementing Dart Patterns
## Contents
- [Pattern Selection Strategy](#pattern-selection-strategy)
- [Switch Statements vs. Expressions](#switch-statements-vs-expressions)
- [Core Pattern Implementations](#core-pattern-implementations)
+ - [Pragmatic Balance & Anti-Patterns](#pragmatic-balance--anti-patterns)
- [Workflows](#workflows)
- [Examples](#examples)
## Pattern Selection Strategy
Apply specific pattern types based on the data structure and desired outcome. Follow these conditional guidelines:
- * **If validating and extracting from deserialized data (e.g., JSON):** Use Map and List patterns to simultaneously check structure and destructure key-value pairs.
+ * **If validating and extracting from deserialized data (e.g., JSON):** Use Map, List, and Object patterns to validate schema structure and destructure properties in a single step.
+ * **If inspecting URL/path segments (`uri.pathSegments`, `p.split(path)`) or `String.split()` tokens:** Use List patterns with rest elements (`['api', 'comments', ...]`, `['assets', ...final rest]`, `[..., final parent, _]`) instead of manual `.length` checks, `.first`, `.skip(1)`, or `length - N` index arithmetic.
+ * **If handling polymorphic payloads or responses:** Use `switch` expressions over map discriminant keys to deserialize into `sealed` class hierarchies.
* **If handling multiple return values:** Use Record patterns to destructure fields directly into local variables.
* **If executing type-specific behavior (Algebraic Data Types):** Use Object patterns combined with `sealed` classes to ensure exhaustiveness.
- * **If matching numeric ranges or conditions:** Use Relational (`>=`, `<=`) and Logical-and (`&&`) patterns.
+ * **If matching numeric ranges or conditions:** Use Relational (`>=`, `<=`) and Logical-and (`&&`) patterns within switch arms.
* **If multiple cases share logic:** Use Logical-or (`||`) patterns to share a single case body or guard clause.
* **If ignoring specific values:** Use the Wildcard pattern (`_`) or a non-matching Rest element (`...`) in collections.
## Switch Statements vs. Expressions
Select the appropriate switch construct based on the execution context:
* **If producing a value:** Use a **switch expression**.
* Syntax: `switch (value) { pattern => expression, }`
* Rule: Each case must be a single expression. No implicit fallthrough. Must be exhaustive.
* **If executing statements or side effects:** Use a **switch statement**.
* Syntax: `switch (value) { case pattern: statements; }`
* Rule: Empty cases fall through to the next case. Non-empty cases implicitly break (no `break` keyword required).
## Core Pattern Implementations
Implement patterns using the following syntax and rules:
* **Logical-or (`||`):** `pattern1 || pattern2`. Both branches must define the exact same set of variables.
* **Logical-and (`&&`):** `pattern1 && pattern2`. Branches must *not* define overlapping variables.
* **Relational:** `==`, `!=`, `<`, `>`, `<=`, `>=` followed by a constant expression.
* **Cast (`as`):** `pattern as Type`. Throws if the value does not match the type. Use to forcibly assert types during destructuring.
* **Null-check (`?`):** `pattern?`. Fails the match if the value is null. Binds the variable to the non-nullable base type.
* **Null-assert (`!`):** `pattern!`. Throws if the value is null.
* **Variable:** `var name` or `Type name`. Binds the matched value to a new local variable.
* **Wildcard (`_`):** Matches any value and discards it.
* **List:** `[pattern1, pattern2]`. Matches lists of exact length unless a Rest element (`...` or `...var rest`) is used.
* **Map:** `{"key": pattern}`. Matches maps containing the specified keys. Ignores unmatched keys.
* **Record:** `(pattern1, named: pattern2)`. Matches records of the exact shape. Use `:var name` to infer the getter name.
* **Object:** `ClassName(field: pattern)`. Matches instances of `ClassName`. Use `:var field` to infer the getter name.
+ ## Pragmatic Balance & Anti-Patterns
+
+ Pattern matching and switch expressions should simplify code, not add syntactic overhead. Observe the following boundaries:
+
+ ### 1. Prefer `is` Type Promotion over `if-case` for Single Promotable Variables
+ When checking or promoting a single variable, use standard `is` checks instead of `if-case` patterns that introduce shadow aliases.
+
+ * **Prefer:**
+ ```dart
+ // ✅ Promotes `key` directly in-place without extra variables
+ for (final MapEntry(:key, :value) in map.entries) {
+ if (key is String && value != null) {
+ process(key, value);
+ }
+ }
+ ```
+ * **Avoid:**
+ ```dart
+ // ❌ Anti-pattern: Introduces unnecessary alias variable `k`
+ for (final MapEntry(:key, :value) in map.entries) {
+ if (key case final String k when value != null) {
+ process(k, value);
+ }
+ }
+ ```
+
+ ### 2. Consolidate Nullable Types in Switch Arms
+ When mapping or returning values where both `null` and a type `T` are valid and handled identically, match the nullable type `T?` directly rather than creating redundant `null` arms.
+
+ * **Prefer:**
+ ```dart
+ // ✅ Clean nullable pattern match
+ switch (value) {
+ final String? s => s,
+ _ => throw FormatException('Invalid value: $value'),
+ }
+ ```
+ * **Avoid:**
+ ```dart
+ // ❌ Redundant separate null arm
+ switch (value) {
+ final String s => s,
+ null => null,
+ _ => throw FormatException('Invalid value: $value'),
+ }
+ ```
+
+ ### 3. Preserve Fast-Fail Validation (Do Not Silently Drop Data)
+ Do not use `if-case` in loops or deserialization to filter elements if malformed data should trigger an error or diagnostic warning.
+
+ * **Prefer:**
+ ```dart
+ // ✅ Fast-fail with explicit diagnostic error
+ for (final raw in rawTasks) {
+ if (raw is! Map<String, dynamic>) {
+ throw FormatException('Expected Map item, got ${raw.runtimeType}: $raw');
+ }
+ _applyTask(raw);
+ }
+ ```
+ * **Avoid:**
+ ```dart
+ // ❌ Silently ignores malformed items
+ for (final raw in rawTasks) {
+ if (raw case final Map<String, dynamic> taskMap) {
+ _applyTask(taskMap);
+ }
+ }
+ ```
+
+ ### 4. Avoid Single-Case or Boolean Switches
+ * Use `if (x is T)` instead of a `switch` statement with only 1 case and `default: break;`.
+ * Use standard conditional ternary operators (`condition ? a : b`) instead of `switch (condition) { true => a, false => b }`.
+
+ ### 5. Avoid Gratuitous Object Destructuring
+ Use standard property access (`user.name`) rather than object pattern destructuring (`final User(:name) = user;`) when reading a single property on a known non-null instance.
+
+ ### 6. Avoid `if-case` for Standalone Scalar Comparisons
+ Use standard boolean operators (`if (code >= 200 && code < 300)`) instead of `if (code case >= 200 && < 300)` for standalone conditions. Reserve relational patterns for multi-arm `switch` tables.
+
## Workflows
### Task Progress: Implementing Pattern Matching
Copy this checklist to track progress when implementing complex pattern matching logic:
- - [ ] Identify the data structure being evaluated (JSON, Record, Class, Enum).
+ - [ ] Identify the data structure being evaluated (JSON, List/Segments, Record, Class, Enum).
- [ ] Select the appropriate switch construct (Expression for values, Statement for side-effects).
- [ ] Define the required patterns (Object, Map, List, Record).
- - [ ] Extract required data using Variable patterns (`var x`, `:var y`).
+ - [ ] Extract required data using Variable patterns (`var x`, `:var y`, `...final rest`).
- [ ] Apply Guard clauses (`when condition`) for logic that cannot be expressed via patterns.
- [ ] Handle unmatched cases using a Wildcard (`_`) or `default` clause (if not using a sealed class).
- - [ ] Run exhaustiveness validator.
+ - [ ] Run static analyzer for exhaustiveness and dead code (`dart analyze`).
+ - [ ] Verify runtime Map/JSON pattern behavior on omitted keys (`containsKey` semantics) vs explicit `null` values.
- ### Feedback Loop: Exhaustiveness Checking
- When switching over `sealed` classes or enums, you must ensure all subtypes are handled.
+ ### Feedback Loop 1: Exhaustiveness Checking (Static Verification)
+ When switching over `sealed` classes or enums, ensure all subtypes are handled at compile time:
- 1. **Run validator:** Execute `dart analyze`.
- 2. **Review errors:** Look for "The type 'X' is not exhaustively matched by the switch cases" errors.
- 3. **Fix:** Add the missing Object patterns for the unhandled subtypes, or add a Wildcard (`_`) case if a default fallback is acceptable.
+ 1. **Run analyzer:** Execute `dart analyze`.
+ 2. **Review errors:** Look for "The type 'X' is not exhaustively matched by the switch cases" or unreachable pattern arm warnings.
+ 3. **Fix:** Add the missing Object patterns for unhandled subtypes, or add an explicit wildcard (`_`) arm if a default fallback or error is acceptable.
+ ### Feedback Loop 2: Runtime Map & JSON Pattern Verification
+ Because `dart analyze` cannot statically verify dynamic `Map<String, dynamic>` keys, validate runtime pattern semantics explicitly:
+
+ 1. **Omitted Keys vs. Explicit `null`**: A map pattern `{'key': String? val}` checks `map.containsKey('key')`. If `'key'` is omitted from the JSON payload, the pattern fails to match at runtime even though `String?` is nullable. Extract optional keys from the validated map directly (`map['key'] as String?`).
+ 2. **Fast-Fail Fallback**: Ensure unmatched or malformed map structures hit an explicit `_ => throw FormatException(...)` arm rather than silently failing an `if-case` check.
+
## Examples
- ### JSON Validation and Destructuring
- Use Map and List patterns to validate structure and extract data in a single step.
+ ### Polymorphic JSON Deserialization (Discriminated Unions)
+ Use Map patterns with switch expressions to validate tagged JSON payloads and
+ construct `sealed` class hierarchies. See
+ [examples/json_patterns.dart](examples/json_patterns.dart) for an executable
+ implementation demonstrating tagged `ApiResponse` parsing into `SuccessResponse`
+ and `ErrorResponse`.
- **Input:**
- ```dart
- var data = {
- 'user': ['Lily', 13],
- };
- ```
+ ### Nested JSON Validation and Optional Fields
+ Use nested Map and List patterns to validate required schema structure and
+ extract collections in a single step. See
+ [examples/json_patterns.dart](examples/json_patterns.dart) for an executable
+ implementation of `processUserPayload`.
- **Implementation:**
+ Map patterns check for key existence (`containsKey`). If an optional JSON key
+ might be omitted entirely from the payload (rather than explicitly passed as
+ `'key': null`), destructure required keys via the pattern and extract optional
+ fields directly from the matched submap.
+
+ ### List and Path Segment Destructuring
+ Use `if-case` with list rest elements (`...final rest`) to validate prefixes and
+ extract remaining elements without a 2-arm `_ => null` switch or manual `.first`
+ and `.skip(1)` indexing.
+
```dart
- if (data case {'user': [String name, int age]}) {
- print('User $name is $age years old.');
- } else {
- print('Invalid JSON structure.');
+ String? resolveAllowedAssetSubpath(List<String> segments) {
+ if (segments case ['assets', ...final rest]
+ when rest.isNotEmpty && !rest.contains('..')) {
+ return rest.join('/');
+ }
+ return null;
}
```
### Algebraic Data Types (Sealed Classes)
Use Object patterns with switch expressions to handle family types exhaustively.
- **Implementation:**
```dart
sealed class Shape {}
class Square implements Shape {
final double length;
Square(this.length);
}
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
// Switch expression guarantees exhaustiveness due to `sealed` modifier.
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(:var radius) => math.pi * radius * radius,
};
```
### Variable Swapping and Destructuring
Use variable assignment patterns to swap values or extract record fields without temporary variables.
- **Implementation:**
```dart
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap values
// Destructuring a function return
var (name, age) = getUserInfo();
```
### Guard Clauses and Logical-or
Use `when` to evaluate arbitrary conditions after a pattern matches.
- **Implementation:**
```dart
switch (shape) {
- case Square(size: var s) || Circle(size: var s) when s > 0:
- print('Valid symmetric shape with size $s');
+ case Square(length: var s) || Circle(radius: var s) when s > 0:
+ print('Valid positive shape with dimension $s');
case Square() || Circle():
- print('Invalid or empty shape');
- default:
- print('Unknown shape');
+ print('Zero or negative dimension shape');
}
```