Skip to content
1 change: 0 additions & 1 deletion src/Parser/RichParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,12 @@
if ($openParenthesisCount > 0) {
throw new IgnoreParseException('Unexpected end, unclosed opening parenthesis', $tokenLine ?? 1);
}

Check failure on line 359 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 359 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (7.4, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.
if (count($identifiers) === 0) {
throw new IgnoreParseException('Missing identifier', 1);
}

/** @phpstan-ignore return.type (return type is correct, not sure why it's being changed from array shape to key-value shape) */
return $identifiers;

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.2)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.5)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.4)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan with result cache (8.3)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, ubuntu-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.4, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.2, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.

Check failure on line 364 in src/Parser/RichParser.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.1, windows-latest)

Method PHPStan\Parser\RichParser::parseIdentifiers() should return non-empty-list<array{name: string, comment: string|null}> but returns non-empty-list<array<'comment'|'name', string|null>>.
}

}
84 changes: 62 additions & 22 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,24 +705,7 @@ static function (string $variance): TemplateTypeVariance {
if (count($genericTypes) === 1) { // array<ValueType>
$arrayType = new ArrayType((new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey(), $genericTypes[0]);
} elseif (count($genericTypes) === 2) { // array<KeyType, ValueType>
$originalKey = $genericTypes[0];
if ($this->reportUnsafeArrayStringKeyCasting === ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
$originalKey = TypeTraverser::map($originalKey, static function (Type $type, callable $traverse) {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}

if ($type instanceof StringType) {
return TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true));
}

return $type;
});
}
$keyType = TypeCombinator::intersect($originalKey->toArrayKey(), new UnionType([
new IntegerType(),
new StringType(),
]))->toArrayKey();
$keyType = $this->transformUnsafeArrayKey($genericTypes[0]);
$finiteTypes = $keyType->getFiniteTypes();
if (
count($finiteTypes) === 1
Expand Down Expand Up @@ -1002,6 +985,28 @@ static function (string $variance): TemplateTypeVariance {
return new ErrorType();
}

private function transformUnsafeArrayKey(Type $keyType): Type
{
if ($this->reportUnsafeArrayStringKeyCasting === ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
$keyType = TypeTraverser::map($keyType, static function (Type $type, callable $traverse) {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}

if ($type instanceof StringType) {
return TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true));
}

return $type;
});
}

return TypeCombinator::intersect($keyType->toArrayKey(), new UnionType([
new IntegerType(),
new StringType(),
]))->toArrayKey();
}

private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type
{
$templateTags = [];
Expand Down Expand Up @@ -1101,13 +1106,48 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
$builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional);
}

$isList = in_array($typeNode->kind, [
ArrayShapeNode::KIND_LIST,
ArrayShapeNode::KIND_NON_EMPTY_LIST,
], true);

if (!$typeNode->sealed) {
if ($typeNode->unsealedType === null) {
if ($isList) {
$unsealedKeyType = IntegerRangeType::createAllGreaterThanOrEqualTo(0);
} else {
$unsealedKeyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey();
}
$builder->makeUnsealed(
$unsealedKeyType,
new MixedType(),
);
} else {
if ($typeNode->unsealedType->keyType === null) {
if ($isList) {
$unsealedKeyType = IntegerRangeType::createAllGreaterThanOrEqualTo(0);
} else {
$unsealedKeyType = (new BenevolentUnionType([new IntegerType(), new StringType()]))->toArrayKey();
}
} else {
$unsealedKeyType = $this->transformUnsafeArrayKey($this->resolve($typeNode->unsealedType->keyType, $nameScope));
}
$unsealedKeyFiniteTypes = $unsealedKeyType->getFiniteTypes();
$unsealedValueType = $this->resolve($typeNode->unsealedType->valueType, $nameScope);
if (count($unsealedKeyFiniteTypes) > 0) {
foreach ($unsealedKeyFiniteTypes as $unsealedKeyFiniteType) {
$builder->setOffsetValueType($unsealedKeyFiniteType, $unsealedValueType, true);
}
} else {
$builder->makeUnsealed($unsealedKeyType, $unsealedValueType);
}
}
}

$arrayType = $builder->getArray();

$accessories = [];
if (in_array($typeNode->kind, [
ArrayShapeNode::KIND_LIST,
ArrayShapeNode::KIND_NON_EMPTY_LIST,
], true)) {
if ($isList) {
$accessories[] = new AccessoryArrayListType();
}

Expand Down
Loading
Loading