Skip to content

Commit 45d966a

Browse files
committed
Enhance documentation on generics, callables, and iterators
- Added sections on simultaneous first-use multi-template inference and generic callables with template substitution in generics and bounds documentation. - Expanded explanations on conditional types, negated generic conditionals, and generic iterables with template substitution. - Introduced detailed examples for generic generators and interactive generators, including validation of sent values. - Updated arrays and shapes documentation to include key/value extractions and implicit keyless tuple syntax. - Improved clarity on callable contracts, including strict closure instance contracts and higher-order callables. - Enhanced iterator and generator documentation with lazy validation and multi-level iterator unwrapping.
1 parent 15b3ee6 commit 45d966a

6 files changed

Lines changed: 828 additions & 128 deletions

File tree

docs/advanced/liskov-and-inheritance.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,57 @@ $service->recordAuditLog(-1, 'audit_ok');
283283

284284
---
285285

286+
## Trait Conflict Resolution (`insteadof`)
287+
288+
When a class uses multiple traits with identical method names, PHP requires resolving the collision with `insteadof`. TypePHP respects `insteadof` precedence, enforcing contracts strictly from the selected trait:
289+
290+
```php
291+
trait PrimaryLogger
292+
{
293+
/**
294+
* @param positive-int $level
295+
* @param non-empty-string $message
296+
*/
297+
public function log(int $level, string $message): string
298+
{
299+
return "primary: {$level} - {$message}";
300+
}
301+
}
302+
303+
trait SecondaryLogger
304+
{
305+
/**
306+
* @param negative-int $level
307+
* @param string $message
308+
*/
309+
public function log(int $level, string $message): string
310+
{
311+
return "secondary: {$level} - {$message}";
312+
}
313+
}
314+
315+
class LoggingService
316+
{
317+
// PrimaryLogger::log is selected instead of SecondaryLogger
318+
use SecondaryLogger, PrimaryLogger {
319+
PrimaryLogger::log insteadof SecondaryLogger;
320+
SecondaryLogger::log as secondaryLog;
321+
}
322+
}
323+
324+
$service = new LoggingService();
325+
326+
// 1. Primary log() enforces positive-int and non-empty-string from PrimaryLogger
327+
$service->log(10, 'server_boot'); // Valid
328+
// $service->log(-5, 'server_boot'); // Throws: TypeError: Argument $level must be of type positive-int
329+
330+
// 2. Aliased secondaryLog() enforces negative-int from SecondaryLogger
331+
$service->secondaryLog(-20, 'server_shutdown'); // Valid
332+
// $service->secondaryLog(20, 'server_shutdown'); // Throws: TypeError: Argument $level must be of type negative-int
333+
```
334+
335+
---
336+
286337
## Partial Parameter Overriding (Gap-Filling)
287338

288339
If a child class overrides a method and provides a docblock for **only some** parameters, TypePHP fills in the missing parameter contracts from the parent class or interface:
@@ -338,7 +389,7 @@ When a child class, constructor, or trait implementation overrides an ancestor m
338389
TypePHP resolves parameter contract inheritance using **3-Tier Name & Position Disambiguation**:
339390

340391
1. **Name-First Matching:** If a parameter name in the child method matches a parameter name in the parent class (e.g. `$container`), the parent's contract is mapped to that parameter regardless of its position index in the child.
341-
2. **Position Fallback on Renamed Parameters:** If a parameter is renamed in the child class (e.g., `$id` $\rightarrow$ `$userId`), TypePHP maps the contract using its position index.
392+
2. **Position Fallback on Renamed Parameters:** If a parameter is renamed in the child class (e.g. `$id` $\rightarrow$ `$userId`), TypePHP maps the contract using its position index.
342393
3. **Candidate Disambiguation (Shift Protection):** If a child class inserts a new parameter at index 0 (shifting all subsequent parameters down), TypePHP **verifies that the candidate child parameter does not already exist in the parent under its own name**. This prevents parent parameter contracts from accidentally mis-mapping onto shifted child parameters!
343394

344395
```php
@@ -435,7 +486,7 @@ To ensure that resolving complex inheritance chains introduces zero perceptible
435486

436487
When you call `$userRepo->find(42)` 1,000 times in a loop:
437488
* **Invocation #1:** TypePHP builds the `UserRepository` inheritance tree, parses the docblocks, merges parent gaps, and caches the resolved contract in static RAM.
438-
* **Invocations #2 through #1,000:** TypePHP fetches the pre-resolved contract directly from static RAM in **O(1) constant nanoseconds**—zero Reflection traversal occurs!
489+
* **Invocations #2 through #1,000:** TypePHP fetches the pre-resolved contract directly from static RAM in **$O(1)$ constant nanoseconds**—zero Reflection traversal occurs!
439490

440491
---
441492

@@ -445,3 +496,4 @@ TypePHP protects your application from third-party vendor docblock bugs using **
445496

446497
* If a parent class or interface is located inside an excluded folder (such as `/vendor/`), TypePHP **ignores its inherited docblocks**.
447498
* This prevents third-party package docblock errors or outdated annotations from causing unexpected `TypeError` exceptions in your application code.
499+
```

