Skip to content

Keep list on unset() with nested dim-fetch #3964

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

Open
wants to merge 17 commits into
base: 2.1.x
Choose a base branch
from
11 changes: 10 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5919,9 +5919,18 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
}
$offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types));
}
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0);

$arrayDimFetch = $dimFetchStack[$i] ?? null;
if (
$offsetType !== null
&& $arrayDimFetch !== null
&& $scope->hasExpressionType($arrayDimFetch)->yes()
) {
$valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite);
} else {
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0);
}

if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) {
continue;
}
Expand Down
6 changes: 1 addition & 5 deletions src/Type/Accessory/AccessoryArrayListType.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni

public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
{
if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes()) {
return $this;
}

return new ErrorType();
return $this;
}

public function unsetOffset(Type $offsetType): Type
Expand Down
4 changes: 4 additions & 0 deletions src/Type/Accessory/HasOffsetValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni

public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
{
if (!$offsetType->equals($this->offsetType)) {
return $this;
}

return new self($this->offsetType, $valueType);
}

Expand Down
20 changes: 17 additions & 3 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,9 @@
$offsetType = $offsetType->toArrayKey();
}

if ($offsetType instanceof ConstantStringType || $offsetType instanceof ConstantIntegerType) {

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.2)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.3)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.4)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.1)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 331 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.
if ($offsetType->isSuperTypeOf($this->keyType)->yes()) {
$builder = ConstantArrayTypeBuilder::createEmpty();

Check failure on line 333 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path /home/runner/work/phpstan-src/phpstan-src/src/Type/ArrayType.php is expected to occur 2 times, but occurred 3 times.

Check failure on line 333 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Ignored error pattern #^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$# (phpstanApi.instanceofType) in path D:\a\phpstan-src\phpstan-src\src\Type\ArrayType.php is expected to occur 2 times, but occurred 3 times.
$builder->setOffsetValueType($offsetType, $valueType);
return $builder->getArray();
}
Expand All @@ -356,9 +356,23 @@

public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
{
return new self(
$this->keyType,
TypeCombinator::union($this->itemType, $valueType),
if ($offsetType instanceof ConstantStringType || $offsetType instanceof ConstantIntegerType) {
Copy link
Member

Choose a reason for hiding this comment

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

The idea is that we're overwriting an existing offset so $this->keyType should stay intact as is in the original version of this method.

Of course setExistingOffsetValueType was used when unsetting an offset so some details somewhere might be wrong, but what you're doing here now is basically the same thing setOffsetValueType is doing so it seems wrong.

return TypeCombinator::intersect(
new self(
TypeCombinator::union($this->keyType, $offsetType),
TypeCombinator::union($this->itemType, $valueType),
),
new HasOffsetValueType($offsetType, $valueType),
new NonEmptyArrayType(),
);
}

return TypeCombinator::intersect(
new self(
$this->keyType,
TypeCombinator::union($this->itemType, $valueType),
),
new NonEmptyArrayType(),
);
}

Expand All @@ -367,9 +381,9 @@
$offsetType = $offsetType->toArrayKey();

if (
($offsetType instanceof ConstantIntegerType || $offsetType instanceof ConstantStringType)

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.2)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.3)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.4)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.1)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 384 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.
&& !$this->keyType->isSuperTypeOf($offsetType)->no()
) {

Check failure on line 386 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.

Check failure on line 386 in src/Type/ArrayType.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.
$keyType = TypeCombinator::remove($this->keyType, $offsetType);
if ($keyType instanceof NeverType) {
return new ConstantArrayType([], []);
Expand Down
9 changes: 1 addition & 8 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,15 +686,8 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni

public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
{
$offsetType = $offsetType->toArrayKey();
$builder = ConstantArrayTypeBuilder::createFromConstantArray($this);
foreach ($this->keyTypes as $keyType) {
if ($offsetType->isSuperTypeOf($keyType)->no()) {
continue;
}

$builder->setOffsetValueType($keyType, $valueType);
}
$builder->setOffsetValueType($offsetType, $valueType);

return $builder->getArray();
}
Expand Down
4 changes: 4 additions & 0 deletions src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,10 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni
}
}

if ($this->isList()->yes() && $this->getIterableValueType()->isArray()->yes()) {
$result = TypeCombinator::intersect($result, new AccessoryArrayListType());
}

return $result;
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ private static function findTestFiles(): iterable
yield __DIR__ . '/../Rules/Arrays/data/bug-11679.php';
yield __DIR__ . '/../Rules/Methods/data/bug-4801.php';
yield __DIR__ . '/../Rules/Arrays/data/narrow-superglobal.php';
yield __DIR__ . '/../Rules/Methods/data/bug-12927.php';
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/bug-12274.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ function testKeepNestedListAfterIssetIndex(array $nestedList, int $i, int $j): v
assertType('list<list<int>>', $nestedList);
assertType('list<int>', $nestedList[$i]);
$nestedList[$i][$j] = 21;
assertType('non-empty-list<non-empty-list<int>>', $nestedList);
assertType('non-empty-list<int>', $nestedList[$i]);
assertType('non-empty-list<list<int>>', $nestedList);
assertType('list<int>', $nestedList[$i]);
}
assertType('list<list<int>>', $nestedList);
}
Expand Down
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,11 @@ public function testBug1O580(): void
]);
}

public function testBug12927(): void
{
$this->analyse([__DIR__ . '/data/bug-12927.php'], []);
}

public function testBug4443(): void
{
if (PHP_VERSION_ID < 80000) {
Expand Down
63 changes: 63 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-12927.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Bug12927;

use function PHPStan\Testing\assertType;

class HelloWorld
{
/**
* @param list<array{abc: string}> $list
* @return list<array<string>>
*/
public function sayHello(array $list): array
{
foreach($list as $k => $v) {
unset($list[$k]['abc']);
assertType('non-empty-list<array{}|array{abc: string}>', $list);
assertType('array{}|array{abc: string}', $list[$k]);
}
return $list;
}

/**
* @param list<array<string, string>> $list
*/
public function sayFoo(array $list): void
{
foreach($list as $k => $v) {
unset($list[$k]['abc']);
assertType('non-empty-list<array<string, string>>', $list);
assertType('array<string, string>', $list[$k]);
}
assertType('list<array<string, string>>', $list);
}

/**
* @param list<array<string, string>> $list
*/
public function sayFoo2(array $list): void
{
foreach($list as $k => $v) {
$list[$k]['abc'] = 'world';
assertType("non-empty-list<non-empty-array<string, string>&hasOffsetValue('abc', 'world')>", $list);
assertType("non-empty-array<string, string>&hasOffsetValue('abc', 'world')", $list[$k]);
}
assertType("list<non-empty-array<string, string>&hasOffsetValue('abc', 'world')>", $list);
}

/**
* @param list<array<string, string>> $list
*/
public function sayFooBar(array $list): void
{
foreach($list as $k => $v) {
if (rand(0,1)) {
unset($list[$k]);
}
assertType('array<int<0, max>, array<string, string>>', $list);
assertType('array<string, string>', $list[$k]);
}
assertType('array<string, string>', $list[$k]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -779,4 +779,20 @@ public function testPropertyHooks(): void
]);
}

public function testBug11171(): void
{
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-11171.php'], []);
}

public function testBug8282(): void
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('Test requires PHP 8.0.');
}

$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-8282.php'], []);
}

}
41 changes: 41 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-11171.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Bug11171;

class TypeExpression
{
public string $value;

/**
* @var list<array{start_index: int, expression: self}>
*/
public array $innerTypeExpressions = [];

/**
* @param \Closure(self): void $callback
*/
public function walkTypes(\Closure $callback): void
{
$startIndexOffset = 0;

foreach ($this->innerTypeExpressions as $k => ['start_index' => $startIndexOrig,
'expression' => $inner,]) {
$this->innerTypeExpressions[$k]['start_index'] += $startIndexOffset;

$innerLengthOrig = \strlen($inner->value);

$inner->walkTypes($callback);

$this->value = substr_replace(
$this->value,
$inner->value,
$startIndexOrig + $startIndexOffset,
$innerLengthOrig
);

$startIndexOffset += \strlen($inner->value) - $innerLengthOrig;
}

$callback($this);
}
}
31 changes: 31 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-8282.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php // lint >= 8.0

namespace Bug8282;

