@@ -760,7 +760,7 @@ private function doPrepareTarget(
760760 // 1b. build the write chain (Set*OffsetValueTypeExpr nesting) after
761761 // the root walk, so a property base's holder and current types are
762762 // read from its stored result instead of pricing the unwalked fetch
763- $ assignedPropertyExpr = $ assignedExpr ;
763+ $ assignedPropertyExpr = $ this -> getWrittenExpr ( $ assignedExpr) ;
764764 $ chainVar = $ originalVar ;
765765 while ($ chainVar instanceof ArrayDimFetch) {
766766 $ varForSetOffsetValue = $ chainVar ->var ;
@@ -1097,7 +1097,7 @@ private function doPrepareTarget(
10971097 if ($ var instanceof ExistingArrayDimFetch) {
10981098 $ originalVar = $ var ;
10991099 $ dimFetchStack = [];
1100- $ assignedPropertyExpr = $ assignedExpr ;
1100+ $ assignedPropertyExpr = $ this -> getWrittenExpr ( $ assignedExpr) ;
11011101 while ($ var instanceof ExistingArrayDimFetch) {
11021102 $ varForSetOffsetValue = $ var ->getVar ();
11031103 if ($ varForSetOffsetValue instanceof PropertyFetch || $ varForSetOffsetValue instanceof StaticPropertyFetch) {
@@ -1374,7 +1374,7 @@ public function applyWrite(
13741374 $ conditionalExpressions = $ this ->processSureNotTypesForConditionalExpressionsAfterAssign ($ nodeScopeResolver , $ scope , $ storage , $ var ->name , $ conditionalExpressions , $ identicalSpecifiedTypes , $ falseyType , $ impurePoints , $ assignedExpr , $ storedAssignedExprResult );
13751375 }
13761376
1377- $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new VariableAssignNode ($ var , $ assignedExpr ), $ scopeBeforeAssignEval , $ storage );
1377+ $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new VariableAssignNode ($ var , $ this -> getWrittenExpr ( $ assignedExpr) ), $ scopeBeforeAssignEval , $ storage );
13781378
13791379 $ nativeType = $ this ->readAssignedValueType ($ nodeScopeResolver , $ storedAssignedExprResult , $ assignedExpr , $ scope ->doNotTreatPhpDocTypesAsCertain ());
13801380 $ scope = $ scope ->assignVariable (
@@ -1596,7 +1596,7 @@ public function applyWrite(
15961596 if ($ templateArgumentFrame !== null ) {
15971597 $ scope = $ scope ->addTemplateArgumentConstraints ($ this ->templateArgumentObserver ->collectSend ($ propertyReflection ->getWritableType (), $ assignedExprType ));
15981598 }
1599- $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ assignedExpr , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
1599+ $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ this -> getWrittenExpr ( $ assignedExpr) , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
16001600 if ($ propertyReflection ->canChangeTypeAfterAssignment ()) {
16011601 if ($ propertyReflection ->hasNativeType ()) {
16021602 $ propertyNativeType = $ propertyReflection ->getNativeType ();
@@ -1657,7 +1657,7 @@ public function applyWrite(
16571657 } else {
16581658 // fallback
16591659 $ assignedExprType = $ this ->readAssignedValueType ($ nodeScopeResolver , $ assignedValueResult , $ assignedExpr , $ scope );
1660- $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ assignedExpr , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
1660+ $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ this -> getWrittenExpr ( $ assignedExpr) , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
16611661 $ scope = $ scope ->assignExpression ($ var , $ assignedExprType , $ this ->readAssignedValueType ($ nodeScopeResolver , $ assignedValueResult , $ assignedExpr , $ scope ->doNotTreatPhpDocTypesAsCertain ()));
16621662 // simulate dynamic property assign by __set to get throw points
16631663 if (!$ propertyHolderType ->hasMethod ('__set ' )->no ()) {
@@ -1693,7 +1693,7 @@ public function applyWrite(
16931693 $ scope = $ scope ->addTemplateArgumentConstraints ($ this ->templateArgumentObserver ->collectSend ($ propertyReflection ->getWritableType (), $ assignedExprType ));
16941694 }
16951695 }
1696- $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ assignedExpr , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
1696+ $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ this -> getWrittenExpr ( $ assignedExpr) , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
16971697 if ($ propertyReflection !== null && $ propertyReflection ->canChangeTypeAfterAssignment ()) {
16981698 if ($ propertyReflection ->hasNativeType ()) {
16991699 $ propertyNativeType = $ propertyReflection ->getNativeType ();
@@ -1724,7 +1724,7 @@ public function applyWrite(
17241724 } else {
17251725 // fallback
17261726 $ assignedExprType = $ this ->readAssignedValueType ($ nodeScopeResolver , $ assignedValueResult , $ assignedExpr , $ scope );
1727- $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ assignedExpr , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
1727+ $ nodeScopeResolver ->callNodeCallback ($ nodeCallback , new PropertyAssignNode ($ var , $ this -> getWrittenExpr ( $ assignedExpr) , $ isAssignOp ), $ scopeBeforeAssignEval , $ storage );
17281728 $ scope = $ scope ->assignExpression ($ var , $ assignedExprType , $ this ->readAssignedValueType ($ nodeScopeResolver , $ assignedValueResult , $ assignedExpr , $ scope ->doNotTreatPhpDocTypesAsCertain ()));
17291729 }
17301730 } elseif ($ kind === PreparedAssignTarget::KIND_LIST ) {
@@ -2051,6 +2051,19 @@ private function assignDynamicVariable(MutatingScope $scope, ExpressionResult $n
20512051 return $ scope ;
20522052 }
20532053
2054+ /**
2055+ * `$lvalue ??= value` writes only `value` - the type of the whole
2056+ * expression also covers the non-null old value it keeps in place.
2057+ */
2058+ private function getWrittenExpr (Expr $ assignedExpr ): Expr
2059+ {
2060+ if ($ assignedExpr instanceof Expr \AssignOp \Coalesce) {
2061+ return $ assignedExpr ->expr ;
2062+ }
2063+
2064+ return $ assignedExpr ;
2065+ }
2066+
20542067 private function unwrapAssign (Expr $ expr ): Expr
20552068 {
20562069 if ($ expr instanceof Assign) {
0 commit comments