Skip to content

Commit ea9e320

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents 8c6124e + 7080f40 commit ea9e320

File tree

3 files changed

+106
-16
lines changed

3 files changed

+106
-16
lines changed

src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php

+79-15
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\ShouldNotHappenException;
9+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
910
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
1112
use PHPStan\Type\Constant\ConstantIntegerType;
1213
use PHPStan\Type\Constant\ConstantStringType;
1314
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1415
use PHPStan\Type\IntegerRangeType;
16+
use PHPStan\Type\IntersectionType;
1517
use PHPStan\Type\NullType;
1618
use PHPStan\Type\StringType;
1719
use PHPStan\Type\Type;
@@ -37,8 +39,16 @@ final class ParseUrlFunctionDynamicReturnTypeExtension implements DynamicFunctio
3739
/** @var array<string,Type>|null */
3840
private ?array $componentTypesPairedStrings = null;
3941

42+
/** @var array<int,Type>|null */
43+
private ?array $componentTypesPairedConstantsForLowercaseString = null;
44+
45+
/** @var array<string,Type>|null */
46+
private ?array $componentTypesPairedStringsForLowercaseString = null;
47+
4048
private ?Type $allComponentsTogetherType = null;
4149

50+
private ?Type $allComponentsTogetherTypeForLowercaseString = null;
51+
4252
public function isFunctionSupported(FunctionReflection $functionReflection): bool
4353
{
4454
return $functionReflection->getName() === 'parse_url';
@@ -52,23 +62,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5262

5363
$this->cacheReturnTypes();
5464

65+
$urlType = $scope->getType($functionCall->getArgs()[0]->value);
5566
if (count($functionCall->getArgs()) > 1) {
5667
$componentType = $scope->getType($functionCall->getArgs()[1]->value);
5768

5869
if (!$componentType->isConstantValue()->yes()) {
59-
return $this->createAllComponentsReturnType();
70+
return $this->createAllComponentsReturnType($urlType->isLowercaseString()->yes());
6071
}
6172

6273
$componentType = $componentType->toInteger();
63-
6474
if (!$componentType instanceof ConstantIntegerType) {
65-
return $this->createAllComponentsReturnType();
75+
return $this->createAllComponentsReturnType($urlType->isLowercaseString()->yes());
6676
}
6777
} else {
6878
$componentType = new ConstantIntegerType(-1);
6979
}
7080

71-
$urlType = $scope->getType($functionCall->getArgs()[0]->value);
7281
if (count($urlType->getConstantStrings()) > 0) {
7382
$types = [];
7483
foreach ($urlType->getConstantStrings() as $constantString) {
@@ -86,21 +95,44 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
8695
}
8796

8897
if ($componentType->getValue() === -1) {
89-
return TypeCombinator::union($this->createComponentsArray(), new ConstantBooleanType(false));
98+
return TypeCombinator::union(
99+
$this->createComponentsArray($urlType->isLowercaseString()->yes()),
100+
new ConstantBooleanType(false)
101+
);
102+
}
103+
104+
if ($urlType->isLowercaseString()->yes()) {
105+
return $this->componentTypesPairedConstantsForLowercaseString[$componentType->getValue()] ?? new ConstantBooleanType(false);
90106
}
91107

92108
return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false);
93109
}
94110

95-
private function createAllComponentsReturnType(): Type
111+
private function createAllComponentsReturnType(bool $urlIsLowercase): Type
96112
{
113+
if ($urlIsLowercase) {
114+
if ($this->allComponentsTogetherTypeForLowercaseString === null) {
115+
$returnTypes = [
116+
new ConstantBooleanType(false),
117+
new NullType(),
118+
IntegerRangeType::fromInterval(0, 65535),
119+
new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]),
120+
$this->createComponentsArray(true),
121+
];
122+
123+
$this->allComponentsTogetherTypeForLowercaseString = TypeCombinator::union(...$returnTypes);
124+
}
125+
126+
return $this->allComponentsTogetherTypeForLowercaseString;
127+
}
128+
97129
if ($this->allComponentsTogetherType === null) {
98130
$returnTypes = [
99131
new ConstantBooleanType(false),
100132
new NullType(),
101133
IntegerRangeType::fromInterval(0, 65535),
102134
new StringType(),
103-
$this->createComponentsArray(),
135+
$this->createComponentsArray(false),
104136
];
105137

106138
$this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes);
@@ -109,19 +141,29 @@ private function createAllComponentsReturnType(): Type
109141
return $this->allComponentsTogetherType;
110142
}
111143

