How TypePHP Resolves Universal Methods at Compile Time: Static Dispatch in Swoole's PHP Compiler

TypePHP resolves universal methods like length(), upper(), and count() by looking up static type information in the UNIVERSAL_METHODS table at compile time and generating direct C++ calls to underlying PHP functions, eliminating runtime dispatch overhead.

The swoole/typephp project provides a compiled PHP implementation that transforms high-level method calls into optimized native code. When TypePHP encounters universal methods—standard operations available on strings, arrays, and other built-in types—it bypasses dynamic method resolution entirely by resolving these calls during the compilation phase.

The 7-Step Compile-Time Resolution Pipeline

TypePHP handles universal method resolution through a deterministic pipeline defined across three core files. The process transforms a PHP method call into a direct function invocation without runtime overhead.

Step 1: AST Interception via MethodCallTrait

When the parser encounters a method invocation, it produces a Node\Expr\MethodCall AST node. The MethodCallTrait intercepts this node at lines 568-737 in src/Parser/MethodCallTrait.php and immediately forwards it to the universal method handling path.

The trait determines whether the current node qualifies for universal method processing before delegating to the specialized parser.

Step 2: Static Type Detection

Type inference occurs via CompilerBase, which uses the UniversalMethodCall trait. At line 3126 in CompilerBase.php, the compiler calls detectUniversalMethodReturnType to determine the receiver's compile-time type—such as Type::STR for strings or Type::ARRAY for arrays.

This static type information is crucial because it dictates which universal method entries are valid for the specific receiver.

Step 3: UNIVERSAL_METHODS Table Lookup

The core resolution logic resides in src/Parser/UniversalMethodCall.php. The findUniversalMethodAnyType() method first queries the static UNIVERSAL_METHODS table defined at lines 17-84 for strings and lines 107-108 for arrays.

Each entry contains:

  • A handler type (e.g., php_fn, calc_op)
  • The underlying PHP or C++ function name (fn or method)
  • The expected return type (return_type)
  • Argument constraints (min_args, max_args)

Step 4: Extension Method Fallback

If the method is absent from UNIVERSAL_METHODS, the compiler falls back to user-defined extensions. The findExtensionMethod() function searches the MethodsFor registry, which getMethodsForRegistry() builds lazily at lines 41-104 in UniversalMethodCall.php.

This registry allows developers to register custom static extensions that behave like built-in universal methods.

Step 5: Argument Validation

Before code generation, validateUniversalMethodArgs() at line 678 enforces constraints:

  • Verifies the argument count matches min_args and max_args
  • Ensures mutable receivers are allowed when using mutating handlers

Step 6: Target-Specific Code Generation

Based on the handler type, TypePHP invokes specific generators:

  • genUniversalPhpFn() (lines 810-827): Emits calls to PHP built-ins like strlen or count
  • genUniversalCalcOp(): Generates arithmetic operations (+, -, etc.)
  • genUniversalDirectMethod(): Produces direct C++ class method invocations

For example, genUniversalPhpFn() generates code in the format php::fn($receiver, ...).

Step 7: IR Insertion

The generated C++ snippet replaces the original method-call node in the intermediate representation. This insertion occurs inside parseUniversalMethodCall() around line 601. The compiled binary contains the direct function call with no runtime dispatch logic.

Concrete Examples: From PHP to C++

The following examples demonstrate how TypePHP transforms high-level PHP code into low-level C++ calls during compilation.

String Length Resolution

When compiling $s->length() where $s is Type::STR:

// PHP source
$s = "hello";
$len = $s->length();

The compiler looks up the length entry in UNIVERSAL_METHODS:

'length' => [
    'handler' => 'php_fn', 
    'fn' => 'strlen', 
    'return_type' => Type::INT
]

This generates the C++ code:

// Generated C++
php::strlen($s)

String Uppercase Resolution

For $s->upper() on a string receiver:

// PHP source
$upper = $s->upper();

Maps to:

// Generated C++
php::strtoupper($s)

Array Count Resolution

For $arr->count() where $arr is Type::ARRAY:

// PHP source
$count = $arr->count();

Resolves to:

// Generated C++
php::count($arr)

Key Source Files and Architecture

Understanding the file structure helps navigate the resolution implementation:

  • src/Parser/UniversalMethodCall.php: Contains the UNIVERSAL_METHODS table, return type resolution logic, MethodsFor registry construction, and code generation helpers (genUniversalPhpFn, genUniversalCalcOp, genUniversalDirectMethod).

  • src/Parser/MethodCallTrait.php: Handles AST node detection at lines 568-737, determines universal method applicability, and delegates to parseUniversalMethodCall().

  • src/CompilerBase.php: Integrates the UniversalMethodCall trait into the main compilation pipeline and provides type inference via detectUniversalMethodReturnType at line 3126.

Summary

TypePHP eliminates runtime method lookup overhead for standard operations through compile-time resolution:

  • Static analysis determines receiver types using CompilerBase and detectUniversalMethodReturnType
  • Lookup tables in UNIVERSAL_METHODS map method names to handlers and underlying functions
  • Fallback registry allows user-defined extensions via the MethodsFor system
  • Direct code generation produces C++ calls to php::strlen, php::count, and similar functions
  • Zero runtime cost results because the compiled binary contains only the resolved function calls

Frequently Asked Questions

What happens if a universal method is not found in the UNIVERSAL_METHODS table?

If findUniversalMethodAnyType() fails to locate the method in the static UNIVERSAL_METHODS table, TypePHP falls back to the extension method registry. The findExtensionMethod() function searches the MethodsFor registry, which is built lazily by getMethodsForRegistry() in UniversalMethodCall.php. If no extension exists, the compiler raises a resolution error.

How does TypePHP handle argument validation for universal methods?

The validateUniversalMethodArgs() method at line 678 in UniversalMethodCall.php enforces compile-time checks. It validates that the provided argument count falls within the min_args and max_args constraints defined in the method entry, and verifies that mutable receivers are only used with mutating handlers when required.

Can users extend universal methods with custom handlers?

Yes. Developers can register custom handlers through the MethodsFor registry system. The getMethodsForRegistry() method at lines 41-104 builds this registry lazily, allowing user-defined static extensions to integrate seamlessly with the universal method resolution pipeline alongside built-in methods like length() and count().

Why does TypePHP compile universal methods to direct function calls?

By resolving length(), upper(), and count() at compile time through the UNIVERSAL_METHODS lookup and generating direct C++ calls via genUniversalPhpFn(), TypePHP eliminates dynamic dispatch overhead. This approach provides zero-cost abstractions where standard PHP operations compile to direct invocations of php::strlen or php::count without runtime method resolution or virtual table lookups.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →