[PHP 8.6] Internal functions with #[\Deprecated]: native-fallback parity coverage - #231
Merged
Merged
Conversation
…ctions PHP 8.6 attaches #[\Deprecated] to a number of previously-undeprecated internal functions, strcoll() among them. Internal functions have no source file, so the AST parser can never reflect them and the native reflection is always used instead, which reports the attribute correctly without any change in this library. Pins that behaviour with parity tests next to the existing #[\Deprecated] coverage: - internal functions are never resolved to a parsed reflection, while parsed functions are always user-defined and remain drop-in \ReflectionFunction replacements; - utf8_encode(), deprecated since PHP 8.2, proves the mechanism is not specific to PHP 8.6, with strlen() as a negative control; - strcoll() reports the expected #[\Deprecated] payload on PHP 8.6 and reports no deprecation before it, pinning the transition from both sides. Refs #215 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #215
Outcome: confirmed, no production code change needed
Verified on a local PHP 8.6.0beta1 build that
strcoll()and the other internal functions newly annotated in 8.6 are reflected correctly, and that no gap exists in this library.Findings
The fallback is structural, not conditional.
Go\ParserReflection\ReflectionFunctionis constructed exclusively from aPhpParser\Node\Stmt\Function_AST node (ReflectionFileNamespace::findFunctions()is the only construction site insrc/). Internal functions have no source file (getFileName()isfalse), so they can never enter the parsing path — a consumer asking forstrcoll()necessarily gets a plain\ReflectionFunction. Since the parsed class extends the native one, code type-hinting\ReflectionFunctionaccepts both transparently. Consistently,ReflectionFunctionLikeTrait::isInternal()hard-returnsfalseandisUserDefined()hard-returnstrue.Native reflection reports everything as expected on 8.6. Probed directly on the 8.6 binary:
(new \ReflectionFunction('strcoll'))->isDeprecated()→true(it isfalseon 8.5)getAttributes(\Deprecated::class)→ one attribute,getName()isDeprecated, arguments['message' => 'use Collator::compare() instead', 'since' => '8.6'], andnewInstance()yields a real\Deprecatedinstance__toString()→Function [ <internal, deprecated:standard> function strcoll ] { ... }For scale: 37 internal functions carry
#[\Deprecated]on 8.5 versus 55 on 8.6, so the downstreamgoaop/frameworkproxy-generator diff is the expected consequence of the engine change rather than a reflection defect here.No deprecation notices are emitted by reflecting these functions —
isDeprecated(),getAttributes()and__toString()are all side-effect free, so the added tests are safe under strict error reporting.Coverage added
Four test methods in
tests/DeprecatedAndFunctionLikeGapsTest.php, alongside the existing PHP 8.4#[\Deprecated]coverage (tests only, nosrc/changes):testInternalFunctionsAreReflectedByTheNativeFallback()— runtime-agnostic: internal functions have no file name and are never resolved to a parsed reflection, while a parsed function stays user-defined and remains a drop-in\ReflectionFunction.testDeprecatedInternalFunctionIsReflectedOnEveryRuntime()— runtime-agnostic:utf8_encode()(deprecated since 8.2) reports its attribute payload and itsdeprecatedmarker in__toString(), withstrlen()as a negative control. This proves the mechanism is not specific to 8.6.testInternalFunctionDeprecatedInPhp86IsReflectedWithItsAttributePayload()— guarded byPHP_VERSION_ID >= 80600:strcoll()reports deprecated withsince: '8.6'andmessage: 'use Collator::compare() instead'.testInternalFunctionDeprecatedInPhp86IsNotDeprecatedBefore()— guarded byPHP_VERSION_ID < 80600:strcoll()reports no deprecation and no attribute, pinning the transition from the other side.Each version-specific test is skipped (not failed) on the runtime it does not target, so the suite stays green on both.
Unrelated observation (not addressed here)
While probing, one pre-existing difference surfaced in the parsed attribute layer, unrelated to internal functions and to this issue: for a user-land
#[\Deprecated(message: '...', since: '4.0')], nativeReflectionAttribute::getArguments()returns the named arguments keyed by name, whereasGo\ParserReflection\ReflectionAttribute::getArguments()returns them positionally. It affects every attribute with named arguments, not just\Deprecated. Left untouched to keep this verification ticket minimal — worth its own issue if wanted.Local validation
vendor/bin/phpunit(PHP 8.5.9)php8.6 vendor/bin/phpunit(PHP 8.6.0beta1)vendor/bin/phpstan analyse src --no-progress(level 10)[OK] No errorsBaseline on
master(PHP 8.5) was 13743 tests / 133 skipped; the four new tests account for the difference, one of which is always skipped on a given runtime by design.🤖 Generated with Claude Code
https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn