From 39555b0cf7701c6376c36f5edab0efd0aa3f0944 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 29 Jul 2022 16:49:33 +0200 Subject: [PATCH] support mysqli_fetch* type inference --- ...sqliFetchFnsDynamicReturnTypeExtension.php | 110 ++++++++++++++++++ .../MysqliResultReflection.php | 9 ++ tests/default/data/mysqli.php | 23 ++++ 3 files changed, 142 insertions(+) create mode 100644 src/Extensions/MysqliFetchFnsDynamicReturnTypeExtension.php create mode 100644 src/MysqliReflection/MysqliResultReflection.php diff --git a/src/Extensions/MysqliFetchFnsDynamicReturnTypeExtension.php b/src/Extensions/MysqliFetchFnsDynamicReturnTypeExtension.php new file mode 100644 index 000000000..a95d75f3e --- /dev/null +++ b/src/Extensions/MysqliFetchFnsDynamicReturnTypeExtension.php @@ -0,0 +1,110 @@ +phpVersion = $phpVersion; + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return in_array(strtolower($functionReflection->getName()), ['mysqli_fetch_assoc', 'mysqli_fetch_row', 'mysqli_fetch_object', 'mysqli_fetch_array'], true); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $args = $functionCall->getArgs(); + $defaultReturn = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + + if (QueryReflection::getRuntimeConfiguration()->throwsMysqliExceptions($this->phpVersion)) { + $defaultReturn = TypeCombinator::remove($defaultReturn, new ConstantBooleanType(false)); + } + + if (\count($args) < 1) { + return $defaultReturn; + } + + try { + $resultType = $this->inferResultType($args[1]->value, $scope); + if (null !== $resultType) { + return $resultType; + } + } catch (UnresolvableQueryException $e) { + // simulation not possible.. use default value + } + + return $defaultReturn; + } + + /** + * @throws UnresolvableQueryException + */ + private function inferResultType(Expr $mysqliExpr, Scope $scope): ?Type + { + $mysqliType = $scope->getType($mysqliExpr); + + if (!$mysqliType instanceof MysqliResultObjectType) { + return null; + } + + $queryReflection = new QueryReflection(); + $queryStrings = $queryReflection->resolveQueryStrings($mysqliExpr, $scope); + + $genericObjects = []; + foreach ($queryStrings as $queryString) { + $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_ASSOC); + + if (null === $resultType) { + return null; + } + + $genericObjects[] = new MysqliResultObjectType($resultType); + } + + if (0 === \count($genericObjects)) { + return null; + } + + $resultType = TypeCombinator::union(...$genericObjects); + + if (!QueryReflection::getRuntimeConfiguration()->throwsMysqliExceptions($this->phpVersion)) { + return TypeCombinator::union( + $resultType, + new ConstantBooleanType(false), + ); + } + + return $resultType; + } +} diff --git a/src/MysqliReflection/MysqliResultReflection.php b/src/MysqliReflection/MysqliResultReflection.php new file mode 100644 index 000000000..35f03e8d4 --- /dev/null +++ b/src/MysqliReflection/MysqliResultReflection.php @@ -0,0 +1,9 @@ +}>', $result); + + while ($row = mysqli_fetch_assoc($result)) { + assertType('array{email: string, adaid: int<-32768, 32767>}', $row); + } + + while ($row = mysqli_fetch_row($result)) { + assertType('array{string, int<-32768, 32767>}', $row); + } + + while ($row = mysqli_fetch_object($result)) { + assertType('int<-32768, 32767>', $row->adaid); + assertType('string', $row->email); + } + + while ($row = mysqli_fetch_array($result)) { + assertType('array{0: string, 1: int<-32768, 32767>, email: string, adaid: int<-32768, 32767>}', $row); + } + } }