Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ parameters:
-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
count: 4
count: 5
path: src/Analyser/TypeSpecifier.php

-
Expand Down
46 changes: 46 additions & 0 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,52 @@
return $exprType;
}

if (
Comment thread
VincentLanglet marked this conversation as resolved.
$issetExpr instanceof ArrayDimFetch
&& $issetExpr->dim !== null
) {
$varType = $scope->getType($issetExpr->var);
if (!$varType instanceof MixedType) {
$dimType = $scope->getType($issetExpr->dim);

if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
$types = $varType instanceof UnionType ? $varType->getTypes() : [$varType];
$typesToRemove = [];
foreach ($types as $innerType) {
$hasOffset = $innerType->hasOffsetValueType($dimType);
if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) {

Check warning on line 936 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $typesToRemove = []; foreach ($types as $innerType) { $hasOffset = $innerType->hasOffsetValueType($dimType); - if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + if (!$hasOffset->yes() || $innerType->getOffsetValueType($dimType)->isNull()->yes()) { continue; }

Check warning on line 936 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $typesToRemove = []; foreach ($types as $innerType) { $hasOffset = $innerType->hasOffsetValueType($dimType); - if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + if ($hasOffset->no() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { continue; }

Check warning on line 936 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $typesToRemove = []; foreach ($types as $innerType) { $hasOffset = $innerType->hasOffsetValueType($dimType); - if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + if (!$hasOffset->yes() || $innerType->getOffsetValueType($dimType)->isNull()->yes()) { continue; }

Check warning on line 936 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $typesToRemove = []; foreach ($types as $innerType) { $hasOffset = $innerType->hasOffsetValueType($dimType); - if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + if ($hasOffset->no() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { continue; }
continue;
}

$typesToRemove[] = $innerType;
}

if ($typesToRemove !== []) {
$typeToRemove = TypeCombinator::union(...$typesToRemove);
$result = $this->create(
$issetExpr->var,
$typeToRemove,
TypeSpecifierContext::createFalse(),
$scope,
)->setRootExpr($expr);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This need to be reworked cf the test

// Could be "array{a: null, c: string}|array{d: string}"
assertType("array{a: string|null, c: string}|array{a?: string, d: string}", $a);

it would be great to refining the constant array without totally removing them

array{a: string|null, c: string} => array{a: null, c: string}
array{a?: string, d: string} => array{c: string}

Do you see an easy way to do such thing @staabm ?

Copy link
Copy Markdown
Contributor

@staabm staabm Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it work to iterate over getConstantArrays() instead?

and re-union the results?

Copy link
Copy Markdown
Contributor Author

@VincentLanglet VincentLanglet Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried lot of different thing without success.
I'm not sure it's easy to remove a constant array {a: string, b: string} from another {a?: string, b: string}.

I used getConstantArrays instead of UnionType, and I think we could merge it like that as a first step to close 5 issues.


if ($scope->hasExpressionType($issetExpr->var)->maybe()) {
$result = $result->unionWith(
$this->create(
new IssetExpr($issetExpr->var),
new NullType(),
TypeSpecifierContext::createTruthy(),
$scope,
)->setRootExpr($expr),
);
}

return $result;
}
}
}
}

return new SpecifiedTypes();
}

Expand Down
57 changes: 57 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-9908.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types = 1);

namespace Bug9908;

use function PHPStan\Testing\assertType;

class HelloWorld
{
public function test(): void
{
$a = [];
if (rand() % 2) {
$a = ['bar' => 'string'];
}

if (isset($a['bar'])) {
$a['bar'] = 1;
}

assertType("array{}|array{bar: 1}", $a);
}

/**
* @param array{bar?: int} $foo
*/
public function sayHello(array $foo): void
{
echo 'Hello' . print_r($foo, true);
}

public function test2(): void
{
$a = [];
if (rand() % 2) {
$a = ['bar' => 'string'];
}

if (isset($a['bar'])) {
$a['bar'] = 1;
}

$this->sayHello($a);
}

/**
* @param array{a: string, b: string}|array{a: string|null, c: string}|array{a?: string, d: string} $a
*/
public function moreTests($a): void
{
if (isset($a['a'])) {
assertType("array{a: string, b: string}|array{a: string, c: string}|array{a: string, d: string}", $a);
} else {
// Could be "array{a: null, c: string}|array{d: string}"
assertType("array{a: string|null, c: string}|array{a?: string, d: string}", $a);
}
}
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/tagged-unions.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function doFoo4(array $foo)
if (isset($foo['C'])) {
assertType("array{A: string, C: 1}", $foo);
} else {
assertType("array{A: int, B: 1}|array{A: string, C: 1}", $foo); // could be array{A: int, B: 1}
assertType("array{A: int, B: 1}", $foo);
}

assertType('array{A: int, B: 1}|array{A: string, C: 1}', $foo);
Expand Down
Loading