@@ -3182,7 +3182,8 @@ public function specifyExpressionType(Expr $expr, Type $type, ?Type $nativeType
31823182}
31833183
31843184$ nativeTypes = $ this ->nativeExpressionTypes ;
3185- $ nativeTypes [sprintf ('$%s ' , $ variableName )] = $ nativeType ;
3185+ $ exprString = sprintf ('$%s ' , $ variableName );
3186+ $ nativeTypes [$ exprString ] = $ nativeType ;
31863187
31873188return $ this ->scopeFactory ->create (
31883189$ this ->context ,
@@ -3362,20 +3363,90 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
33623363});
33633364
33643365$ scope = $ this ;
3366+ $ typeGuards = [];
33653367foreach ($ typeSpecifications as $ typeSpecification ) {
33663368$ expr = $ typeSpecification ['expr ' ];
33673369$ type = $ typeSpecification ['type ' ];
33683370if ($ typeSpecification ['sure ' ]) {
3369- $ scope = $ scope ->specifyExpressionType ($ expr , $ specifiedTypes ->shouldOverwrite () ? $ type : TypeCombinator::intersect ($ type , $ this ->getType ($ expr )));
3371+ $ typeGuard = TypeCombinator::intersect ($ type , $ this ->getType ($ expr ));
3372+ $ scope = $ scope ->specifyExpressionType ($ expr , $ specifiedTypes ->shouldOverwrite () ? $ type : $ typeGuard );
33703373
33713374if ($ expr instanceof Variable && is_string ($ expr ->name )) {
33723375$ scope ->nativeExpressionTypes [sprintf ('$%s ' , $ expr ->name )] = $ specifiedTypes ->shouldOverwrite () ? $ type : TypeCombinator::intersect ($ type , $ this ->getNativeType ($ expr ));
33733376}
33743377} else {
33753378$ scope = $ scope ->removeTypeFromExpression ($ expr , $ type );
3379+ $ typeGuard = TypeCombinator::remove ($ scope ->getType ($ expr ), $ type );
33763380}
3381+
3382+ if (
3383+ !$ expr instanceof Variable
3384+ || !is_string ($ expr ->name )
3385+ || $ specifiedTypes ->shouldOverwrite ()
3386+ ) {
3387+ continue ;
3388+ }
3389+
3390+ $ scope ->typeGuards ['$ ' . $ expr ->name ] = $ typeGuard ;
3391+ $ typeGuards ['$ ' . $ expr ->name ] = $ typeGuard ;
33773392}
33783393
3394+ $ newConditionalExpressions = [];
3395+ foreach ($ this ->conditionalExpressions as $ variableExprString => $ conditionalExpressions ) {
3396+ if (array_key_exists ($ variableExprString , $ typeGuards )) {
3397+ continue ;
3398+ }
3399+
3400+ $ typeHolder = null ;
3401+
3402+ $ variableName = substr ($ variableExprString , 1 );
3403+ foreach ($ conditionalExpressions as $ conditionalExpression ) {
3404+ $ matchingConditions = [];
3405+ foreach ($ conditionalExpression ->getConditionExpressionTypes () as $ conditionExprString => $ conditionalType ) {
3406+ if (!array_key_exists ($ conditionExprString , $ typeGuards )) {
3407+ continue ;
3408+ }
3409+
3410+ if (!$ typeGuards [$ conditionExprString ]->equals ($ conditionalType )) {
3411+ continue 2 ;
3412+ }
3413+
3414+ $ matchingConditions [$ conditionExprString ] = $ conditionalType ;
3415+ }
3416+
3417+ if (count ($ matchingConditions ) === 0 ) {
3418+ $ newConditionalExpressions [$ variableExprString ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
3419+ continue ;
3420+ }
3421+
3422+ if (count ($ matchingConditions ) < count ($ conditionalExpression ->getConditionExpressionTypes ())) {
3423+ $ filteredConditions = $ conditionalExpression ->getConditionExpressionTypes ();
3424+ foreach (array_keys ($ matchingConditions ) as $ conditionExprString ) {
3425+ unset($ filteredConditions [$ conditionExprString ]);
3426+ }
3427+
3428+ $ holder = new ConditionalExpressionHolder ($ filteredConditions , $ conditionalExpression ->getTypeHolder ());
3429+ $ newConditionalExpressions [$ variableExprString ][$ holder ->getKey ()] = $ holder ;
3430+ continue ;
3431+ }
3432+
3433+ $ typeHolder = $ conditionalExpression ->getTypeHolder ();
3434+ break ;
3435+ }
3436+
3437+ if ($ typeHolder === null ) {
3438+ continue ;
3439+ }
3440+
3441+ if ($ typeHolder ->getCertainty ()->no ()) {
3442+ unset($ scope ->variableTypes [$ variableName ]);
3443+ } else {
3444+ $ scope ->variableTypes [$ variableName ] = $ typeHolder ;
3445+ }
3446+ }
3447+
3448+ $ scope ->conditionalExpressions = $ newConditionalExpressions ;
3449+
33793450return $ scope ;
33803451}
33813452
@@ -3473,6 +3544,33 @@ public function mergeWith(?self $otherScope): self
34733544}
34743545}
34753546
3547+ $ mergedVariableHolders = $ this ->mergeVariableHolders ($ ourVariableTypes , $ theirVariableTypes );
3548+ $ conditionalExpressions = $ this ->intersectConditionalExpressions ($ otherScope ->conditionalExpressions );
3549+ $ conditionalExpressions = $ this ->createConditionalExpressions (
3550+ $ conditionalExpressions ,
3551+ $ this ->typeGuards ,
3552+ $ ourVariableTypes ,
3553+ $ mergedVariableHolders
3554+ );
3555+ $ conditionalExpressions = $ this ->createConditionalExpressions (
3556+ $ conditionalExpressions ,
3557+ $ otherScope ->typeGuards ,
3558+ $ theirVariableTypes ,
3559+ $ mergedVariableHolders
3560+ );
3561+ $ typeGuards = [];
3562+ foreach ($ this ->typeGuards as $ guardExprString => $ typeGuard ) {
3563+ if (!array_key_exists ($ guardExprString , $ otherScope ->typeGuards )) {
3564+ continue ;
3565+ }
3566+
3567+ if (!$ typeGuard ->equals ($ otherScope ->typeGuards [$ guardExprString ])) {
3568+ continue ;
3569+ }
3570+
3571+ $ typeGuards [$ guardExprString ] = $ typeGuard ;
3572+ }
3573+
34763574return $ this ->scopeFactory ->create (
34773575$ this ->context ,
34783576$ this ->isDeclareStrictTypes (),
@@ -3482,10 +3580,10 @@ public function mergeWith(?self $otherScope): self
34823580)),
34833581$ this ->getFunction (),
34843582$ this ->getNamespace (),
3485- $ this -> mergeVariableHolders ( $ ourVariableTypes , $ theirVariableTypes ) ,
3583+ $ mergedVariableHolders ,
34863584$ this ->mergeVariableHolders ($ this ->moreSpecificTypes , $ otherScope ->moreSpecificTypes ),
3487- [], // todo
3488- [], // todo
3585+ $ typeGuards ,
3586+ $ conditionalExpressions ,
34893587$ this ->inClosureBindScopeClass ,
34903588$ this ->anonymousFunctionReflection ,
34913589$ this ->inFirstLevelStatement ,
@@ -3502,6 +3600,77 @@ public function mergeWith(?self $otherScope): self
35023600);
35033601}
35043602
3603+ /**
3604+ * @param array<string, ConditionalExpressionHolder[]> $otherConditionalExpressions
3605+ * @return array<string, ConditionalExpressionHolder[]>
3606+ */
3607+ private function intersectConditionalExpressions (array $ otherConditionalExpressions ): array
3608+ {
3609+ $ newConditionalExpressions = [];
3610+ foreach ($ this ->conditionalExpressions as $ exprString => $ holders ) {
3611+ if (!array_key_exists ($ exprString , $ otherConditionalExpressions )) {
3612+ continue ;
3613+ }
3614+
3615+ $ otherHolders = $ otherConditionalExpressions [$ exprString ];
3616+ foreach (array_keys ($ holders ) as $ key ) {
3617+ if (!array_key_exists ($ key , $ otherHolders )) {
3618+ continue 2 ;
3619+ }
3620+ }
3621+
3622+ $ newConditionalExpressions [$ exprString ] = $ holders ;
3623+ }
3624+
3625+ return $ newConditionalExpressions ;
3626+ }
3627+
3628+ /**
3629+ * @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
3630+ * @param array<string, Type> $typeGuards
3631+ * @param array<string, VariableTypeHolder> $variableTypes
3632+ * @param array<string, VariableTypeHolder> $mergedVariableHolders
3633+ * @return array<string, ConditionalExpressionHolder[]>
3634+ */
3635+ private function createConditionalExpressions (
3636+ array $ conditionalExpressions ,
3637+ array $ typeGuards ,
3638+ array $ variableTypes ,
3639+ array $ mergedVariableHolders
3640+ ): array
3641+ {
3642+ if (count ($ typeGuards ) === 0 ) {
3643+ return $ conditionalExpressions ;
3644+ }
3645+
3646+ foreach ($ variableTypes as $ name => $ holder ) {
3647+ if (array_key_exists ('$ ' . $ name , $ typeGuards )) {
3648+ continue ;
3649+ }
3650+
3651+ if (
3652+ array_key_exists ($ name , $ mergedVariableHolders )
3653+ && $ mergedVariableHolders [$ name ]->equals ($ holder )
3654+ ) {
3655+ continue ;
3656+ }
3657+
3658+ $ conditionalExpression = new ConditionalExpressionHolder ($ typeGuards , $ holder );
3659+ $ conditionalExpressions ['$ ' . $ name ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
3660+ }
3661+
3662+ foreach (array_keys ($ mergedVariableHolders ) as $ name ) {
3663+ if (array_key_exists ($ name , $ variableTypes )) {
3664+ continue ;
3665+ }
3666+
3667+ $ conditionalExpression = new ConditionalExpressionHolder ($ typeGuards , new VariableTypeHolder (new ErrorType (), TrinaryLogic::createNo ()));
3668+ $ conditionalExpressions ['$ ' . $ name ][$ conditionalExpression ->getKey ()] = $ conditionalExpression ;
3669+ }
3670+
3671+ return $ conditionalExpressions ;
3672+ }
3673+
35053674/**
35063675 * @param VariableTypeHolder[] $ourVariableTypeHolders
35073676 * @param VariableTypeHolder[] $theirVariableTypeHolders
0 commit comments