Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f0fb097
Add ByRefService and ByRefServiceInterface with by-reference paramete…
rcalicdan Aug 15, 2026
788583c
Add UnpackService and ArgumentUnpackingTest for argument unpacking fu…
rcalicdan Aug 15, 2026
fda9fa1
Add UnsealedPayloadService and ComplexUnsealedShapesTest for dynamic …
rcalicdan Aug 15, 2026
e78b3a8
Add CollisionService and related traits for method conflict resolutio…
rcalicdan Aug 15, 2026
bda8e3b
Add trait alias caching and retrieval in HierarchyResolver for improv…
rcalicdan Aug 15, 2026
ccb6025
Add ReadonlyUser and UninitializedReadonlyContainer classes with test…
rcalicdan Aug 15, 2026
f5a9093
Add AnonymousContractInterface and ReadonlyOrder classes with tests f…
rcalicdan Aug 15, 2026
511d9a8
Add Suit and TransactionStatus enums with tests for key-of and value-…
rcalicdan Aug 15, 2026
bf38954
Add ConditionalReturnService and MultiBranchConditionalReturnsTest fo…
rcalicdan Aug 15, 2026
67912d8
Add DnfService and DnfAndComplexIntersectionsTest for handling comple…
rcalicdan Aug 15, 2026
09a82fa
Add DeepOffsetContainer and DeepOffsetAccessTest for multi-level nest…
rcalicdan Aug 15, 2026
60a4150
Add CurriedPipelineService and tests for curried callable validation …
rcalicdan Aug 15, 2026
34a1754
Add CallableTypeNode handling in ReturnChecker for callable return ty…
rcalicdan Aug 15, 2026
93f9bc1
Add CallableTypeNode support and enhance callable handling in various…
rcalicdan Aug 15, 2026
2ea2fc7
Add GenericStreamService and tests for generic iterable and generator…
rcalicdan Aug 15, 2026
47990a8
Enhance GeneratorChecker and RuntimeTypeChecker to support context-aw…
rcalicdan Aug 15, 2026
0f89f4e
Add FirstClassCallableService and corresponding tests for first-class…
rcalicdan Aug 15, 2026
8866ab8
Add MultiTemplateBag class and corresponding tests for multi-template…
rcalicdan Aug 15, 2026
f6d78e8
Add CRLF line-drift stress tests for source transformation
rcalicdan Aug 15, 2026
21b9b90
Refactor CRLF line-drift tests to ensure Windows compatibility and im…
rcalicdan Aug 15, 2026
15b3ee6
Fix debug_backtrace function retrieval in ParamChecker and ReturnChec…
rcalicdan Aug 15, 2026
45d966a
Enhance documentation on generics, callables, and iterators
rcalicdan Aug 15, 2026
fa715cd
Fix path comparison in TypeError exception handling for CRLF tests
rcalicdan Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ jobs:
if: matrix.os == 'ubuntu-latest' && matrix.php == '8.3'

- name: Run Test Suite (Pest)
run: ./vendor/bin/pest --ci
run: ./vendor/bin/pest --compact
56 changes: 54 additions & 2 deletions docs/advanced/liskov-and-inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,57 @@ $service->recordAuditLog(-1, 'audit_ok');

---

## Trait Conflict Resolution (`insteadof`)

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:

```php
trait PrimaryLogger
{
/**
* @param positive-int $level
* @param non-empty-string $message
*/
public function log(int $level, string $message): string
{
return "primary: {$level} - {$message}";
}
}

trait SecondaryLogger
{
/**
* @param negative-int $level
* @param string $message
*/
public function log(int $level, string $message): string
{
return "secondary: {$level} - {$message}";
}
}

class LoggingService
{
// PrimaryLogger::log is selected instead of SecondaryLogger
use SecondaryLogger, PrimaryLogger {
PrimaryLogger::log insteadof SecondaryLogger;
SecondaryLogger::log as secondaryLog;
}
}

$service = new LoggingService();

// 1. Primary log() enforces positive-int and non-empty-string from PrimaryLogger
$service->log(10, 'server_boot'); // Valid
// $service->log(-5, 'server_boot'); // Throws: TypeError: Argument $level must be of type positive-int

// 2. Aliased secondaryLog() enforces negative-int from SecondaryLogger
$service->secondaryLog(-20, 'server_shutdown'); // Valid
// $service->secondaryLog(20, 'server_shutdown'); // Throws: TypeError: Argument $level must be of type negative-int
```

