Repository navigation
Preserve property narrowing in match expression results - #6708
Conversation
ecc8927 to
0660e88
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The expected Turbo version does not match the rebased native-source commit SHA.
1 open finding
What changed in this PR
Preserves narrowed property types when computing match expression results, fixing #15433.
Changes:
- Uses holder-aware type reads where void-to-null projection is unnecessary.
- Mirrors behavior in Turbo and adds property regression coverage.
- Updates the expected Turbo extension version.
| File | Description |
|---|---|
src/Analyser/ExpressionResult.php |
Preserves narrowed holder types. |
turbo-ext/src/ExpressionResult.cpp |
Mirrors the PHP implementation. |
tests/PHPStan/Analyser/nsrt/bug-15433.php |
Tests ordinary properties and controls. |
tests/PHPStan/Analyser/nsrt/bug-15433-property-hooks.php |
Tests PHP 8.4 property hooks. |
src/Turbo/TurboExtensionEnabler.php |
Updates the expected native version. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
0660e88 to
5f101c9
Compare
|
please double check whether the failling CI checks are related |
| zend_long projects = projectsVoidToNull(nativeTypesPromoted); | ||
| if (UNEXPECTED(projects < 0)) return zv::Val(); | ||
| if (projects == 0) { | ||
| return nativeTypesPromoted ? getNativeType() : getType(); |
|
I checked the five failures on The Rector, Rector downgrade, Laravel collection and PHP 7.4 Update, October 13: CI on |
| // Only a call's value read can project void to null. Other expressions | ||
| // must honour their narrowed holder even when the raw type is mixed | ||
| // (whose isVoid() is maybe, not evidence of an actual void arm). | ||
| if (!$this->projectsVoidToNull($nativeTypesPromoted)) { |
There was a problem hiding this comment.
does this if-block still serve a purpose?
There was a problem hiding this comment.
It is no longer needed for correctness after the projection comparison below, but it keeps the non-projecting fast path. With a tracked holder, getType()/getNativeType() can return the narrowed type without resolving the raw callback; I checked both flavours and removing this guard changes that from zero callback calls to one, with the same final type.
Preserve narrowed mixed calls when keeping void
89bb51a to
867f37c
Compare
|
thank you! |


Fixes phpstan/phpstan#15433.
match (true)currently loses narrowing when an arm returns amixedproperty. Normalizing a property to an array infersmixed, while the equivalentif/elsecorrectly infersarray<mixed>. This also affects ordinary properties, not only PHP 8.4 interface property hooks.ExpressionResult::getKeepVoidType()returns the raw type wheneverisVoid()is not no. Formixed, that answer is maybe, so the method bypasses the already-narrowed expression holder. The raw type is now kept only when void-to-null projection actually removes a void component. Otherwise the normal holder-aware type read preserves narrowing, including repeatable calls returningmixed. ReusingprojectVoidToNull()shares its non-void/non-union fast path, and an identity check avoids comparing unchanged types. The native implementation mirrors the change.The regressions cover ordinary and hooked properties, reversed conditions, integer narrowing, native types, and unchanged variable/if controls. Added pure function, instance-method and static-method cases catch the same narrowing loss for calls returning
mixed. Unit controls verify explicit void and void-containing unions are preserved, while ordinary reads still project void to null. The original property fixtures failed before the initial fix; the call cases fail before the review follow-up in both PHP and Turbo.Validation: