Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 7 additions & 1 deletion src/Type/Accessory/AccessoryNumericStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,13 @@ public function toArray(): Type

public function toArrayKey(): Type
{
return new IntegerType();
return new UnionType([
new IntegerType(),
new IntersectionType([
new StringType(),
new AccessoryNumericStringType(),
]),
]);
}

public function isNull(): TrinaryLogic
Expand Down
12 changes: 9 additions & 3 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,17 @@ public function findTypeAndMethodNames(): array

public function hasOffsetValueType(Type $offsetType): TrinaryLogic
{
$offsetType = $offsetType->toArrayKey();
$offsetArrayKeyType = $offsetType->toArrayKey();

return $this->recursiveHasOffsetValueType($offsetArrayKeyType);
}

private function recursiveHasOffsetValueType(Type $offsetType): TrinaryLogic
{
if ($offsetType instanceof UnionType) {
$results = [];
foreach ($offsetType->getTypes() as $innerType) {
$results[] = $this->hasOffsetValueType($innerType);
$results[] = $this->recursiveHasOffsetValueType($innerType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With the previous logic, toArrayKey was again called, creating a new UnionType for numeric string and an infinite loop.

}

return TrinaryLogic::extremeIdentity(...$results);
Expand All @@ -623,7 +629,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
if ($finiteTypes !== []) {
$results = [];
foreach ($finiteTypes as $innerType) {
$results[] = $this->hasOffsetValueType($innerType);
$results[] = $this->recursiveHasOffsetValueType($innerType);
}

return TrinaryLogic::extremeIdentity(...$results);
Expand Down
5 changes: 4 additions & 1 deletion src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,10 @@ public function toArray(): Type
public function toArrayKey(): Type
{
if ($this->isNumericString()->yes()) {
return new IntegerType();
return new UnionType([
Copy link
Member

Choose a reason for hiding this comment

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

This should be TypeCombinator::union() instead of new UnionType to ensure type normalization.

new IntegerType(),
$this,
]);
}

if ($this->isString()->yes()) {
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-3133.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function doLorem(
{
$a = [];
$a[$numericString] = 'foo';
assertType('non-empty-array<int, \'foo\'>', $a);
assertType('non-empty-array<int|numeric-string, \'foo\'>', $a);
}

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

namespace Bug8592;

use function PHPStan\Testing\assertType;

/**
* @param array<numeric-string, mixed> $foo
*/
function foo(array $foo): void
{
foreach ($foo as $key => $value) {
assertType('int|numeric-string', $key);
}
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class Bug4671
*/
public function doFoo(int $intput, array $strings): void
{
assertType('false', isset($strings[(string) $intput]));
assertType('bool', isset($strings[(string) $intput]));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,11 @@ public function testBug2634(): void
$this->analyse([__DIR__ . '/data/bug-2634.php'], []);
}

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

public function testInternalClassesWithOverloadedOffsetAccess(): void
{
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access.php'], []);
Expand Down
29 changes: 29 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/bug-11390.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types = 1);

namespace Bug11390;

/**
* @param array<array{
* id: numeric-string,
* tagName: string
* }> $tags
* @param numeric-string $tagId
*/
function printTagName(array $tags, string $tagId): void
{
// Adding the second `*` to either of the following lines makes the error disappear

$tagsById = array_combine(array_column($tags, 'id'), $tags);
if (false !== $tagsById) {
echo $tagsById[$tagId]['tagName'] . PHP_EOL;
}
}

printTagName(
[
['id' => '123', 'tagName' => 'abc'],
['id' => '4.5', 'tagName' => 'def'],
['id' => '6e78', 'tagName' => 'ghi']
],
'4.5'
);
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,16 @@ public function testBug10715(): void
$this->analyse([__DIR__ . '/data/bug-10715.php'], []);
}

public function testBug4163(): void
{
$this->analyse([__DIR__ . '/data/bug-4163.php'], [
[
'Method Bug4163\HelloWorld::lall() should return array<string, string> but returns array<int|string, string>.',
28,
],
]);
}

public function testBug11663(): void
{
$this->analyse([__DIR__ . '/data/bug-11663.php'], []);
Expand Down
30 changes: 30 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4163.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);

namespace Bug4163;

class HelloWorld
{
/**
* @phpstan-var numeric
*/
public $lall = 0;

/**
* @phpstan-return array<string, string>
*/
function lall() {
$helloCollection = [new HelloWorld(), new HelloWorld()];
$result = [];

foreach ($helloCollection as $hello) {
$key = (string)$hello->lall;

if (!isset($result[$key])) {
$lall = 'do_something_here';
$result[$key] = $lall;
}
}

return $result;
}
}
5 changes: 1 addition & 4 deletions tests/PHPStan/Rules/Variables/IssetRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,7 @@ public function testBug4671(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->strictUnnecessaryNullsafePropertyFetch = false;
$this->analyse([__DIR__ . '/data/bug-4671.php'], [[
'Offset numeric-string on array<string, string> in isset() does not exist.',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was wrong

13,
]]);
$this->analyse([__DIR__ . '/data/bug-4671.php'], []);
}

public function testVariableCertaintyInIsset(): void
Expand Down
Loading