66use PHPStan \Analyser \Scope ;
77use PHPStan \Reflection \FunctionReflection ;
88use PHPStan \ShouldNotHappenException ;
9+ use PHPStan \Type \Accessory \AccessoryLowercaseStringType ;
910use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
1011use PHPStan \Type \Constant \ConstantBooleanType ;
1112use PHPStan \Type \Constant \ConstantIntegerType ;
1213use PHPStan \Type \Constant \ConstantStringType ;
1314use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
1415use PHPStan \Type \IntegerRangeType ;
16+ use PHPStan \Type \IntersectionType ;
1517use PHPStan \Type \NullType ;
1618use PHPStan \Type \StringType ;
1719use 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}
0 commit comments