/**
* @phpstan-type record array{id: positive-int, name: string}
*/
class Collection
{
/** @param list<record> $list */
public function __construct(
public array $list
)
{
}

public function updateName(int $index, string $name): void
{
assert(isset($this->list[$index]));
$this->list[$index]['name'] = $name;
}

public function updateNameById(int $id, string $name): void
{
foreach ($this->list as $index => $entry) {
if ($entry['id'] === $id) {
$this->list[$index]['name'] = $name;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Rules\Rule as TRule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<ParameterOutAssignedTypeRule>
Expand Down Expand Up @@ -43,7 +44,7 @@ public function testRule(): void
47,
],
[
'Parameter &$p @param-out type of method ParameterOutAssignedType\Foo::doBaz3() expects list<list<int>>, array<int<0, max>, array<int<0, max>, int>> given.',
'Parameter &$p @param-out type of method ParameterOutAssignedType\Foo::doBaz3() expects list<list<int>>, list<array<int<0, max>, int>> given.',
56,
],
[
Expand All @@ -64,4 +65,12 @@ public function testBenevolentArrayKey(): void
$this->analyse([__DIR__ . '/data/benevolent-array-key.php'], []);
}

public function testBug12754(): void
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped('PHP 8.0+ is required for this test.');
}
$this->analyse([__DIR__ . '/data/bug-12754.php'], []);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ public function testBug11363(): void
$this->analyse([__DIR__ . '/data/bug-11363.php'], []);
}

public function testBug12330(): void
{
$this->analyse([__DIR__ . '/data/bug-12330.php'], []);
}

}
25 changes: 25 additions & 0 deletions tests/PHPStan/Rules/Variables/data/bug-12330.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Bug12330;

/**
* @param array{items: list<array<string, mixed>>} $options
* @param-out array{items: list<array<string, mixed>>} $options
*/
function alterItems(array &$options): void
{
foreach ($options['items'] as $i => $item) {
$options['items'][$i]['options']['title'] = $item['name'];
}
}

/**
* @param array{items: array<int, array<string, mixed>>} $options
* @param-out array{items: array<int, array<string, mixed>>} $options
*/
function alterItems2(array &$options): void
{
foreach ($options['items'] as $i => $item) {
$options['items'][$i]['options']['title'] = $item['name'];
}
}
26 changes: 26 additions & 0 deletions tests/PHPStan/Rules/Variables/data/bug-12754.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Bug12754;

class HelloWorld
{
/**
* @param list<array{string, string}> $list
* @return void
*/
public function modify(array &$list): void
{
foreach ($list as $int => $array) {
$list[$int][1] = $this->apply($array[1]);
}
}

/**
* @param string $value
* @return string
*/
public function apply(string $value): mixed
{
return $value;
}
}
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Variables/data/bug-8113.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ function () {
];
assertType("non-empty-array<array<mixed>>&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', non-empty-array&hasOffsetValue('review', null))", $review);
unset($review['SurveyInvitation']['review']);
assertType("non-empty-array<array<mixed>>&hasOffsetValue('Review', array<mixed~'review', mixed>)&hasOffsetValue('SurveyInvitation', array<mixed~'review', mixed>)", $review);
assertType("non-empty-array<array<mixed>>&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', array<mixed~'review', mixed>)", $review);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this expectation change is correct.. previously unset($review['SurveyInvitation']['review']); affected the offset Review (uppercase R) which was a bug IMO

}
assertType('array<array<mixed>>', $review);
if (array_key_exists('User', $review['Review'])) {
assertType("non-empty-array<array<mixed>>&hasOffsetValue('Review', non-empty-array&hasOffset('User'))", $review);
$review['User'] = $review['Review']['User'];
assertType("non-empty-array&hasOffsetValue('Review', non-empty-array&hasOffset('User'))&hasOffsetValue('User', mixed)", $review);
unset($review['Review']['User']);
assertType("non-empty-array&hasOffsetValue('Review', array<mixed~'User', mixed>)&hasOffsetValue('User', array<mixed~'User', mixed>)", $review);
assertType("non-empty-array&hasOffsetValue('Review', array<mixed~'User', mixed>)&hasOffsetValue('User', mixed)", $review);
}
assertType("non-empty-array&hasOffsetValue('Review', array)", $review);
};
Loading