Skip to content

Commit 00809e5

Browse files
authored
feat: introduce assert_return() (#48)
1 parent 00aa917 commit 00809e5

10 files changed

+139
-0
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@
1212
Function that should have never been called.
1313
Useful for `default` case in exhaustive matching.
1414

15+
### assert_return()
16+
17+
Asserts the value via the expression and returns it.
18+
19+
It's useful when you want to assert inline so for example you can keep the arrow function in place.
20+
21+
It uses native `assert()` internally.
22+
23+
```php
24+
use function Cdn77\Functions\assert_return;
25+
26+
array_map(
27+
fn (mixed $value) => new RequiresInt(assert_return($value, is_int(...))),
28+
[1, 2, 3]
29+
);
30+
```
31+
1532
### noop()
1633

1734
Does nothing. Useful e.g. for `match` expression that currently supports single-line expressions in blocks.

composer-dependency-analyser.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,23 @@
33
declare(strict_types=1);
44

55
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
6+
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
67

78
$config = new Configuration();
89

910
return $config
11+
->ignoreErrorsOnPath(
12+
'tests/AssertTest.php',
13+
[ErrorType::UNKNOWN_FUNCTION],
14+
)
15+
->ignoreErrorsOnPackageAndPaths(
16+
'phpstan/phpstan',
17+
['phpstan'],
18+
[ErrorType::DEV_DEPENDENCY_IN_PROD],
19+
)
20+
->ignoreErrorsOnPackageAndPaths(
21+
'nikic/php-parser',
22+
['phpstan'],
23+
[ErrorType::SHADOW_DEPENDENCY],
24+
)
1025
->addPathToScan(__DIR__ . '/src', isDev: false);

composer.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"shipmonk/composer-dependency-analyser": "^1.5"
3636
},
3737
"autoload": {
38+
"psr-4": {
39+
"Cdn77\\Functions\\PHPStan\\": "phpstan"
40+
},
3841
"files": [
3942
"src/functions_include.php"
4043
]
@@ -53,5 +56,12 @@
5356
"phpstan/extension-installer": true
5457
},
5558
"sort-packages": true
59+
},
60+
"extra": {
61+
"phpstan": {
62+
"includes": [
63+
"phpstan-extension.neon"
64+
]
65+
}
5666
}
5767
}

phpcs.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
<rule ref="Cdn77" />
99

10+
<file>composer-dependency-analyser.php</file>
1011
<file>src/</file>
1112
<file>tests/</file>
1213
</ruleset>

phpstan-extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
-
3+
class: Cdn77\Functions\PHPStan\AssertReturnFunctionTypeSpecifyingExtension
4+
tags:
5+
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

phpstan.neon.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
includes:
2+
- phpstan-extension.neon
3+
14
parameters:
25
level: max
36
paths:
47
- %currentWorkingDirectory%/src
8+
- %currentWorkingDirectory%/phpstan
59
- %currentWorkingDirectory%/tests
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Cdn77\Functions\PHPStan;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Analyser\SpecifiedTypes;
8+
use PHPStan\Analyser\TypeSpecifier;
9+
use PHPStan\Analyser\TypeSpecifierAwareExtension;
10+
use PHPStan\Analyser\TypeSpecifierContext;
11+
use PHPStan\Reflection\FunctionReflection;
12+
use PHPStan\Type\FunctionTypeSpecifyingExtension;
13+
14+
class AssertReturnFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
15+
{
16+
private TypeSpecifier $typeSpecifier;
17+
18+
public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
19+
{
20+
return $functionReflection->getName() === 'Cdn77\Functions\assert_return';
21+
// && isset($node->getArgs()[0]);
22+
}
23+
24+
public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
25+
{
26+
$arg1 = $node->getArgs()[1]->value;
27+
assert($arg1 instanceof FuncCall, 'Second argument of assert_return must be a function call');
28+
$new = new \PhpParser\Node\Expr\FuncCall($arg1->name, [$node->getArgs()[0]], $arg1->getAttributes());
29+
return $this->typeSpecifier->specifyTypesInCondition($scope, $new, TypeSpecifierContext::createTruthy());
30+
}
31+
32+
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
33+
{
34+
$this->typeSpecifier = $typeSpecifier;
35+
}
36+
37+
}

src/assert.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Functions;
6+
7+
use Throwable;
8+
9+
use function assert;
10+
11+
/** @phpstan-pure */
12+
function assert_return(mixed $value, callable $assertionFn, Throwable|string|null $description = null): mixed
13+
{
14+
assert($assertionFn($value), $description);
15+
16+
return $value;
17+
}

src/functions_include.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
declare(strict_types=1);
44

55
require_once __DIR__ . '/absurd.php';
6+
require_once __DIR__ . '/assert.php';
67
require_once __DIR__ . '/ds.php';
78
require_once __DIR__ . '/generator.php';
89
require_once __DIR__ . '/iterable.php';

tests/AssertTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Functions\Tests;
6+
7+
use PHPUnit\Framework\Attributes\CoversFunction;
8+
use PHPUnit\Framework\TestCase;
9+
10+
use function Cdn77\Functions\assert_return;
11+
use function is_int;
12+
use function PHPStan\Testing\assertType;
13+
14+
#[CoversFunction('Cdn77\Functions\assert_return')]
15+
final class AssertTest extends TestCase
16+
{
17+
public function testAssertReturn(): void
18+
{
19+
$value = 1;
20+
self::assertSame(
21+
$value,
22+
assert_return($value, is_int(...)),
23+
);
24+
}
25+
26+
public function phpstanType(mixed $value): void
27+
{
28+
$_ = assert_return($value, is_int(...));
29+
30+
assertType('int', $value);
31+
}
32+
}

0 commit comments

Comments
 (0)