Skip to content

Commit 520d85a

Browse files
committed
Performance optimization
1 parent 821b256 commit 520d85a

6 files changed

Lines changed: 183 additions & 68 deletions

File tree

src/Contract/ContractParser.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ public static function substituteAliases(TypeNode $node, array $aliases): TypeNo
759759

760760
if ($node instanceof CallableTypeNode) {
761761
$parameters = array_map(
762-
fn(CallableTypeParameterNode $param) => new CallableTypeParameterNode(
762+
fn (CallableTypeParameterNode $param) => new CallableTypeParameterNode(
763763
self::substituteAliases($param->type, $aliases),
764764
$param->isReference,
765765
$param->isVariadic,
@@ -793,7 +793,7 @@ public static function substituteAliases(TypeNode $node, array $aliases): TypeNo
793793
if ($node instanceof GenericTypeNode) {
794794
$genericType = self::substituteAliases($node->type, $aliases);
795795
$genericTypes = array_map(
796-
fn($t) => self::substituteAliases($t, $aliases),
796+
fn ($t) => self::substituteAliases($t, $aliases),
797797
$node->genericTypes
798798
);
799799

@@ -810,14 +810,14 @@ public static function substituteAliases(TypeNode $node, array $aliases): TypeNo
810810

811811
if ($node instanceof UnionTypeNode) {
812812
return new UnionTypeNode(array_map(
813-
fn($t) => self::substituteAliases($t, $aliases),
813+
fn ($t) => self::substituteAliases($t, $aliases),
814814
$node->types
815815
));
816816
}
817817

818818
if ($node instanceof IntersectionTypeNode) {
819819
return new IntersectionTypeNode(array_map(
820-
fn($t) => self::substituteAliases($t, $aliases),
820+
fn ($t) => self::substituteAliases($t, $aliases),
821821
$node->types
822822
));
823823
}

src/Internal/Visitor/FunctionContractInjector.php

Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static function inject(Node\Stmt\Function_|Node\Stmt\ClassMethod $node):
4949

5050
$injectedStmts = [];
5151
if ($hasParam) {
52-
$injectedStmts = self::buildParamInjections($node->params, $docText, $thisArg, $isClassMethod);
52+
$injectedStmts = self::buildParamInjections($node->params, $docText, $thisArg);
5353
}
5454

5555
if ($hasReturn) {
@@ -120,35 +120,37 @@ public function enterNode(Node $n): ?int
120120
private static function buildParamInjections(
121121
array $params,
122122
string $docText,
123-
Node\Expr $thisArg,
124-
bool $isClassMethod
123+
Node\Expr $thisArg
125124
): array {
126-
$injectedStmts = [self::buildSetupScopeStmt($thisArg)];
125+
$injectedStmts = [self::buildSetupScopeStmt($params, $thisArg)];
127126

128-
$callableWrappers = self::buildParamWrappers(
129-
$params,
130-
'\TypePHP\Internal\RuntimeTypeChecker::wrapCallable',
131-
$thisArg,
132-
$isClassMethod || str_contains($docText, 'callable') || str_contains($docText, 'Closure')
133-
);
134-
135-
$iterableWrappers = self::buildParamWrappers(
136-
$params,
137-
'\TypePHP\Internal\RuntimeTypeChecker::wrapIterable',
138-
$thisArg,
139-
str_contains($docText, 'iterable') || str_contains($docText, 'Traversable') || str_contains($docText, 'Generator') || str_contains($docText, 'Iterator')
140-
);
127+
$callableWrappers = self::buildCallableParamWrappers($params, $docText, $thisArg);
128+
$iterableWrappers = self::buildIterableParamWrappers($params, $docText, $thisArg);
141129

142130
return [...$injectedStmts, ...$callableWrappers, ...$iterableWrappers];
143131
}
144132

145-
private static function buildSetupScopeStmt(Node\Expr $thisArg): Node\Stmt\If_
133+
/**
134+
* @param array<Node\Param> $params
135+
*/
136+
private static function buildSetupScopeStmt(array $params, Node\Expr $thisArg): Node\Stmt\If_
146137
{
138+
$arrayItems = [];
139+
foreach ($params as $param) {
140+
if ($param->var instanceof Node\Expr\Variable && \is_string($param->var->name)) {
141+
$pName = $param->var->name;
142+
$arrayItems[] = new Node\ArrayItem(
143+
new Node\Expr\Variable($pName),
144+
new Node\Scalar\String_($pName)
145+
);
146+
}
147+
}
148+
147149
$checkCall = new Node\Expr\FuncCall(
148150
new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::setupScope'),
149151
[
150152
new Node\Arg(new Node\Scalar\MagicConst\Method()),
151-
new Node\Arg(new Node\Expr\FuncCall(new Node\Name('get_defined_vars'))),
153+
new Node\Arg(new Node\Expr\Array_($arrayItems)),
152154
new Node\Arg($thisArg),
153155
]
154156
);
@@ -173,21 +175,50 @@ private static function buildSetupScopeStmt(Node\Expr $thisArg): Node\Stmt\If_
173175
*
174176
* @return array<Node\Stmt>
175177
*/
176-
private static function buildParamWrappers(array $params, string $wrapperFunc, Node\Expr $thisArg, bool $shouldWrap): array
178+
private static function buildCallableParamWrappers(array $params, string $docText, Node\Expr $thisArg): array
177179
{
178-
if (! $shouldWrap) {
179-
return [];
180+
$wrappers = [];
181+
foreach ($params as $param) {
182+
if (self::isCallableCandidate($param, $docText) && $param->var instanceof Node\Expr\Variable && \is_string($param->var->name)) {
183+
$paramName = $param->var->name;
184+
$expr = new Node\Stmt\Expression(
185+
new Node\Expr\Assign(
186+
new Node\Expr\Variable($paramName),
187+
new Node\Expr\FuncCall(
188+
new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::wrapCallable'),
189+
[
190+
new Node\Arg(new Node\Scalar\MagicConst\Method()),
191+
new Node\Arg(new Node\Scalar\String_($paramName)),
192+
new Node\Arg(new Node\Expr\Variable($paramName)),
193+
new Node\Arg($thisArg),
194+
]
195+
)
196+
)
197+
);
198+
$expr->setAttribute('typephp_injected', true);
199+
$wrappers[] = $expr;
200+
}
180201
}
181202

203+
return $wrappers;
204+
}
205+
206+
/**
207+
* @param array<Node\Param> $params
208+
*
209+
* @return array<Node\Stmt>
210+
*/
211+
private static function buildIterableParamWrappers(array $params, string $docText, Node\Expr $thisArg): array
212+
{
182213
$wrappers = [];
183214
foreach ($params as $param) {
184-
if ($param->var instanceof Node\Expr\Variable && \is_string($param->var->name)) {
215+
if (self::isIterableCandidate($param, $docText) && $param->var instanceof Node\Expr\Variable && \is_string($param->var->name)) {
185216
$paramName = $param->var->name;
186217
$expr = new Node\Stmt\Expression(
187218
new Node\Expr\Assign(
188219
new Node\Expr\Variable($paramName),
189220
new Node\Expr\FuncCall(
190-
new Node\Name($wrapperFunc),
221+
new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::wrapIterable'),
191222
[
192223
new Node\Arg(new Node\Scalar\MagicConst\Method()),
193224
new Node\Arg(new Node\Scalar\String_($paramName)),
@@ -205,6 +236,81 @@ private static function buildParamWrappers(array $params, string $wrapperFunc, N
205236
return $wrappers;
206237
}
207238

239+
private static function isCallableCandidate(Node\Param $param, string $docText): bool
240+
{
241+
if (
242+
str_contains($docText, 'callable')
243+
|| str_contains($docText, 'Closure')
244+
|| str_contains($docText, 'pure-callable')
245+
|| str_contains($docText, 'static-closure')
246+
) {
247+
return true;
248+
}
249+
250+
if ($param->type instanceof Node\Identifier) {
251+
return strtolower($param->type->name) === 'callable';
252+
}
253+
254+
if ($param->type instanceof Node\Name) {
255+
return strtolower($param->type->getLast()) === 'closure';
256+
}
257+
258+
if ($param->type instanceof Node\UnionType || $param->type instanceof Node\IntersectionType) {
259+
foreach ($param->type->types as $t) {
260+
if ($t instanceof Node\Identifier && strtolower($t->name) === 'callable') {
261+
return true;
262+
}
263+
if ($t instanceof Node\Name && strtolower($t->getLast()) === 'closure') {
264+
return true;
265+
}
266+
}
267+
}
268+
269+
return false;
270+
}
271+
272+
private static function isIterableCandidate(Node\Param $param, string $docText): bool
273+
{
274+
if (
275+
str_contains($docText, 'iterable')
276+
|| str_contains($docText, 'Traversable')
277+
|| str_contains($docText, 'Generator')
278+
|| str_contains($docText, 'Iterator')
279+
|| str_contains($docText, 'IteratorAggregate')
280+
) {
281+
return true;
282+
}
283+
284+
$iterableTypes = [
285+
'iterable' => true,
286+
'traversable' => true,
287+
'generator' => true,
288+
'iterator' => true,
289+
'iteratoraggregate' => true,
290+
];
291+
292+
if ($param->type instanceof Node\Identifier) {
293+
return isset($iterableTypes[strtolower($param->type->name)]);
294+
}
295+
296+
if ($param->type instanceof Node\Name) {
297+
return isset($iterableTypes[strtolower($param->type->getLast())]);
298+
}
299+
300+
if ($param->type instanceof Node\UnionType || $param->type instanceof Node\IntersectionType) {
301+
foreach ($param->type->types as $t) {
302+
if ($t instanceof Node\Identifier && isset($iterableTypes[strtolower($t->name)])) {
303+
return true;
304+
}
305+
if ($t instanceof Node\Name && isset($iterableTypes[strtolower($t->getLast())])) {
306+
return true;
307+
}
308+
}
309+
}
310+
311+
return false;
312+
}
313+
208314
public static function buildTypeErrorThrowStmt(Node\Expr $errorVar): Node\Stmt\Expression
209315
{
210316
return new Node\Stmt\Expression(

src/Validator/ArrayValidator.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ public function validate(mixed $value, TypeNode $node, string $context, TypeVali
3838
}
3939

4040
foreach ($value as $k => $v) {
41-
$keyStr = (\is_scalar($k) || $k === null) ? (string) $k : get_debug_type($k);
42-
$err = $registry->validate($v, $arrayNode->type, $context . '[' . $keyStr . ']');
41+
$err = $registry->validate($v, $arrayNode->type, '');
4342
if ($err !== null) {
44-
return $err;
43+
$keyStr = (\is_scalar($k) || $k === null) ? (string) $k : get_debug_type($k);
44+
45+
return ErrorFactory::createError($context . '[' . $keyStr . ']' . $err->getMessage());
4546
}
4647
}
4748

src/Validator/GenericValidator.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,14 @@ private function validateList(mixed $value, GenericTypeNode $node, string $conte
376376
if ($valueTypeNode !== null) {
377377
foreach ($value as $k => $v) {
378378
if ($valueTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valueTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true)) {
379-
$err = $this->validateObjectGeneric($v, $valueTypeNode, $context . '[' . $k . ']');
379+
$err = $this->validateObjectGeneric($v, $valueTypeNode, '');
380380
if ($err !== null) {
381-
return $err;
381+
return ErrorFactory::createError($context . '[' . $k . ']' . $err->getMessage());
382382
}
383383
} else {
384-
$err = $registry->validate($v, $valueTypeNode, $context . '[' . $k . ']');
384+
$err = $registry->validate($v, $valueTypeNode, '');
385385
if ($err !== null) {
386-
return $err;
386+
return ErrorFactory::createError($context . '[' . $k . ']' . $err->getMessage());
387387
}
388388
}
389389
}
@@ -416,35 +416,35 @@ private function validateArray(mixed $value, GenericTypeNode $node, string $cont
416416
$valTypeNode = $node->genericTypes[0];
417417
foreach ($value as $k => $v) {
418418
if ($valTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true)) {
419-
$err = $this->validateObjectGeneric($v, $valTypeNode, $context . '[' . $k . ']');
419+
$err = $this->validateObjectGeneric($v, $valTypeNode, '');
420420
if ($err !== null) {
421-
return $err;
421+
return ErrorFactory::createError($context . '[' . $k . ']' . $err->getMessage());
422422
}
423423
} else {
424-
$err = $registry->validate($v, $valTypeNode, $context . '[' . $k . ']');
424+
$err = $registry->validate($v, $valTypeNode, '');
425425
if ($err !== null) {
426-
return $err;
426+
return ErrorFactory::createError($context . '[' . $k . ']' . $err->getMessage());
427427
}
428428
}
429429
}
430430
} elseif ($typesCount >= 2) {
431431
$keyTypeNode = $node->genericTypes[0];
432432
$valTypeNode = $node->genericTypes[1];
433433
foreach ($value as $k => $v) {
434-
$err = $registry->validate($k, $keyTypeNode, $context . ' key');
434+
$err = $registry->validate($k, $keyTypeNode, '');
435435
if ($err !== null) {
436-
return $err;
436+
return ErrorFactory::createError($context . ' key' . $err->getMessage());
437437
}
438438

439439
if ($valTypeNode instanceof GenericTypeNode && ! \in_array(strtolower($valTypeNode->type->name), ['class-string', 'list', 'array', 'iterable'], strict: true)) {
440-
$err = $this->validateObjectGeneric($v, $valTypeNode, $context . "['" . $k . "']");
440+
$err = $this->validateObjectGeneric($v, $valTypeNode, '');
441441
if ($err !== null) {
442-
return $err;
442+
return ErrorFactory::createError($context . "['" . $k . "']" . $err->getMessage());
443443
}
444444
} else {
445-
$err = $registry->validate($v, $valTypeNode, $context . "['" . $k . "']");
445+
$err = $registry->validate($v, $valTypeNode, '');
446446
if ($err !== null) {
447-
return $err;
447+
return ErrorFactory::createError($context . "['" . $k . "']" . $err->getMessage());
448448
}
449449
}
450450
}

tests/Fixtures/Services/VariadicPropertyService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ public function getTags(): array
3535
{
3636
return $this->tags;
3737
}
38-
}
38+
}

0 commit comments

Comments
 (0)