Skip to content

Commit b4f526a

Browse files
committed
Enhance type handling and testing across various components; introduce BaseGenericFactory and BaseGenericMap for improved generic handling; add comprehensive tests for nested type aliases and late static binding scenarios.
1 parent f7de56e commit b4f526a

19 files changed

Lines changed: 415 additions & 94 deletions

src/Internal/Checker/ParamChecker.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use TypePHP\Internal\TypeFormatter;
1818
use TypePHP\Resolver\SpecialTypeResolver;
1919
use TypePHP\Resolver\TemplateManager;
20+
use TypePHP\Resolver\TemplateSubstitutor;
2021
use TypePHP\Validator\TypeValidatorRegistry;
2122

2223
/**
@@ -78,6 +79,9 @@ public static function checkParams(string $function, array $vars, object|string|
7879
TemplateManager::resolveInheritedTemplates($thisObj, $declaringClass);
7980
}
8081

82+
$boundTemplates = TemplateManager::getBoundTemplates($effectiveFunction, $thisObj, $templates);
83+
$declaredTemplates = $templates;
84+
8185
foreach ($contract['types'] as $paramName => $typeNode) {
8286
if (! \array_key_exists($paramName, $vars)) {
8387
continue;
@@ -94,6 +98,18 @@ public static function checkParams(string $function, array $vars, object|string|
9498
$typeNode = $aliases[$typeNode->name];
9599
}
96100

101+
$isClassStringT = ($typeNode instanceof GenericTypeNode && self::isClassStringTemplate($typeNode, $templates));
102+
103+
$isBareTemplate = ($typeNode instanceof IdentifierTypeNode && isset($templates[$typeNode->name]))
104+
|| ($typeNode instanceof ArrayTypeNode && $typeNode->type instanceof IdentifierTypeNode && isset($templates[$typeNode->type->name]));
105+
106+
$shouldSkipTemplateSub = $isBareTemplate || $isClassStringT;
107+
108+
if (! $shouldSkipTemplateSub && (\count($boundTemplates) > 0 || \count($declaredTemplates) > 0)) {
109+
$typeNode = TemplateSubstitutor::substitute($typeNode, $boundTemplates, $declaredTemplates);
110+
$typeNode = SpecialTypeResolver::resolve($typeNode, $effectiveFunction, $thisObj);
111+
}
112+
97113
if ($typeNode instanceof GenericTypeNode && self::isClassStringTemplate($typeNode, $templates)) {
98114
$err = self::resolveClassStringTemplate($typeNode, $val, $paramName, $effectiveFunction, $thisObj, $templates);
99115
if ($err !== null) {
@@ -342,4 +358,4 @@ private static function resolveTemplateParam(TypeNode $typeNode, mixed $val, str
342358

343359
return null;
344360
}
345-
}
361+
}

src/Internal/Checker/ReturnChecker.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,4 @@ private static function resolveConditionalReturnType(
193193

194194
return $returnTypeNode;
195195
}
196-
}
196+
}

src/Internal/RuntimeTypeChecker.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,4 @@ public static function getRegistry(): TypeValidatorRegistry
210210
{
211211
return self::$registry ??= new TypeValidatorRegistry();
212212
}
213-
}
213+
}

src/Internal/Visitor/FunctionContractInjector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,4 +545,4 @@ public function enterNode(Node $n): int|array|null
545545

546546
return $newStmts;
547547
}
548-
}
548+
}

src/Resolver/SpecialTypeResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ private static function getReflectionContext(\ReflectionClass|\ReflectionFunctio
281281

282282
/**
283283
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod $ref
284+
* @param \ReflectionClass<object>|\ReflectionFunction|\ReflectionMethod|string $context
284285
*/
285286
private static function resolveIdentifier(
286287
IdentifierTypeNode $node,
@@ -359,7 +360,6 @@ private static function resolveConstType(ConstTypeNode $node, ?string $declaring
359360
*/
360361
private static function resolveOffsetAccess(OffsetAccessTypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj): TypeNode
361362
{
362-
$ref = self::getReflectionContext($context);
363363
$baseType = self::resolve($node->type, $context, $thisObj);
364364
$offsetType = self::resolve($node->offset, $context, $thisObj);
365365

@@ -1004,4 +1004,4 @@ private static function parseFileMetadata(string $fileName, string $source): voi
10041004
// Silently fall back to empty metadata if parsing fails
10051005
}
10061006
}
1007-
}
1007+
}

