Skip to content

PHP 8.0–8.5 native reflection coverage: close API gaps found by audit - #213

Merged
lisachenko merged 13 commits into
masterfrom
claude/parser-reflection-php8-coverage-oyi5ra
Aug 1, 2026
Merged

PHP 8.0–8.5 native reflection coverage: close API gaps found by audit#213
lisachenko merged 13 commits into
masterfrom
claude/parser-reflection-php8-coverage-oyi5ra

Conversation

@lisachenko

@lisachenko lisachenko commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Closes the PHP 8.0–8.5 native reflection coverage gaps found by a full audit of the library against native reflection (mechanical API diff + runtime probes against PHP 8.4.19, PHP 8.5 grammar via php-parser 5.8). One commit per audit ticket:

All static behavior operates purely on the AST — no class_exists()-style calls that can trigger autoloading; methods that inherently need runtime state initialize the internal reflection and delegate to the native parent.

Known residual limitations (documented deliberately)

  • Exact-name getAttributes($name) filtering (flags = 0) remains case-sensitive as before, while native is case-insensitive; the new IS_INSTANCEOF path is case-insensitive.
  • A constant that is genuinely unresolvable still aborts a whole getConstants() batch (ReflectionFileNamespace::findConstants() / collectSelfConstants() have no per-constant isolation); with the new closure handlers the previously reported symptom no longer occurs.
  • ReflectionConstant does not pass instanceof \ReflectionConstant (native class is final); define()-style constants are out of scope for static analysis.

Test plan

  • Full PHPUnit suite green after every commit; final state: 13,743 tests, 15,379 assertions, 0 failures, 0 errors (146 skipped, 1 pre-existing incomplete, Inherited methods should contain inherits section for the method __toString #55).
  • Each fix ships targeted tests: static-path assertions proving the reflected classes are never loaded (class_exists(..., false) spies, autoload-spy for the resolver) plus native-parity assertions where stubs are loadable on PHP 8.4; PHP 8.5 syntax stubs are parse-only and never included.

🤖 Generated with Claude Code

https://claude.ai/code/session_019wBszKXQG1s3UFRqpaRSF1

… version

ReflectionEngine now defaults to createForNewestSupportedVersion() so that
PHP 8.5 sources (pipe operator, etc.) can be statically reflected on older
runtimes. An explicit PhpVersion can still be passed to init() to pin an
older grammar. Covered by a PHP 8.5 syntax stub that is parsed but never
loaded.

Fixes #210
…ag, raw-value/lazy delegation)

getHooks() is now composed statically from the AST hook nodes (keyed by
PropertyHookType value, native ordering) and isDynamic() answers false for
declared/promoted properties. Object-bound members (isLazy, getRawValue,
setRawValue, setRawValueWithoutLazyInitialization, skipLazyInitialization)
initialize the internal reflection and delegate to the native parent.

Fixes #207
Enum cases now answer getModifiers()/hasType()/getType()/isDeprecated()
statically from the EnumCase AST node, matching native semantics
(IS_PUBLIC only, untyped, #[\Deprecated]-aware), and isFinal() is aligned
with native reflection which reports false for enum cases. The lazy-object
API (newLazyGhost, newLazyProxy, resetAsLazyGhost/Proxy,
initializeLazyObject, isUninitializedLazyObject,
markLazyObjectAsInitialized, getLazyInitializer) delegates to the native
parent after initializing the internal reflection, for both classes and
enums.

Fixes #205
Fixes #208
PHP 8.5 allows the final modifier on constructor-promoted properties.
isFinal() now reads the FINAL flag from the promoted Param node as well,
instead of only from Property nodes, while keeping the implicit finality
of private(set) properties. getModifiers() picks the bit up automatically.

Fixes #212
…] support

Tentative return type accessors, closure introspection
(getClosureCalledClass, getClosureUsedVariables), isAnonymous()/isStatic()
on functions and createFromMethodName() on methods now answer statically
from the AST instead of fataling on parser-based instances. isDeprecated()
honors the PHP 8.4 #[\Deprecated] attribute for functions, methods and
class constants via a pure-AST attribute check.

Fixes #204
Fixes #206
PropertyHookType::$value is not recognized as string by PHPStan's stubs,
widening the inferred key type to int|string; cast the key explicitly to
match the declared array<string, ReflectionMethod> return type.
…ing in resolver

NodeExpressionResolver now handles PHP 8.5 closures and arrow functions in
constant expressions by name-resolving, pretty-printing and evaluating the
node as a static unbound closure, and first-class callables of undefined
functions fail with a descriptive message instead of a missing-handler
error. Object instantiation for new-in-initializers no longer triggers the
autoloader: the class source is located explicitly through the registered
locator (same for static-call targets), keeping reflection side-effect
free.

Fixes #203
Fixes #211
Drop a redundant is_callable() guard already covered by function_exists(),
narrow the closure node type before pretty-printing, and mark
isClassDefinitionLoaded() as impure since its result changes after the
located file is included.
…ibutes()

The flags argument is now honored: IS_INSTANCEOF matches attributes whose
class extends or implements the requested name, resolving ancestry without
autoloading (native reflection for already-loaded classes, parser-based
reflection through the locator otherwise). Invalid flag values raise the
same ValueError as native reflection.

Fixes #202
Mirrors the native PHP 8.4 ReflectionConstant API (the internal class is
final, so it cannot be extended) on top of the AST: getName, getShortName,
getNamespaceName, getValue, isDeprecated and __toString are resolved
statically, and the PHP 8.5 getAttributes() extension works for attributes
on constants including #[\Deprecated]. ReflectionFileNamespace exposes
the new reflections via getReflectionConstant()/getReflectionConstants()
without changing the existing getConstants() API. define()-style constants
stay out of scope for static analysis.

Fixes #209
Annotate the resolved attribute name as class-string and print
non-stringable constant values via their debug type instead of an
unchecked string cast.
@lisachenko
lisachenko marked this pull request as ready for review August 1, 2026 11:41
@lisachenko
lisachenko merged commit 7fed485 into master Aug 1, 2026
9 checks passed
@lisachenko lisachenko added this to the 4.0.0 milestone Aug 1, 2026
@lisachenko
lisachenko deleted the claude/parser-reflection-php8-coverage-oyi5ra branch August 1, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

1 participant