Skip to content

Language: instance-method FCC poisons later static::class / :static in the same unit (Zend/zend_compile.c) #32083

Description

@PurHur

Category

Language · php-src-strict / first-class callable + late static binding

Problem

An instance-method first-class callable $obj->m(...) in a compile unit poisons later static resolution in that same unit. Subsequent static::class and : static return types resolve to the FCC's class (FM) instead of the class that declared them.

Isolated static::class / : static (no preceding FCC) already match Zend. Distinct from closed #27835 (LSB inside an FCC) and #24431 (Closure::fromCallable LSB).

Probed 2026-08-18 @ ad82044529 — Zend 8.2.32 vs php bin/vm.php / php bin/jit.php.

Repro Zend 8.2.32 VM/JIT
FCC $c = (new FM())->m(...); $c(4) 8 8
Base::who() / Child::who() after that FCC (static::class) Base Child FM FM
(new B3())->f() with function f(): static after that FCC B3 TypeError: B3::f(): Return value must be of type FM, B3 returned

php-src reference

PHP implementation target

  • lib/Compiler.php — first-class callable lowering (ZEND_AST_CALLABLE_CONVERT); called-scope / current-class for later static::class and : static return types
  • lib/VM.php — runtime static::class / return-type check must use the method's class, not the last FCC class
  • No new runtime/*.c

Repro

./script/docker-exec.sh -- bash -lc 'php bin/vm.php test/repro/maintainer_gap_fcc_poisons_static.php'
./script/docker-exec.sh -- bash -lc 'php bin/jit.php test/repro/maintainer_gap_fcc_poisons_static.php'
./script/docker-exec.sh -- bash -lc 'php test/repro/maintainer_gap_fcc_poisons_static.php'  # Zend baseline
<?php
error_reporting(E_ALL);
class FM {
    public function m($x) { return $x * 2; }
}
$c = (new FM())->m(...);
echo 'fcc=', $c(4), "\n";

class Base {
    public static function who() { return static::class; }
}
class Child extends Base {}
echo 'lsb=', Base::who(), ' ', Child::who(), "\n";

class A3 {
    public function f(): object { return new stdClass(); }
}
class B3 extends A3 {
    public function f(): static { return $this; }
}
echo 'ret=', get_class((new B3())->f()), "\n";

Done when

  • VM/JIT: after (new FM())->m(...), Base::who() is Base and Child::who() is Child (Zend)
  • VM/JIT: (new B3())->f() with : static returns a B3 (no TypeError naming FM)
  • Isolated static::class / : static without an FCC still match Zend
  • Compliance .phpt under test/compliance/cases/language/
  • php-src-strict; no php-compiler-strict shortcut

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:compilerCompiler / CFG / JITarea:vmVirtual machinebugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-2:languagePhase 2 – language features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions