Skip to content

Commit 53ed687

Browse files
committed
Add Late Static Binding return contracts and improve documentation; remove variadic parameter contracts section
1 parent b4f526a commit 53ed687

1 file changed

Lines changed: 94 additions & 24 deletions

File tree

docs/core-concepts/function-contracts.md

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ registerUser(-5, 'Alice', 'admin');
3636
> **Execution Order Note:** Native PHP type hints (e.g., `int $id`, `string $username`) are evaluated by PHP's C-engine *before* function execution begins. TypePHP's extended PHPDoc contracts (e.g., `positive-int`, `non-empty-string`) execute at the very start of the function/method body. If a native type hint fails, PHP throws its native `TypeError` before TypePHP's guard rails run.
3737
3838
---
39+
3940
## PHP 8.0+ Named Arguments
4041

4142
TypePHP natively supports PHP 8.0+ Named Arguments. Because parameter contracts are mapped by parameter name rather than argument position index, you can pass named arguments in any order, and TypePHP will accurately validate each parameter:
@@ -62,6 +63,7 @@ registerUser(age: 25, username: 'Alice', id: 42);
6263
registerUser(age: 25, username: 'Alice', id: -5);
6364
// Throws: TypeError: registerUser(): Argument $id must be of type positive-int, negative int (-5) given
6465
```
66+
6567
---
6668

6769
## Class Methods (Instance & Static)
@@ -189,29 +191,6 @@ getUserStatus(-10);
189191
190192
---
191193

192-
## Variadic Parameter Contracts
193-
194-
When a function or method accepts variadic arguments (`...$items`), TypePHP validates every element passed in the variadic argument list:
195-
196-
```php
197-
/**
198-
* @param positive-int ...$ids
199-
*/
200-
function deleteUsers(int ...$ids): void
201-
{
202-
// ...
203-
}
204-
205-
// Valid Call
206-
deleteUsers(10, 20, 30);
207-
208-
// Invalid Call (3rd variadic item violates positive-int)
209-
deleteUsers(10, 20, -5);
210-
// Throws: TypeError: deleteUsers(): Argument $ids[2] must be of type positive-int
211-
```
212-
213-
---
214-
215194
## Fluent `$this` Identity Returns
216195

217196
For fluent builder or service classes annotated with `@return $this`, TypePHP verifies strict object identity (`$result === $this`), preventing accidental instantiation of new instances:
@@ -247,6 +226,96 @@ $builder->cloneSelf();
247226

248227
---
249228

229+
## Late Static Binding Return Contracts (`@return static`)
230+
231+
When a parent class method (static factory method or fluent instance method) is annotated with `@return static`, TypePHP enforces **Late Static Binding** at runtime.
232+
233+
It dynamically verifies that the returned object is an instance of the **actual calling class** (`UserEntityFactory`), strictly rejecting parent instances (`BaseEntityFactory`), sibling instances (`AdminEntityFactory`), or generic objects (`stdClass`):
234+
235+
```php
236+
abstract class BaseEntityFactory
237+
{
238+
/**
239+
* @return static
240+
*/
241+
public static function create(): static
242+
{
243+
return new static();
244+
}
245+
246+
/**
247+
* @return static
248+
*/
249+
public static function createSibling(): object
250+
{
251+
return new AdminEntityFactory(); // Invalid: Returns sibling instead of calling class!
252+
}
253+
}
254+
255+
class UserEntityFactory extends BaseEntityFactory {}
256+
class AdminEntityFactory extends BaseEntityFactory {}
257+
258+
// Valid: Returns UserEntityFactory instance matching the late-static calling class
259+
$user = UserEntityFactory::create();
260+
261+
// Invalid: UserEntityFactory called, but AdminEntityFactory was returned!
262+
UserEntityFactory::createSibling();
263+
// Throws: TypeError: UserEntityFactory::createSibling(): Return value must be of type App\UserEntityFactory, App\AdminEntityFactory returned
264+
```
265+
266+
### Late Static Binding with Generics (`static<T>`)
267+
268+
Late static binding seamlessly integrates with TypePHP's Reified Generics engine. A static factory can return a specialized generic instance of the late-static-bound calling class:
269+
270+
```php
271+
/**
272+
* @template T
273+
*/
274+
abstract class BaseGenericFactory
275+
{
276+
/**
277+
* @template TValue
278+
* @param TValue $value
279+
* @return static<TValue>
280+
*/
281+
public static function of(mixed $value): static
282+
{
283+
return new static($value);
284+
}
285+
}
286+
287+
class UserGenericFactory extends BaseGenericFactory {}
288+
289+
// 1. Returns UserGenericFactory instance
290+
// 2. Binds generic template T = Dog in WeakMap memory!
291+
$factory = UserGenericFactory::of(new Dog());
292+
```
293+
294+
---
295+
296+
## Variadic Parameter Contracts
297+
298+
When a function or method accepts variadic arguments (`...$items`), TypePHP validates every element passed in the variadic argument list:
299+
300+
```php
301+
/**
302+
* @param positive-int ...$ids
303+
*/
304+
function deleteUsers(int ...$ids): void
305+
{
306+
// ...
307+
}
308+
309+
// Valid Call
310+
deleteUsers(10, 20, 30);
311+
312+
// Invalid Call (3rd variadic item violates positive-int)
313+
deleteUsers(10, 20, -5);
314+
// Throws: TypeError: deleteUsers(): Argument $ids[2] must be of type positive-int
315+
```
316+
317+
---
318+
250319
## Conditional Return Types
251320

252321
TypePHP supports parameter-based conditional return types (`@return ($param is true ? TypeA : TypeB)`):
@@ -270,13 +339,14 @@ formatValue(false, 'hello'); // Evaluates return type as non-empty-string
270339
formatValue(true, 'not_an_int');
271340
// Throws: TypeError: formatValue(): Return value must be of type positive-int
272341
```
342+
273343
---
274344

275345
## PHP 8.0+ Attributes Coexistence
276346

277347
TypePHP seamlessly coexists with native PHP 8.0+ Attributes (`#[Route]`, `#[Inject]`, `#[Validate]`).
278348

279-
You can place your PHPDoc annotations **either above or below** native PHP attributes on properties, methods/functions. TypePHP's AST engine and PHP's Reflection API process both metadata channels independently without any syntax conflicts:
349+
You can place your PHPDoc annotations **either above or below** native PHP attributes on properties, methods, or functions. TypePHP's AST engine and PHP's Reflection API process both metadata channels independently without any syntax conflicts:
280350

281351
```php
282352
// Option A: DocBlock ABOVE Attribute (Supported)

0 commit comments

Comments
 (0)