Runtime: the mochi/runtime Composer package
Author: research pass for MEP-55 (Mochi-to-PHP 8.4 transpiler).
Date: 2026-05-29 15:00 (GMT+7).
Sources: transpiler3/php/runtime/composer.json,
transpiler3/php/runtime/src/Mochi/Runtime/IO.php,
transpiler3/php/runtime/psalm.xml,
transpiler3/php/runtime/phpstan.neon,
.github/workflows/transpiler3-php-test.yml.
The mochi/runtime Composer package is the PHP-side runtime library for
MEP-55-generated programs. It ships under the Mochi\Runtime PSR-4
namespace, has zero Composer runtime dependencies, and requires exactly
two PHP extensions (ext-mbstring and ext-gmp). See
02-design-philosophy for the rationale behind zero runtime deps and
the GMP requirement.
1. Directory structure
transpiler3/php/runtime/
composer.json # package manifest
psalm.xml # Psalm 6 configuration
phpstan.neon # PHPStan level-9 configuration
phpunit.xml.dist # PHPUnit 11.3 configuration
TRUST.md # package trust and signing notes
src/
Mochi/
Runtime/
IO.php # print primitives
tests/
Mochi/
Runtime/ # PHPUnit test suite (autoloaded via autoload-dev)
The autoload stanza in composer.json maps Mochi\Runtime\ to
src/Mochi/Runtime/ (PSR-4). The autoload-dev stanza maps
Mochi\Runtime\Tests\ to tests/Mochi/Runtime/.
2. The IO class
src/Mochi/Runtime/IO.php defines final class IO in the Mochi\Runtime
namespace with four static methods:
IO::printString(string $value): void— echoes$valueplus"\n".IO::printInt(int $value): void— echoes$valueplus"\n".IO::printBool(bool $value): void— echoes"true\n"or"false\n".IO::printFloat(float $value): void— mirrors Go'sstrconv.FormatFloat('g', -1, 64)contract: NaN →"NaN", +Inf →"+Inf", -Inf →"-Inf", whole-number floats → integer digits (so4.0prints as"4"), others echo PHP's default float-to-string.
The float formatter in IO.php (lines 57-73) is identical in intent to
the mochi_print_f64 inline helper in lower.go (lines 171-184). The
inline helper is used in Phase 1-14 (before the Composer package lands);
Phase 15 switches to the Composer-autoloaded \Mochi\Runtime\IO class.
3. The @api annotation and why it is needed
The IO class is referenced only from lowered Mochi programs, which live
outside the src/ directory tree that Psalm scans. From Psalm's
perspective, no code in src/ calls IO::printString, so Psalm's
UnusedClass check would report the entire class as dead code and fail
the CI gate.
The @api PHPDoc tag on the class docblock tells Psalm that IO is an
externally-consumed public surface and suppresses the UnusedClass
finding. This pattern is standard in Psalm for library packages whose
entry points are not exercised from within the package's own source tree.
The TRUST.md file in the runtime directory documents this annotation
convention and why it cannot be removed.
4. devDependencies
The require-dev stanza in composer.json (lines 13-19) lists:
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-strict-rules": "^1.6",
"phpstan/phpstan-deprecation-rules": "^1.2",
"vimeo/psalm": "^6.0",
"friendsofphp/php-cs-fixer": "^3.65",
"phpunit/phpunit": "^11.3"
None of these appear in require (runtime dependencies). They are
dev-only and do not ship inside a vendor/ tree when a consuming project
runs composer install --no-dev.
4.1 PHPStan 1.12
PHPStan at level 9 (the strictest level) is configured via
phpstan.neon. Level 9 enables:
- All type-mismatch checks.
- Dead code detection (unused variables, unreachable branches).
- Array/property access safety.
The CI gate runs vendor/bin/phpstan analyse --no-progress.
4.2 Psalm 6 (not 5)
The constraint "vimeo/psalm": "^6.0" is significant. Psalm 5.x
had a PHP 8.4 platform detection failure: Psalm 5 did not recognise
PHP 8.4 in its phpversion analysis and flagged abstract readonly class
syntax as invalid. Upgrading to Psalm 6 was required during the first
audit round of MEP-55. Psalm 6 added PHP 8.4 support.
The CI runs vendor/bin/psalm --no-progress --no-cache.
4.3 php-cs-fixer 3.65
Configured to enforce PER-CS2.0 (PHP-FIG Extended Coding Style 2.0)
plus the PHP 8.4 migration ruleset. The CI gate runs
vendor/bin/php-cs-fixer fix --dry-run --diff (zero rewrites required).
The formatter enforces 4-space indentation (which the ptree emit pass
also uses: indent(n) in nodes.go returns strings.Repeat(" ", n)), single blank lines between declarations, and PSR-4 file structure.
4.4 PHPUnit 11.3
The runtime test suite lives under tests/Mochi/Runtime/. PHPUnit 11.3
requires PHP 8.2+, so it is compatible with the ^8.4 platform floor.
5. Runtime (production) dependencies
The require stanza has exactly three entries:
"php": "^8.4",
"ext-mbstring": "*",
"ext-gmp": "*"
No Composer packages appear in require. This means composer install
on a project that depends on mochi/runtime does not pull in any
third-party PHP code. The two extensions are:
ext-mbstring: multibyte string functions. Required for UTF-8-aware string operations in Phase 12+ FFI and fetch layers.ext-gmp: GNU Multiple Precision Arithmetic. Required for the Phase 13 DJB2 cassette key computation (the hash can exceed PHP_INT_MAX, so GMP is used for correct uint64 arithmetic). See 02-design-philosophy for the rationale.
Both extensions are available on all major PHP distributions (Ubuntu,
Debian, Alpine, macOS Homebrew). The CI matrix installs them via
shivammathur/setup-php@v2 with extensions: mbstring, gmp.
6. Minimum-stability
"minimum-stability": "stable" in composer.json ensures that
composer install only resolves stable releases of dev dependencies.
No alpha or RC builds of PHPStan or Psalm are allowed in the lock file.
7. Composer configuration
"config": {
"sort-packages": true,
"preferred-install": "dist"
}
sort-packages: true keeps composer.json sorted deterministically,
matching the Phase 16 reproducibility goal for the source tree.
preferred-install: dist prefers zip distributions over source clones,
which speeds up CI.
8. Phase 15: Composer package staging
Phase 15 wires the Composer package into the build sandbox. The
runtimeSourceDir() function in build.go (lines 183-195) locates the
transpiler3/php/runtime/ directory relative to the Go source file using
runtime.Caller(0). The copyFile(dst, src) helper (lines 199-215)
copies individual files into the sandbox, creating parent directories as
needed.
The inline runtime helpers (mochi_print_str, mochi_str_contains, etc.)
that Phase 1-14 inject directly into the emitted PHP file are superseded
in Phase 15 by the Composer-autoloaded equivalents. The transition is
transparent to the fixtures because the public interface (function name,
parameter types, return type) is identical.