{"id":"dart-3-updates","name":"dart-3-updates","summary":"スイッチ文の作成、if-elseチェーンのリファクタリング、データクラスの作成、レコードとクラスの選択、値の解体、またはDart-3以前のコードを近代化する際に使用します。","body":"# Dart 3 Updates Skill\n\nApply Dart 3 language features — branches, patterns, pattern types, and records — correctly and idiomatically.\n\n## When to Use\n\nUse this skill when:\n\n* Writing or refactoring `switch` statements or `if-else` chains.\n* Creating new data-holding classes and deciding between sealed classes, records, or plain classes.\n* Destructuring values from maps, lists, records, or objects.\n* Modernizing pre-Dart-3 code to use patterns, exhaustiveness checks, or switch expressions.\n\n---\n\n## 1. Branches\n\n### if / if-case\n\n```dart\n// Standard if\nif (score >= 90) {\n  grade = 'A';\n} else if (score >= 80) {\n  grade = 'B';\n} else {\n  grade = 'C';\n}\n\n// if-case: match and destructure against a single pattern\nif (pair case [int x, int y]) {\n  print('$x, $y');\n}\n```\n\n- `if` conditions must evaluate to a `bool`.\n- In `if-case`, variables declared in the pattern are scoped to the matching branch.\n- If the pattern does not match, control flows to the `else` branch (if present).\n\n### switch statements\n\n```dart\nswitch (command) {\n  case 'quit':\n    quit();\n  case 'start' || 'begin': // logical-or pattern\n    startGame();\n  default:\n    print('Unknown command');\n}\n```\n\n- Each matched `case` body executes and jumps to the end — `break` is **not required**.\n- Non-empty cases can end with `continue`, `throw`, or `return`.\n- Use `default` or `_` to handle unmatched values.\n- Empty cases fall through; use `break` to prevent fallthrough in an empty case.\n- Use `continue` with a label for non-sequential fallthrough.\n- Use logical-or patterns (`case a || b`) to share a body between cases.\n\n### switch expressions\n\n```dart\nfinal color = switch (shape) {\n  Circle() => 'red',\n  Square() => 'blue',\n  _ => 'unknown',\n};\n```\n\n- Omit `case`; use `=>` for bodies; separate cases with commas.\n- Default must use `_` (not `default`).\n- Produces a value.\n\n### Exhaustiveness\n\n- Dart checks exhaustiveness in `switch` statements and expressions at compile time.\n- Use `default`/`_`, enums, or `sealed` types to satisfy exhaustiveness.\n\n```dart\nsealed class Shape {}\nclass Circle extends Shape {}\nclass Square extends Shape {}\n\n// Dart knows all subtypes — no default needed:\nString describe(Shape s) => switch (s) {\n  Circle() => 'circle',\n  Square() => 'square',\n};\n```\n\n### Guard clauses\n\n```dart\nswitch (point) {\n  case (int x, int y) when x == y:\n    print('Diagonal: $x');\n  case (int x, int y):\n    print('$x, $y');\n}\n```\n\n- Add `when condition` after a pattern to further constrain matching.\n- Usable in `if-case`, `switch` statements, and `switch` expressions.\n- If the guard is `false`, execution proceeds to the next case.\n\n---\n\n## 2. Patterns\n\nPatterns represent the **shape** of a value for matching and destructuring.\n\n### Uses\n\n```dart\n// Variable declaration\nvar (a, [b, c]) = ('str', [1, 2]);\n\n// Variable assignment (swap)\n(b, a) = (a, b);\n\n// for-in loop destructuring\nfor (final MapEntry(:key, :value) in map.entries) { ... }\n\n// switch / if-case (see Branches section)\n```\n\n- Wildcard `_` ignores parts of a matched value.\n- Rest elements (`...`) in list patterns ignore remaining elements.\n- Case patterns are **refutable**: if no match, execution continues to the next case.\n- Destructured values in a case become local variables scoped to that case body.\n\n### Object patterns\n\n```dart\nvar Foo(:one, :two) = myFoo;\n```\n\n### JSON / nested data validation\n\n```dart\nif (data case {'user': [String name, int age]}) {\n  print('$name, $age');\n}\n```\n\n---\n\n## 3. Pattern Types\n\n| Pattern | Syntax | Description |\n|---|---|---|\n| Logical-or | `p1 \\|\\| p2` | Matches if any branch matches. All branches must bind the same variables. |\n| Logical-and | `p1 && p2` | Matches if both match. Variable names must not overlap. |\n| Relational | `== c`, `< c`, `>= c` | Compares value to a constant. Combine with `&&` for ranges. |\n| Cast | `subpattern as Type` | Asserts type, then matches inner pattern. Throws if type mismatch. |\n| Null-check | `subpattern?` | Matches non-null; binds non-nullable type. |\n| Null-assert | `subpattern!` | Matches non-null or throws. Use in declarations to eliminate nulls. |\n| Constant | `42`, `'str'`, `const Foo()` | Matches if value equals the constant. |\n| Variable | `var name`, `final Type name` | Binds matched value to a new variable. Typed form only matches the declared type. |\n| Wildcard | `_`, `Type _` | Matches any value without binding. |\n| Parenthesized | `(subpattern)` | Controls precedence. |\n| List | `[p1, p2]` | Matches lists by position. Length must match unless a rest element is used. |\n| Rest element | `...`, `...rest` | Matches arbitrary-length tails or collects remaining elements. |\n| Map | `{'key': subpattern}` | Matches maps by key. Missing keys throw `StateError`. |\n| Record | `(p1, p2)`, `(x: p1, y: p2)` | Matches records by shape; field names can be omitted if inferred. |\n| Object | `ClassName(field: p)` | Matches by type and destructures via getters. Extra fields ignored. |\n\n- Use parentheses to group lower-precedence patterns.\n- All pattern types can be **nested and combined**.\n\n---\n\n## 4. Records\n\n```dart\n// Create\nvar record = ('first', a: 2, b: true, 'last');\n\n// Type annotation\n({int a, bool b}) namedRecord;\n\n// Access\nprint(record.$1);   // positional: 'first'\nprint(record.a);    // named: 2\n```\n\n- Records are **anonymous, immutable, fixed-size** aggregates.\n- Each field can have a different type (heterogeneous).\n- Fields are accessed via built-in getters (`$1`, `$2`, `.name`); no setters.\n- Two records are equal if they have the same shape and equal field values.\n- `hashCode` and `==` are automatically defined.\n\n### Multiple return values\n\n```dart\n(String name, int age) userInfo(Map<String, dynamic> json) {\n  return (json['name'] as String, json['age'] as int);\n}\n\nvar (name, age) = userInfo(json);\n// Named fields:\nfinal (:name, :age) = userInfo(json);\n```\n\n### Records vs. data classes\n\nUse a **record** when:\n- Returning multiple values from a single function (small, one-time use).\n- Grouping a few values locally with no reuse across the codebase.\n- You need structural equality with no additional behavior.\n\nUse a **class** when:\n- The type is reused across multiple files or features.\n- You need methods, encapsulation, inheritance, or `copyWith`.\n- The type is part of a public API or long-lived data model.\n- Changing the shape must be caught by the type system across the codebase.\n\n### Other best practices\n\n- Use `typedef` for record types to improve readability and maintainability.\n- Changing a record type alias does not guarantee type safety across the codebase — only classes provide full abstraction.\n\n---\n\n## 5. Migration Workflow\n\nWhen modernizing pre-Dart-3 code, follow these steps:\n\n### Step 1 — Replace if-else chains with switch expressions\n\n```dart\n// Before (pre-Dart 3)\nString label;\nif (status == Status.loading) {\n  label = 'Loading...';\n} else if (status == Status.success) {\n  label = 'Done';\n} else {\n  label = 'Error';\n}\n\n// After (Dart 3)\nfinal label = switch (status) {\n  Status.loading => 'Loading...',\n  Status.success => 'Done',\n  Status.error => 'Error',\n};\n```\n\n### Step 2 — Convert abstract class hierarchies to sealed classes\n\n```dart\n// Before\nabstract class Result {}\nclass Success extends Result { final String data; Success(this.data); }\nclass Failure extends Result { final String error; Failure(this.error); }\n\n// After — enables exhaustive switch\nsealed class Result {}\nfinal class Success extends Result { const Success(this.data); final String data; }\nfinal class Failure extends Result { const Failure(this.error); final String error; }\n```\n\n### Step 3 — Use destructuring for multiple return values\n\nReplace wrapper classes used solely for returning multiple values with records.\n\n### Step 4 — Validate\n\nRun `dart analyze` to confirm exhaustiveness and type safety after each change.\n\n---","author":"@evanca","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/evanca/flutter-ai-rules/tree/main/skills/dart-3-updates","license":"MIT","category":"writing","lang":"en","tokens":1995,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":[]}}