Skip to content

Commit 774f1ab

Browse files
committed
Fix crash about ObjectShapeType property with numeric name
1 parent 331fc0b commit 774f1ab

File tree

5 files changed

+39
-22
lines changed

5 files changed

+39
-22
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ parameters:
3333
-
3434
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
3535
identifier: phpstanApi.instanceofType
36-
count: 3
36+
count: 2
3737
path: src/Analyser/MutatingScope.php
3838

3939
-

src/Analyser/MutatingScope.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,10 +1722,6 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
17221722
$properties = [];
17231723
$optionalProperties = [];
17241724
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
1725-
if (!$keyType instanceof ConstantStringType) {
1726-
// an object with integer properties is >weird<
1727-
continue;
1728-
}
17291725
$valueType = $constantArray->getValueTypes()[$i];
17301726
$optional = $constantArray->isOptionalKey($i);
17311727
if ($optional) {

src/Type/ObjectShapeType.php

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,23 @@ class ObjectShapeType implements Type
4444

4545
/**
4646
* @api
47-
* @param array<string, Type> $properties
48-
* @param list<string> $optionalProperties
47+
* @param array<int|string, Type> $properties
48+
* @param list<int|string> $optionalProperties
4949
*/
5050
public function __construct(private array $properties, private array $optionalProperties)
5151
{
5252
}
5353

5454
/**
55-
* @return array<string, Type>
55+
* @return array<int|string, Type>
5656
*/
5757
public function getProperties(): array
5858
{
5959
return $this->properties;
6060
}
6161

6262
/**
63-
* @return list<string>
63+
* @return list<int|string>
6464
*/
6565
public function getOptionalProperties(): array
6666
{
@@ -143,7 +143,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
143143
$result = AcceptsResult::createYes();
144144
$scope = new OutOfClassScope();
145145
foreach ($this->properties as $propertyName => $propertyType) {
146-
$typeHasProperty = $type->hasProperty($propertyName);
146+
$typeHasProperty = $type->hasProperty((string) $propertyName);
147147
$hasProperty = new AcceptsResult(
148148
$typeHasProperty,
149149
$typeHasProperty->yes() ? [] : [
@@ -174,7 +174,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
174174

175175
$result = $result->and($hasProperty);
176176
try {
177-
$otherProperty = $type->getProperty($propertyName, $scope);
177+
$otherProperty = $type->getProperty((string) $propertyName, $scope);
178178
} catch (MissingPropertyFromReflectionException) {
179179
return AcceptsResult::createNo(
180180
[
@@ -260,7 +260,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
260260
$result = IsSuperTypeOfResult::createYes();
261261
$scope = new OutOfClassScope();
262262
foreach ($this->properties as $propertyName => $propertyType) {
263-
$hasProperty = new IsSuperTypeOfResult($type->hasProperty($propertyName), []);
263+
$hasProperty = new IsSuperTypeOfResult($type->hasProperty((string) $propertyName), []);
264264
if ($hasProperty->no()) {
265265
if (in_array($propertyName, $this->optionalProperties, true)) {
266266
continue;
@@ -279,7 +279,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
279279

280280
$result = $result->and($hasProperty);
281281
try {
282-
$otherProperty = $type->getProperty($propertyName, $scope);
282+
$otherProperty = $type->getProperty((string) $propertyName, $scope);
283283
} catch (MissingPropertyFromReflectionException) {
284284
return IsSuperTypeOfResult::createNo(
285285
[
@@ -354,7 +354,7 @@ public function tryRemove(Type $typeToRemove): ?Type
354354
if ($typeToRemove instanceof HasPropertyType) {
355355
$properties = $this->properties;
356356
unset($properties[$typeToRemove->getPropertyName()]);
357-
$optionalProperties = array_values(array_filter($this->optionalProperties, static fn (string $propertyName) => $propertyName !== $typeToRemove->getPropertyName()));
357+
$optionalProperties = array_values(array_filter($this->optionalProperties, static fn (int|string $propertyName) => $propertyName !== $typeToRemove->getPropertyName()));
358358

359359
return new self($properties, $optionalProperties);
360360
}
@@ -365,7 +365,7 @@ public function tryRemove(Type $typeToRemove): ?Type
365365
public function makePropertyRequired(string $propertyName): self
366366
{
367367
if (array_key_exists($propertyName, $this->properties)) {
368-
$optionalProperties = array_values(array_filter($this->optionalProperties, static fn (string $currentPropertyName) => $currentPropertyName !== $propertyName));
368+
$optionalProperties = array_values(array_filter($this->optionalProperties, static fn (int|string $currentPropertyName) => $currentPropertyName !== $propertyName));
369369

370370
return new self($this->properties, $optionalProperties);
371371
}
@@ -383,12 +383,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
383383
$typeMap = TemplateTypeMap::createEmpty();
384384
$scope = new OutOfClassScope();
385385
foreach ($this->properties as $name => $propertyType) {
386-
if ($receivedType->hasProperty($name)->no()) {
386+
if ($receivedType->hasProperty((string) $name)->no()) {
387387
continue;
388388
}
389389

390390
try {
391-
$receivedProperty = $receivedType->getProperty($name, $scope);
391+
$receivedProperty = $receivedType->getProperty((string) $name, $scope);
392392
} catch (MissingPropertyFromReflectionException) {
393393
continue;
394394
}
@@ -474,10 +474,10 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
474474

475475
$scope = new OutOfClassScope();
476476
foreach ($this->properties as $name => $propertyType) {
477-
if (!$right->hasProperty($name)->yes()) {
477+
if (!$right->hasProperty((string) $name)->yes()) {
478478
return $this;
479479
}
480-
$transformed = $cb($propertyType, $right->getProperty($name, $scope)->getReadableType());
480+
$transformed = $cb($propertyType, $right->getProperty((string) $name, $scope)->getReadableType());
481481
if ($transformed !== $propertyType) {
482482
$stillOriginal = false;
483483
}
@@ -513,10 +513,10 @@ public function toPhpDocNode(): TypeNode
513513
{
514514
$items = [];
515515
foreach ($this->properties as $name => $type) {
516-
if (ConstantArrayType::isValidIdentifier($name)) {
517-
$keyNode = new IdentifierTypeNode($name);
516+
if (ConstantArrayType::isValidIdentifier((string) $name)) {
517+
$keyNode = new IdentifierTypeNode((string) $name);
518518
} else {
519-
$keyPhpDocNode = (new ConstantStringType($name))->toPhpDocNode();
519+
$keyPhpDocNode = (new ConstantStringType((string) $name))->toPhpDocNode();
520520
if (!$keyPhpDocNode instanceof ConstTypeNode) {
521521
continue;
522522
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,12 @@ public function testBug5091(): void
12251225
$this->assertNoErrors($errors);
12261226
}
12271227

1228+
public function testBug13507(): void
1229+
{
1230+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-13507.php');
1231+
$this->assertNoErrors($errors);
1232+
}
1233+
12281234
public function testBug9459(): void
12291235
{
12301236
$errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php');
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Bug13507;
4+
5+
/**
6+
* @return object{
7+
* '-1': bool,
8+
* }
9+
*/
10+
function test(bool $value): object
11+
{
12+
return (object)[
13+
'-1' => $value,
14+
];
15+
}

0 commit comments

Comments
 (0)