145145use function array_map ;
146146use function array_merge ;
147147use function array_pop ;
148+ use function array_reduce ;
148149use function array_slice ;
149150use function array_values ;
150151use function count ;
@@ -3886,65 +3887,74 @@ public function isInFirstLevelStatement(): bool
38863887 return $ this ->inFirstLevelStatement ;
38873888 }
38883889
3889- public function mergeWith (?self $ otherScope ): self
3890+ public function mergeWith (?self ... $ otherScopes ): self
38903891 {
3891- if ($ otherScope === null || $ this === $ otherScope ) {
3892+ $ otherScopes = array_filter ($ otherScopes , fn ($ scope ) => $ scope !== null && $ scope !== $ this );
3893+ if (count ($ otherScopes ) === 0 ) {
38923894 return $ this ;
38933895 }
3894- $ ourExpressionTypes = $ this ->expressionTypes ;
3895- $ theirExpressionTypes = $ otherScope ->expressionTypes ;
38963896
3897- $ mergedExpressionTypes = $ this -> mergeVariableHolders ( $ ourExpressionTypes , $ theirExpressionTypes );
3898- $ conditionalExpressions = $ this -> intersectConditionalExpressions ( $ otherScope -> conditionalExpressions ) ;
3899- $ conditionalExpressions = $ this -> createConditionalExpressions (
3900- $ conditionalExpressions ,
3901- $ ourExpressionTypes ,
3902- $ theirExpressionTypes ,
3903- $ mergedExpressionTypes ,
3904- ) ;
3905- $ conditionalExpressions = $ this -> createConditionalExpressions (
3906- $ conditionalExpressions ,
3907- $ theirExpressionTypes ,
3908- $ ourExpressionTypes ,
3909- $ mergedExpressionTypes,
3910- );
3897+ /** @var self[] $scopes */
3898+ $ scopes = [ $ this , ... $ otherScopes ] ;
3899+ $ listOfExpressionTypes = [];
3900+ foreach ( $ scopes as $ scope ) {
3901+ $ listOfExpressionTypes [] = $ scope -> expressionTypes ;
3902+ }
3903+
3904+ $ listOfNativeExpressionTypes = [] ;
3905+ foreach ( $ scopes as $ scope ) {
3906+ $ listOfNativeExpressionTypes [] = $ scope -> nativeExpressionTypes ;
3907+ }
3908+
3909+ $ mergedExpressionTypes = $ this -> mergeVariableHolders ( $ listOfExpressionTypes );
3910+
39113911 return $ this ->scopeFactory ->create (
39123912 $ this ->context ,
39133913 $ this ->isDeclareStrictTypes (),
39143914 $ this ->getFunction (),
39153915 $ this ->getNamespace (),
39163916 $ mergedExpressionTypes ,
3917- $ this ->mergeVariableHolders ($ this ->nativeExpressionTypes , $ otherScope ->nativeExpressionTypes ),
3918- $ conditionalExpressions ,
3917+ $ this ->mergeVariableHolders ($ listOfNativeExpressionTypes ),
3918+ $ this ->createConditionalExpressions (
3919+ $ this ->intersectConditionalExpressions ($ otherScopes ),
3920+ $ listOfExpressionTypes ,
3921+ $ mergedExpressionTypes ,
3922+ ),
39193923 $ this ->inClosureBindScopeClasses ,
39203924 $ this ->anonymousFunctionReflection ,
39213925 $ this ->inFirstLevelStatement ,
39223926 [],
39233927 [],
39243928 [],
3925- $ this -> afterExtractCall && $ otherScope ->afterExtractCall ,
3929+ array_reduce ( $ scopes , static fn ( $ carry , $ scope ) => $ carry && $ scope ->afterExtractCall , true ) ,
39263930 $ this ->parentScope ,
39273931 $ this ->nativeTypesPromoted ,
39283932 );
39293933 }
39303934
39313935 /**
3932- * @param array<string, ConditionalExpressionHolder[] > $otherConditionalExpressions
3936+ * @param array<self > $otherScopes
39333937 * @return array<string, ConditionalExpressionHolder[]>
39343938 */
3935- private function intersectConditionalExpressions (array $ otherConditionalExpressions ): array
3939+ private function intersectConditionalExpressions (array $ otherScopes ): array
39363940 {
3941+ if (count ($ otherScopes ) === 0 ) {
3942+ return $ this ->conditionalExpressions ;
3943+ }
3944+
39373945 $ newConditionalExpressions = [];
39383946 foreach ($ this ->conditionalExpressions as $ exprString => $ holders ) {
3939- if (!array_key_exists ($ exprString , $ otherConditionalExpressions )) {
3940- continue ;
3941- }
3942-
3943- $ otherHolders = $ otherConditionalExpressions [$ exprString ];
3944- foreach (array_keys ($ holders ) as $ key ) {
3945- if (!array_key_exists ($ key , $ otherHolders )) {
3947+ foreach ($ otherScopes as $ scope ) {
3948+ if (!array_key_exists ($ exprString , $ scope ->conditionalExpressions )) {
39463949 continue 2 ;
39473950 }
3951+
3952+ $ otherHolders = $ scope ->conditionalExpressions [$ exprString ];
3953+ foreach (array_keys ($ holders ) as $ key ) {
3954+ if (!array_key_exists ($ key , $ otherHolders )) {
3955+ continue 3 ;
3956+ }
3957+ }
39483958 }
39493959
39503960 $ newConditionalExpressions [$ exprString ] = $ holders ;
@@ -3955,102 +3965,150 @@ private function intersectConditionalExpressions(array $otherConditionalExpressi
39553965
39563966 /**
39573967 * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
3958- * @param array<string, ExpressionTypeHolder> $ourExpressionTypes
3959- * @param array<string, ExpressionTypeHolder> $theirExpressionTypes
3968+ * @param list<array<string, ExpressionTypeHolder>> $listOfExpressionTypes
39603969 * @param array<string, ExpressionTypeHolder> $mergedExpressionTypes
39613970 * @return array<string, ConditionalExpressionHolder[]>
39623971 */
39633972 private function createConditionalExpressions (
39643973 array $ conditionalExpressions ,
3965- array $ ourExpressionTypes ,
3966- array $ theirExpressionTypes ,
3974+ array $ listOfExpressionTypes ,
39673975 array $ mergedExpressionTypes ,
39683976 ): array
39693977 {
3970- $ newVariableTypes = $ ourExpressionTypes ;
3971- foreach ($ theirExpressionTypes as $ exprString => $ holder ) {
3972- if (!array_key_exists ($ exprString , $ mergedExpressionTypes )) {
3973- continue ;
3978+ foreach ($ listOfExpressionTypes as $ index => $ ourExpressionTypes ) {
3979+ $ otherExpressionTypes = [];
3980+ foreach ($ listOfExpressionTypes as $ otherIndex => $ otherExpTypes ) {
3981+ if ($ otherIndex === $ index ) {
3982+ continue ;
3983+ }
3984+ foreach ($ otherExpTypes as $ exprString => $ holder ) {
3985+ $ otherExpressionTypes [$ exprString ][] = $ holder ;
3986+ }
39743987 }
39753988
3976- if (!$ mergedExpressionTypes [$ exprString ]->getType ()->equals ($ holder ->getType ())) {
3977- continue ;
3978- }
3989+ $ newVariableTypes = $ ourExpressionTypes ;
3990+ foreach ($ otherExpressionTypes as $ exprString => $ holders ) {
3991+ if (!array_key_exists ($ exprString , $ mergedExpressionTypes )) {
3992+ continue ;
3993+ }
39793994
3980- unset($ newVariableTypes [$ exprString ]);
3981- }
3995+ $ allMatch = true ;
3996+ foreach ($ holders as $ holder ) {
3997+ if (!$ mergedExpressionTypes [$ exprString ]->getType ()->equals ($ holder ->getType ())) {
3998+ $ allMatch = false ;
3999+ break ;
4000+ }
4001+ }
39824002
3983- $ typeGuards = [];
3984- foreach ($ newVariableTypes as $ exprString => $ holder ) {
3985- if (!$ holder ->getCertainty ()->yes ()) {
3986- continue ;
3987- }
3988- if (!array_key_exists ($ exprString , $ mergedExpressionTypes )) {
3989- continue ;
3990- }
3991- if ($ mergedExpressionTypes [$ exprString ]->getType ()->equals ($ holder ->getType ())) {
3992- continue ;
4003+ if (!$ allMatch ) {
4004+ continue ;
4005+ }
4006+
4007+ unset($ newVariableTypes [$ exprString ]);
39934008 }
39944009
3995- $ typeGuards [$ exprString ] = $ holder ;
3996- }
4010+ $ typeGuards = [];
4011+ foreach ($ newVariableTypes as $ exprString => $ holder ) {
4012+ if (!$ holder ->getCertainty ()->yes ()) {
4013+ continue ;
4014+ }
4015+ if (!array_key_exists ($ exprString , $ mergedExpressionTypes )) {
4016+ continue ;
4017+ }
4018+ if ($ mergedExpressionTypes [$ exprString ]->getType ()->equals ($ holder ->getType ())) {
4019+ continue ;
4020+ }
39974021
3998- if (count ($ typeGuards ) === 0 ) {
3999- return $ conditionalExpressions ;
4000- }
4022+ $ typeGuards [$ exprString ] = $ holder ;
4023+ }
40014024
4002- foreach ($ newVariableTypes as $ exprString => $ holder ) {
4003- if (
4004- array_key_exists ($ exprString , $ mergedExpressionTypes )
4005- && $ mergedExpressionTypes [$ exprString ]->equals ($ holder )
4006- ) {
4025+ if (count ($ typeGuards ) === 0 ) {
40074026 continue ;
40084027 }
40094028
4010- $ variableTypeGuards = $ typeGuards ;
4011- unset($ variableTypeGuards [$ exprString ]);
4029+ foreach ($ newVariableTypes as $ exprString => $ holder ) {
4030+ if (
4031+ array_key_exists ($ exprString , $ mergedExpressionTypes )
4032+ && $ mergedExpressionTypes [$ exprString ]->equals ($ holder )
4033+ ) {
4034+ continue ;
4035+ }
40124036
4013- if (count ($ variableTypeGuards ) === 0 ) {
4014- continue ;
4015- }
4037+ $ variableTypeGuards = $ typeGuards ;
4038+ unset($ variableTypeGuards [$ exprString ]);
40164039
4017- $ conditionalExpression = new ConditionalExpressionHolder ( $ variableTypeGuards, $ holder );
4018- $ conditionalExpressions [ $ exprString ][ $ conditionalExpression -> getKey ()] = $ conditionalExpression ;
4019- }
4040+ if ( count ( $ variableTypeGuards) === 0 ) {
4041+ continue ;
4042+ }
40204043
4021- foreach ($ mergedExpressionTypes as $ exprString => $ mergedExprTypeHolder ) {
4022- if (array_key_exists ($ exprString , $ ourExpressionTypes )) {
4023- continue ;
4044+ $ conditionalExpression = new ConditionalExpressionHolder ($ variableTypeGuards , $ holder );
4045+ $ conditionalExpressions [$ exprString ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
40244046 }
40254047
4026- $ conditionalExpression = new ConditionalExpressionHolder ($ typeGuards , new ExpressionTypeHolder ($ mergedExprTypeHolder ->getExpr (), new ErrorType (), TrinaryLogic::createNo ()));
4027- $ conditionalExpressions [$ exprString ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
4048+ foreach ($ mergedExpressionTypes as $ exprString => $ mergedExprTypeHolder ) {
4049+ if (array_key_exists ($ exprString , $ ourExpressionTypes )) {
4050+ continue ;
4051+ }
4052+
4053+ $ conditionalExpression = new ConditionalExpressionHolder ($ typeGuards , new ExpressionTypeHolder ($ mergedExprTypeHolder ->getExpr (), new ErrorType (), TrinaryLogic::createNo ()));
4054+ $ conditionalExpressions [$ exprString ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
4055+ }
40284056 }
40294057
40304058 return $ conditionalExpressions ;
40314059 }
40324060
40334061 /**
4034- * @param array<string, ExpressionTypeHolder> $ourVariableTypeHolders
4035- * @param array<string, ExpressionTypeHolder> $theirVariableTypeHolders
4062+ * @param list<array<string, ExpressionTypeHolder>> $listOfExpressionTypes
40364063 * @return array<string, ExpressionTypeHolder>
40374064 */
4038- private function mergeVariableHolders (array $ ourVariableTypeHolders , array $ theirVariableTypeHolders ): array
4065+ private function mergeVariableHolders (array $ listOfExpressionTypes ): array
40394066 {
4040- $ intersectedVariableTypeHolders = [];
4067+ $ exprStrings = [];
4068+ foreach ($ listOfExpressionTypes as $ expressionTypes ) {
4069+ foreach ($ expressionTypes as $ exprString => $ expressionTypeHolder ) {
4070+ $ exprStrings [$ exprString ] = $ expressionTypeHolder ->getExpr ();
4071+ }
4072+ }
4073+
4074+ $ mergedExpressionTypeHolders = [];
40414075 $ globalVariableCallback = fn (Node $ node ) => $ node instanceof Variable && is_string ($ node ->name ) && $ this ->isGlobalVariable ($ node ->name );
40424076 $ nodeFinder = new NodeFinder ();
4043- foreach ($ ourVariableTypeHolders as $ exprString => $ variableTypeHolder ) {
4044- if (isset ($ theirVariableTypeHolders [$ exprString ])) {
4045- if ($ variableTypeHolder === $ theirVariableTypeHolders [$ exprString ]) {
4046- $ intersectedVariableTypeHolders [$ exprString ] = $ variableTypeHolder ;
4077+ foreach ($ exprStrings as $ exprString => $ expr ) {
4078+ $ exprTypeHolders = [];
4079+ $ inAllScopes = true ;
4080+ foreach ($ listOfExpressionTypes as $ expressionTypes ) {
4081+ if (!array_key_exists ($ exprString , $ expressionTypes )) {
4082+ $ inAllScopes = false ;
40474083 continue ;
40484084 }
40494085
4050- $ intersectedVariableTypeHolders [$ exprString ] = $ variableTypeHolder ->and ($ theirVariableTypeHolders [$ exprString ]);
4086+ $ exprTypeHolder = $ expressionTypes [$ exprString ];
4087+ foreach ($ exprTypeHolders as $ existingTypeHolder ) {
4088+ if ($ existingTypeHolder !== $ exprTypeHolder ) {
4089+ continue ;
4090+ }
4091+ continue 2 ;
4092+ }
4093+
4094+ $ exprTypeHolders [] = $ exprTypeHolder ;
4095+ }
4096+
4097+ if ($ exprTypeHolders === []) {
4098+ continue ;
4099+ }
4100+
4101+ if (count ($ exprTypeHolders ) === 1 ) {
4102+ $ holder = $ exprTypeHolders [0 ];
40514103 } else {
4052- $ expr = $ variableTypeHolder ->getExpr ();
4104+ $ holder = $ exprTypeHolders [0 ]->and (...array_slice ($ exprTypeHolders , 1 ));
4105+ }
4106+
4107+ if (!$ inAllScopes ) {
4108+ $ holder = new ExpressionTypeHolder ($ holder ->getExpr (), $ holder ->getType (), TrinaryLogic::createMaybe ());
4109+ }
40534110
4111+ if (!$ holder ->getCertainty ()->yes ()) {
40544112 if (!$ expr instanceof Variable && !$ expr instanceof VirtualNode) {
40554113 continue ;
40564114 }
@@ -4061,33 +4119,12 @@ private function mergeVariableHolders(array $ourVariableTypeHolders, array $thei
40614119 if ($ expr ->getAttribute (self ::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME ) === true ) {
40624120 continue ;
40634121 }
4064-
4065- $ intersectedVariableTypeHolders [$ exprString ] = ExpressionTypeHolder::createMaybe ($ expr , $ variableTypeHolder ->getType ());
4066- }
4067- }
4068-
4069- foreach ($ theirVariableTypeHolders as $ exprString => $ variableTypeHolder ) {
4070- if (isset ($ intersectedVariableTypeHolders [$ exprString ])) {
4071- continue ;
4072- }
4073-
4074- $ expr = $ variableTypeHolder ->getExpr ();
4075-
4076- if (!$ expr instanceof Variable && !$ expr instanceof VirtualNode) {
4077- continue ;
4078- }
4079-
4080- if (!$ expr ->hasAttribute (self ::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME )) {
4081- $ expr ->setAttribute (self ::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME , $ nodeFinder ->findFirst ($ expr , $ globalVariableCallback ) !== null );
4082- }
4083- if ($ expr ->getAttribute (self ::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME ) === true ) {
4084- continue ;
40854122 }
40864123
4087- $ intersectedVariableTypeHolders [$ exprString ] = ExpressionTypeHolder:: createMaybe ( $ expr , $ variableTypeHolder -> getType ()) ;
4124+ $ mergedExpressionTypeHolders [$ exprString ] = $ holder ;
40884125 }
40894126
4090- return $ intersectedVariableTypeHolders ;
4127+ return $ mergedExpressionTypeHolders ;
40914128 }
40924129
40934130 public function mergeInitializedProperties (self $ calledMethodScope ): self
0 commit comments