Skip to main content

Release: 0.43 - First-Class Callable

Latest update

0.43.0 - First-Class Callable

Released 2026-06-09 · GitHub release

Up to ~3.7× faster compiler hot paths, first-class PHP callables, readonly structs + enum methods, typed interface constants

🎉 Added

  • php/callable: first-class callable interop emitting PHP 8.1 (...) for free functions ((php/callable \strlen)), static methods ((php/callable Foo bar)), and instance methods ((php/callable obj method))
  • defstruct ^:php/readonly: typed fields become PHP readonly properties (untagged → readonly mixed); put/assoc still return copies via a constructor rebuild
  • defenum methods and interfaces: after the cases, an enum can implement interfaces and carry a :php method block (pure and backed enums)
  • ^:php/override: emits PHP 8.3 #[\Override] on generated methods (struct/enum interface impls, definterface methods); inline struct/enum methods also accept :php/attr and :php/doc
  • definterface typed class constants: a trailing :php/const block emits PHP 8.3 typed constants, e.g. (^{:tag int} MAX 100)const int MAX = 100;

Performance

  • NodeEnvironment local lookups are now O(1) hash-indexed, so symbol resolution no longer degrades with let/fn/loop nesting depth (~1.7× faster at depth 20)
  • Symbol::hash() caches its crc32 in a readonly property (like Keyword), so repeated hashing no longer recomputes (~1.9× faster)
  • Analyzer type-checker set-membership (CallTypeExpectationResolver, ReturnTypeInferrer, ConstantFolder, DefSymbol) uses isset maps instead of in_array scans (~3.7× faster)
  • LetSimplifier: the unused-binding pass tracks live references in a set, turning its O(n²) scan into O(n) over a let's bindings
  • Parser: AtomParser skips the number-regex for atoms that cannot start a number (~1.5× faster per symbol atom); Parser dispatch uses an isset map instead of in_array

⚖️ Changed

  • Architecture: satellite-module factories (Lsp, Lint, Watch, Nrepl, Profile) inject the Phel\Shared\Facade\*FacadeInterface contracts instead of neighbour modules' concrete facades, matching the "inject interfaces" rule; RunFacadeInterface gains autoDetectEntryPoint(): ?string
  • Architecture: the eval-result value objects (EvalResult, EvalError, StackFrame) moved to the leaf Phel\Shared\Eval namespace, and their orchestration to Run\Application\StructuredEvaluator, keeping the VOs logic-free
  • Architecture: version detection consolidated into Phel\Shared\VersionResolver; Console and Run both resolve through it, removing Run's dependency on the whole Console module
  • Architecture: the pure Base64-VLQ codec moved to the leaf Phel\Shared\SourceMap\VLQ (stateless, like Munge); the SourceMap producer/consumer stay in Compiler

🐛 Fixed

  • :tag: a never/void/null return tag on a value-returning function is now a compile error instead of a load-time fatal; mixed, ?T, and union/intersection tags still pass
  • defenum: the bare enum name now resolves to its PHP class, so EnumName/case access works

👥 Contributors

@JesusValeraDev @Chemaclass @titanniya542-spec

Full Changelog: v0.42.0...v0.43.0

Downloads

v0.43.0


View release on GitHub