6
6
use PHPStan \Analyser \Scope ;
7
7
use PHPStan \Reflection \FunctionReflection ;
8
8
use PHPStan \ShouldNotHappenException ;
9
+ use PHPStan \Type \Accessory \AccessoryLowercaseStringType ;
9
10
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
10
11
use PHPStan \Type \Constant \ConstantBooleanType ;
11
12
use PHPStan \Type \Constant \ConstantIntegerType ;
12
13
use PHPStan \Type \Constant \ConstantStringType ;
13
14
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
14
15
use PHPStan \Type \IntegerRangeType ;
16
+ use PHPStan \Type \IntersectionType ;
15
17
use PHPStan \Type \NullType ;
16
18
use PHPStan \Type \StringType ;
17
19
use PHPStan \Type \Type ;
@@ -37,8 +39,16 @@ final class ParseUrlFunctionDynamicReturnTypeExtension implements DynamicFunctio
37
39
/** @var array<string,Type>|null */
38
40
private ?array $ componentTypesPairedStrings = null ;
39
41
42
+ /** @var array<int,Type>|null */
43
+ private ?array $ componentTypesPairedConstantsForLowercaseString = null ;
44
+
45
+ /** @var array<string,Type>|null */
46
+ private ?array $ componentTypesPairedStringsForLowercaseString = null ;
47
+
40
48
private ?Type $ allComponentsTogetherType = null ;
41
49
50
+ private ?Type $ allComponentsTogetherTypeForLowercaseString = null ;
51
+
42
52
public function isFunctionSupported (FunctionReflection $ functionReflection ): bool
43
53
{
44
54
return $ functionReflection ->getName () === 'parse_url ' ;
@@ -52,23 +62,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
52
62
53
63
$ this ->cacheReturnTypes ();
54
64
65
+ $ urlType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
55
66
if (count ($ functionCall ->getArgs ()) > 1 ) {
56
67
$ componentType = $ scope ->getType ($ functionCall ->getArgs ()[1 ]->value );
57
68
58
69
if (!$ componentType ->isConstantValue ()->yes ()) {
59
- return $ this ->createAllComponentsReturnType ();
70
+ return $ this ->createAllComponentsReturnType ($ urlType -> isLowercaseString ()-> yes () );
60
71
}
61
72
62
73
$ componentType = $ componentType ->toInteger ();
63
-
64
74
if (!$ componentType instanceof ConstantIntegerType) {
65
- return $ this ->createAllComponentsReturnType ();
75
+ return $ this ->createAllComponentsReturnType ($ urlType -> isLowercaseString ()-> yes () );
66
76
}
67
77
} else {
68
78
$ componentType = new ConstantIntegerType (-1 );
69
79
}
70
80
71
- $ urlType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
72
81
if (count ($ urlType ->getConstantStrings ()) > 0 ) {
73
82
$ types = [];
74
83
foreach ($ urlType ->getConstantStrings () as $ constantString ) {
@@ -86,21 +95,44 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
86
95
}
87
96
88
97
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 );
90
106
}
91
107
92
108
return $ this ->componentTypesPairedConstants [$ componentType ->getValue ()] ?? new ConstantBooleanType (false );
93
109
}
94
110
95
- private function createAllComponentsReturnType (): Type
111
+ private function createAllComponentsReturnType (bool $ urlIsLowercase ): Type
96
112
{
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
+
97
129
if ($ this ->allComponentsTogetherType === null ) {
98
130
$ returnTypes = [
99
131
new ConstantBooleanType (false ),
100
132
new NullType (),
101
133
IntegerRangeType::fromInterval (0 , 65535 ),
102
134
new StringType (),
103
- $ this ->createComponentsArray (),
135
+ $ this ->createComponentsArray (false ),
104
136
];
105
137
106
138
$ this ->allComponentsTogetherType = TypeCombinator::union (...$ returnTypes );
@@ -109,19 +141,29 @@ private function createAllComponentsReturnType(): Type
109
141
return $ this ->allComponentsTogetherType ;
110
142
}
111
143
112
- private function createComponentsArray (): Type
144
+ private function createComponentsArray (bool $ urlIsLowercase ): Type
113
145
{
114
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
146
+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
115
147
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
+ }
119
160
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
+ }
122
164
}
123
165
124
- return $ builder ->getArray ();
166
+ return $ builder ->getArray ();
125
167
}
126
168
127
169
private function cacheReturnTypes (): void
@@ -131,11 +173,13 @@ private function cacheReturnTypes(): void
131
173
}
132
174
133
175
$ string = new StringType ();
176
+ $ lowercaseString = new IntersectionType ([new StringType (), new AccessoryLowercaseStringType ()]);
134
177
$ port = IntegerRangeType::fromInterval (0 , 65535 );
135
178
$ false = new ConstantBooleanType (false );
136
179
$ null = new NullType ();
137
180
138
181
$ stringOrFalseOrNull = TypeCombinator::union ($ string , $ false , $ null );
182
+ $ lowercaseStringOrFalseOrNull = TypeCombinator::union ($ lowercaseString , $ false , $ null );
139
183
$ portOrFalseOrNull = TypeCombinator::union ($ port , $ false , $ null );
140
184
141
185
$ this ->componentTypesPairedConstants = [
@@ -148,6 +192,16 @@ private function cacheReturnTypes(): void
148
192
PHP_URL_QUERY => $ stringOrFalseOrNull ,
149
193
PHP_URL_FRAGMENT => $ stringOrFalseOrNull ,
150
194
];
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
+ ];
151
205
152
206
$ this ->componentTypesPairedStrings = [
153
207
'scheme ' => $ string ,
@@ -159,6 +213,16 @@ private function cacheReturnTypes(): void
159
213
'query ' => $ string ,
160
214
'fragment ' => $ string ,
161
215
];
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
+ ];
162
226
}
163
227
164
228
}
0 commit comments