Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Analyser/ScopeOps.php
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,15 @@
if ($remainder instanceof NeverType) {
continue;
}
if (
$remainder !== $holder->getType()
&& !$theirHolder->getType()->isSuperTypeOf($remainder)->no()

Check warning on line 510 in src/Analyser/ScopeOps.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ } if ( $remainder !== $holder->getType() - && !$theirHolder->getType()->isSuperTypeOf($remainder)->no() + && !$remainder->isSuperTypeOf($theirHolder->getType())->no() ) { // only part of their type was subtracted (e.g. null from // string|null, but not non-empty-string from string), so the

Check warning on line 510 in src/Analyser/ScopeOps.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ } if ( $remainder !== $holder->getType() - && !$theirHolder->getType()->isSuperTypeOf($remainder)->no() + && !$remainder->isSuperTypeOf($theirHolder->getType())->no() ) { // only part of their type was subtracted (e.g. null from // string|null, but not non-empty-string from string), so the
) {
// only part of their type was subtracted (e.g. null from
// string|null, but not non-empty-string from string), so the
// remainder still overlaps their branch and cannot prove ours
$remainder = $holder->getType();
}
if ($mergedExpressionTypes[$exprString]->getType()->equals($remainder)) {
// matching this guard later would not discriminate the branches -
// the merged scope already guarantees it
Expand Down
77 changes: 77 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-15483.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug15483;

use function PHPStan\Testing\assertType;

interface Input
{
public function isInteractive(): bool;
}

function prompt(): ?string
{
return null;
}

function f(Input $input, mixed $option): void
{
$title = \is_string($option) && $option !== '' ? $option : null;
if ($title === null && $input->isInteractive()) {
$title = prompt();
}
if ($title === null) {
return;
}
assertType('bool', $input->isInteractive());
}

function g(Input $input, mixed $option): void
{
$title = \is_string($option) ? $option : null;
if ($title === null && $input->isInteractive()) {
$title = prompt();
}
if ($title === null) {
return;
}
assertType('bool', $input->isInteractive());
}

/** @param non-empty-string|null $title */
function h(Input $input, ?string $title): void
{
if ($title === null && $input->isInteractive()) {
$title = prompt();
}
if ($title === null) {
return;
}
assertType('bool', $input->isInteractive());
}

/** @param int<1, max>|null $count */
function i(Input $input, ?int $count, ?int $other): void
{
if ($count === null && $input->isInteractive()) {
$count = $other;
}
if ($count === null) {
return;
}
assertType('bool', $input->isInteractive());
}

/** @param non-empty-array<int>|null $items */
function j(Input $input, ?array $items, ?array $other): void
{
if ($items === null && $input->isInteractive()) {
$items = $other;
}
if ($items === null) {
return;
}
assertType('bool', $input->isInteractive());
}
9 changes: 9 additions & 0 deletions turbo-ext/src/ScopeOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,15 @@ class ScopeOps
zv::Val remainder = typeCombinatorRemove(holderType(holder), holderType(theirHolder));
if (UNEXPECTED(remainder.isUndef())) return zv::Val();
if (remainder.ref().instanceOf(neverTypeCe)) continue;
if (Z_OBJ_P(remainder.raw()) != Z_OBJ_P(holderType(holder))) {
/* a partial subtraction that still overlaps their branch cannot
* prove ours - fall back to our full type, like the twin */
zend_long theirIsSuperTypeOfRemainder;
if (UNEXPECTED(!isSuperTypeOfValue(holderType(theirHolder), remainder.raw(), &theirIsSuperTypeOfRemainder))) return zv::Val();
if (theirIsSuperTypeOfRemainder != PT_TRI_NO) {
remainder = zv::Val::copyOf(zv::Ref(holderType(holder)));
}
}
{
zv::Ref mergedHolder = zv::Ref(mergedSlot).deref();
if (UNEXPECTED(!pt_check_holder(mergedHolder.raw()))) return zv::Val();
Expand Down
Loading