diff --git a/src/Type/Php/PgDmlDynamicReturnTypeExtension.php b/src/Type/Php/PgDmlDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..769cd0ad39 --- /dev/null +++ b/src/Type/Php/PgDmlDynamicReturnTypeExtension.php @@ -0,0 +1,104 @@ + */ + private array $flagsArgPositions = [ + 'pg_insert' => 3, + 'pg_update' => 4, + 'pg_delete' => 3, + 'pg_select' => 3, + ]; + + public function __construct(private BitwiseFlagHelper $bitwiseFlagAnalyser) + { + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return array_key_exists($functionReflection->getName(), $this->flagsArgPositions); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $functionName = $functionReflection->getName(); + $args = $functionCall->getArgs(); + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $args, + $functionReflection->getVariants(), + )->getReturnType(); + + $flagsArgPosition = $this->flagsArgPositions[$functionName]; + if (!isset($args[$flagsArgPosition])) { + // default flags are PGSQL_DML_EXEC + return $this->getTypeFromFlags($functionName, TrinaryLogic::createNo(), TrinaryLogic::createYes(), $defaultReturnType); + } + + $flagsExpr = $args[$flagsArgPosition]->value; + + return $this->getTypeFromFlags( + $functionName, + $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsExpr, $scope, 'PGSQL_DML_STRING'), + $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsExpr, $scope, 'PGSQL_DML_EXEC'), + $defaultReturnType, + ); + } + + private function getTypeFromFlags(string $functionName, TrinaryLogic $containsString, TrinaryLogic $containsExec, Type $defaultReturnType): ?Type + { + if ($functionName === 'pg_insert') { + // with PGSQL_DML_EXEC the result object is returned even when PGSQL_DML_STRING is also set + if ($containsExec->yes()) { + return TypeCombinator::remove( + TypeCombinator::remove($defaultReturnType, new StringType()), + new ConstantBooleanType(true), + ); + } + if (!$containsExec->no()) { + return null; + } + if ($containsString->yes()) { + return TypeCombinator::union(new StringType(), new ConstantBooleanType(false)); + } + if ($containsString->no()) { + return new BooleanType(); + } + + return null; + } + + if ($containsString->yes()) { + return TypeCombinator::union(new StringType(), new ConstantBooleanType(false)); + } + if (!$containsString->no()) { + return null; + } + + return TypeCombinator::remove($defaultReturnType, new StringType()); + } + +} diff --git a/src/Type/Php/PgLastNoticeDynamicReturnTypeExtension.php b/src/Type/Php/PgLastNoticeDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..1daab3787c --- /dev/null +++ b/src/Type/Php/PgLastNoticeDynamicReturnTypeExtension.php @@ -0,0 +1,79 @@ +getName() === 'pg_last_notice'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): Type + { + $args = $functionCall->getArgs(); + if (!isset($args[1])) { + return new StringType(); + } + + $types = []; + foreach ($scope->getType($args[1]->value)->getConstantScalarValues() as $value) { + if ($value === self::NOTICE_LAST) { + $types[] = new StringType(); + } elseif ($value === self::NOTICE_ALL) { + $types[] = $this->createAllNoticesType(); + } elseif ($value === self::NOTICE_CLEAR) { + $types[] = new ConstantBooleanType(true); + } else { + return $this->createUnknownModeType(); + } + } + + if ($types === []) { + return $this->createUnknownModeType(); + } + + return TypeCombinator::union(...$types); + } + + private function createAllNoticesType(): Type + { + return TypeCombinator::intersect( + new ArrayType(new IntegerType(), new StringType()), + new AccessoryArrayListType(), + ); + } + + private function createUnknownModeType(): Type + { + return TypeCombinator::union( + new StringType(), + $this->createAllNoticesType(), + new ConstantBooleanType(true), + ); + } + +} diff --git a/src/Type/Php/PgResultStatusDynamicReturnTypeExtension.php b/src/Type/Php/PgResultStatusDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..8dc1326a9f --- /dev/null +++ b/src/Type/Php/PgResultStatusDynamicReturnTypeExtension.php @@ -0,0 +1,56 @@ +getName() === 'pg_result_status'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + if (!isset($args[1])) { + return new IntegerType(); + } + + $types = []; + foreach ($scope->getType($args[1]->value)->getConstantScalarValues() as $value) { + if ($value === self::STATUS_LONG) { + $types[] = new IntegerType(); + } elseif ($value === self::STATUS_STRING) { + $types[] = new StringType(); + } else { + return null; + } + } + + if ($types === []) { + return null; + } + + return TypeCombinator::union(...$types); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/pg-dml.php b/tests/PHPStan/Analyser/nsrt/pg-dml.php new file mode 100644 index 0000000000..8fb25d2468 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pg-dml.php @@ -0,0 +1,46 @@ +|false', pg_select($connection, 'table', [])); + assertType('array|false', pg_select($connection, 'table', [], PGSQL_DML_EXEC)); + assertType('string|false', pg_select($connection, 'table', [], PGSQL_DML_STRING)); + assertType('array|string|false', pg_select($connection, 'table', [], $flags)); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/pg-last-notice.php b/tests/PHPStan/Analyser/nsrt/pg-last-notice.php new file mode 100644 index 0000000000..6d618f28d8 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pg-last-notice.php @@ -0,0 +1,27 @@ +', pg_last_notice($connection, PGSQL_NOTICE_ALL)); + assertType('true', pg_last_notice($connection, PGSQL_NOTICE_CLEAR)); + assertType('list|string|true', pg_last_notice($connection, $mode)); + assertType('list|string|true', pg_last_notice($connection, 42)); + + if ($mode === PGSQL_NOTICE_LAST || $mode === PGSQL_NOTICE_ALL) { + assertType('list|string', pg_last_notice($connection, $mode)); + } + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/pg-result-status.php b/tests/PHPStan/Analyser/nsrt/pg-result-status.php new file mode 100644 index 0000000000..cd4750974d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pg-result-status.php @@ -0,0 +1,22 @@ +