From 9078c2bcc2978da086d2ae43ef28d6c53d602d69 Mon Sep 17 00:00:00 2001 From: PurHur Date: Sat, 30 May 2026 12:59:41 +0200 Subject: [PATCH] Add enum implements interface compliance coverage (#3373). VM dispatch for enum case objects with interface methods and instanceof is already on master via EnumCaseSupport; add phpt, unit test, and capability matrix notes. Co-authored-by: Cursor --- docs/capabilities-syntax.md | 22 +++++----- script/capability-syntax-lib.php | 8 +++- test/compliance/EnumVMTest.php | 1 + .../language/enum_implements_interface.phpt | 26 ++++++++++++ test/unit/VmEnumImplementsInterfaceTest.php | 40 +++++++++++++++++++ 5 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 test/compliance/cases/language/enum_implements_interface.phpt create mode 100644 test/unit/VmEnumImplementsInterfaceTest.php diff --git a/docs/capabilities-syntax.md b/docs/capabilities-syntax.md index 1d208e5f046..d7c5c76ac74 100644 --- a/docs/capabilities-syntax.md +++ b/docs/capabilities-syntax.md @@ -1,4 +1,3 @@ -Wrote /compiler/docs/capabilities-syntax.md (56 constructs). # Language construct capability matrix Auto-generated by `script/capability-syntax.php`. Do not edit by hand. @@ -11,8 +10,8 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145] | Construct | VM | JIT | AOT | Issue | Notes | |-----------|:--:|:---:|:---:|-------|-------| | `class` / `new` | yes | yes | yes | [#58](https://github.com/PurHur/php-compiler/issues/58) | compliance PHPT; bootstrap AOT | -| Anonymous class `new class { }` | yes | yes | yes | [#1233](https://github.com/PurHur/php-compiler/issues/1233) | php-cfg inline Stmt\Class_ in parseExpr_New; synthetic AnonymousClass@line name | -| Enum declarations `enum Foo: string { case Bar = 'x'; }` | yes | yes | yes | [#1356](https://github.com/PurHur/php-compiler/issues/1356) | Backed enum cases as class constants; `Foo::Bar` const-like fetch; `enum_exists` registry; `implements` metadata (#2299); static methods (#2299) | +| Anonymous class `new class { }` | yes | yes | no | [#1233](https://github.com/PurHur/php-compiler/issues/1233) | php-cfg inline Stmt\Class_ in parseExpr_New; synthetic AnonymousClass@line name | +| Enum declarations `enum Foo: string { case Bar = 'x'; }` | yes | yes | yes | [#1356](https://github.com/PurHur/php-compiler/issues/1356) | Backed enum case objects with `->name` / `->value`; string context coerces to backed scalar (#3518); `Foo::Bar` singleton fetch; `enum_exists` registry; `implements` interface list + instance methods + `instanceof` (#3373); static methods (#2299); `Enum::cases()` VM (#3308) | | Instance methods (`ClassMethod` / `Expr_MethodCall`) | yes | yes | yes | [#58](https://github.com/PurHur/php-compiler/issues/58) | MiniWebApp `$router->dispatch()` (#2059); compliance PHPT; bootstrap AOT | | Static methods on user classes (`Expr_StaticCall`) | yes | yes | yes | [#2209](https://github.com/PurHur/php-compiler/issues/2209) | Public static methods without $this; `Router::fromConfig()` factory (#2059); Late static `static::method()` tracked separately (#1231) | | Constructors (`__construct`) | yes | yes | yes | [#145](https://github.com/PurHur/php-compiler/issues/145) | Router `__construct(array $config)` (#2059); compliance PHPT; bootstrap AOT | @@ -20,18 +19,19 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145] | Private methods | yes | yes | yes | [#145](https://github.com/PurHur/php-compiler/issues/145) | Router private `render*` paths (#2059); compliance PHPT; bootstrap AOT | | Property fetch `$this->x` | yes | yes | yes | [#58](https://github.com/PurHur/php-compiler/issues/58) | compliance PHPT; bootstrap AOT | | Constructor property promotion | yes | yes | yes | [#1359](https://github.com/PurHur/php-compiler/issues/1359) | Promoted params declare property + assign in __construct | -| Method return types (`: string` / `: void`) | yes | yes | yes | [#55](https://github.com/PurHur/php-compiler/issues/55) | #55 native `: string`/`: int`/`: bool`/`: float`/`: array`/`: ?T` LLVM returns; MCJIT execute #2055 | +| Method return types (`: string` / `: void`) | yes | yes | yes | [#55](https://github.com/PurHur/php-compiler/issues/55) | #55 native `: string`/`: int`/`: bool`/`: float`/`: array`/`: ?T` LLVM returns; MCJIT execute #2055; compliance PHPT; bootstrap AOT | | Dynamic property access `$obj->$name` | yes | yes | yes | [#1227](https://github.com/PurHur/php-compiler/issues/1227) | JIT compares runtime name to declared properties; unknown names abort at runtime; compliance PHPT | | Variable function call `$fn()` | yes | yes | yes | [#56](https://github.com/PurHur/php-compiler/issues/56) | VM resolves callee at runtime; JIT when callee name is compile-time string in variable (#56) | | Native user-class link (`phpc build --project`) | yes | yes | yes | [#764](https://github.com/PurHur/php-compiler/issues/764) | AOT link yes (#568 closed); native execute ✅ (#764 closed); compliance PHPT; bootstrap AOT | | `instanceof` | yes | yes | yes | [#138](https://github.com/PurHur/php-compiler/issues/138) | compliance PHPT; bootstrap AOT | -| `instanceof` union RHS `(A\|B)` | yes | yes | yes | [#3461](https://github.com/PurHur/php-compiler/issues/3461) | php-cfg parses BitwiseOr of class names from php-parser; VM + JIT union OR (#3461); compliance PHPT | +| `instanceof` union RHS `(A|B)` | yes | yes | yes | [#3461](https://github.com/PurHur/php-compiler/issues/3461) | php-cfg parses BitwiseOr of class names from php-parser; VM + JIT union OR (#3461) | | `match` expression | yes | yes | yes | [#143](https://github.com/PurHur/php-compiler/issues/143) | Lowered in php-cfg to === / jump-if / assign (#143); Wave-3 literal-arm subset (#2398); acceptance PHPT (#2428); compliance PHPT; bootstrap AOT | | Closures `function () { }` / `use ($var)` | yes | yes | no | [#72](https://github.com/PurHur/php-compiler/issues/72) | VM ClosureState + __invoke; use() by-value and by-ref (#3081, #3108); array_map/filter/usort callbacks (#3086); JIT ClosureHelper: TYPE_CLOSURE + use() value/ref IR (#3092, #3108); indirect $arr[0]() via __closure_target (#3089, #3092); bootstrap / self-host AOT stubs null; bin/jit.php MCJIT execute still probe-dependent (#98); compliance PHPT | | Arrow functions `fn () =>` | yes | yes | no | [#142](https://github.com/PurHur/php-compiler/issues/142) | Desugars to TYPE_CLOSURE (#142); VM + JIT same as closures (#3092, #3108); compliance PHPT | | Generators (`yield` / `foreach`) | yes | no | no | [#167](https://github.com/PurHur/php-compiler/issues/167) | VM GeneratorState + foreach; keyed yield (#3085); bin/jit.php VM fallback via Block::requiresVmLowering (#3085); see docs/generators-jit-aot.md; `yield from` generator path supported on VM (#167); compliance PHPT; VM-only lowering | | `ClassName::class` / `static::class` | yes | yes | yes | [#740](https://github.com/PurHur/php-compiler/issues/740) | Compile-time class name string; related to __CLASS__ (#199); compliance PHPT; bootstrap AOT | | Class member constants `public` / `private` / `protected const` | yes | yes | yes | [#2199](https://github.com/PurHur/php-compiler/issues/2199) | MiniWebApp `Router::DEFAULT_CONTACT_NAME_MAX` (#2059); compliance PHPT; bootstrap AOT | +| PHP 8.3 typed class constants (`const array X = [1,2];`, `const string S = 'a';`) | yes | yes | yes | [#3592](https://github.com/PurHur/php-compiler/issues/3592) | Compile-time literal fold for array/scalar const values; typed mismatch is compile-time TypeError; JIT lowers immutable array constants (#3592) | | Late static binding `static::method()` / `static::class` | yes | yes | yes | [#1231](https://github.com/PurHur/php-compiler/issues/1231) | VM/JIT called-class propagation; parent::method/class/$prop and static:: LSB (#1858, #3093) | | `new static()` and `: static` return type (late-bound class) | yes | yes | yes | [#3412](https://github.com/PurHur/php-compiler/issues/3412) | VM resolveClassScopeName on TYPE_NEW; returnTypeStatic verify (#3412) | | Magic constants `__CLASS__`, `__METHOD__`, `__FUNCTION__` | yes | yes | yes | [#199](https://github.com/PurHur/php-compiler/issues/199) | Lowered at parse time via php-cfg MagicStringResolver; compliance PHPT | @@ -44,10 +44,11 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145] | Static property `Class::$prop` | yes | yes | yes | [#1225](https://github.com/PurHur/php-compiler/issues/1225) | Class-scoped storage; `self::` / `static::`; literal property names in JIT | | Error-control operator `@` on expressions | yes | yes | yes | [#3546](https://github.com/PurHur/php-compiler/issues/3546) | php-cfg ErrorSuppressBlock + Simplifier preserve (#3546); VM masks error_reporting; JIT/AOT no-op until native silence | | `unset()` on variables, array offsets, and object properties | yes | yes | yes | [#2273](https://github.com/PurHur/php-compiler/issues/2273) | Locals, `$this->prop`, public properties, string/int keys (#1224); Static properties via TYPE_STATIC_PROPERTY_UNSET (#2256) | -| Function-local `static $var` / `static $var = ` | yes | yes | yes | [#2286](https://github.com/PurHur/php-compiler/issues/2286) | Literal int/string init only in v1; VM + JIT + AOT; uninitialized `static $x;` with isset guard — Zend parity (#3533); `static &$x` is not valid PHP syntax (php-src `static_var` grammar) | +| Function-local `static $var` / `static $var = ` | yes | yes | yes | [#2286](https://github.com/PurHur/php-compiler/issues/2286) | Literal int/string init only in v1; VM + JIT + AOT; Uninitialized `static $x;` with isset guard — Zend parity (#3533); `static &$x` is not valid PHP syntax (php-src `static_var` grammar) | | Keyed array destructuring (`["a" => $x]`) | yes | yes | yes | [#1234](https://github.com/PurHur/php-compiler/issues/1234) | Skip string-key CFG split for fetch+assign destructuring pairs (#1234) | | `goto` / labels (function scope) | yes | yes | yes | [#1228](https://github.com/PurHur/php-compiler/issues/1228) | php-cfg lowers labels to CFG Jump; VM avoids frame nesting on same-block back-edges; compliance PHPT | | `declare(strict_types=1)` scalar parameter checks | yes | yes | yes | [#1229](https://github.com/PurHur/php-compiler/issues/1229) | VM #156; JIT enforces at user call sites via JIT\TypeCheck + Native::compileArg weak casts | +| PHP 8.3+ generic array types `list` / `array` (parameters and properties) | yes | yes | yes | [#3705](https://github.com/PurHur/php-compiler/issues/3705) | Source rewrite to magic identifier types for php-parser v4; VM list shape check (#3705); Zend generic-array RFC not merged in php-src 8.4 — parity target is proposed list/array forms | | Variable variables (`$$name`) | yes | yes | yes | [#1226](https://github.com/PurHur/php-compiler/issues/1226) | php-cfg nests Operand\Variable name; VM resolves runtime local by name; JIT compile-time name fold (#1226); compliance PHPT | | Variable function calls (`$fn()`) | yes | yes | yes | [#56](https://github.com/PurHur/php-compiler/issues/56) | VM resolves callee at runtime; compiler folds literal assignment; JIT uses compile-time string | | Invokable objects (`$obj()` / `__invoke`) | yes | yes | yes | [#1232](https://github.com/PurHur/php-compiler/issues/1232) | Object-typed FuncCall lowered to __invoke method dispatch; VM runtime fallback | @@ -55,18 +56,17 @@ Tracking issues: [#58](https://github.com/PurHur/php-compiler/issues/58), [#145] | `use function` / `use const` imports | yes | yes | yes | [#2325](https://github.com/PurHur/php-compiler/issues/2325) | php-cfg resolves imported names at parse time; no Stmt_Use lowering required | | Namespace group use (`use Foo\{Bar, Baz}`) | yes | yes | yes | [#2443](https://github.com/PurHur/php-compiler/issues/2443) | PhpParser Stmt_GroupUse; NameResolver registers aliases; GroupUseStripper before PHPCfg | | Flexible heredoc/nowdoc indentation stripping (PHP 7.3+) | yes | yes | yes | [#3636](https://github.com/PurHur/php-compiler/issues/3636) | php-parser Emulative FlexibleDocStringEmulator + parseDocString stripIndentation (#3636); Indented closing label sets docIndentation column; basic heredoc/nowdoc #178; Zend/zend_language_scanner.l flexible heredoc/nowdoc parity; compliance PHPT | -| `never` return type | yes | yes | yes | [#1358](https://github.com/PurHur/php-compiler/issues/1358) | php-cfg Op\Type\Never_; any `return` in body is a compile error; normal completion via throw/exit; `never` as a parameter type is a compile error ([#3506](https://github.com/PurHur/php-compiler/issues/3506)) | +| `never` return type | yes | yes | yes | [#1358](https://github.com/PurHur/php-compiler/issues/1358) | php-cfg Op\Type\Never_; any `return` in body is a compile error; normal completion via throw/exit | | Intersection types (`A&B`) | yes | yes | yes | [#1357](https://github.com/PurHur/php-compiler/issues/1357) | php-cfg Op\Type\Intersection; VM checks object implements each interface at call | | `trait` declarations with method bodies | yes | yes | yes | [#2312](https://github.com/PurHur/php-compiler/issues/2312) | VM registers traits in class table with isTrait; trait use in classes is #144; interface_exists/trait_exists distinguish from class_exists | -| Simple `use Trait;` in class body | yes | no | no | [#2314](https://github.com/PurHur/php-compiler/issues/2314) | php-cfg-trait-use.patch; VM merges trait methods into class; TraitUseAdaptation (alias/insteadof) is #144; VM-only lowering | +| Simple `use Trait;` in class body | yes | no | no | [#2314](https://github.com/PurHur/php-compiler/issues/2314) | php-cfg-trait-use.patch; VM merges trait methods into class; TraitUseAdaptation (alias/insteadof) is #144; Horizontal trait method collision fatals at compile time (#3416); VM-only lowering | | Array/argument unpack `...$x` | yes | yes | yes | [#1361](https://github.com/PurHur/php-compiler/issues/1361) | php-cfg spread.patch (#141); VM HashTable::spreadFrom; JIT HashTableHelper::spreadInto + mergeCallArgEntries; compliance PHPT | | `__serialize` / `__unserialize` magic methods | yes | yes | yes | [#1365](https://github.com/PurHur/php-compiler/issues/1365) | serialize()/unserialize() call __serialize/__unserialize when present; VM via VmSerialize (#3368) | | Multi-type catch `catch (A|B $e)` | yes | yes | yes | [#1362](https://github.com/PurHur/php-compiler/issues/1362) | php-cfg records union types per catch; VM filters TYPE_CATCH via OpCode.catchTypes; compliance PHPT | -| `try` / `catch` / `throw` | yes | yes | yes | [#57](https://github.com/PurHur/php-compiler/issues/57) | throw lowering #195; php-cfg TryCatch overlay (#2084); VM TYPE_TRY/CATCH/THROW/FINALLY; VM finally-before-catch + return-through-finally (#3081, #3106); TryCatchComplianceTest (10 tests); JIT TryCatchHelper IR verify (#3107); bin/jit.php VM fallback via requiresVmLowering (#2114); MCJIT execute unsafe; compliance PHPT; bootstrap AOT | +| `try` / `catch` / `throw` | yes | yes | yes | [#57](https://github.com/PurHur/php-compiler/issues/57) | throw lowering #195; php-cfg TryCatch overlay (#2084); VM TYPE_TRY/CATCH/THROW/FINALLY; VM finally-before-catch + return-through-finally (#3081, #3106); TryCatchComplianceTest (10 tests); JIT TryCatchHelper IR verify (#3107); bin/jit.php VM fallback via requiresVmLowering (#2114); MCJIT execute probe TryCatchJitExecuteTest; compliance PHPT; bootstrap AOT | | readonly classes | yes | yes | yes | [#1360](https://github.com/PurHur/php-compiler/issues/1360) | php-cfg Class_::flags MODIFIER_READONLY; VM rejects instance property writes after __construct | -| Property hooks (`get` / `set` on properties) | yes | no | no | [#3145](https://github.com/PurHur/php-compiler/issues/3145) | SourcePreprocessor lowers hooks for php-parser v4; VM dispatches set/get via `__phpc_property_*` methods; php-src `Zend/zend_compile.c` | | PHP 8 attributes — `ReflectionClass` / `ReflectionMethod` metadata | yes | yes | yes | [#1936](https://github.com/PurHur/php-compiler/issues/1936) | php-cfg preserves `attrGroups`; compiler stores attribute class names on TYPE_DECLARE_CLASS / TYPE_DECLARE_METHOD; VM reflection reads from VM ClassEntry; JIT/AOT mirror class+method attribute tables into VMContext for reflection; Read path: `getAttributes()` count + `ReflectionAttribute::getName()`; no `newInstance()` or parameter attributes | -| WeakReference / WeakMap | yes | no | yes | [#3282](https://github.com/PurHur/php-compiler/issues/3282) | VM: WeakReference::create/get via indirect target slot; unset clears get immediately; GC-backed weak refs via WeakRefRegistry — referent collected by gc_collect_cycles() clears get(); WeakMap uses object-id string keys; entries removed when key object is collected; JIT may compile references but method bodies are VM-only | +| WeakReference / WeakMap | yes | no | yes | [#3282](https://github.com/PurHur/php-compiler/issues/3282) | VM: WeakReference::create/get via indirect target slot; unset clears get immediately; GC-backed weak refs via WeakRefRegistry — referent collected by gc_collect_cycles() clears get(); WeakMap uses object-id string keys; entries removed when key object is collected; JIT may compile references but method bodies are VM-only; VM-only lowering | | DateTime / DateTimeZone OOP | yes | no | no | [#3072](https://github.com/PurHur/php-compiler/issues/3072) | VM builtins: DateTime::__construct/format/getTimestamp/setTimezone; DateTimeZone::__construct; Host PHP date extension for parsing/formatting; UTC and named timezone subset (php-src ext/date/php_datetime.c); JIT/AOT method bodies VM-only in phase 1; VM-only lowering | _Syntax AOT column reflects `Runtime::MODE_AOT` compile probes unless a row pins AOT (e.g. native user-class link)._ diff --git a/script/capability-syntax-lib.php b/script/capability-syntax-lib.php index 139084a086f..ded3f95ccd5 100644 --- a/script/capability-syntax-lib.php +++ b/script/capability-syntax-lib.php @@ -43,8 +43,12 @@ function syntaxRowDefinitions(): array 'construct' => 'Enum declarations `enum Foo: string { case Bar = \'x\'; }`', 'opcodes' => ['TYPE_DECLARE_ENUM', 'TYPE_DECLARE_CLASS_CONST', 'TYPE_CLASS_CONST_FETCH'], 'issue' => 1356, - 'notes' => ['Backed enum case objects with `->name` / `->value`; string context coerces to backed scalar (#3518); `Foo::Bar` singleton fetch; `enum_exists` registry; `implements` metadata (#2299); static methods (#2299); `Enum::cases()` VM (#3308)'], - 'probe' => 'enum Status: string { case Ok = \'ok\'; public static function tag(): string { return \'ok\'; } } echo Status::Ok; echo Status::tag();', + 'notes' => [ + 'Backed enum case objects with `->name` / `->value`; string context coerces to backed scalar (#3518)', + '`Foo::Bar` singleton fetch; `enum_exists` registry; `implements` interface list + instance methods + `instanceof` (#3373)', + 'static methods (#2299); `Enum::cases()` VM (#3308)', + ], + 'probe' => 'interface L { public function n(): string; } enum S: string implements L { case A = "a"; public function n(): string { return $this->name; } } echo S::A->n();', ], [ 'id' => 'instance_methods', diff --git a/test/compliance/EnumVMTest.php b/test/compliance/EnumVMTest.php index df9621769c1..272758907d2 100644 --- a/test/compliance/EnumVMTest.php +++ b/test/compliance/EnumVMTest.php @@ -20,6 +20,7 @@ public static function providePHPTests(): \Generator 'enum_cases.phpt', 'enum_instanceof.phpt', 'enum_implements_metadata.phpt', + 'enum_implements_interface.phpt', 'enum_static_method.phpt', 'unit_enum_basic.phpt', 'unit_enum_case_name.phpt', diff --git a/test/compliance/cases/language/enum_implements_interface.phpt b/test/compliance/cases/language/enum_implements_interface.phpt new file mode 100644 index 00000000000..d6154e43f69 --- /dev/null +++ b/test/compliance/cases/language/enum_implements_interface.phpt @@ -0,0 +1,26 @@ +--TEST-- +Language: backed enum implements interface — instance methods + instanceof (#3373) +--FILE-- +name; + } +} + +echo Status::Open->label(); +echo "\n"; +echo Status::Open instanceof HasName ? '1' : '0'; +echo "\n"; +echo Status::Open; +--EXPECT-- +Open +1 +open diff --git a/test/unit/VmEnumImplementsInterfaceTest.php b/test/unit/VmEnumImplementsInterfaceTest.php new file mode 100644 index 00000000000..b6b388814d2 --- /dev/null +++ b/test/unit/VmEnumImplementsInterfaceTest.php @@ -0,0 +1,40 @@ +name; + } +} + +echo Status::Open->label(); +echo Status::Open instanceof HasName ? '1' : '0'; +echo Status::Open; +PHP; + $runtime = new Runtime(); + $block = $runtime->parseAndCompile($code, 'enum_iface.php'); + ob_start(); + $runtime->run($block); + $output = ob_get_clean(); + + $this->assertSame('Open1open', $output); + } +}