Skip to content
16

Compile-Time Check Elimination ​

A common concern with runtime type enforcement is performance overhead. If every return statement, variable assignment, and property initialization were blindly wrapped in runtime assertions, high-frequency functions and tight loops would suffer from unnecessary Zend VM function-call overhead.

TypePHP solves this with Compile-Time Check Elimination (eliminate_statically_proven_checks => true). During file transformation, TypePHP analyzes AST expressions against declared DocBlock contracts. If an expression is mathematically, logically, or syntactically proven to satisfy its contract, TypePHP completely skips injecting runtime assertion wrappers for that statement.


The Performance Difference: Before vs. After ​

Consider a standard getter method returning a string literal:

php
/** @return non-empty-string */
public function getVersion(): string
{
    return '2.0.0';
}

Without Elimination (Standard Runtime Wrapping) ​

php
return ($__typephpRet = \TypePHP\Internal\RuntimeTypeChecker::checkReturn(__METHOD__, '2.0.0', $this)) instanceof \TypePHP\Internal\Diagnostic\ErrorMessage 
    ? throw ... 
    : $__typephpRet;
  • Pushes arguments to the VM stack → invokes RuntimeTypeChecker::checkReturn().
  • Looks up function contract in memory → validates string length > 0.
  • Executes T_INSTANCEOF → evaluates ternary branch.
  • Cost: ~50 Zend VM opcodes and stack frame allocations on every single call.

With Compile-Time Elimination (TypePHP Optimization) ​

Because '2.0.0' is proven to be a non-empty-string at compile time, TypePHP emits clean, unmodified PHP code:

php
return '2.0.0';
  • Cost: 1 single Zend VM opcode (ZEND_RETURN).
  • Execution Speed: Runs at 100% native PHP C-speed (~15–30M calls/sec).
  • JIT Inlining Synergy: Because there are no dynamic wrapper calls or exception branches, PHP 8's Tracing JIT compiler can inline the getter directly into caller machine code.

Interactive Demo: Inspecting Transformed Source Code ​

Click the Transformed button in any of the snippets below to inspect how TypePHP compiles the code. Notice that statements with proven literals, shapes, constants, or variables have zero assertion wrappers injected, while dynamic expressions maintain runtime protection.


Scalars, Numbers & Arithmetic Expressions ​

ScalarEvaluator and ArithmeticEvaluator analyze scalar literals, constants, string prefixes, and folded arithmetic expressions. Operations like 60 * 60 or "user_" . $id are evaluated statically:

Scalar & Arithmetic Compile-Time Elimination
Generating AST transformation...

Class Constants, Wildcards & Enums ​

ConstantAndEnumEvaluator statically inspects class constants, wildcard patterns (Class::PREFIX_*), and PHP 8.1+ Enum cases and backing values:

Constant & Enum Elimination
Generating AST transformation...

Literal Arrays, Lists & Shapes ​

ArrayLiteralEvaluator recursively inspects array structures. If every key and element expression in an array is statically proven, TypePHP skips injecting collection loops and shape assertion wrappers:

Array Literal, List & Shape Elimination
Generating AST transformation...

Local Typed Variables (@var) & Assignments ​

Compile-time check elimination applies directly to local variable assignments and destructuring. When a variable is initialized with a proven literal, TypePHP emits a raw $x = ...; assignment without injecting RuntimeTypeChecker::checkVariable():

Typed Variable (@var) Elimination
Generating AST transformation...

Object Instantiations & Identity Returns ($this, new static) ​

ObjectEvaluator statically handles exact class instantiations and strict object identity:

Object & $this Elimination
Generating AST transformation...

Preceding Local Assignment Propagation ​

TypePHP tracks immediate predecessor local variable assignments within function bodies. If a local variable was just assigned a proven literal, returning that variable benefits from compile-time elimination:

Preceding Assignment Propagation
Generating AST transformation...

Generator Yield & Send Optimization ​

