Skip to content

Commit 0856b92

Browse files
committed
Add generic classes for testing: ChildSingleAbstractService, DeepGenericChildService, DeepGenericMidParent, and SingleAbstractGenericParent
1 parent 219a75c commit 0856b92

6 files changed

Lines changed: 110 additions & 20 deletions

File tree

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\Generics;
6+
7+
class ChildSingleAbstractService extends SingleAbstractGenericParent
8+
{
9+
}
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\Generics;
6+
7+
class DeepGenericChildService extends DeepGenericMidParent
8+
{
9+
}
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\Generics;
6+
7+
abstract class DeepGenericMidParent extends DeepGenericRootParent
8+
{
9+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Generics;
6+
7+
/**
8+
* @template T
9+
*/
10+
abstract class DeepGenericRootParent
11+
{
12+
/**
13+
* @param T $element
14+
*/
15+
public function processElement(mixed $element): bool
16+
{
17+
return true;
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Tests\Fixtures\Generics;
6+
7+
/**
8+
* @template T
9+
*/
10+
abstract class SingleAbstractGenericParent
11+
{
12+
/**
13+
* @param T $item
14+
*/
15+
public function setItem(mixed $item): bool
16+
{
17+
return true;
18+
}
19+
}

tests/TypeChecking/Generics/InheritedInterfaceTemplateBindingTest.php

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,55 @@
77
use TypePHP\Tests\Fixtures\Domain\Cat;
88
use TypePHP\Tests\Fixtures\Domain\Dog;
99
use TypePHP\Tests\Fixtures\Generics\ChildComboService;
10+
use TypePHP\Tests\Fixtures\Generics\ChildSingleAbstractService;
1011
use TypePHP\Tests\Fixtures\Generics\ChildWithoutTemplateDocblock;
12+
use TypePHP\Tests\Fixtures\Generics\DeepGenericChildService;
1113
use TypePHP\TypePHP;
1214

1315
describe('Inherited Interface Template Discovery during Instance Pre-binding', function () {
14-
test('binds generic union template when child class inherits template from interface without declaring it locally', function () {
15-
/** @var ChildWithoutTemplateDocblock<Dog|Cat> $container */
16-
$container = new ChildWithoutTemplateDocblock();
16+
describe('Interface Template Inheritance', function () {
17+
test('binds generic union template when child class inherits template from interface without declaring it locally', function () {
18+
/** @var ChildWithoutTemplateDocblock<Dog|Cat> $container */
19+
$container = new ChildWithoutTemplateDocblock();
1720

18-
$container->push(new Dog());
21+
$container->push(new Dog());
1922

20-
expect($container->push(new Cat()))->toBeTrue();
21-
expect(TypePHP::getGenericType($container))->toBe('(' . Dog::class . ' | ' . Cat::class . ')');
23+
expect($container->push(new Cat()))->toBeTrue();
24+
expect(TypePHP::getGenericType($container))->toBe('(' . Dog::class . ' | ' . Cat::class . ')');
25+
});
26+
27+
test('throws TypeError when item violates pre-bound union template on child inheriting interface', function () {
28+
/** @var ChildWithoutTemplateDocblock<Dog|Cat> $container */
29+
$container = new ChildWithoutTemplateDocblock();
30+
31+
expect(fn () => $container->push(new Car()))
32+
->toThrow(TypeError::class, 'must be of type (' . Dog::class . ' | ' . Cat::class . ')');
33+
});
34+
});
35+
36+
describe('Single Abstract Class Generic Inheritance', function () {
37+
test('pre-binds template inherited from single abstract parent without child docblock', function () {
38+
/** @var ChildSingleAbstractService<Dog|Cat> $service */
39+
$service = new ChildSingleAbstractService();
40+
41+
expect($service->setItem(new Dog()))->toBeTrue();
42+
expect($service->setItem(new Cat()))->toBeTrue();
43+
44+
expect(fn () => $service->setItem(new Car()))
45+
->toThrow(TypeError::class, 'must be of type (' . Dog::class . ' | ' . Cat::class . ')');
46+
});
2247
});
2348

24-
test('throws TypeError when item violates pre-bound union template on child inheriting interface', function () {
25-
/** @var ChildWithoutTemplateDocblock<Dog|Cat> $container */
26-
$container = new ChildWithoutTemplateDocblock();
49+
describe('Multi-Nested Deep Generic Inheritance (Root -> Mid -> Child)', function () {
50+
test('pre-binds template inherited across 3-tier deep abstract inheritance chain without child docblock', function () {
51+
/** @var DeepGenericChildService<positive-int> $service */
52+
$service = new DeepGenericChildService();
2753

28-
expect(fn () => $container->push(new Car()))
29-
->toThrow(TypeError::class, 'must be of type (' . Dog::class . ' | ' . Cat::class . ')')
30-
;
54+
expect($service->processElement(100))->toBeTrue();
55+
56+
expect(fn () => $service->processElement(-50))
57+
->toThrow(TypeError::class, 'must be of type positive-int');
58+
});
3159
});
3260

3361
describe('Abstract Class + Interface + Trait Combo Hierarchy', function () {
@@ -37,19 +65,16 @@
3765

3866
expect($service->processData(42))->toBeTrue();
3967
expect(fn () => $service->processData(-5))
40-
->toThrow(TypeError::class, 'must be of type positive-int')
41-
;
68+
->toThrow(TypeError::class, 'must be of type positive-int');
4269

4370
expect($service->setKey('valid_key'))->toBeTrue();
4471
expect(fn () => $service->setKey(''))
45-
->toThrow(TypeError::class, 'must be of type non-empty-string')
46-
;
47-
72+
->toThrow(TypeError::class, 'must be of type non-empty-string');
73+
4874
expect($service->setVal(new Dog()))->toBeTrue();
4975
expect($service->setVal(new Cat()))->toBeTrue();
5076
expect(fn () => $service->setVal(new Car()))
51-
->toThrow(TypeError::class, 'must be of type (' . Dog::class . ' | ' . Cat::class . ')')
52-
;
77+
->toThrow(TypeError::class, 'must be of type (' . Dog::class . ' | ' . Cat::class . ')');
5378
});
5479
});
55-
});
80+
});

0 commit comments

Comments
 (0)