Repository navigation
Preserve property narrowing in match expression results #6708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -543,10 +543,18 @@ private function projectsVoidToNull(bool $nativeTypesPromoted): bool | |
| */ | ||
| public function getKeepVoidType(bool $nativeTypesPromoted): Type | ||
| { | ||
| // 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)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this if-block still serve a purpose?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. |
||
| return $nativeTypesPromoted ? $this->getNativeType() : $this->getType(); | ||
|
staabm marked this conversation as resolved.
Resolved
|
||
| } | ||
|
|
||
| $rawType = $this->resolveOwnRawType($nativeTypesPromoted); | ||
| if (!$rawType->isVoid()->no()) { | ||
| // there is void to keep - the raw type is the answer, and no read that | ||
| // projects it away may run | ||
| $projectedType = $this->projectVoidToNull($rawType, $nativeTypesPromoted); | ||
| if ($projectedType !== $rawType && !$projectedType->equals($rawType)) { | ||
| // Keep the raw type only when projection removes an actual void arm. | ||
| // mixed::isVoid() is maybe, but its projection leaves it unchanged. | ||
| return $rawType; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| <?php // lint >= 8.4 | ||
|
|
||
| namespace Bug15433PropertyHooks; | ||
|
|
||
| use function PHPStan\Testing\assertNativeType; | ||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| interface Value | ||
| { | ||
|
|
||
| public mixed $value { get; } | ||
|
|
||
| } | ||
|
|
||
| function normalize(Value $object): void | ||
| { | ||
| $matched = match (true) { | ||
| is_array($object->value) => $object->value, | ||
| default => [$object->value], | ||
| }; | ||
| assertType('array<mixed>', $matched); | ||
| assertNativeType('array<mixed>', $matched); | ||
|
|
||
| $reversed = match (true) { | ||
| !is_array($object->value) => [$object->value], | ||
| default => $object->value, | ||
| }; | ||
| assertType('array<mixed>', $reversed); | ||
| assertNativeType('array<mixed>', $reversed); | ||
|
|
||
| if (is_array($object->value)) { | ||
| $branched = $object->value; | ||
| } else { | ||
| $branched = [$object->value]; | ||
| } | ||
| assertType('array<mixed>', $branched); | ||
| assertNativeType('array<mixed>', $branched); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| <?php // lint >= 8.0 | ||
|
|
||
| namespace Bug15433; | ||
|
|
||
| use function PHPStan\Testing\assertNativeType; | ||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| class Value | ||
| { | ||
|
|
||
| public mixed $value; | ||
|
|
||
| /** @phpstan-pure */ | ||
| public function getValue(): mixed | ||
| { | ||
| return $this->value; | ||
| } | ||
|
|
||
| /** @phpstan-pure */ | ||
| public static function getStaticValue(mixed $value): mixed | ||
| { | ||
| return $value; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| /** @phpstan-pure */ | ||
| function getValue(mixed $value): mixed | ||
| { | ||
| return $value; | ||
| } | ||
|
|
||
| function normalize(Value $object, mixed $value): void | ||
| { | ||
| $matched = match (true) { | ||
| is_array($object->value) => $object->value, | ||
| default => [$object->value], | ||
| }; | ||
| assertType('array<mixed>', $matched); | ||
| assertNativeType('array<mixed>', $matched); | ||
|
|
||
| $reversed = match (true) { | ||
| !is_array($object->value) => [$object->value], | ||
| default => $object->value, | ||
| }; | ||
| assertType('array<mixed>', $reversed); | ||
| assertNativeType('array<mixed>', $reversed); | ||
|
|
||
| $integer = match (true) { | ||
| is_int($object->value) => $object->value, | ||
| default => 0, | ||
| }; | ||
| assertType('int', $integer); | ||
| assertNativeType('int', $integer); | ||
|
|
||
| $variable = match (true) { | ||
| is_array($value) => $value, | ||
| default => [$value], | ||
| }; | ||
| assertType('array<mixed>', $variable); | ||
| assertNativeType('array<mixed>', $variable); | ||
| } | ||
|
|
||
| function normalizeCalls(Value $object, mixed $value): void | ||
| { | ||
| $method = match (true) { | ||
| is_array($object->getValue()) => $object->getValue(), | ||
| default => [$object->getValue()], | ||
| }; | ||
| assertType('array<mixed>', $method); | ||
| assertNativeType('array<mixed>', $method); | ||
|
|
||
| $staticMethod = match (true) { | ||
| is_array(Value::getStaticValue($value)) => Value::getStaticValue($value), | ||
| default => [Value::getStaticValue($value)], | ||
| }; | ||
| assertType('array<mixed>', $staticMethod); | ||
| assertNativeType('array<mixed>', $staticMethod); | ||
|
|
||
| $function = match (true) { | ||
| is_array(getValue($value)) => getValue($value), | ||
| default => [getValue($value)], | ||
| }; | ||
| assertType('array<mixed>', $function); | ||
| assertNativeType('array<mixed>', $function); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.