---

## Partial Parameter Overriding (Gap-Filling)

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:
Expand Down Expand Up @@ -338,7 +389,7 @@ When a child class, constructor, or trait implementation overrides an ancestor m
TypePHP resolves parameter contract inheritance using **3-Tier Name & Position Disambiguation**:

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.
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.
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.
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!

```php
Expand Down Expand Up @@ -435,7 +486,7 @@ To ensure that resolving complex inheritance chains introduces zero perceptible

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

---

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

* If a parent class or interface is located inside an excluded folder (such as `/vendor/`), TypePHP **ignores its inherited docblocks**.
* This prevents third-party package docblock errors or outdated annotations from causing unexpected `TypeError` exceptions in your application code.
```
142 changes: 142 additions & 0 deletions docs/core-concepts/function-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,148 @@ registerUser(age: 25, username: 'Alice', id: -5);

---

## Arguments Passed By-Reference (`&$param`)

TypePHP natively supports PHP's by-reference parameter semantics (`function update(int &$value)`).

### How By-Reference Validation Works

1. **Entry Guard Rails:** TypePHP inspects and validates the variable's value *on function entry* before the function body executes.
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.
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**.

```php
<?php

declare(strict_types=1);

/**
* @param positive-int &$score
* @param non-empty-string &$username
*/
function applyBonus(int &$score, string &$username): void
{
$score += 50;
$username = strtoupper($username);
}

// 1. Valid Call: Value is validated on entry and mutated in caller scope
$userScore = 100;
$userName = 'alice';

applyBonus($userScore, $userName);

echo $userScore; // Output: 150
echo $userName; // Output: 'ALICE'

// 2. Invalid Call: Throws TypeError on entry, leaving caller variable untouched!
$invalidScore = -10;
$userTag = 'alice';

try {
applyBonus($invalidScore, $userTag);
} catch (\TypeError $e) {
echo $invalidScore; // Still -10 (Caller variable was never corrupted!)
}
```

### DocBlock Syntax Flexibility (`&$param` vs. `$param`)

You can write your DocBlock annotations with or without the leading ampersand (`&`). Both are recognized identically by TypePHP's parser:

```php
// Option A: Explicit ampersand in DocBlock (Recommended)
/**
* @param positive-int &$count
*/
function incrementCount(int &$count): void { $count++; }

// Option B: Ampersand in native PHP signature only (Fully supported)
/**
* @param positive-int $count
*/
function incrementCount(int &$count): void { $count++; }
```

### By-Reference Arrays & Shapes

Mutating collections or array shapes in-place preserves caller scope bindings:

```php
/**
* @param list<positive-int> &$scores
*/
function appendReward(array &$scores): void
{
$scores[] = 500; // Mutates array in caller scope
}

$myScores = [10, 20, 30];
appendReward($myScores);

print_r($myScores); // [10, 20, 30, 500]
```

### Variadic By-Reference Parameters (`&...$params`)

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:

```php
/**
* @param positive-int &...$numbers
*/
function doubleAll(int &...$numbers): void
{
foreach ($numbers as &$num) {
$num *= 2;
}
}

$a = 5;
$b = 10;
$c = 15;

doubleAll($a, $b, $c);

echo "$a, $b, $c"; // Output: 10, 20, 30
```

### OOP & Interface Inheritance for By-Reference Parameters

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):

```php
interface StatusUpdaterInterface
{
/**
* @param non-empty-string &$status
* @param positive-int &$code
*/
public function update(string &$status, int &$code): void;
}

class StatusUpdater implements StatusUpdaterInterface
{
// Inherits contracts and by-reference semantics seamlessly
public function update(string &$status, int &$statusCode): void
{
$status = strtoupper($status);
$statusCode += 100;
}
}

$updater = new StatusUpdater();
$currentStatus = 'pending';
$currentCode = 200;

$updater->update($currentStatus, $currentCode);

echo $currentStatus; // 'PENDING'
echo $currentCode; // 300
```

---

## Class Methods (Instance & Static)

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