Zephir v0.22.0 Release Notes

Release Date: 2026-05-29 // 16 days ago
  • โž• Added

    • โž• Added support for variadic functions/methods using the ... operator (e.g. function f(int first, ...rest)) #2025
    • ๐Ÿ“œ Compiler now recognizes the parser-emitted yield AST node (bare yield;, yield expr;, yield key, value;) #1849
    • โž• Added PHPBench-based runtime benchmarks suites under tests/Benchmark/ #2541

    ๐Ÿ”„ Changed

    • for k, v in expr now skips the unreachable branch when the iterand's dynamic type is known #1878

    ๐Ÿ›  Fixed

    • ๐Ÿ›  Fixed dynamic init new {className}() ignoring constructor visibility, allowing classes with a protected/private constructor to be instantiated from any scope #882
    • Fixed unset(obj->{variable}) (dynamic property name) and unset(obj->{"literal"}) (string-literal brace syntax) throwing CompilerException: Cannot use expression type: property-dynamic-access in "unset". UnsetStatement now handles property-dynamic-access and property-string-access nodes, emitting the new zephir_unset_property_zval() kernel helper for variable keys #808
    • ๐Ÿ›  Fixed continue inside for ... in loops over PHP Iterator/Traversable objects producing an infinite loop #2546
    • ๐Ÿ›  Fixed elseif conditions that contain sub-expressions with side effects (such as array element access myvar[0]) being evaluated unconditionally before the outer if, causing spurious "Cannot use a scalar value as an array" notices when a preceding branch had already returned #1097
    • ๐Ÿ›  Fixed cross-class chained <static> (and <self>/<parent>) return-type resolution. The same-class case landed in #2537 by substituting the lexical class for the reserved keyword. The cross-class case โ€” other->returnsStatic()->method() where other is a local variable typed as a different class โ€” was still resolved against the call site's lexical class, so the chained method lookup ran on the wrong definition and the build aborted with Class '<EnclosingClass>' does not implement method: '<method>'. MethodCall.php already resolves the receiver's $classDefinition earlier in the function (from $variableVariable->getClassTypes()); the substitution now uses that definition, which is identical to $compilationContext->classDefinition for the this branch and preserves existing behavior there. #2505

    ๐Ÿ“š Documentation

    • โฌ‡๏ธ Documented the workaround for [ClassName, "protectedOrPrivateMethod"] arrays passed as callbacks to PHP higher-order functions (array_reduce, usort, preg_replace_callback, etc.) #2167