mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-07-04 13:43:38 +02:00
147 lines
6.0 KiB
Markdown
147 lines
6.0 KiB
Markdown
|
|
---
|
||
|
|
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
|
||
|
|
---
|
||
|
|
# Implementing Dart Patterns
|
||
|
|
|
||
|
|
## Contents
|
||
|
|
- [Pattern Selection Strategy](#pattern-selection-strategy)
|
||
|
|
- [Switch Statements vs. Expressions](#switch-statements-vs-expressions)
|
||
|
|
- [Core Pattern Implementations](#core-pattern-implementations)
|
||
|
|
- [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 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 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.
|
||
|
|
|
||
|
|
## 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).
|
||
|
|
- [ ] 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`).
|
||
|
|
- [ ] 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.
|
||
|
|
|
||
|
|
### Feedback Loop: Exhaustiveness Checking
|
||
|
|
When switching over `sealed` classes or enums, you must ensure all subtypes are handled.
|
||
|
|
|
||
|
|
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.
|
||
|
|
|
||
|
|
## Examples
|
||
|
|
|
||
|
|
### JSON Validation and Destructuring
|
||
|
|
Use Map and List patterns to validate structure and extract data in a single step.
|
||
|
|
|
||
|
|
**Input:**
|
||
|
|
```dart
|
||
|
|
var data = {
|
||
|
|
'user': ['Lily', 13],
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
**Implementation:**
|
||
|
|
```dart
|
||
|
|
if (data case {'user': [String name, int age]}) {
|
||
|
|
print('User $name is $age years old.');
|
||
|
|
} else {
|
||
|
|
print('Invalid JSON structure.');
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 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() || Circle():
|
||
|
|
print('Invalid or empty shape');
|
||
|
|
default:
|
||
|
|
print('Unknown shape');
|
||
|
|
}
|
||
|
|
```
|