Skip to content

[PHP 8.6] Override on class constants, __debugInfo on enums, attribute API audit - #232

Merged
lisachenko merged 2 commits into
masterfrom
claude/php86-225-misc-parity
Aug 25, 2026
Merged

[PHP 8.6] Override on class constants, __debugInfo on enums, attribute API audit#232
lisachenko merged 2 commits into
masterfrom
claude/php86-225-misc-parity

Conversation

@lisachenko

Copy link
Copy Markdown
Member

Refs #225

What is done

1. #[\Override] on class constants

New stub tests/Stub/FileWithOverrideConstants86.php covers the attribute on a class constant inherited from an abstract parent, on an interface constant re-declared by an extending interface, on an interface constant implemented by a class, and on enum constants — alone, combined with a userland attribute inside the same attribute group, and combined with a repeated userland attribute in separate groups.

AttributeResolverTrait already resolved constant attributes, so no engine change was needed for the resolution itself; the new tests/Php86MiscParityTest.php asserts names, arguments, isRepeated(), filtering by name and the AST node of every attribute, plus full parity with native reflection on 8.6.

2. __debugInfo() on enums

New stub tests/Stub/FileWithDebuggableEnums86.php declares a pure enum and a backed enum with __debugInfo() (plus one enum without it as a negative case). The parsed ReflectionClass/ReflectionEnum report it as an ordinary method (hasMethod(), getMethods(), visibility, return type, declaring class) and match native reflection method-for-method on 8.6.

3. Attribute API additions (audit + implementation)

Diffing get_class_methods()/class constants of every Reflection* class between the local php (8.5.9) and php8.6 (8.6.0beta1) binaries gives the complete list of 8.6 reflection additions:

Class Added in 8.6.0beta1 Handled here
ReflectionAttribute getShortName(), getNamespaceName(), inNamespace() implemented in src/ReflectionAttribute.php
ReflectionConstant inNamespace() mirrored in src/ReflectionConstant.php
ReflectionParameter getDocComment() out of scope — #221
ReflectionProperty isReadable(), isWritable() out of scope — #222

No new Reflection* classes and no new class constants appear in the beta.

The three ReflectionAttribute methods cannot simply be inherited: the internal implementations read the attribute structure that is never initialized for a parsed attribute, so on 8.6 they fatal today:

$ php8.6 probe.php     # parsed attribute from this library, before the fix
getShortName     => Error: Internal error: Failed to retrieve the reflection object
getNamespaceName => Error: Internal error: Failed to retrieve the reflection object
inNamespace      => Error: Internal error: Failed to retrieve the reflection object

They are now resolved from the attribute name itself (through getName(), so the anonymous subclass used by ReflectionConstant keeps working) and are asserted equal to the native results on 8.6.

ReflectionAttribute::getCurrent() does not exist in 8.6.0beta1php8.6 -r 'var_dump(method_exists(ReflectionAttribute::class, "getCurrent"));' prints bool(false) — so nothing is implemented for it. testAttributeApiAdditionsAreEitherImplementedOrAbsent() marks itself incomplete as soon as a runtime ships the method, and otherwise asserts that every native ReflectionAttribute method is implemented by the parsed one.

Two more verified behaviours worth recording:

  • #[\Override] on a constant is validated at link time, exactly like on methods: Foo::BAR has #[\Override] attribute, but no matching parent constant exists — the stub therefore always declares a real parent/interface constant.
  • The \Override attribute itself now declares the targets 28 (TARGET_METHOD | TARGET_PROPERTY | TARGET_CLASS_CONSTANT) instead of 12 on 8.5, where loading such a constant fails with Attribute "Override" cannot target class constant.

Parity fixes required by the above

  • Named attribute arguments were collapsed into a positional list by AttributeResolverTrait, ReflectionAttribute::getNode() and ReflectionConstant::getAttributes(); native reflection keys them by name (['tag' => 'weight', 'priority' => 5]). They are now keyed the same way, which is what the new 8.6 parity assertions compare against. Two expectations in ReflectionConstantTest that documented the old shape were updated.
  • ReflectionClassConstant::__toString() did not print the doc comment of the constant, while native reflection does. It does now — this also turns four previously skipped __toString parity data sets green (ClassWithPhp56ArrayConstants::A/B, ClassWithPhp56ComplexConstantsAndInheritance::K/L... skipped count drops from 133 to 129 on 8.6).

Stub loading convention

Both stubs parse on any runtime but only compile on 8.6, so they are resolved through a CallableLocator in the new test (like FileWithPropertyHooks84.php) and are never yielded by AbstractTestCase::getFilesToAnalyze(), whose providers include_once every stub. Every assertion that needs the stub loaded is guarded by PHP_VERSION_ID >= 80600.

Local results

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn


Generated by Claude Code

claude added 2 commits August 25, 2026 10:08
…() on enums

PHP 8.6 allows the #[\Override] attribute on class, interface and enum constants and
the __debugInfo() magic method on enums. Both constructs are parsed by PHP-Parser on
any runtime, so static reflection works everywhere, while their stub files may only be
included by a PHP 8.6+ runtime (they are compile errors before that).

- add tests/Stub/FileWithOverrideConstants86.php and tests/Stub/FileWithDebuggableEnums86.php,
  resolved through a dedicated CallableLocator instead of the parity data providers, so that
  they are never included on PHP 8.5
- add tests/Php86MiscParityTest.php with static assertions for every runtime and native
  reflection parity assertions guarded by PHP_VERSION_ID >= 80600
- implement ReflectionAttribute::getShortName(), getNamespaceName() and inNamespace(): they
  were added to the native reflection in 8.6, but their internal implementation works on the
  attribute structure that is never initialized for the parsed reflection and fatals otherwise
- mirror the new \ReflectionConstant::inNamespace() method in the parsed ReflectionConstant
- keep named attribute arguments keyed by their names in getArguments(), like native
  reflection does, instead of collapsing them into a positional list
- print the doc comment of a class constant in ReflectionClassConstant::__toString(), which
  also makes four previously skipped __toString parity cases pass

Audit of the 8.6 attribute API: ReflectionAttribute::getCurrent() does not exist in
8.6.0beta1, so there is nothing to emulate for it yet; the new test marks itself incomplete
once the method appears in a runtime.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
@lisachenko
lisachenko marked this pull request as ready for review August 25, 2026 14:09
@lisachenko
lisachenko merged commit 7007918 into master Aug 25, 2026
7 checks passed
@lisachenko
lisachenko deleted the claude/php86-225-misc-parity branch August 25, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants