Skip to content

Commit f7de56e

Browse files
committed
Refactor type handling in checkParams and checkReturn methods; add BaseEntityFactory and related entity factories for testing
1 parent 431dc4c commit f7de56e

10 files changed

Lines changed: 269 additions & 60 deletions

File tree

src/Internal/Checker/ParamChecker.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ final class ParamChecker
2727
/**
2828
* @param array<string, mixed> $vars
2929
*/
30-
public static function checkParams(string $function, array $vars, ?object $thisObj, TypeValidatorRegistry $registry): ?ErrorMessage
30+
public static function checkParams(string $function, array $vars, object|string|null $thisOrClass, TypeValidatorRegistry $registry): ?ErrorMessage
3131
{
3232
if (! (bool) (Config::get()['params'] ?? true)) {
3333
return null;
3434
}
3535

36+
$thisObj = \is_object($thisOrClass) ? $thisOrClass : null;
3637
$effectiveFunction = $function;
37-
if ($thisObj !== null && str_contains($function, '::')) {
38+
39+
if (str_contains($function, '::')) {
3840
[$classOrTrait, $methodName] = explode('::', $function, 2);
39-
$actualClassName = \get_class($thisObj);
40-
if ($actualClassName !== $classOrTrait) {
41+
$actualClassName = \is_object($thisOrClass) ? \get_class($thisOrClass) : (\is_string($thisOrClass) ? $thisOrClass : null);
42+
if ($actualClassName !== null && $actualClassName !== $classOrTrait) {
4143
$effectiveFunction = $actualClassName . '::' . $methodName;
4244
}
4345
}
@@ -340,4 +342,4 @@ private static function resolveTemplateParam(TypeNode $typeNode, mixed $val, str
340342

341343
return null;
342344
}
343-
}
345+
}

src/Internal/Checker/ReturnChecker.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,19 @@ final class ReturnChecker
2525
/**
2626
* @param array<string, mixed> $vars
2727
*/
28-
public static function checkReturn(string $function, mixed $value, ?object $thisObj, array $vars, TypeValidatorRegistry $registry, callable $wrapIterableCallback): mixed
28+
public static function checkReturn(string $function, mixed $value, object|string|null $thisOrClass, array $vars, TypeValidatorRegistry $registry, callable $wrapIterableCallback): mixed
2929
{
3030
if (! (bool) (Config::get()['returns'] ?? true)) {
3131
return $value;
3232
}
3333

34+
$thisObj = \is_object($thisOrClass) ? $thisOrClass : null;
3435
$effectiveFunction = $function;
35-
if ($thisObj !== null && str_contains($function, '::')) {
36+
37+
if (str_contains($function, '::')) {
3638
[$classOrTrait, $methodName] = explode('::', $function, 2);
37-
$actualClassName = \get_class($thisObj);
38-
if ($actualClassName !== $classOrTrait) {
39+
$actualClassName = \is_object($thisOrClass) ? \get_class($thisOrClass) : (\is_string($thisOrClass) ? $thisOrClass : null);
40+
if ($actualClassName !== null && $actualClassName !== $classOrTrait) {
3941
$effectiveFunction = $actualClassName . '::' . $methodName;
4042
}
4143
}
@@ -191,4 +193,4 @@ private static function resolveConditionalReturnType(
191193

192194
return $returnTypeNode;
193195
}
194-
}
196+
}

src/Internal/RuntimeTypeChecker.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,25 @@ public static function checkProperty(mixed $value, mixed $objectOrClass, string
7171
*
7272
* @param array<string, mixed> $vars
7373
*/
74-
public static function setupScope(string $function, array $vars, ?object $thisObj = null): ErrorMessage|ScopeCleaner|null
74+
public static function setupScope(string $function, array $vars, object|string|null $thisOrClass = null): ErrorMessage|ScopeCleaner|null
7575
{
7676
if (! self::isEnabled()) {
7777
return null;
7878
}
7979

80-
$err = self::checkParams($function, $vars, $thisObj);
80+
$err = self::checkParams($function, $vars, $thisOrClass);
8181

8282
if ($err !== null) {
83+
$thisObj = \is_object($thisOrClass) ? $thisOrClass : null;
8384
if ($thisObj === null) {
8485
TemplateManager::popCallFrame($function);
8586
}
8687

8788
return $err;
8889
}
8990

91+
$thisObj = \is_object($thisOrClass) ? $thisOrClass : null;
92+
9093
return $thisObj === null ? new ScopeCleaner($function) : null;
9194
}
9295

@@ -95,27 +98,27 @@ public static function setupScope(string $function, array $vars, ?object $thisOb
9598
*
9699
* @param array<string, mixed> $vars
97100
*/
98-
public static function checkParams(string $function, array $vars, ?object $thisObj = null): ?ErrorMessage
101+
public static function checkParams(string $function, array $vars, object|string|null $thisOrClass = null): ?ErrorMessage
99102
{
100103
if (! self::isEnabled()) {
101104
return null;
102105
}
103106

104-
return ParamChecker::checkParams($function, $vars, $thisObj, self::getRegistry());
107+
return ParamChecker::checkParams($function, $vars, $thisOrClass, self::getRegistry());
105108
}
106109

107110
/**
108111
* Validates a function or method's return value against its declared contract and returns value or ErrorMessage.
109112
*
110113
* @param array<string, mixed> $vars
111114
*/
112-
public static function checkReturn(string $function, mixed $value, ?object $thisObj = null, array $vars = []): mixed
115+
public static function checkReturn(string $function, mixed $value, object|string|null $thisOrClass = null, array $vars = []): mixed
113116
{
114117
if (! self::isEnabled()) {
115118
return $value;
116119
}
117120

118-
return ReturnChecker::checkReturn($function, $value, $thisObj, $vars, self::getRegistry(), [self::class, 'wrapIterable']);
121+
return ReturnChecker::checkReturn($function, $value, $thisOrClass, $vars, self::getRegistry(), [self::class, 'wrapIterable']);
119122
}
120123

121124
/**
@@ -207,4 +210,4 @@ public static function getRegistry(): TypeValidatorRegistry
207210
{
208211
return self::$registry ??= new TypeValidatorRegistry();
209212
}
210-
}
213+
}

src/Internal/Visitor/FunctionContractInjector.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ public static function inject(Node\Stmt\Function_|Node\Stmt\ClassMethod $node):
4848

4949
$isNativeVoid = $node->returnType instanceof Node\Identifier && strtolower($node->returnType->name) === 'void';
5050
$hasThis = $isClassMethod && ! $node->isStatic();
51+
52+
// Pass $this for instance methods, static::class for static methods, or null for global functions
5153
$thisArg = $hasThis
5254
? new Node\Expr\Variable('this')
53-
: new Node\Expr\ConstFetch(new Node\Name('null'));
55+
: ($isClassMethod ? new Node\Expr\ClassConstFetch(new Node\Name('static'), 'class') : new Node\Expr\ConstFetch(new Node\Name('null')));
5456

5557
$injectedStmts = [];
5658

@@ -543,4 +545,4 @@ public function enterNode(Node $n): int|array|null
543545

544546
return $newStmts;
545547
}
546-
}
548+
}

src/Resolver/SpecialTypeResolver.php

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,16 @@ public static function resolve(TypeNode $node, \ReflectionClass|\ReflectionFunct
8282
}
8383

8484
if ($node instanceof IdentifierTypeNode) {
85-
return self::resolveIdentifier($node, $declaringClass, $ref);
85+
return self::resolveIdentifier($node, $declaringClass, $ref, $context, $thisObj);
8686
}
8787

8888
if ($node instanceof ConstTypeNode) {
8989
return self::resolveConstType($node, $declaringClass, $ref);
9090
}
9191

9292
if ($node instanceof GenericTypeNode) {
93-
$genericType = self::resolve($node->type, $ref, $thisObj);
94-
$innerTypes = array_map(fn ($t) => self::resolve($t, $ref, $thisObj), $node->genericTypes);
93+
$genericType = self::resolve($node->type, $context, $thisObj);
94+
$innerTypes = array_map(fn ($t) => self::resolve($t, $context, $thisObj), $node->genericTypes);
9595

9696
return new GenericTypeNode(
9797
$genericType instanceof IdentifierTypeNode ? $genericType : $node->type,
@@ -101,55 +101,55 @@ public static function resolve(TypeNode $node, \ReflectionClass|\ReflectionFunct
101101
}
102102

103103
if ($node instanceof OffsetAccessTypeNode) {
104-
return self::resolveOffsetAccess($node, $ref, $thisObj);
104+
return self::resolveOffsetAccess($node, $context, $thisObj);
105105
}
106106

107107
if ($node instanceof ArrayShapeNode) {
108-
return self::resolveArrayShape($node, $ref, $thisObj);
108+
return self::resolveArrayShape($node, $context, $thisObj);
109109
}
110110

111111
if ($node instanceof ObjectShapeNode) {
112-
return self::resolveObjectShape($node, $ref, $thisObj);
112+
return self::resolveObjectShape($node, $context, $thisObj);
113113
}
114114

115115
if ($node instanceof CallableTypeNode) {
116-
return self::resolveCallable($node, $ref, $thisObj);
116+
return self::resolveCallable($node, $context, $thisObj);
117117
}
118118

119119
if ($node instanceof ConditionalTypeNode) {
120120
return new ConditionalTypeNode(
121-
self::resolve($node->subjectType, $ref, $thisObj),
122-
self::resolve($node->targetType, $ref, $thisObj),
123-
self::resolve($node->if, $ref, $thisObj),
124-
self::resolve($node->else, $ref, $thisObj),
121+
self::resolve($node->subjectType, $context, $thisObj),
122+
self::resolve($node->targetType, $context, $thisObj),
123+
self::resolve($node->if, $context, $thisObj),
124+
self::resolve($node->else, $context, $thisObj),
125125
$node->negated
126126
);
127127
}
128128

129129
if ($node instanceof ConditionalTypeForParameterNode) {
130130
return new ConditionalTypeForParameterNode(
131131
$node->parameterName,
132-
self::resolve($node->targetType, $ref, $thisObj),
133-
self::resolve($node->if, $ref, $thisObj),
134-
self::resolve($node->else, $ref, $thisObj),
132+
self::resolve($node->targetType, $context, $thisObj),
133+
self::resolve($node->if, $context, $thisObj),
134+
self::resolve($node->else, $context, $thisObj),
135135
$node->negated
136136
);
137137
}
138138

139139
if ($node instanceof NullableTypeNode) {
140-
return new NullableTypeNode(self::resolve($node->type, $ref, $thisObj));
140+
return new NullableTypeNode(self::resolve($node->type, $context, $thisObj));
141141
}
142142

143143
if ($node instanceof ArrayTypeNode) {
144-
return new ArrayTypeNode(self::resolve($node->type, $ref, $thisObj));
144+
return new ArrayTypeNode(self::resolve($node->type, $context, $thisObj));
145145
}
146146

147147
if ($node instanceof UnionTypeNode) {
148-
return new UnionTypeNode(array_map(fn ($t) => self::resolve($t, $ref, $thisObj), $node->types));
148+
return new UnionTypeNode(array_map(fn ($t) => self::resolve($t, $context, $thisObj), $node->types));
149149
}
150150

151151
if ($node instanceof IntersectionTypeNode) {
152-
return new IntersectionTypeNode(array_map(fn ($t) => self::resolve($t, $ref, $thisObj), $node->types));
152+
return new IntersectionTypeNode(array_map(fn ($t) => self::resolve($t, $context, $thisObj), $node->types));
153153
}
154154

155155
return $node;
@@ -282,11 +282,35 @@ private static function getReflectionContext(\ReflectionClass|\ReflectionFunctio
282282
/**
283283
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
284284
*/
285-
private static function resolveIdentifier(IdentifierTypeNode $node, ?string $declaringClass, \ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref): IdentifierTypeNode
286-
{
285+
private static function resolveIdentifier(
286+
IdentifierTypeNode $node,
287+
?string $declaringClass,
288+
\ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref,
289+
\ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context,
290+
?object $thisObj = null
291+
): IdentifierTypeNode {
287292
$lower = strtolower($node->name);
288293

289-
if ($lower === '$this' || $lower === 'static') {
294+
if ($lower === '$this') {
295+
if ($thisObj !== null) {
296+
return new IdentifierTypeNode(\get_class($thisObj));
297+
}
298+
299+
return $node;
300+
}
301+
302+
if ($lower === 'static') {
303+
if ($thisObj !== null) {
304+
return new IdentifierTypeNode(\get_class($thisObj));
305+
}
306+
307+
if (\is_string($context) && str_contains($context, '::')) {
308+
$callingClass = explode('::', $context, 2)[0];
309+
if (class_exists($callingClass) || interface_exists($callingClass) || trait_exists($callingClass) || enum_exists($callingClass)) {
310+
return new IdentifierTypeNode($callingClass);
311+
}
312+
}
313+
290314
return $node;
291315
}
292316

@@ -331,12 +355,13 @@ private static function resolveConstType(ConstTypeNode $node, ?string $declaring
331355
}
332356

333357
/**
334-
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
358+
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod|string $context
335359
*/
336-
private static function resolveOffsetAccess(OffsetAccessTypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref, ?object $thisObj): TypeNode
360+
private static function resolveOffsetAccess(OffsetAccessTypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj): TypeNode
337361
{
338-
$baseType = self::resolve($node->type, $ref, $thisObj);
339-
$offsetType = self::resolve($node->offset, $ref, $thisObj);
362+
$ref = self::getReflectionContext($context);
363+
$baseType = self::resolve($node->type, $context, $thisObj);
364+
$offsetType = self::resolve($node->offset, $context, $thisObj);
340365

341366
$offsetKey = self::extractOffsetKey($offsetType);
342367

@@ -362,11 +387,12 @@ private static function resolveOffsetAccess(OffsetAccessTypeNode $node, \Reflect
362387
}
363388

364389
/**
365-
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
390+
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod|string $context
366391
*/
367-
private static function resolveArrayShape(ArrayShapeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref, ?object $thisObj): ArrayShapeNode
392+
private static function resolveArrayShape(ArrayShapeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj): ArrayShapeNode
368393
{
369-
$items = array_map(function ($item) use ($ref, $thisObj) {
394+
$ref = self::getReflectionContext($context);
395+
$items = array_map(function ($item) use ($ref, $context, $thisObj) {
370396
/** @var ConstExprIntegerNode|ConstExprStringNode|ConstFetchNode|IdentifierTypeNode|null $keyName */
371397
$keyName = $item->keyName;
372398

@@ -404,7 +430,7 @@ private static function resolveArrayShape(ArrayShapeNode $node, \ReflectionClass
404430
return new ArrayShapeItemNode(
405431
$keyName,
406432
$item->optional,
407-
self::resolve($item->valueType, $ref, $thisObj)
433+
self::resolve($item->valueType, $context, $thisObj)
408434
);
409435
}, $node->items);
410436

@@ -414,46 +440,46 @@ private static function resolveArrayShape(ArrayShapeNode $node, \ReflectionClass
414440

415441
$unsealedType = null;
416442
if ($node->unsealedType !== null) {
417-
$unsealedKey = $node->unsealedType->keyType !== null ? self::resolve($node->unsealedType->keyType, $ref, $thisObj) : null;
418-
$unsealedValue = self::resolve($node->unsealedType->valueType, $ref, $thisObj);
443+
$unsealedKey = $node->unsealedType->keyType !== null ? self::resolve($node->unsealedType->keyType, $context, $thisObj) : null;
444+
$unsealedValue = self::resolve($node->unsealedType->valueType, $context, $thisObj);
419445
$unsealedType = new ArrayShapeUnsealedTypeNode($unsealedValue, $unsealedKey);
420446
}
421447

422448
return ArrayShapeNode::createUnsealed($items, $unsealedType, $node->kind);
423449
}
424450

425451
/**
426-
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
452+
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod|string $context
427453
*/
428-
private static function resolveObjectShape(ObjectShapeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref, ?object $thisObj): ObjectShapeNode
454+
private static function resolveObjectShape(ObjectShapeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj): ObjectShapeNode
429455
{
430-
$items = array_map(function ($item) use ($ref, $thisObj) {
456+
$items = array_map(function ($item) use ($context, $thisObj) {
431457
return new ObjectShapeItemNode(
432458
$item->keyName,
433459
$item->optional,
434-
self::resolve($item->valueType, $ref, $thisObj)
460+
self::resolve($item->valueType, $context, $thisObj)
435461
);
436462
}, $node->items);
437463

438464
return new ObjectShapeNode($items);
439465
}
440466

441467
/**
442-
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
468+
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod|string $context
443469
*/
444-
private static function resolveCallable(CallableTypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod $ref, ?object $thisObj): CallableTypeNode
470+
private static function resolveCallable(CallableTypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj): CallableTypeNode
445471
{
446-
$resolvedParameters = array_map(function (CallableTypeParameterNode $param) use ($ref, $thisObj) {
472+
$resolvedParameters = array_map(function (CallableTypeParameterNode $param) use ($context, $thisObj) {
447473
return new CallableTypeParameterNode(
448-
self::resolve($param->type, $ref, $thisObj),
474+
self::resolve($param->type, $context, $thisObj),
449475
$param->isReference,
450476
$param->isVariadic,
451477
$param->parameterName,
452478
$param->isOptional
453479
);
454480
}, $node->parameters);
455481

456-
$resolvedReturnType = self::resolve($node->returnType, $ref, $thisObj);
482+
$resolvedReturnType = self::resolve($node->returnType, $context, $thisObj);
457483

458484
return new CallableTypeNode($node->identifier, $resolvedParameters, $resolvedReturnType, $node->templateTypes);
459485
}
@@ -978,4 +1004,4 @@ private static function parseFileMetadata(string $fileName, string $source): voi
9781004
// Silently fall back to empty metadata if parsing fails
9791005
}
9801006
}
981-
}
1007+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Services;
6+
7+
class AdminEntityFactory extends BaseEntityFactory
8+
{
9+
}

0 commit comments

Comments
 (0)