Skip to content
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

Fix invalid array bounds during static analysis #518

Merged
merged 1 commit into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@

final class ArrayBoundsCalculator
{
/**
* All lists will start from index 0.
*
* The maximum amount of items in the list will be maxOccurs - 1, since arrays are zero-index based.
* Edge cases like -1 (unbounded) and 0 (empty) are handled as well.
*
* These bounds don't take into account minOccurs, since minOccurs still starts from 0.
*/
public function __invoke(TypeMeta $meta): string
{
$min = $meta->minOccurs()
->map(fn (int $min): string => $min === -1 ? 'min' : (string) $min)
->unwrapOr('min');
$max = $meta->maxOccurs()->unwrapOr(-1);

$max = $meta->maxOccurs()
->map(fn (int $max): string => $max === -1 ? 'max' : (string) $max)
->unwrapOr('max');

return 'int<'.$min.','.$max.'>';
return match (true) {
$max < 0 => 'int<0,max>',
$max === 0 => 'never',
default => 'int<0,'.($max - 1).'>'
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ public function asDocBlockType(string $type): string

$isArray = $this->meta->isList()->unwrapOr(false);
if ($isArray) {
$type = 'array<'.(new ArrayBoundsCalculator())($this->meta).', '.$type.'>';
$nonEmpty = $this->meta->minOccurs()->unwrapOr(0) > 0;
$arrayType = $nonEmpty ? 'non-empty-array' : 'array';

$type = $arrayType.'<'.(new ArrayBoundsCalculator())($this->meta).', '.$type.'>';
}

$isNullable = (new IsConsideredNullableType())($this->meta);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class MyType
/**
* Constructor
*
* @param array<int<min,max>, string> \$prop1
* @param array<int<0,max>, string> \$prop1
*/
public function __construct(array \$prop1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ function it_assembles_a_fluent_setter_with_advanced_types()
class MyType
{
/**
* @param array<int<min,max>, string> \$prop1
* @param array<int<0,max>, string> \$prop1
* @return \$this
*/
public function setProp1(array \$prop1) : static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ function it_assembles_a_property_with_advanced_types()
class MyType
{
/**
* @return array<int<min,max>, string>
* @return array<int<0,max>, string>
*/
public function getProp1() : array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ function it_assembles_a_fluent_setter_with_advanced_types()
class MyType
{
/**
* @param array<int<min,max>, string> \$prop1
* @param array<int<0,max>, string> \$prop1
* @return static
*/
public function withProp1(array \$prop1) : static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ function it_assembles_a_type()
use IteratorAggregate;

/**
* @phpstan-implements \IteratorAggregate<int<1,2>, string>
* @psalm-implements \IteratorAggregate<int<1,2>, string>
* @phpstan-implements \IteratorAggregate<int<0,1>, string>
* @psalm-implements \IteratorAggregate<int<0,1>, string>
*/
class MyType implements IteratorAggregate
{
/**
* @return \ArrayIterator|string[]
* @phpstan-return \ArrayIterator<int<1,2>, string>
* @psalm-return \ArrayIterator<int<1,2>, string>
* @phpstan-return \ArrayIterator<int<0,1>, string>
* @psalm-return \ArrayIterator<int<0,1>, string>
*/
public function getIterator() : \ArrayIterator
{
Expand Down Expand Up @@ -98,15 +98,15 @@ function it_assembles_a_type_with_no_occurs_information()
use IteratorAggregate;

/**
* @phpstan-implements \IteratorAggregate<int<min,max>, string>
* @psalm-implements \IteratorAggregate<int<min,max>, string>
* @phpstan-implements \IteratorAggregate<int<0,max>, string>
* @psalm-implements \IteratorAggregate<int<0,max>, string>
*/
class MyType implements IteratorAggregate
{
/**
* @return \ArrayIterator|string[]
* @phpstan-return \ArrayIterator<int<min,max>, string>
* @psalm-return \ArrayIterator<int<min,max>, string>
* @phpstan-return \ArrayIterator<int<0,max>, string>
* @psalm-return \ArrayIterator<int<0,max>, string>
*/
public function getIterator() : \ArrayIterator
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ function it_assembles_properties_with_advanced_types()
class MyType
{
/**
* @var array<int<min,max>, string>
* @var array<int<0,max>, string>
*/
private array \$prop1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function it_assembles_a_setter_with_advanced_types()
class MyType
{
/**
* @param array<int<min,max>, string> \$prop1
* @param array<int<0,max>, string> \$prop1
*/
public function setProp1(array \$prop1) : void
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,31 @@ public function provideExpectations()
{
yield 'simpleType' => [
new TypeMeta(),
'int<min,max>',
'int<0,max>',
];
yield 'array' => [
(new TypeMeta())->withIsList(true),
'int<min,max>',
'int<0,max>',
];
yield 'min' => [
(new TypeMeta())->withIsList(true)->withMinOccurs(1),
'int<1,max>',
'int<0,max>',
];
yield 'max' => [
(new TypeMeta())->withIsList(true)->withMaxOccurs(3),
'int<min,3>',
'int<0,2>',
];
yield 'min-max' => [
(new TypeMeta())->withIsList(true)->withMinOccurs(1)->withMaxOccurs(3),
'int<1,3>',
'int<0,2>',
];
yield 'max-1' => [
(new TypeMeta())->withIsList(true)->withMaxOccurs(1),
'int<0,0>',
];
yield 'max-0' => [
(new TypeMeta())->withIsList(true)->withMaxOccurs(0),
'never',
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ public function provideExpectations()
yield 'array' => [
(new TypeMeta())->withIsList(true),
'simple',
'array<int<min,max>, simple>',
'array<int<0,max>, simple>',
'array',
];
yield 'min' => [
(new TypeMeta())->withIsList(true)->withMinOccurs(1),
'simple',
'array<int<1,max>, simple>',
'non-empty-array<int<0,max>, simple>',
'array',
];
yield 'max' => [
(new TypeMeta())->withIsList(true)->withMaxOccurs(3),
'simple',
'array<int<min,3>, simple>',
'array<int<0,2>, simple>',
'array',
];
yield 'nullable' => [
Expand All @@ -60,7 +60,7 @@ public function provideExpectations()
yield 'nullable-array' => [
(new TypeMeta())->withIsList(true)->withIsNullable(true),
'simple',
'null | array<int<min,max>, simple>',
'null | array<int<0,max>, simple>',
'?array',
];
yield 'enum' => [
Expand All @@ -72,13 +72,13 @@ public function provideExpectations()
yield 'enum-list' => [
(new TypeMeta())->withEnums(['a', 'b'])->withIsList(true),
'string',
"array<int<min,max>, 'a' | 'b'>",
"array<int<0,max>, 'a' | 'b'>",
'array',
];
yield 'nullable-enum-list' => [
(new TypeMeta())->withEnums(['a', 'b'])->withIsList(true)->withIsNullable(true),
'string',
"null | array<int<min,max>, 'a' | 'b'>",
"null | array<int<0,max>, 'a' | 'b'>",
'?array',
];
yield 'nullable-enum' => [
Expand Down Expand Up @@ -130,7 +130,7 @@ public function provideExpectations()
['type' => 'int', 'isList' => true, 'namespace' => 'xx'],
]),
'unionType',
"array<int<min,max>, string | list<int>>",
"array<int<0,max>, string | list<int>>",
'array',
];
yield 'nullable-array-of-union-with-list' => [
Expand All @@ -143,7 +143,7 @@ public function provideExpectations()
['type' => 'int', 'isList' => true, 'namespace' => 'xx'],
]),
'unionType',
"null | array<int<min,max>, string | list<int>>",
"null | array<int<0,max>, string | list<int>>",
'?array',
];
}
Expand Down
Loading