From cf2262fbaff3c87cedac997d7f9dc8f482c4da27 Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Thu, 18 Jun 2026 22:11:30 +0900 Subject: [PATCH 1/2] docs: recommend match() method for exhaustiveness with generics preserved --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index baf398b..a1c0aa2 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,12 @@ and leans on several of its generics features: `instanceof` checks is recognized as exhaustive, and the `else` branch of an `instanceof Ok` check narrows to `Err`. Note that `instanceof` narrowing loses the type arguments (a known PHPStan limitation: `Result` narrows to - plain `Ok`, so `unwrap()` becomes `mixed`) — use `instanceof` for exhaustiveness - checks only, and narrow with `isOk()`/`isErr()` when you need the values. + plain `Ok`, so `unwrap()` becomes `mixed`), so use `instanceof` in a + `match (true)` purely for exhaustiveness. When you also need the values, prefer + the `match()` method — it is exhaustive by construction (both arms are required) + and keeps `T`/`E` — or narrow with `isOk()`/`isErr()`. (`isOk()`/`isErr()` arms + inside a `match (true)` keep the type arguments but are *not* recognized as + exhaustive, so they require a `default` arm.) - **Covariant type parameters** — `T` and `E` are declared `@template-covariant`, so `Ok` (which is `Result`) and `Err` (which is `Result`) are assignable to any `Result`. A function declared to return From 3a7ba383a2a1f3654521ac6316d1651b9a4095ef Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Thu, 18 Jun 2026 22:40:31 +0900 Subject: [PATCH 2/2] docs: correct match() exhaustiveness claim, document the trade-off --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a1c0aa2..f7b5a2f 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,17 @@ and leans on several of its generics features: `instanceof Ok` check narrows to `Err`. Note that `instanceof` narrowing loses the type arguments (a known PHPStan limitation: `Result` narrows to plain `Ok`, so `unwrap()` becomes `mixed`), so use `instanceof` in a - `match (true)` purely for exhaustiveness. When you also need the values, prefer - the `match()` method — it is exhaustive by construction (both arms are required) - and keeps `T`/`E` — or narrow with `isOk()`/`isErr()`. (`isOk()`/`isErr()` arms - inside a `match (true)` keep the type arguments but are *not* recognized as - exhaustive, so they require a `default` arm.) + `match (true)` purely for exhaustiveness. When you also need the values, the + `match()` method handles both cases and keeps `T`/`E` (it requires both an `ok` + and an `err` arm), or narrow with `isOk()`/`isErr()`. These two goals are a + trade-off in PHPStan 2.2.2: *enforced* exhaustiveness — where adding a new + `Result` variant would turn every unhandled site into an analysis error — comes + only from `instanceof` in a `match (true)`, which is exactly the form that drops + the type arguments. The `match()` method and `isOk()`/`isErr()` keep the generics + but are not checked against variant additions (`isOk()`/`isErr()` arms in a + `match (true)` also need a `default`). So per call site you currently pick one: + enforced exhaustiveness *or* preserved generics. (In practice `Result` is fixed + at `Ok|Err`, so the `match()` method covering both is total for all real cases.) - **Covariant type parameters** — `T` and `E` are declared `@template-covariant`, so `Ok` (which is `Result`) and `Err` (which is `Result`) are assignable to any `Result`. A function declared to return