2020use PHPStan \Analyser \SpecifiedTypes ;
2121use PHPStan \Analyser \TypeSpecifierContext ;
2222use PHPStan \DependencyInjection \AutowiredService ;
23- use PHPStan \Node \Expr \AlwaysRememberedExpr ;
24- use PHPStan \Node \Printer \ExprPrinter ;
2523use PHPStan \Reflection \InitializerExprTypeResolver ;
2624use PHPStan \Reflection \ReflectionProvider ;
2725use PHPStan \Type \Accessory \AccessoryNonEmptyStringType ;
2826use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
2927use PHPStan \Type \Accessory \NonEmptyArrayType ;
28+ use PHPStan \Type \ArrayType ;
29+ use PHPStan \Type \BooleanType ;
3030use PHPStan \Type \Constant \ConstantArrayType ;
3131use PHPStan \Type \Constant \ConstantBooleanType ;
3232use PHPStan \Type \Constant \ConstantIntegerType ;
3333use PHPStan \Type \Constant \ConstantStringType ;
34+ use PHPStan \Type \FloatType ;
35+ use PHPStan \Type \Generic \GenericClassStringType ;
3436use PHPStan \Type \IntegerRangeType ;
37+ use PHPStan \Type \IntegerType ;
38+ use PHPStan \Type \MixedType ;
3539use PHPStan \Type \NeverType ;
40+ use PHPStan \Type \NullType ;
3641use PHPStan \Type \ObjectType ;
42+ use PHPStan \Type \ObjectWithoutClassType ;
43+ use PHPStan \Type \ResourceType ;
44+ use PHPStan \Type \StringType ;
3745use PHPStan \Type \Type ;
3846use PHPStan \Type \TypeCombinator ;
3947use PHPStan \Type \UnionType ;
48+ use function array_merge ;
49+ use function count ;
50+ use function in_array ;
51+ use function is_string ;
52+ use function strtolower ;
4053
4154/**
4255 * @implements ExprHandler<Identical>
@@ -51,7 +64,6 @@ public function __construct(
5164 private InitializerExprTypeResolver $ initializerExprTypeResolver ,
5265 private SpecifiedTypesHelper $ specifiedTypesHelper ,
5366 private ReflectionProvider $ reflectionProvider ,
54- private ExprPrinter $ exprPrinter ,
5567 )
5668 {
5769 }
@@ -119,13 +131,14 @@ private function resolveIdenticalSpecifiedTypes(
119131 }
120132
121133 $ unwrappedLeftExpr = $ leftExpr ;
134+ /*$unwrappedRightExpr = $rightExpr;
122135 if ($leftExpr instanceof AlwaysRememberedExpr) {
123- // $unwrappedLeftExpr = $leftExpr->getExpr();
136+ $unwrappedLeftExpr = $leftExpr->getExpr();
124137 }
125- $ unwrappedRightExpr = $ rightExpr ;
138+
126139 if ($rightExpr instanceof AlwaysRememberedExpr) {
127- // $unwrappedRightExpr = $rightExpr->getExpr();
128- }
140+ $unwrappedRightExpr = $rightExpr->getExpr();
141+ }*/
129142
130143 $ rightType = $ rightResult ->type ;
131144
@@ -177,7 +190,7 @@ private function resolveIdenticalSpecifiedTypes(
177190
178191 $ modeArgType = null ;
179192 if (count ($ unwrappedLeftExpr ->getArgs ()) > 1 ) {
180- $ modeArgType = yield ExprAnalysisRequest::createNoopRequest ($ unwrappedLeftExpr ->getArgs ()[1 ]->value , $ scope );
193+ $ modeArgType = ( yield ExprAnalysisRequest::createNoopRequest ($ unwrappedLeftExpr ->getArgs ()[1 ]->value , $ scope ))-> type ;
181194 }
182195
183196 $ specifiedTypes = $ this ->specifiedTypesHelper ->specifyTypesForCountFuncCall (
@@ -297,11 +310,287 @@ private function resolveIdenticalSpecifiedTypes(
297310 }
298311 }
299312
313+ if (
314+ $ unwrappedLeftExpr instanceof FuncCall
315+ && $ unwrappedLeftExpr ->name instanceof Name
316+ && in_array (strtolower ($ unwrappedLeftExpr ->name ->toString ()), [
317+ 'substr ' , 'strstr ' , 'stristr ' , 'strchr ' , 'strrchr ' , 'strtolower ' , 'strtoupper ' , 'ucfirst ' , 'lcfirst ' ,
318+ 'mb_substr ' , 'mb_strstr ' , 'mb_stristr ' , 'mb_strchr ' , 'mb_strrchr ' , 'mb_strtolower ' , 'mb_strtoupper ' , 'mb_ucfirst ' , 'mb_lcfirst ' ,
319+ 'ucwords ' , 'mb_convert_case ' , 'mb_convert_kana ' ,
320+ ], true )
321+ && isset ($ unwrappedLeftExpr ->getArgs ()[0 ])
322+ && $ rightType ->isNonEmptyString ()->yes ()
323+ ) {
324+ $ argType = (yield ExprAnalysisRequest::createNoopRequest ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , $ scope ))->type ;
325+
326+ if ($ argType ->isString ()->yes ()) {
327+ if ($ rightType ->isNonFalsyString ()->yes ()) {
328+ return [
329+ $ this ->specifiedTypesHelper ->create (
330+ $ unwrappedLeftExpr ->getArgs ()[0 ]->value ,
331+ TypeCombinator::intersect ($ argType , new AccessoryNonFalsyStringType ()),
332+ TypeSpecifierContext::createTruthy (),
333+ )->setRootExpr ($ identicalExpr ),
334+ new SpecifiedTypes (),
335+ ];
336+ }
337+
338+ return [
339+ $ this ->specifiedTypesHelper ->create (
340+ $ unwrappedLeftExpr ->getArgs ()[0 ]->value ,
341+ TypeCombinator::intersect ($ argType , new AccessoryNonEmptyStringType ()),
342+ TypeSpecifierContext::createTruthy (),
343+ )->setRootExpr ($ identicalExpr ),
344+ new SpecifiedTypes (),
345+ ];
346+ }
347+ }
348+
349+ if ($ rightType ->isString ()->yes ()) {
350+ $ truthyTypes = null ;
351+ $ falseyTypes = null ;
352+ foreach ($ rightType ->getConstantStrings () as $ constantString ) {
353+ $ specifiedTypesGen = $ this ->specifyTypesForConstantStringBinaryExpression ($ unwrappedLeftExpr , $ constantString , $ identicalExpr , $ scope );
354+ yield from $ specifiedTypesGen ;
355+ $ specifiedTypes = $ specifiedTypesGen ->getReturn ();
356+
357+ if ($ specifiedTypes === null ) {
358+ continue ;
359+ }
360+ if ($ truthyTypes === null ) {
361+ $ truthyTypes = $ specifiedTypes [0 ];
362+ continue ;
363+ }
364+ if ($ falseyTypes === null ) {
365+ $ falseyTypes = $ specifiedTypes [1 ];
366+ continue ;
367+ }
368+
369+ $ truthyTypes = $ truthyTypes ->intersectWith ($ specifiedTypes [0 ]);
370+ $ falseyTypes = $ falseyTypes ->intersectWith ($ specifiedTypes [1 ]);
371+ }
372+
373+ if ($ truthyTypes !== null && $ falseyTypes !== null ) {
374+ if ($ leftExpr !== $ unwrappedLeftExpr ) {
375+ $ truthyTypes = $ truthyTypes ->unionWith ($ this ->specifiedTypesHelper ->create ($ leftExpr , $ rightType , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr ));
376+ $ falseyTypes = $ falseyTypes ->unionWith ($ this ->specifiedTypesHelper ->create ($ leftExpr , $ rightType , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr ));
377+ }
378+ return [
379+ $ truthyTypes ,
380+ $ falseyTypes ,
381+ ];
382+ }
383+ }
384+
385+ $ expressions = $ this ->specifiedTypesHelper ->findTypeExpressionsFromBinaryOperation ($ leftResult ->type , $ rightResult ->type , $ identicalExpr );
386+ if ($ expressions !== null ) {
387+ $ exprNode = $ expressions [0 ];
388+ $ constantType = $ expressions [1 ];
389+
390+ $ unwrappedExprNode = $ exprNode ;
391+ /*if ($exprNode instanceof AlwaysRememberedExpr) {
392+ $unwrappedExprNode = $exprNode->getExpr();
393+ }*/
394+
395+ $ specifiedTypesGen = $ this ->specifyTypesForConstantBinaryExpression ($ unwrappedExprNode , $ constantType , $ identicalExpr , $ scope );
396+ yield from $ specifiedTypesGen ;
397+ $ specifiedTypes = $ specifiedTypesGen ->getReturn ();
398+ if ($ specifiedTypes !== null ) {
399+ if ($ exprNode !== $ unwrappedExprNode ) {
400+ return [
401+ $ specifiedTypes [0 ]->unionWith (
402+ $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr ),
403+ ),
404+ $ specifiedTypes [1 ]->unionWith (
405+ $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr ),
406+ ),
407+ ];
408+ }
409+
410+ return $ specifiedTypes ;
411+ }
412+ }
413+
414+ // todo
415+ // $a::class === 'Foo'
300416
301417 return [
302418 new SpecifiedTypes (),
303419 new SpecifiedTypes (),
304420 ];
305421 }
306422
423+ /**
424+ * @return Generator<int, GeneratorTValueType, GeneratorTSendType, array{SpecifiedTypes, SpecifiedTypes}|null>
425+ */
426+ private function specifyTypesForConstantBinaryExpression (
427+ Expr $ exprNode ,
428+ Type $ constantType ,
429+ Identical $ identicalExpr ,
430+ GeneratorScope $ scope ,
431+ ): Generator
432+ {
433+ if ($ constantType ->isFalse ()->yes ()) {
434+ $ truthyTypes = $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr );
435+ $ falseyTypes = $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr );
436+
437+ $ exprNodeResult = yield ExprAnalysisRequest::createNoopRequest ($ exprNode , $ scope );
438+ if (!$ exprNode instanceof Expr \NullsafeMethodCall && !$ exprNode instanceof Expr \NullsafePropertyFetch) {
439+ $ falseyTypes = $ falseyTypes ->unionWith ($ exprNodeResult ->specifiedTruthyTypes );
440+ }
441+
442+ return [
443+ $ truthyTypes ->unionWith ($ exprNodeResult ->specifiedFalseyTypes )->setRootExpr ($ identicalExpr ),
444+ $ falseyTypes ->setRootExpr ($ identicalExpr ),
445+ ];
446+ }
447+
448+ if ($ constantType ->isTrue ()->yes ()) {
449+ $ truthyTypes = $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr );
450+ $ falseyTypes = $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr );
451+
452+ $ exprNodeResult = yield ExprAnalysisRequest::createNoopRequest ($ exprNode , $ scope );
453+ if (!$ exprNode instanceof Expr \NullsafeMethodCall && !$ exprNode instanceof Expr \NullsafePropertyFetch) {
454+ $ falseyTypes = $ falseyTypes ->unionWith ($ exprNodeResult ->specifiedFalseyTypes );
455+ }
456+
457+ return [
458+ $ truthyTypes ->unionWith ($ exprNodeResult ->specifiedTruthyTypes )->setRootExpr ($ identicalExpr ),
459+ $ falseyTypes ->setRootExpr ($ identicalExpr ),
460+ ];
461+ }
462+
463+ return null ;
464+ }
465+
466+ /**
467+ * @return Generator<int, GeneratorTValueType, GeneratorTSendType, ?array{SpecifiedTypes, SpecifiedTypes}>
468+ */
469+ private function specifyTypesForConstantStringBinaryExpression (
470+ Expr $ exprNode ,
471+ Type $ constantType ,
472+ Expr $ identicalExpr ,
473+ GeneratorScope $ scope ,
474+ ): Generator
475+ {
476+ $ scalarValues = $ constantType ->getConstantScalarValues ();
477+ if (count ($ scalarValues ) !== 1 || !is_string ($ scalarValues [0 ])) {
478+ return null ;
479+ }
480+ $ constantStringValue = $ scalarValues [0 ];
481+
482+ if (
483+ $ exprNode instanceof FuncCall
484+ && $ exprNode ->name instanceof Name
485+ && strtolower ($ exprNode ->name ->toString ()) === 'gettype '
486+ && isset ($ exprNode ->getArgs ()[0 ])
487+ ) {
488+ $ type = null ;
489+ if ($ constantStringValue === 'string ' ) {
490+ $ type = new StringType ();
491+ }
492+ if ($ constantStringValue === 'array ' ) {
493+ $ type = new ArrayType (new MixedType (), new MixedType ());
494+ }
495+ if ($ constantStringValue === 'boolean ' ) {
496+ $ type = new BooleanType ();
497+ }
498+ if (in_array ($ constantStringValue , ['resource ' , 'resource (closed) ' ], true )) {
499+ $ type = new ResourceType ();
500+ }
501+ if ($ constantStringValue === 'integer ' ) {
502+ $ type = new IntegerType ();
503+ }
504+ if ($ constantStringValue === 'double ' ) {
505+ $ type = new FloatType ();
506+ }
507+ if ($ constantStringValue === 'NULL ' ) {
508+ $ type = new NullType ();
509+ }
510+ if ($ constantStringValue === 'object ' ) {
511+ $ type = new ObjectWithoutClassType ();
512+ }
513+
514+ if ($ type !== null ) {
515+ return [
516+ $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr )
517+ ->unionWith ($ this ->specifiedTypesHelper ->create ($ exprNode ->getArgs ()[0 ]->value , $ type , TypeSpecifierContext::createTruthy ())->setRootExpr ($ identicalExpr )),
518+ $ this ->specifiedTypesHelper ->create ($ exprNode , $ constantType , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr )
519+ ->unionWith ($ this ->specifiedTypesHelper ->create ($ exprNode ->getArgs ()[0 ]->value , $ type , TypeSpecifierContext::createFalsey ())->setRootExpr ($ identicalExpr )),
520+ ];
521+ }
522+ }
523+
524+ if (
525+ $ exprNode instanceof FuncCall
526+ && $ exprNode ->name instanceof Name
527+ && strtolower ((string ) $ exprNode ->name ) === 'get_parent_class '
528+ && isset ($ exprNode ->getArgs ()[0 ])
529+ ) {
530+ $ argType = (yield ExprAnalysisRequest::createNoopRequest ($ exprNode ->getArgs ()[0 ]->value , $ scope ))->type ;
531+ $ objectType = new ObjectType ($ constantStringValue );
532+ $ classStringType = new GenericClassStringType ($ objectType );
533+
534+ if ($ argType ->isString ()->yes ()) {
535+ return [
536+ $ this ->specifiedTypesHelper ->create (
537+ $ exprNode ->getArgs ()[0 ]->value ,
538+ $ classStringType ,
539+ TypeSpecifierContext::createTruthy (),
540+ )->setRootExpr ($ identicalExpr ),
541+ new SpecifiedTypes (),
542+ ];
543+ }
544+
545+ if ($ argType ->isObject ()->yes ()) {
546+ return [
547+ $ this ->specifiedTypesHelper ->create (
548+ $ exprNode ->getArgs ()[0 ]->value ,
549+ $ objectType ,
550+ TypeSpecifierContext::createTruthy (),
551+ )->setRootExpr ($ identicalExpr ),
552+ new SpecifiedTypes (),
553+ ];
554+ }
555+
556+ return [
557+ $ this ->specifiedTypesHelper ->create (
558+ $ exprNode ->getArgs ()[0 ]->value ,
559+ TypeCombinator::union ($ objectType , $ classStringType ),
560+ TypeSpecifierContext::createTruthy (),
561+ )->setRootExpr ($ identicalExpr ),
562+ new SpecifiedTypes (),
563+ ];
564+ }
565+
566+ if (
567+ $ exprNode instanceof FuncCall
568+ && $ exprNode ->name instanceof Name
569+ && in_array (strtolower ((string ) $ exprNode ->name ), [
570+ 'trim ' , 'ltrim ' , 'rtrim ' ,
571+ 'mb_trim ' , 'mb_ltrim ' , 'mb_rtrim ' ,
572+ ], true )
573+ && isset ($ exprNode ->getArgs ()[0 ])
574+ && $ constantStringValue === ''
575+ ) {
576+ $ argValue = $ exprNode ->getArgs ()[0 ]->value ;
577+ $ argType = (yield ExprAnalysisRequest::createNoopRequest ($ argValue , $ scope ))->type ;
578+ if ($ argType ->isString ()->yes ()) {
579+ return [
580+ new SpecifiedTypes (),
581+ $ this ->specifiedTypesHelper ->create (
582+ $ argValue ,
583+ TypeCombinator::intersect (
584+ new StringType (),
585+ new AccessoryNonEmptyStringType (),
586+ ),
587+ TypeSpecifierContext::createTruthy (),
588+ )->setRootExpr ($ identicalExpr ),
589+ ];
590+ }
591+ }
592+
593+ return null ;
594+ }
595+
307596}
0 commit comments