diff --git a/docs/component/type.md b/docs/component/type.md index 7656de12..6c8c8d71 100644 --- a/docs/component/type.md +++ b/docs/component/type.md @@ -40,7 +40,7 @@ - [non_empty_dict](./../../src/Psl/Type/non_empty_dict.php#L18) - [non_empty_string](./../../src/Psl/Type/non_empty_string.php#L14) - [non_empty_vec](./../../src/Psl/Type/non_empty_vec.php#L16) -- [nonnull](./../../src/Psl/Type/nonnull.php#L16) +- [nonnull](./../../src/Psl/Type/nonnull.php#L18) - [null](./../../src/Psl/Type/null.php#L14) - [nullable](./../../src/Psl/Type/nullable.php#L16) - [num](./../../src/Psl/Type/num.php#L14) diff --git a/src/Psl/Type/Internal/NonNullType.php b/src/Psl/Type/Internal/NonNullType.php index fc428930..f8138c13 100644 --- a/src/Psl/Type/Internal/NonNullType.php +++ b/src/Psl/Type/Internal/NonNullType.php @@ -18,9 +18,15 @@ final readonly class NonNullType extends Type\Type { /** - * @psalm-assert-if-true mixed $value + * @template T of mixed + * + * @param T $value + * + * @psalm-assert-if-true T $value * * @ara-assert-if-true nonnull $value + * + * @return (T is null ? false : true) */ public function matches(mixed $value): bool { @@ -28,9 +34,13 @@ public function matches(mixed $value): bool } /** + * @template T of mixed + * + * @param T $value + * * @ara-return nonnull * - * @return mixed + * @return (T is null ? never : T) */ public function coerce(mixed $value): mixed { @@ -42,13 +52,17 @@ public function coerce(mixed $value): mixed } /** + * @template T + * + * @param T $value + * * @ara-assert nonnull $value * - * @psalm-assert mixed $value + * @psalm-assert T $value * * @ara-return nonnull * - * @return mixed + * @return (T is null ? never : T) */ public function assert(mixed $value): mixed { diff --git a/src/Psl/Type/nonnull.php b/src/Psl/Type/nonnull.php index 822310f2..73f21159 100644 --- a/src/Psl/Type/nonnull.php +++ b/src/Psl/Type/nonnull.php @@ -4,6 +4,8 @@ namespace Psl\Type; +use Psl\Type\Internal\NonNullType; + /** * @psalm-pure * @@ -11,7 +13,7 @@ * * @ara-return TypeInterface * - * @return TypeInterface + * @return NonNullType */ function nonnull(): TypeInterface { diff --git a/tests/static-analysis/Type/nonnull.php b/tests/static-analysis/Type/nonnull.php new file mode 100644 index 00000000..37463fb6 --- /dev/null +++ b/tests/static-analysis/Type/nonnull.php @@ -0,0 +1,67 @@ +assert($state); +} + +/** + * @throws Type\Exception\AssertException + * + * Asserting still leeds to mixed (including null) return type. + * This won't be solvable until psalm supports a TNotNull type. + */ +function returns_non_null_assertion_asserted(?string $state): ?string +{ + Type\nonnull()->assert($state); + + return $state; +} + +/** + * @throws Type\Exception\CoercionException + */ +function returns_non_null_coercion(?string $state): string +{ + return Type\nonnull()->coerce($state); +} + +/** + * @return true + */ +function returns_truthy_match(string $state): bool +{ + return Type\nonnull()->matches($state); +} + +/** + * @return false + */ +function returns_falsy_match(null $state = null): bool +{ + return Type\nonnull()->matches($state); +} + +/** + * @throws Type\Exception\CoercionException + * + * Wrapping it in container types still leeds to mixed (including null) return type. + * This won't be solvable until psalm supports a TNotNull type. + * + * @return array<'mightBeNull', mixed> + */ +function returns_mixed_in_shape(mixed $data): array +{ + return Type\shape([ + 'mightBeNull' => Type\nonnull(), + ])->coerce($data); +}