Skip to content

Commit

Permalink
Fixes parse_url return type when $component is -1 returning more type…
Browse files Browse the repository at this point in the history
…s than were valid. Adds missing types when $component is not a constant value.
  • Loading branch information
ChrisBrenton committed Feb 15, 2024
1 parent 0b78c55 commit b03214e
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
29 changes: 18 additions & 11 deletions src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php
Expand Up @@ -86,7 +86,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
}

if ($componentType->getValue() === -1) {
return $this->createAllComponentsReturnType();
return TypeCombinator::union($this->createComponentsArray(), new ConstantBooleanType(false));
}

return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false);
Expand All @@ -97,24 +97,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 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 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
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Expand Up @@ -5458,7 +5458,7 @@ public function dataFunctions(): array
'$parseUrlConstantUrlWithoutComponent2',
],
[
'array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false',
'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',
'$parseUrlConstantUrlUnknownComponent',
],
[
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -1431,6 +1431,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-inheritance.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9123.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10037.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4754.php');
}

/**
Expand Down
42 changes: 42 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4754.php
@@ -0,0 +1,42 @@
<?php

namespace Bug4754;

use function PHPStan\Testing\assertType;

/**
* The standard PHP parse_url() function doesn't work with relative URI's only absolute URL's
* so this function works around that by adding a http://domain prefix
* if needed and passing through the results
*
* @param string $url
* @param int $component
*
* @return string|int|array|bool|null
*/
function parseUrl($url, $component = -1)
{
$parsedComponentNotSpecified = parse_url($url);
$parsedNotConstant = parse_url($url, $component);
$parsedAllConstant = parse_url($url, -1);
$parsedSchemeConstant = parse_url($url, PHP_URL_SCHEME);
$parsedHostConstant = parse_url($url, PHP_URL_HOST);
$parsedPortConstant = parse_url($url, PHP_URL_PORT);
$parsedUserConstant = parse_url($url, PHP_URL_USER);
$parsedPassConstant = parse_url($url, PHP_URL_PASS);
$parsedPathConstant = parse_url($url, PHP_URL_PATH);
$parsedQueryConstant = parse_url($url, PHP_URL_QUERY);
$parsedFragmentConstant = parse_url($url, PHP_URL_FRAGMENT);

assertType('array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedComponentNotSpecified);
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', $parsedNotConstant);
assertType('array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedAllConstant);
assertType('string|false|null', $parsedSchemeConstant);
assertType('string|false|null', $parsedHostConstant);
assertType('int<0, 65535>|false|null', $parsedPortConstant);
assertType('string|false|null', $parsedUserConstant);
assertType('string|false|null', $parsedPassConstant);
assertType('string|false|null', $parsedPathConstant);
assertType('string|false|null', $parsedQueryConstant);
assertType('string|false|null', $parsedFragmentConstant);
}

0 comments on commit b03214e

Please sign in to comment.