Skip to content

Commit 375aef6

Browse files
vranaclaude
andcommitted
Add dynamic return type extension for pg_insert(), pg_update(), pg_delete() and pg_select()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2dade7d commit 375aef6

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\TrinaryLogic;
11+
use PHPStan\Type\BitwiseFlagHelper;
12+
use PHPStan\Type\BooleanType;
13+
use PHPStan\Type\Constant\ConstantBooleanType;
14+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
15+
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Type;
17+
use PHPStan\Type\TypeCombinator;
18+
use PHPStan\Type\UnionType;
19+
use function array_key_exists;
20+
21+
#[AutowiredService]
22+
final class PgDmlDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
23+
{
24+
25+
/** @var array<string, int> */
26+
private array $flagsArgPositions = [
27+
'pg_insert' => 3,
28+
'pg_update' => 4,
29+
'pg_delete' => 3,
30+
'pg_select' => 3,
31+
];
32+
33+
public function __construct(private BitwiseFlagHelper $bitwiseFlagAnalyser)
34+
{
35+
}
36+
37+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
38+
{
39+
return array_key_exists($functionReflection->getName(), $this->flagsArgPositions);
40+
}
41+
42+
public function getTypeFromFunctionCall(
43+
FunctionReflection $functionReflection,
44+
FuncCall $functionCall,
45+
Scope $scope,
46+
): ?Type
47+
{
48+
$functionName = $functionReflection->getName();
49+
$args = $functionCall->getArgs();
50+
$getDefaultReturnType = static fn (): Type => ParametersAcceptorSelector::selectFromArgs(
51+
$scope,
52+
$args,
53+
$functionReflection->getVariants(),
54+
)->getReturnType();
55+
56+
$flagsArgPosition = $this->flagsArgPositions[$functionName];
57+
if (!isset($args[$flagsArgPosition])) {
58+
// default flags are PGSQL_DML_EXEC
59+
return $this->getTypeFromFlags($functionName, TrinaryLogic::createNo(), TrinaryLogic::createYes(), $getDefaultReturnType);
60+
}
61+
62+
$flagsExpr = $args[$flagsArgPosition]->value;
63+
64+
return $this->getTypeFromFlags(
65+
$functionName,
66+
$this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsExpr, $scope, 'PGSQL_DML_STRING'),
67+
$this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsExpr, $scope, 'PGSQL_DML_EXEC'),
68+
$getDefaultReturnType,
69+
);
70+
}
71+
72+
/**
73+
* @param callable(): Type $getDefaultReturnType
74+
*/
75+
private function getTypeFromFlags(string $functionName, TrinaryLogic $containsString, TrinaryLogic $containsExec, callable $getDefaultReturnType): ?Type
76+
{
77+
if ($functionName === 'pg_insert') {
78+
// with PGSQL_DML_EXEC the result object is returned even when PGSQL_DML_STRING is also set
79+
if ($containsExec->yes()) {
80+
return TypeCombinator::remove(
81+
$getDefaultReturnType(),
82+
new UnionType([new StringType(), new ConstantBooleanType(true)]),
83+
);
84+
}
85+
if (!$containsExec->no()) {
86+
return null;
87+
}
88+
if ($containsString->yes()) {
89+
return new UnionType([new StringType(), new ConstantBooleanType(false)]);
90+
}
91+
if ($containsString->no()) {
92+
return new BooleanType();
93+
}
94+
95+
return null;
96+
}
97+
98+
if ($containsString->yes()) {
99+
return new UnionType([new StringType(), new ConstantBooleanType(false)]);
100+
}
101+
if (!$containsString->no()) {
102+
return null;
103+
}
104+
105+
return TypeCombinator::remove($getDefaultReturnType(), new StringType());
106+
}
107+
108+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace PgDml;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doInsert(\PgSql\Connection $connection, int $flags): void
11+
{
12+
assertType('PgSql\Result|false', pg_insert($connection, 'table', []));
13+
assertType('PgSql\Result|false', pg_insert($connection, 'table', [], PGSQL_DML_EXEC));
14+
assertType('PgSql\Result|false', pg_insert($connection, 'table', [], PGSQL_DML_EXEC | PGSQL_DML_STRING));
15+
assertType('string|false', pg_insert($connection, 'table', [], PGSQL_DML_STRING));
16+
assertType('string|false', pg_insert($connection, 'table', [], PGSQL_DML_NO_CONV | PGSQL_DML_STRING));
17+
assertType('bool', pg_insert($connection, 'table', [], PGSQL_DML_ASYNC));
18+
assertType('bool|PgSql\Result|string', pg_insert($connection, 'table', [], $flags));
19+
}
20+
21+
public function doUpdate(\PgSql\Connection $connection, int $flags): void
22+
{
23+
assertType('bool', pg_update($connection, 'table', [], []));
24+
assertType('bool', pg_update($connection, 'table', [], [], PGSQL_DML_EXEC));
25+
assertType('string|false', pg_update($connection, 'table', [], [], PGSQL_DML_STRING));
26+
assertType('string|false', pg_update($connection, 'table', [], [], PGSQL_DML_EXEC | PGSQL_DML_STRING));
27+
assertType('bool|string', pg_update($connection, 'table', [], [], $flags));
28+
}
29+
30+
public function doDelete(\PgSql\Connection $connection, int $flags): void
31+
{
32+
assertType('bool', pg_delete($connection, 'table', []));
33+
assertType('bool', pg_delete($connection, 'table', [], PGSQL_DML_EXEC));
34+
assertType('string|false', pg_delete($connection, 'table', [], PGSQL_DML_STRING));
35+
assertType('bool|string', pg_delete($connection, 'table', [], $flags));
36+
}
37+
38+
public function doSelect(\PgSql\Connection $connection, int $flags): void
39+
{
40+
assertType('array<int, array>|false', pg_select($connection, 'table', []));
41+
assertType('array<int, array>|false', pg_select($connection, 'table', [], PGSQL_DML_EXEC));
42+
assertType('string|false', pg_select($connection, 'table', [], PGSQL_DML_STRING));
43+
assertType('array<int, array>|string|false', pg_select($connection, 'table', [], $flags));
44+
}
45+
46+
}

0 commit comments

Comments
 (0)