58
58
use function array_reduce ;
59
59
use function array_shift ;
60
60
use function count ;
61
+ use function is_array ;
61
62
use function lcfirst ;
62
63
use function substr ;
63
64
@@ -104,7 +105,7 @@ public function isStaticMethodSupported(
104
105
}
105
106
106
107
$ resolver = $ resolvers [$ trimmedName ];
107
- $ resolverReflection = new ReflectionObject ($ resolver );
108
+ $ resolverReflection = new ReflectionObject (Closure:: fromCallable ( $ resolver) );
108
109
109
110
return count ($ node ->getArgs ()) >= count ($ resolverReflection ->getMethod ('__invoke ' )->getParameters ()) - 1 ;
110
111
}
@@ -156,50 +157,60 @@ static function (Type $type) {
156
157
);
157
158
}
158
159
159
- $ expression = self ::createExpression ($ scope , $ staticMethodReflection ->getName (), $ node ->getArgs ());
160
- if ($ expression === null ) {
160
+ [ $ expr , $ rootExpr ] = self ::createExpression ($ scope , $ staticMethodReflection ->getName (), $ node ->getArgs ());
161
+ if ($ expr === null ) {
161
162
return new SpecifiedTypes ([], []);
162
163
}
163
164
164
165
return $ this ->typeSpecifier ->specifyTypesInCondition (
165
166
$ scope ,
166
- $ expression ,
167
- TypeSpecifierContext::createTruthy ()
167
+ $ expr ,
168
+ TypeSpecifierContext::createTruthy (),
169
+ $ rootExpr
168
170
);
169
171
}
170
172
171
173
/**
172
174
* @param Arg[] $args
175
+ * @return array{?Expr, ?Expr}
173
176
*/
174
177
private static function createExpression (
175
178
Scope $ scope ,
176
179
string $ name ,
177
180
array $ args
178
- ): ? Expr
181
+ ): array
179
182
{
180
183
$ trimmedName = self ::trimName ($ name );
181
184
$ resolvers = self ::getExpressionResolvers ();
182
185
$ resolver = $ resolvers [$ trimmedName ];
183
- $ expression = $ resolver ($ scope , ...$ args );
184
- if ($ expression === null ) {
185
- return null ;
186
+
187
+ $ resolverResult = $ resolver ($ scope , ...$ args );
188
+ if (is_array ($ resolverResult )) {
189
+ [$ expr , $ rootExpr ] = $ resolverResult ;
190
+ } else {
191
+ $ expr = $ resolverResult ;
192
+ $ rootExpr = null ;
193
+ }
194
+
195
+ if ($ expr === null ) {
196
+ return [null , null ];
186
197
}
187
198
188
199
if (substr ($ name , 0 , 6 ) === 'nullOr ' ) {
189
- $ expression = new BooleanOr (
190
- $ expression ,
200
+ $ expr = new BooleanOr (
201
+ $ expr ,
191
202
new Identical (
192
203
$ args [0 ]->value ,
193
204
new ConstFetch (new Name ('null ' ))
194
205
)
195
206
);
196
207
}
197
208
198
- return $ expression ;
209
+ return [ $ expr , $ rootExpr ] ;
199
210
}
200
211
201
212
/**
202
- * @return Closure[]
213
+ * @return array<string, callable(Scope, Arg...): (Expr|array{?Expr, ?Expr}|null)>
203
214
*/
204
215
private static function getExpressionResolvers (): array
205
216
{
@@ -723,6 +734,38 @@ private static function getExpressionResolvers(): array
723
734
);
724
735
},
725
736
];
737
+
738
+ foreach (['contains ' , 'startsWith ' , 'endsWith ' ] as $ name ) {
739
+ self ::$ resolvers [$ name ] = static function (Scope $ scope , Arg $ value , Arg $ subString ): array {
740
+ if ($ scope ->getType ($ subString ->value )->isNonEmptyString ()->yes ()) {
741
+ return self ::createIsNonEmptyStringAndSomethingExprPair ([$ value ]);
742
+ }
743
+
744
+ return [self ::$ resolvers ['string ' ]($ scope , $ value ), null ];
745
+ };
746
+ }
747
+
748
+ $ assertionsResultingAtLeastInNonEmptyString = [
749
+ 'startsWithLetter ' ,
750
+ 'unicodeLetters ' ,
751
+ 'alpha ' ,
752
+ 'digits ' ,
753
+ 'alnum ' ,
754
+ 'lower ' ,
755
+ 'upper ' ,
756
+ 'uuid ' ,
757
+ 'ip ' ,
758
+ 'ipv4 ' ,
759
+ 'ipv6 ' ,
760
+ 'email ' ,
761
+ 'notWhitespaceOnly ' ,
762
+ ];
763
+ foreach ($ assertionsResultingAtLeastInNonEmptyString as $ name ) {
764
+ self ::$ resolvers [$ name ] = static function (Scope $ scope , Arg $ value ): array {
765
+ return self ::createIsNonEmptyStringAndSomethingExprPair ([$ value ]);
766
+ };
767
+ }
768
+
726
769
}
727
770
728
771
return self ::$ resolvers ;
@@ -790,15 +833,16 @@ private function handleAll(
790
833
{
791
834
$ args = $ node ->getArgs ();
792
835
$ args [0 ] = new Arg (new ArrayDimFetch ($ args [0 ]->value , new LNumber (0 )));
793
- $ expression = self ::createExpression ($ scope , $ methodName , $ args );
794
- if ($ expression === null ) {
836
+ [ $ expr , $ rootExpr ] = self ::createExpression ($ scope , $ methodName , $ args );
837
+ if ($ expr === null ) {
795
838
return new SpecifiedTypes ();
796
839
}
797
840
798
841
$ specifiedTypes = $ this ->typeSpecifier ->specifyTypesInCondition (
799
842
$ scope ,
800
- $ expression ,
801
- TypeSpecifierContext::createTruthy ()
843
+ $ expr ,
844
+ TypeSpecifierContext::createTruthy (),
845
+ $ rootExpr
802
846
);
803
847
804
848
$ sureNotTypes = $ specifiedTypes ->getSureNotTypes ();
@@ -817,7 +861,8 @@ private function handleAll(
817
861
$ node ->getArgs ()[0 ]->value ,
818
862
static function () use ($ type ): Type {
819
863
return $ type ;
820
- }
864
+ },
865
+ $ rootExpr
821
866
);
822
867
}
823
868
@@ -827,7 +872,8 @@ static function () use ($type): Type {
827
872
private function arrayOrIterable (
828
873
Scope $ scope ,
829
874
Expr $ expr ,
830
- Closure $ typeCallback
875
+ Closure $ typeCallback ,
876
+ ?Expr $ rootExpr = null
831
877
): SpecifiedTypes
832
878
{
833
879
$ currentType = TypeCombinator::intersect ($ scope ->getType ($ expr ), new IterableType (new MixedType (), new MixedType ()));
@@ -859,7 +905,8 @@ private function arrayOrIterable(
859
905
$ specifiedType ,
860
906
TypeSpecifierContext::createTruthy (),
861
907
false ,
862
- $ scope
908
+ $ scope ,
909
+ $ rootExpr
863
910
);
864
911
}
865
912
@@ -900,4 +947,31 @@ static function (?ArrayItem $item) use ($scope, $value, $resolver) {
900
947
return self ::implodeExpr ($ resolvers , BooleanOr::class);
901
948
}
902
949
950
+ /**
951
+ * @param Arg[] $args
952
+ * @return array{Expr, Expr}
953
+ */
954
+ private static function createIsNonEmptyStringAndSomethingExprPair (array $ args ): array
955
+ {
956
+ $ expr = new BooleanAnd (
957
+ new FuncCall (
958
+ new Name ('is_string ' ),
959
+ [$ args [0 ]]
960
+ ),
961
+ new NotIdentical (
962
+ $ args [0 ]->value ,
963
+ new String_ ('' )
964
+ )
965
+ );
966
+
967
+ $ rootExpr = new BooleanAnd (
968
+ $ expr ,
969
+ new FuncCall (new Name ('FAUX_FUNCTION ' ), [
970
+ new Arg ($ args [0 ]->value ),
971
+ ])
972
+ );
973
+
974
+ return [$ expr , $ rootExpr ];
975
+ }
976
+
903
977
}
0 commit comments