GeneratorEvaluator statically optimizes generator bodies, eliminating yield, yield from, send, and return wrappers when contracts are proven:

Generator Elimination
Generating AST transformation...

Elimination Safety Policy (EliminationSafetyPolicy) ​

To ensure that compile-time check elimination never compromises type safety or creates false negatives, TypePHP enforces strict safety policies.

When Class Methods Are Strictly Deferred to Runtime ​

Return checks on class methods are strictly deferred to runtime unless:

  1. The method is private: Private methods cannot override parent contracts and cannot be overridden by child classes.
  2. The class has no inheritance (hasRealInheritance === false): Standalone classes with no extends, implements, or use Trait cannot violate Liskov Substitution.
  3. Standalone Functions & Closures: Global functions, anonymous closures, and arrow functions have zero inheritance risk and are always eligible for elimination.

All public and protected methods in classes with inheritance preserve runtime return checks to guarantee parent contracts cannot be bypassed.

Why Reified Generic Objects Are Never Eliminated ​

When an expression instantiates a generic class (new Collection([1, 2])), TypePHP never eliminates the check.

Runtime generic checks perform the critical side-effect of stamping template bindings (T = int) onto the object instance in \WeakMap memory. Omitting the check would prevent downstream methods ($col->add()) from knowing the instance's generic state!

(Note: Generic array containers like list<T> and array<K, V> carry no object state and are fully eligible for compile-time elimination).


Summary of Statically Eliminated Scenarios ​

Expression PatternTarget ContractElimination Result
Literal Scalarsreturn 42; vs @return positive-int
return 'admin'; vs @return non-empty-string
Injected check is completely omitted. Statement remains clean return 'admin';.
Integer Rangesreturn 50; vs @return int<1, 100>Evaluated statically against range bounds. Check is omitted.
String Concatenationsreturn 'user_' . $id; vs @return non-empty-stringPrefix ≥1 char guarantees length ≥1. Check is omitted.
Class Constantsreturn self::MODE_TEST; vs @return self::MODE_*Constant wildcard prefix matched at compile time. Check is omitted.
Enum Cases & Valuesreturn Status::PENDING; vs @return Status
return Status::PENDING->value; vs value-of<Status>
Case and backing values matched at compile time. Check is omitted.
Empty Array Literalsreturn []; vs @return list<T> or array<K, V>In PHP, [] is always a valid list/array. Check is omitted.
Sequential Listsreturn [1, 2, 3]; vs @return list<positive-int>All keys verified sequential and values satisfy contract. Check is omitted.
Array Shapesreturn ['id' => 1]; vs array{id: int}All required keys present and values statically proven. Check is omitted.
Exact Class Instantiationsreturn new User('Alice'); vs @return UserFQCN matches without reflection. Check is omitted.
Identity Returnsreturn $this; vs @return $this / @return self
return new static(); vs @return static
Strict identity truth. Check is omitted; fluent chaining runs at native speed.
Property Defaultspublic int $timeout = 30; vs @var positive-intDefault value is statically proven. No constructor assertions synthesized.
Local @var Assignments/** @var positive-int $x */ $x = 10;Emits clean $x = 10; without checkVariable() wrapper.
Safe By-Ref Foreachforeach ($scores as &$s) { $s += 5; }Post-loop container assertion is omitted when loop body is free of opaque mutations.

Configuration & Disabling Check Elimination ​

Compile-time check elimination is enabled by default (true). You can disable it for deep debugging, tracing every single check in stack traces, or benchmarking unoptimized AST instrumentation:

In typephp.php ​

php
return [
    'eliminate_statically_proven_checks' => false,
];

In composer.json ​

json
{
  "extra": {
    "typephp": {
      "eliminate-statically-proven-checks": false
    }
  }
}

Via Environment Variable ​

bash
export TYPEPHP_ELIMINATE_STATICALLY_PROVEN_CHECKS=false