Skip to content

Commit

Permalink
parse generic Closure with narrowed template
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Jun 24, 2023
1 parent e494f37 commit c2b0ec8
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 6 deletions.
56 changes: 50 additions & 6 deletions src/DocBlock/TypeExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,19 @@ final class TypeExpression
(?<callable_name>(?i)(?:callable|\\\\?Closure)(?-i))
(?<callable_template>
(?<callable_template_start>\h*<\h*)
(?<callable_template_types>
(?&identifier)
(?<callable_template_inners>
(?<callable_template_inner>
(?<callable_template_inner_name>
(?&identifier)
)
(?<callable_template_inner_of>
\h+(?i)of(?-i)\h+
(?<callable_template_inner_of_types>(?&types_inner))
|)
)
(?:
\h*,\h*
(?&identifier)
(?&callable_template_inner)
)*
)
\h*>
Expand Down Expand Up @@ -370,10 +378,10 @@ private function parse(): void
$matches['generic_types'][0]
);
} elseif ('' !== ($matches['callable'][0] ?? '') && $matches['callable'][1] === $nullableLength) {
$this->parseCommaSeparatedInnerTypes(
$this->parseCallableTemplateInnerTypes(
$index + \strlen($matches['callable_name'][0])
+ \strlen($matches['callable_template_start'][0]),
$matches['callable_template_types'][0]
$matches['callable_template_inners'][0]
);

$this->parseCommaSeparatedInnerTypes(
Expand Down Expand Up @@ -455,6 +463,41 @@ private function parseCommaSeparatedInnerTypes(int $startIndex, string $value):
}
}

private function parseCallableTemplateInnerTypes(int $startIndex, string $value): void
{
$index = 0;
while (\strlen($value) !== $index) {
Preg::match(
'{\G(?:(?=1)0'.self::REGEX_TYPES.'|(?<_callable_template_inner>(?&callable_template_inner))(?:\h*,\h*|$))}',
$value,
$prematches,
0,
$index
);
$consumedValue = $prematches['_callable_template_inner'];
$consumedValueLength = \strlen($consumedValue);
$consumedCommaLength = \strlen($prematches[0]) - $consumedValueLength;

$addedPrefix = 'Closure<';
Preg::match(
'{^'.self::REGEX_TYPES.'$}',
$addedPrefix.$consumedValue.'>(): void',
$matches,
PREG_OFFSET_CAPTURE
);

if ('' !== $matches['callable_template_inner_of'][0]) {
$this->innerTypeExpressions[] = [
'start_index' => $startIndex + $index + $matches['callable_template_inner_of_types'][1]
- \strlen($addedPrefix),
'expression' => $this->inner($matches['callable_template_inner_of_types'][0]),
];
}

$index += $consumedValueLength + $consumedCommaLength;
}
}

private function parseArrayShapeInnerTypes(int $startIndex, string $value): void
{
$index = 0;
Expand All @@ -479,7 +522,8 @@ private function parseArrayShapeInnerTypes(int $startIndex, string $value): void
);

$this->innerTypeExpressions[] = [
'start_index' => $startIndex + $index + $matches['array_shape_inner_value'][1] - \strlen($addedPrefix),
'start_index' => $startIndex + $index + $matches['array_shape_inner_value'][1]
- \strlen($addedPrefix),
'expression' => $this->inner($matches['array_shape_inner_value'][0]),
];

Expand Down
5 changes: 5 additions & 0 deletions tests/DocBlock/TypeExpressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,11 @@ public static function provideSortTypesCases(): iterable
'Closure<B, A>(x|y, A|B|U<o|p>): (B|X|Y)',
];

yield 'generic Closure with narrowed template' => [
'Closure<B of J|I, C, A of V|U, D of object>(B|A): array{B, A, B, C, D}',
'Closure<B of I|J, C, A of U|V, D of object>(A|B): array{B, A, B, C, D}',
];

yield 'nullable generic' => [
'?array<Foo|Bar>',
'?array<Bar|Foo>',
Expand Down

0 comments on commit c2b0ec8

Please sign in to comment.