112-
private function createComponentsArray(): Type
144+
private function createComponentsArray(bool $urlIsLowercase): Type
113145
{
114-
$builder = ConstantArrayTypeBuilder::createEmpty();
146+
$builder = ConstantArrayTypeBuilder::createEmpty();
115147

116-
if ($this->componentTypesPairedStrings === null) {
117-
throw new ShouldNotHappenException();
118-
}
148+
if ($urlIsLowercase) {
149+
if ($this->componentTypesPairedStringsForLowercaseString === null) {
150+
throw new ShouldNotHappenException();
151+
}
152+
153+
foreach ($this->componentTypesPairedStringsForLowercaseString as $componentName => $componentValueType) {
154+
$builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true);
155+
}
156+
} else {
157+
if ($this->componentTypesPairedStrings === null) {
158+
throw new ShouldNotHappenException();
159+
}
119160

120-
foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) {
121-
$builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true);
161+
foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) {
162+
$builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true);
163+
}
122164
}
123165

124-
return $builder->getArray();
166+
return $builder->getArray();
125167
}
126168

127169
private function cacheReturnTypes(): void
@@ -131,11 +173,13 @@ private function cacheReturnTypes(): void
131173
}
132174

133175
$string = new StringType();
176+
$lowercaseString = new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]);
134177
$port = IntegerRangeType::fromInterval(0, 65535);
135178
$false = new ConstantBooleanType(false);
136179
$null = new NullType();
137180

138181
$stringOrFalseOrNull = TypeCombinator::union($string, $false, $null);
182+
$lowercaseStringOrFalseOrNull = TypeCombinator::union($lowercaseString, $false, $null);
139183
$portOrFalseOrNull = TypeCombinator::union($port, $false, $null);
140184

141185
$this->componentTypesPairedConstants = [
@@ -148,6 +192,16 @@ private function cacheReturnTypes(): void
148192
PHP_URL_QUERY => $stringOrFalseOrNull,
149193
PHP_URL_FRAGMENT => $stringOrFalseOrNull,
150194
];
195+
$this->componentTypesPairedConstantsForLowercaseString = [
196+
PHP_URL_SCHEME => $lowercaseStringOrFalseOrNull,
197+
PHP_URL_HOST => $lowercaseStringOrFalseOrNull,
198+
PHP_URL_PORT => $portOrFalseOrNull,
199+
PHP_URL_USER => $lowercaseStringOrFalseOrNull,
200+
PHP_URL_PASS => $lowercaseStringOrFalseOrNull,
201+
PHP_URL_PATH => $lowercaseStringOrFalseOrNull,
202+
PHP_URL_QUERY => $lowercaseStringOrFalseOrNull,
203+
PHP_URL_FRAGMENT => $lowercaseStringOrFalseOrNull,
204+
];
151205

152206
$this->componentTypesPairedStrings = [
153207
'scheme' => $string,
@@ -159,6 +213,16 @@ private function cacheReturnTypes(): void
159213
'query' => $string,
160214
'fragment' => $string,
161215
];
216+
$this->componentTypesPairedStringsForLowercaseString = [
217+
'scheme' => $lowercaseString,
218+
'host' => $lowercaseString,
219+
'port' => $port,
220+
'user' => $lowercaseString,
221+
'pass' => $lowercaseString,
222+
'path' => $lowercaseString,
223+
'query' => $lowercaseString,
224+
'fragment' => $lowercaseString,
225+
];
162226
}
163227

164228
}

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5492,7 +5492,7 @@ public function dataFunctions(): array
54925492
'$parseUrlConstantUrlWithoutComponent2',
54935493
],
54945494
[
5495-
'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',
5495+
'array{scheme?: lowercase-string, host?: lowercase-string, port?: int<0, 65535>, user?: lowercase-string, pass?: lowercase-string, path?: lowercase-string, query?: lowercase-string, fragment?: lowercase-string}|int<0, 65535>|lowercase-string|false|null',
54965496
'$parseUrlConstantUrlUnknownComponent',
54975497
],
54985498
[
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace LowercaseStringParseUrl;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param lowercase-string $lowercase
12+
*/
13+
public function doParseUrl(string $lowercase): void
14+
{
15+
assertType('array{scheme?: lowercase-string, host?: lowercase-string, port?: int<0, 65535>, user?: lowercase-string, pass?: lowercase-string, path?: lowercase-string, query?: lowercase-string, fragment?: lowercase-string}|false', parse_url($lowercase));
16+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_SCHEME));
17+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_HOST));
18+
assertType('int<0, 65535>|false|null', parse_url($lowercase, PHP_URL_PORT));
19+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_USER));
20+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_PASS));
21+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_PATH));
22+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_QUERY));
23+
assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_FRAGMENT));
24+
}
25+
26+
}

0 commit comments

Comments
 (0)