From 016c2676c8ccf4bafb0c057d46c483b15b5db251 Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Tue, 7 Jul 2026 13:10:37 +0900 Subject: [PATCH 1/4] test: pin expect()/expectErr() behavior and conditional return types expect(string $message) returns the success value or throws LogicException with the caller-supplied message; expectErr() is the mirror for the error side, matching Rust's Result::expect/expect_err. Currently RED: the methods do not exist yet. Claude-Session: https://claude.ai/code/session_017XTM7pxbWPVNLV639i5WgK --- tests/ErrTest.php | 16 ++++++++++++++++ tests/OkTest.php | 16 ++++++++++++++++ tests/Types/result.php | 11 +++++++++++ 3 files changed, 43 insertions(+) diff --git a/tests/ErrTest.php b/tests/ErrTest.php index 30b5aa9..62421f7 100644 --- a/tests/ErrTest.php +++ b/tests/ErrTest.php @@ -58,6 +58,22 @@ public function unwrap_throws_exception(): void $err->unwrap(); } + #[Test] + public function expect_throws_withGivenMessage(): void + { + $err = new Err('error'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('config file should be readable'); + $err->expect('config file should be readable'); + } + + #[Test] + public function expectErr_returns_error_value(): void + { + $err = new Err('error'); + $this->assertSame('error', $err->expectErr('should have an error')); + } + #[Test] public function unwrapErr_returns_error_value(): void { diff --git a/tests/OkTest.php b/tests/OkTest.php index 309282c..2645207 100644 --- a/tests/OkTest.php +++ b/tests/OkTest.php @@ -89,6 +89,22 @@ public function unwrapErr_throws_exception(): void $ok->unwrapErr(); } + #[Test] + public function expect_returns_value(): void + { + $ok = new Ok(42); + $this->assertSame(42, $ok->expect('should have a value')); + } + + #[Test] + public function expectErr_throws_withGivenMessage(): void + { + $ok = new Ok(42); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('should have an error'); + $ok->expectErr('should have an error'); + } + #[Test] public function unwrapOr_returns_value(): void { diff --git a/tests/Types/result.php b/tests/Types/result.php index 1ba6366..5b7ec14 100644 --- a/tests/Types/result.php +++ b/tests/Types/result.php @@ -254,6 +254,17 @@ function testUnwrapOnGenericReceiver(Result $result): void assertType('RuntimeException', $result->unwrapErr()); } +/** + * expect / expectErr も unwrap / unwrapErr と同じ条件付き戻り値型が解決される. + * + * @param Result $result + */ +function testExpectOnGenericReceiver(Result $result): void +{ + assertType('int', $result->expect('should have a value')); + assertType('RuntimeException', $result->expectErr('should have an error')); +} + /** * 具象レシーバでの unwrapOr / unwrapOrElse: 実行時に起こり得ない側の型を混ぜない. * From ed90782dd5b2c62c09811cf9af18732eb07a35dc Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Tue, 7 Jul 2026 13:11:50 +0900 Subject: [PATCH 2/4] feat: add expect() and expectErr() Rust's Result::expect/expect_err equivalents: return the value or throw LogicException with a caller-supplied message that explains why the value was expected to be present. Conditional return types match unwrap()/unwrapErr() (never on the impossible side). Claude-Session: https://claude.ai/code/session_017XTM7pxbWPVNLV639i5WgK --- README.md | 2 ++ src/Err.php | 15 +++++++++++++++ src/Ok.php | 15 +++++++++++++++ src/Result.php | 18 ++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/README.md b/README.md index f28649f..7cc92bd 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,8 @@ All Result types (both Ok and Err) implement these methods: #### Value Extraction - `unwrap(): mixed` - Returns the success value or throws LogicException - `unwrapErr(): mixed` - Returns the error value or throws LogicException +- `expect(string $message): mixed` - Returns the success value or throws LogicException with the given message +- `expectErr(string $message): mixed` - Returns the error value or throws LogicException with the given message - `unwrapOr(mixed $default): mixed` - Returns the success value or a default - `unwrapOrElse(callable $fn): mixed` - Returns the success value or computes it from the error diff --git a/src/Err.php b/src/Err.php index 968d589..d433e9a 100644 --- a/src/Err.php +++ b/src/Err.php @@ -62,6 +62,21 @@ public function unwrapErr(): mixed return $this->value; } + #[Override] + public function expect(string $message): never + { + throw new \LogicException($message); + } + + /** + * @return E + */ + #[Override] + public function expectErr(string $message): mixed + { + return $this->value; + } + /** * @template U * @param U $default diff --git a/src/Ok.php b/src/Ok.php index fea4b7b..93a4895 100644 --- a/src/Ok.php +++ b/src/Ok.php @@ -62,6 +62,21 @@ public function unwrapErr(): never throw new \LogicException('called Result::unwrapErr() on an Ok value'); } + /** + * @return T + */ + #[Override] + public function expect(string $message): mixed + { + return $this->value; + } + + #[Override] + public function expectErr(string $message): never + { + throw new \LogicException($message); + } + /** * @template U * @param U $default diff --git a/src/Result.php b/src/Result.php index 67f2fd4..5f9fdcf 100644 --- a/src/Result.php +++ b/src/Result.php @@ -70,6 +70,24 @@ public function unwrap(): mixed; */ public function unwrapErr(): mixed; + /** + * 成功値を返します。失敗の場合は指定したメッセージで例外を投げます. + * + * @param string $message 失敗時の例外メッセージ + * + * @return ($this is Ok ? T : never) + */ + public function expect(string $message): mixed; + + /** + * エラー値を返します。成功の場合は指定したメッセージで例外を投げます. + * + * @param string $message 成功時の例外メッセージ + * + * @return ($this is Err ? E : never) + */ + public function expectErr(string $message): mixed; + /** * 成功値またはデフォルト値を返します. * From 2afeadaec35f5bdcf28dc919bdc28671d299fbef Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Tue, 7 Jul 2026 13:43:03 +0900 Subject: [PATCH 3/4] test: pin UnwrapException with value context for expect()/expectErr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback: expect()/expectErr() throwing bare LogicException while unwrap()/unwrapErr() throw UnwrapException leaves an inconsistent contract — catch (UnwrapException) would miss expect() failures — and drops the value context Rust's expect includes. Currently RED. Claude-Session: https://claude.ai/code/session_017XTM7pxbWPVNLV639i5WgK --- tests/ErrTest.php | 9 +++++++++ tests/OkTest.php | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/tests/ErrTest.php b/tests/ErrTest.php index 64285a7..97b1d31 100644 --- a/tests/ErrTest.php +++ b/tests/ErrTest.php @@ -162,6 +162,15 @@ public function unwrap_withMultilineStringError_keepsMessageSingleLine(): void } } + #[Test] + public function expect_throwsUnwrapException_withErrorValueInMessage(): void + { + $err = new Err(new \RuntimeException('boom')); + $this->expectException(UnwrapException::class); + $this->expectExceptionMessage('config file should be readable: RuntimeException: boom'); + $err->expect('config file should be readable'); + } + #[Test] public function unwrapErr_returns_error_value(): void { diff --git a/tests/OkTest.php b/tests/OkTest.php index 8e245b9..f8bf470 100644 --- a/tests/OkTest.php +++ b/tests/OkTest.php @@ -130,6 +130,15 @@ public function __toString(): string $ok->unwrapErr(); } + #[Test] + public function expectErr_throwsUnwrapException_withValueInMessage(): void + { + $ok = new Ok(42); + $this->expectException(UnwrapException::class); + $this->expectExceptionMessage('should have an error: 42'); + $ok->expectErr('should have an error'); + } + #[Test] public function unwrapOr_returns_value(): void { From a5ece5c03028c128774c261ad2c0db74090e749b Mon Sep 17 00:00:00 2001 From: Takuma Kajikawa Date: Tue, 7 Jul 2026 13:44:46 +0900 Subject: [PATCH 4/4] feat: expect()/expectErr() throw UnwrapException with value context --- src/Err.php | 2 +- src/Ok.php | 2 +- src/Result.php | 8 ++++++-- src/UnwrapException.php | 8 ++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Err.php b/src/Err.php index edde0c4..476c171 100644 --- a/src/Err.php +++ b/src/Err.php @@ -65,7 +65,7 @@ public function unwrapErr(): mixed #[Override] public function expect(string $message): never { - throw new \LogicException($message); + throw UnwrapException::withMessage($message, $this->value); } /** diff --git a/src/Ok.php b/src/Ok.php index 70113f6..865ee7c 100644 --- a/src/Ok.php +++ b/src/Ok.php @@ -74,7 +74,7 @@ public function expect(string $message): mixed #[Override] public function expectErr(string $message): never { - throw new \LogicException($message); + throw UnwrapException::withMessage($message, $this->value); } /** diff --git a/src/Result.php b/src/Result.php index d392825..c10998f 100644 --- a/src/Result.php +++ b/src/Result.php @@ -77,18 +77,22 @@ public function unwrapErr(): mixed; /** * 成功値を返します。失敗の場合は指定したメッセージで例外を投げます. * - * @param string $message 失敗時の例外メッセージ + * @param string $message 失敗時の例外メッセージ(エラー値の要約が付加されます) * * @return ($this is Ok ? T : never) + * + * @throws UnwrapException $this が Err の場合 */ public function expect(string $message): mixed; /** * エラー値を返します。成功の場合は指定したメッセージで例外を投げます. * - * @param string $message 成功時の例外メッセージ + * @param string $message 成功時の例外メッセージ(成功値の要約が付加されます) * * @return ($this is Err ? E : never) + * + * @throws UnwrapException $this が Ok の場合 */ public function expectErr(string $message): mixed; diff --git a/src/UnwrapException.php b/src/UnwrapException.php index 6fb9eaf..ed8dca5 100644 --- a/src/UnwrapException.php +++ b/src/UnwrapException.php @@ -35,6 +35,14 @@ public static function unwrapErrOnOk(mixed $value): self return new self(\sprintf('called Result::unwrapErr() on an Ok value: %s', self::describe($value))); } + /** + * expect() / expectErr() 用に、呼び出し側のメッセージと値の要約から例外を生成します. + */ + public static function withMessage(string $message, mixed $value): self + { + return new self(\sprintf('%s: %s', $message, self::describe($value))); + } + /** * 例外メッセージ用に値の要約を生成します. *