docs/core-concepts/function-contracts.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,148 @@ registerUser(age: 25, username: 'Alice', id: -5);
6666

6767
---
6868

69+
## Arguments Passed By-Reference (`&$param`)
70+
71+
TypePHP natively supports PHP's by-reference parameter semantics (`function update(int &$value)`).
72+
73+
### How By-Reference Validation Works
74+
75+
1. **Entry Guard Rails:** TypePHP inspects and validates the variable's value *on function entry* before the function body executes.
76+
2. **In-Place Caller Scope Mutation:** If the argument passes validation, the function body executes normally, and any modifications to the variable mutate the caller's variable in the caller's scope.
77+
3. **Safety Guarantee on Failure:** If an invalid value is passed into a by-reference parameter, a `TypeError` is thrown *before* any code in the function body runs, ensuring the caller's variable remains **100% un-mutated and un-corrupted**.
78+
79+
```php
80+
<?php
81+
82+
declare(strict_types=1);
83+
84+
/**
85+
* @param positive-int &$score
86+
* @param non-empty-string &$username
87+
*/
88+
function applyBonus(int &$score, string &$username): void
89+
{
90+
$score += 50;
91+
$username = strtoupper($username);
92+
}
93+
94+
// 1. Valid Call: Value is validated on entry and mutated in caller scope
95+
$userScore = 100;
96+
$userName = 'alice';
97+
98+
applyBonus($userScore, $userName);
99+
100+
echo $userScore; // Output: 150
101+
echo $userName; // Output: 'ALICE'
102+
103+
// 2. Invalid Call: Throws TypeError on entry, leaving caller variable untouched!
104+
$invalidScore = -10;
105+
$userTag = 'alice';
106+
107+
try {
108+
applyBonus($invalidScore, $userTag);
109+
} catch (\TypeError $e) {
110+
echo $invalidScore; // Still -10 (Caller variable was never corrupted!)
111+
}
112+
```
113+
114+
### DocBlock Syntax Flexibility (`&$param` vs. `$param`)
115+
116+
You can write your DocBlock annotations with or without the leading ampersand (`&`). Both are recognized identically by TypePHP's parser:
117+
118+
```php
119+
// Option A: Explicit ampersand in DocBlock (Recommended)
120+
/**
121+
* @param positive-int &$count
122+
*/
123+
function incrementCount(int &$count): void { $count++; }
124+
125+
// Option B: Ampersand in native PHP signature only (Fully supported)
126+
/**
127+
* @param positive-int $count
128+
*/
129+
function incrementCount(int &$count): void { $count++; }
130+
```
131+
132+
### By-Reference Arrays & Shapes
133+
134+
Mutating collections or array shapes in-place preserves caller scope bindings:
135+
136+
```php
137+
/**
138+
* @param list<positive-int> &$scores
139+
*/
140+
function appendReward(array &$scores): void
141+
{
142+
$scores[] = 500; // Mutates array in caller scope
143+
}
144+
145+
$myScores = [10, 20, 30];
146+
appendReward($myScores);
147+
148+
print_r($myScores); // [10, 20, 30, 500]
149+
```
150+
151+
### Variadic By-Reference Parameters (`&...$params`)
152+
153+
When accepting a variable number of by-reference arguments (`int &...$numbers`), TypePHP validates every individual argument on entry and preserves in-place mutations across all variadic arguments:
154+
155+
```php
156+
/**
157+
* @param positive-int &...$numbers
158+
*/
159+
function doubleAll(int &...$numbers): void
160+
{
161+
foreach ($numbers as &$num) {
162+
$num *= 2;
163+
}
164+
}
165+
166+
$a = 5;
167+
$b = 10;
168+
$c = 15;
169+
170+
doubleAll($a, $b, $c);
171+
172+
echo "$a, $b, $c"; // Output: 10, 20, 30
173+
```
174+
175+
### OOP & Interface Inheritance for By-Reference Parameters
176+
177+
When a child class implements an interface or overrides a parent method with by-reference parameters, the type contract and reference semantics are inherited automatically (even when child methods rename parameters):
178+
179+
```php
180+
interface StatusUpdaterInterface
181+
{
182+
/**
183+
* @param non-empty-string &$status
184+
* @param positive-int &$code
185+
*/
186+
public function update(string &$status, int &$code): void;
187+
}
188+
189+
class StatusUpdater implements StatusUpdaterInterface
190+
{
191+
// Inherits contracts and by-reference semantics seamlessly
192+
public function update(string &$status, int &$statusCode): void
193+
{
194+
$status = strtoupper($status);
195+
$statusCode += 100;
196+
}
197+
}
198+
199+
$updater = new StatusUpdater();
200+
$currentStatus = 'pending';
201+
$currentCode = 200;
202+
203+
$updater->update($currentStatus, $currentCode);
204+
205+
echo $currentStatus; // 'PENDING'
206+
echo $currentCode; // 300
207+
```
208+
209+
---
210+
69211
## Class Methods (Instance & Static)
70212

71213
All parameter and return contract rules apply identically to **instance methods** (`public`, `protected`, `private`) and **static methods**:

0 commit comments

Comments
 (0)