From 3f383cf94e38cd2a5cff8c295bf4dcdbefdf1f64 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 1 Sep 2022 12:02:59 +0200 Subject: [PATCH] Use TypeUtils::getOldConstantArrays in array_key_first and array_key_last extensions --- src/Type/Constant/ConstantArrayType.php | 30 +++++++++++++++++-- ...rrayKeyFirstDynamicReturnTypeExtension.php | 7 ++--- ...ArrayKeyLastDynamicReturnTypeExtension.php | 7 ++--- .../Analyser/LegacyNodeScopeResolverTest.php | 24 +++++++++++++++ .../PHPStan/Analyser/data/php73_functions.php | 8 ++++- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 66d0921854..a7c4d14510 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -212,6 +212,32 @@ public function getKeyTypes(): array return $this->keyTypes; } + public function getFirstKeyType(): Type + { + $keyTypes = []; + foreach ($this->keyTypes as $i => $keyType) { + $keyTypes[] = $keyType; + if (!$this->isOptionalKey($i)) { + break; + } + } + + return TypeCombinator::union(...$keyTypes); + } + + public function getLastKeyType(): Type + { + $keyTypes = []; + for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { + $keyTypes[] = $this->keyTypes[$i]; + if (!$this->isOptionalKey($i)) { + break; + } + } + + return TypeCombinator::union(...$keyTypes); + } + /** * @return array */ @@ -223,8 +249,8 @@ public function getValueTypes(): array public function getFirstValueType(): Type { $valueTypes = []; - for ($i = 0, $keyTypesCount = count($this->keyTypes); $i < $keyTypesCount; $i++) { - $valueTypes[] = $this->valueTypes[$i]; + foreach ($this->valueTypes as $i => $valueType) { + $valueTypes[] = $valueType; if (!$this->isOptionalKey($i)) { break; } diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index 1fa52f314f..e073476a66 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -33,17 +33,16 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $constantArrays = TypeUtils::getConstantArrays($argType); + $constantArrays = TypeUtils::getOldConstantArrays($argType); if (count($constantArrays) > 0) { $keyTypes = []; foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { + if ($constantArray->isEmpty()) { $keyTypes[] = new NullType(); continue; } - $keyTypes[] = $arrayKeyTypes[0]; + $keyTypes[] = $constantArray->getFirstKeyType(); } return TypeCombinator::union(...$keyTypes); diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index 29f913b62d..149b4c8cc3 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -33,17 +33,16 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $constantArrays = TypeUtils::getConstantArrays($argType); + $constantArrays = TypeUtils::getOldConstantArrays($argType); if (count($constantArrays) > 0) { $keyTypes = []; foreach ($constantArrays as $constantArray) { - $arrayKeyTypes = $constantArray->getKeyTypes(); - if (count($arrayKeyTypes) === 0) { + if ($constantArray->isEmpty()) { $keyTypes[] = new NullType(); continue; } - $keyTypes[] = $arrayKeyTypes[count($arrayKeyTypes) - 1]; + $keyTypes[] = $constantArray->getLastKeyType(); } return TypeCombinator::union(...$keyTypes); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 24e53bd691..30713b59fb 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8772,6 +8772,30 @@ public function dataPhp73Functions(): array '2|3', 'array_key_last($anotherLiteralArray)', ], + [ + "'a'|'b'", + 'array_key_first($constantArrayOptionalKeys1)', + ], + [ + "'c'", + 'array_key_last($constantArrayOptionalKeys1)', + ], + [ + "'a'", + 'array_key_first($constantArrayOptionalKeys2)', + ], + [ + "'c'", + 'array_key_last($constantArrayOptionalKeys2)', + ], + [ + "'a'", + 'array_key_first($constantArrayOptionalKeys3)', + ], + [ + "'b'|'c'", + 'array_key_last($constantArrayOptionalKeys3)', + ], [ 'array{int, int}', '$hrtime1', diff --git a/tests/PHPStan/Analyser/data/php73_functions.php b/tests/PHPStan/Analyser/data/php73_functions.php index 670b013bb3..a2211f6f28 100644 --- a/tests/PHPStan/Analyser/data/php73_functions.php +++ b/tests/PHPStan/Analyser/data/php73_functions.php @@ -11,13 +11,19 @@ class Foo * @param array $mixedArray * @param array $nonEmptyArray * @param array $arrayWithStringKeys + * @param array{a?: 0, b: 1, c: 2} $constantArrayOptionalKeys1 + * @param array{a: 0, b?: 1, c: 2} $constantArrayOptionalKeys2 + * @param array{a: 0, b: 1, c?: 2} $constantArrayOptionalKeys3 */ public function doFoo( $mixed, int $integer, array $mixedArray, array $nonEmptyArray, - array $arrayWithStringKeys + array $arrayWithStringKeys, + array $constantArrayOptionalKeys1, + array $constantArrayOptionalKeys2, + array $constantArrayOptionalKeys3 ) { if (count($nonEmptyArray) === 0) {