Fix phpstan/phpstan#9181: Incorrect assertion of value of object across function call boundary#5106
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Open
Conversation
…ll boundary - When an object created via (object)[] cast is passed to a function with side effects, its property types (embedded in ObjectShapeType) were not being widened - Added widenUniversalObjectCrateProperties() to NodeScopeResolver that resets stdClass to its base type after argument invalidation - New regression test in tests/PHPStan/Analyser/nsrt/bug-9181.php - The root cause was that ObjectShapeType property types are part of the variable's type rather than stored as separate expression types, so invalidateExpression() could not reach them Closes phpstan/phpstan#9181
Comment on lines
+5960
to
+5964
| $stdClassType = new ObjectType('stdClass'); | ||
|
|
||
| if (!in_array('stdClass', $argType->getObjectClassNames(), true)) { | ||
| return $scope; | ||
| } |
Contributor
There was a problem hiding this comment.
I think this should work for any ObjectShapeType and not only stdClass.
Contributor
There was a problem hiding this comment.
should the logic be part of MutatingScope->invalidateExpression() or MutatingScope->shouldInvalidateExpression()?
Contributor
There was a problem hiding this comment.
Maybe, but I think that's already not the right logic.
Contributor
VincentLanglet
left a comment
There was a problem hiding this comment.
This doesn't work with
/**
* @param object{search: null} $data
*/
public function sayHello2(object $data): void
{
assertType('null', $data->search);
$this->possiblyModifyObject($data);
assertType('mixed', $data->search);
if (($search = $data->search) !== null) {
assertType('mixed~null', $search);
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a
stdClassobject created via(object)[]cast is passed to a function/method with side effects, PHPStan incorrectly preserved the specific property types from the cast. This led to false positives like "Strict comparison using !== between null and null will always evaluate to false" because PHPStan thought$data->searchwas stillnulleven after the object was passed to a function that could have modified it.Changes
widenUniversalObjectCrateProperties()method tosrc/Analyser/NodeScopeResolver.phpthat widens stdClass types back to their baseObjectType('stdClass')after argument invalidationinvalidateExpression()in the argument processing code ofprocessArgs()tests/PHPStan/Analyser/nsrt/bug-9181.phpRoot cause
When
(object)['search' => null]is evaluated, PHPStan creates anIntersectionTypeofObjectShapeType{search: null}andObjectType(stdClass). The property type information (nullforsearch) is embedded in the variable's type viaObjectShapeType, not stored as a separate expression type entry in the scope.When the object is passed to a function with side effects,
invalidateExpression($arg, requireMoreCharacters: true)only removes expression type entries that reference the variable (like narrowed$data->searchentries). Since the property type comes from theObjectShapeTypewithin the variable's own type definition, there's nothing inexpressionTypesto invalidate.The fix detects when a stdClass object has additional type information (from
ObjectShapeType) and replaces it with plainObjectType('stdClass')after argument invalidation. This way, subsequent property accesses resolve through stdClass's universal object crate mechanism, returningmixedinstead of the stale specific type.Test
The regression test verifies that after passing a
(object)['search' => null]object to a void method:$data->searchis inferred asmixed(notnull)!== nullcomparison correctly narrows the type tomixed~nullin the true branchFixes phpstan/phpstan#9181