Skip to content

Commit 0a2fc63

Browse files
committed
HasOffsetType - create only for ConstantIntegerType|ConstantStringType
1 parent 5aa9a3c commit 0a2fc63

File tree

8 files changed

+75
-14
lines changed

8 files changed

+75
-14
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -863,14 +863,22 @@ public function specifyTypesInCondition(
863863
&& $var->dim !== null
864864
&& !$scope->getType($var->var) instanceof MixedType
865865
) {
866-
$type = $this->create(
867-
$var->var,
868-
new HasOffsetType($scope->getType($var->dim)),
869-
$context,
870-
false,
871-
$scope,
872-
$rootExpr,
873-
)->unionWith(
866+
$dimType = $scope->getType($var->dim);
867+
868+
if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
869+
$type = $this->create(
870+
$var->var,
871+
new HasOffsetType($dimType),
872+
$context,
873+
false,
874+
$scope,
875+
$rootExpr,
876+
);
877+
} else {
878+
$type = new SpecifiedTypes();
879+
}
880+
881+
$type = $type->unionWith(
874882
$this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr),
875883
);
876884
} else {

src/Type/Accessory/HasOffsetType.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use PHPStan\TrinaryLogic;
66
use PHPStan\Type\CompoundType;
7+
use PHPStan\Type\Constant\ConstantIntegerType;
8+
use PHPStan\Type\Constant\ConstantStringType;
79
use PHPStan\Type\ConstantScalarType;
810
use PHPStan\Type\ErrorType;
911
use PHPStan\Type\IntersectionType;
@@ -33,11 +35,17 @@ class HasOffsetType implements CompoundType, AccessoryType
3335
use NonRemoveableTypeTrait;
3436
use NonGeneralizableTypeTrait;
3537

36-
/** @api */
38+
/**
39+
* @api
40+
* @param ConstantStringType|ConstantIntegerType $offsetType
41+
*/
3742
public function __construct(private Type $offsetType)
3843
{
3944
}
4045

46+
/**
47+
* @return ConstantStringType|ConstantIntegerType
48+
*/
4149
public function getOffsetType(): Type
4250
{
4351
return $this->offsetType;

src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use PHPStan\Reflection\FunctionReflection;
1212
use PHPStan\Type\Accessory\HasOffsetType;
1313
use PHPStan\Type\ArrayType;
14+
use PHPStan\Type\Constant\ConstantIntegerType;
15+
use PHPStan\Type\Constant\ConstantStringType;
1416
use PHPStan\Type\FunctionTypeSpecifyingExtension;
1517
use PHPStan\Type\MixedType;
1618
use PHPStan\Type\TypeCombinator;
@@ -47,6 +49,9 @@ public function specifyTypes(
4749
return new SpecifiedTypes();
4850
}
4951
$keyType = $scope->getType($node->getArgs()[0]->value);
52+
if (!$keyType instanceof ConstantIntegerType && !$keyType instanceof ConstantStringType) {
53+
return new SpecifiedTypes();
54+
}
5055

5156
if ($context->truthy()) {
5257
$type = TypeCombinator::intersect(

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,7 @@ public function testBug7275(): void
865865
public function testBug7500(): void
866866
{
867867
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7500.php');
868-
$this->assertCount(1, $errors);
869-
$this->assertSame('Method Bug7500\HelloWorld::computeForFrontByPosition() should return array<T of Bug7500\PositionEntityInterface&Bug7500\TgEntityInterface> but returns array<Bug7500\PositionEntityInterface&Bug7500\TgEntityInterface>.', $errors[0]->getMessage());
870-
$this->assertSame(38, $errors[0]->getLine());
868+
$this->assertNoErrors($errors);
871869
}
872870

873871
public function testBug7554(): void

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8334,7 +8334,7 @@ public function dataIsset(): array
83348334
'$anotherArrayCopy',
83358335
],
83368336
[
8337-
"array<'a'|'b'|'c', 1|2|3|4|null>",
8337+
'array{a: 1|2|3, b?: 2|3|null, c?: 4}',
83388338
'$yetAnotherArrayCopy',
83398339
],
83408340
[

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,7 @@ public function dataFileAsserts(): iterable
945945
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7663.php');
946946
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7688.php');
947947
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7689.php');
948+
yield from $this->gatherAssertTypes(__DIR__ . '/data/has-offset-type-bug.php');
948949
}
949950

950951
/**

tests/PHPStan/Analyser/data/bug-6399.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ final public function __destruct(){
1515
if(self::$threadLocalStorage !== null){
1616
assertType('ArrayObject<int, array<string, mixed>>', self::$threadLocalStorage);
1717
if (isset(self::$threadLocalStorage[$h = spl_object_id($this)])) {
18-
assertType('ArrayObject<int, array<string, mixed>>&hasOffset(int)', self::$threadLocalStorage);
18+
assertType('ArrayObject<int, array<string, mixed>>', self::$threadLocalStorage);
1919
unset(self::$threadLocalStorage[$h]);
2020
assertType('ArrayObject<int, array<string, mixed>>', self::$threadLocalStorage);
2121
if(self::$threadLocalStorage->count() === 0){
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace HasOffsetTypeBug;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param string[] $errorMessages
12+
* @return void
13+
*/
14+
public function doFoo(array $errorMessages): void
15+
{
16+
$fileErrorsCounts = [];
17+
assertType('array{}', $fileErrorsCounts);
18+
foreach ($errorMessages as $errorMessage) {
19+
assertType('string', $errorMessage);
20+
if (!isset($fileErrorsCounts[$errorMessage])) {
21+
assertType('array<string, int<1, max>>', $fileErrorsCounts);
22+
assertType('int<1, max>', $fileErrorsCounts[$errorMessage]);
23+
$fileErrorsCounts[$errorMessage] = 1;
24+
assertType('non-empty-array<string, int<1, max>>', $fileErrorsCounts);
25+
assertType('1', $fileErrorsCounts[$errorMessage]);
26+
continue;
27+
}
28+
29+
assertType('array<string, int<1, max>>', $fileErrorsCounts);
30+
assertType('int<1, max>', $fileErrorsCounts[$errorMessage]);
31+
32+
$fileErrorsCounts[$errorMessage]++;
33+
34+
assertType('non-empty-array<string, int<1, max>>', $fileErrorsCounts);
35+
assertType('int<2, max>', $fileErrorsCounts[$errorMessage]);
36+
}
37+
38+
assertType('array<string, int<1, max>>', $fileErrorsCounts);
39+
}
40+
41+
}

0 commit comments

Comments
 (0)