tests/Fixtures/Services/AdminEntityFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
class AdminEntityFactory extends BaseEntityFactory
88
{
9-
}
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Services;
6+
7+
/**
8+
* @template T
9+
*
10+
* @extends BaseGenericFactory<T>
11+
*/
12+
class AdminGenericFactory extends BaseGenericFactory
13+
{
14+
}

tests/Fixtures/Services/BaseEntityFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ public function withBadSetting(): object
7676
{
7777
return new AdminEntityFactory();
7878
}
79-
}
79+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Services;
6+
7+
use stdClass;
8+
use TypePHP\Tests\Fixtures\Generics\Producer;
9+
10+
/**
11+
* @template T
12+
*/
13+
abstract class BaseGenericFactory
14+
{
15+
/**
16+
* @param T $item
17+
*/
18+
public function __construct(public mixed $item)
19+
{
20+
}
21+
22+
/**
23+
* Static factory creating an instance of static<TValue>
24+
*
25+
* @template TValue
26+
*
27+
* @param TValue $value
28+
*
29+
* @return static<TValue>
30+
*/
31+
public static function of(mixed $value): static
32+
{
33+
return new static($value);
34+
}
35+
36+
/**
37+
* Static factory returning wrong item violating generic T
38+
*
39+
* @template TValue
40+
*
41+
* @param TValue $value
42+
*
43+
* @return static<TValue>
44+
*/
45+
public static function ofBadItem(mixed $value): static
46+
{
47+
return new static(new stdClass());
48+
}
49+
50+
/**
51+
* Method returning Producer holding static instance: Producer<static<T>>
52+
*
53+
* @return Producer<static<T>>
54+
*/
55+
public function toProducer(): Producer
56+
{
57+
return new Producer($this);
58+
}
59+
60+
/**
61+
* Method returning Producer holding sibling instance
62+
*
63+
* @return Producer<static<T>>
64+
*/
65+
public function toBadProducer(): Producer
66+
{
67+
return new Producer(new AdminGenericFactory($this->item));
68+
}
69+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Services;
6+
7+
/**
8+
* @template K of array-key
9+
* @template V
10+
*/
11+
abstract class BaseGenericMap
12+
{
13+
/**
14+
* @param array<K, V> $entries
15+
*/
16+
public function __construct(public array $entries = [])
17+
{
18+
}
19+
20+
/**
21+
* Static factory creating an instance with multiple bound templates
22+
*
23+
* @template TKey of array-key
24+
* @template TVal
25+
*
26+
* @param TKey $key
27+
* @param TVal $val
28+
*
29+
* @return static<TKey, TVal>
30+
*/
31+
public static function fromEntry(mixed $key, mixed $val): static
32+
{
33+
return new static([$key => $val]);
34+
}
35+
36+
/**
37+
* Factory returning Array Shape holding generic static instance
38+
*
39+
* @template TKey of array-key
40+
* @template TVal
41+
*
42+
* @param TKey $key
43+
* @param TVal $val
44+
*
45+
* @return array{instance: static<TKey, TVal>, count: positive-int}
46+
*/
47+
public static function toShape(mixed $key, mixed $val): array
48+
{
49+
return [
50+
'instance' => new static([$key => $val]),
51+
'count' => 1,
52+
];
53+
}
54+
55+
/**
56+
* Factory returning bad shape with invalid count
57+
*
58+
* @template TKey of array-key
59+
* @template TVal
60+
*
61+
* @param TKey $key
62+
* @param TVal $val
63+
*
64+
* @return array{instance: static<TKey, TVal>, count: positive-int}
65+
*/
66+
public static function toBadShape(mixed $key, mixed $val): array
67+
{
68+
return [
69+
'instance' => new static([$key => $val]),
70+
'count' => -1, // Violates positive-int in shape
71+
];
72+
}
73+
}

0 commit comments

Comments
 (0)