diff --git a/src/WpParseUrlFunctionDynamicReturnTypeExtension.php b/src/WpParseUrlFunctionDynamicReturnTypeExtension.php index 05214b1..b998b9a 100644 --- a/src/WpParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/WpParseUrlFunctionDynamicReturnTypeExtension.php @@ -19,7 +19,7 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\IntegerType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -67,31 +67,41 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $this->cacheReturnTypes(); $componentType = new ConstantIntegerType(-1); + if (count($functionCall->getArgs()) > 1) { $componentType = $scope->getType($functionCall->getArgs()[1]->value); + if (! $componentType->isConstantValue()->yes()) { + return $this->createAllComponentsReturnType(); + } + + $componentType = $componentType->toInteger(); + if (! $componentType instanceof ConstantIntegerType) { return $this->createAllComponentsReturnType(); } } $urlType = $scope->getType($functionCall->getArgs()[0]->value); - if (count($urlType->getConstantStrings()) !== 0) { - $returnTypes = []; + if (count($urlType->getConstantStrings()) > 0) { + $types = []; foreach ($urlType->getConstantStrings() as $constantString) { try { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged $result = @parse_url($constantString->getValue(), $componentType->getValue()); - $returnTypes[] = $scope->getTypeFromValue($result); } catch (\ValueError $e) { - $returnTypes[] = new ConstantBooleanType(false); + $types[] = new ConstantBooleanType(false); + continue; } + + $types[] = $scope->getTypeFromValue($result); } - return TypeCombinator::union(...$returnTypes); + + return TypeCombinator::union(...$types); } if ($componentType->getValue() === -1) { - return $this->createAllComponentsReturnType(); + return TypeCombinator::union($this->createComponentsArray(), new ConstantBooleanType(false)); } return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false); @@ -102,24 +112,31 @@ private function createAllComponentsReturnType(): Type if ($this->allComponentsTogetherType === null) { $returnTypes = [ new ConstantBooleanType(false), + new NullType(), + IntegerRangeType::fromInterval(0, 65535), + new StringType(), + $this->createComponentsArray(), ]; - $builder = ConstantArrayTypeBuilder::createEmpty(); + $this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes); + } - if ($this->componentTypesPairedStrings === null) { - throw new \PHPStan\ShouldNotHappenException(); - } + return $this->allComponentsTogetherType; + } - foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { - $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); - } + private function createComponentsArray(): Type + { + $builder = ConstantArrayTypeBuilder::createEmpty(); - $returnTypes[] = $builder->getArray(); + if ($this->componentTypesPairedStrings === null) { + throw new \PHPStan\ShouldNotHappenException(); + } - $this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes); + foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { + $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); } - return $this->allComponentsTogetherType; + return $builder->getArray(); } private function cacheReturnTypes(): void @@ -129,17 +146,17 @@ private function cacheReturnTypes(): void } $stringType = new StringType(); - $integerType = new IntegerType(); + $port = IntegerRangeType::fromInterval(0, 65535); $falseType = new ConstantBooleanType(false); $nullType = new NullType(); $stringOrFalseOrNull = TypeCombinator::union($stringType, $falseType, $nullType); - $integerOrFalseOrNull = TypeCombinator::union($integerType, $falseType, $nullType); + $portOrFalseOrNull = TypeCombinator::union($port, $falseType, $nullType); $this->componentTypesPairedConstants = [ PHP_URL_SCHEME => $stringOrFalseOrNull, PHP_URL_HOST => $stringOrFalseOrNull, - PHP_URL_PORT => $integerOrFalseOrNull, + PHP_URL_PORT => $portOrFalseOrNull, PHP_URL_USER => $stringOrFalseOrNull, PHP_URL_PASS => $stringOrFalseOrNull, PHP_URL_PATH => $stringOrFalseOrNull, @@ -150,7 +167,7 @@ private function cacheReturnTypes(): void $this->componentTypesPairedStrings = [ 'scheme' => $stringType, 'host' => $stringType, - 'port' => $integerType, + 'port' => $port, 'user' => $stringType, 'pass' => $stringType, 'path' => $stringType, diff --git a/tests/data/wp_parse_url.php b/tests/data/wp_parse_url.php index 4ad1490..c05df9e 100644 --- a/tests/data/wp_parse_url.php +++ b/tests/data/wp_parse_url.php @@ -30,7 +30,7 @@ assertType("array{scheme: 'http', host: 'def.abc'}", $value); $value = wp_parse_url('http://def.abc', $integer); -assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $value); +assertType('array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|int<0, 65535>|string|false|null', $value); $value = wp_parse_url('http://def.abc', PHP_URL_FRAGMENT); assertType('null', $value); @@ -45,10 +45,10 @@ assertType('false', $value); $value = wp_parse_url($string, PHP_URL_PORT); -assertType('int|false|null', $value); +assertType('int<0, 65535>|false|null', $value); $value = wp_parse_url($string); -assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $value); +assertType('array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $value); /** @var 'http://def.abc'|'https://example.com' $union */ $union = $union;