1313use PHPStan \Analyser \ExpressionResult ;
1414use PHPStan \Analyser \ExpressionResultStorage ;
1515use PHPStan \Analyser \ExprHandler ;
16+ use PHPStan \Analyser \ExprHandler \Helper \ImplicitToStringCallHelper ;
1617use PHPStan \Analyser \InternalThrowPoint ;
1718use PHPStan \Analyser \MutatingScope ;
1819use PHPStan \Analyser \NodeScopeResolver ;
@@ -42,6 +43,7 @@ public function __construct(
4243 private InitializerExprTypeResolver $ initializerExprTypeResolver ,
4344 private RicherScopeGetTypeHelper $ richerScopeGetTypeHelper ,
4445 private PhpVersion $ phpVersion ,
46+ private ImplicitToStringCallHelper $ implicitToStringCallHelper ,
4547 )
4648 {
4749 }
@@ -62,20 +64,27 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
6264 $ leftResult = $ nodeScopeResolver ->processExprNode ($ stmt , $ expr ->left , $ scope , $ storage , $ nodeCallback , $ context ->enterDeep ());
6365 $ rightResult = $ nodeScopeResolver ->processExprNode ($ stmt , $ expr ->right , $ leftResult ->getScope (), $ storage , $ nodeCallback , $ context ->enterDeep ());
6466 $ throwPoints = array_merge ($ leftResult ->getThrowPoints (), $ rightResult ->getThrowPoints ());
67+ $ impurePoints = array_merge ($ leftResult ->getImpurePoints (), $ rightResult ->getImpurePoints ());
6568 if (
6669 ($ expr instanceof BinaryOp \Div || $ expr instanceof BinaryOp \Mod) &&
6770 !$ leftResult ->getScope ()->getType ($ expr ->right )->toNumber ()->isSuperTypeOf (new ConstantIntegerType (0 ))->no ()
6871 ) {
6972 $ throwPoints [] = InternalThrowPoint::createExplicit ($ leftResult ->getScope (), new ObjectType (DivisionByZeroError::class), $ expr , false );
7073 }
74+ if ($ expr instanceof BinaryOp \Concat) {
75+ $ leftToStringResult = $ this ->implicitToStringCallHelper ->processImplicitToStringCall ($ expr ->left , $ scope );
76+ $ rightToStringResult = $ this ->implicitToStringCallHelper ->processImplicitToStringCall ($ expr ->right , $ leftResult ->getScope ());
77+ $ throwPoints = array_merge ($ throwPoints , $ leftToStringResult ->getThrowPoints (), $ rightToStringResult ->getThrowPoints ());
78+ $ impurePoints = array_merge ($ impurePoints , $ leftToStringResult ->getImpurePoints (), $ rightToStringResult ->getImpurePoints ());
79+ }
7180 $ scope = $ rightResult ->getScope ();
7281
7382 return new ExpressionResult (
7483 $ scope ,
7584 hasYield: $ leftResult ->hasYield () || $ rightResult ->hasYield (),
7685 isAlwaysTerminating: $ leftResult ->isAlwaysTerminating () || $ rightResult ->isAlwaysTerminating (),
7786 throwPoints: $ throwPoints ,
78- impurePoints: array_merge ( $ leftResult -> getImpurePoints (), $ rightResult -> getImpurePoints ()) ,
87+ impurePoints: $ impurePoints ,
7988 truthyScopeCallback: static fn (): MutatingScope => $ scope ->filterByTruthyValue ($ expr ),
8089 falseyScopeCallback: static fn (): MutatingScope => $ scope ->filterByFalseyValue ($ expr ),
8190 );
0 commit comments