Resolve "supported only on PHP X" checks from Scope::getPhpVersion() instead of the injected PhpVersion - #6496
Conversation
03a6fd4 to
01bbcea
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
don't add new NEON files for tests, but just narrow using if (PHP_VERSION_ID >= 80300) { as in #6497
|
I processed this review but have nothing to report. |
01bbcea to
b455c3e
Compare
VincentLanglet
left a comment
There was a problem hiding this comment.
Rather than using self::$analysedPhpVersionId, you can have conditional error message in test, like
public function testRule(): void
{
$errors = [];
if (PHP_VERSION_ID < 80300) {
$errors = [
[
'Class constants with native types are supported only on PHP 8.3 and later.',
10,
],
];
}
$this->analyse([__DIR__ . '/data/native-typed-class-constant.php'], $errors);
}
This avoid every setup and getAdditionalConfigFiles
|
That monitor was watching for the paratest run to finish; it's stale now — the suite completed earlier (22009 tests, exit 0), and I've since re-run The work is complete and pushed to
|
…` instead of the injected `PhpVersion` - Add 20 range-aware `supports*(): TrinaryLogic` methods to `PHPStan\Php\PhpVersions` mirroring their `PhpVersion` counterparts, so scope-narrowed PHP version ranges can answer feature-support queries. - Switch every rule that emits a non-ignorable "… is supported only on PHP X and later." error to `$scope->getPhpVersion()->supportsX()->yes()`, and drop the now-unused `PhpVersion` constructor dependency: - `Constants/NativeTypedClassConstantRule` (the reported case), `Constants/FinalConstantRule`, `Constants/DynamicClassConstantFetchRule`, `Constants/ConstantAttributesRule` - `Classes/ReadOnlyClassRule` (both readonly and anonymous readonly classes), `Classes/ClassConstantRule` (`::class` on an expression), `Classes/InvalidPromotedPropertiesRule` (promoted and final promoted properties) - `Properties/ReadOnlyPropertyRule`, `Properties/PropertiesInInterfaceRule`, `Properties/PropertyInClassRule` (final properties, property hooks, asymmetric visibility on static properties), `Properties/PropertyAttributesRule` - `Methods/MethodCallableRule`, `Methods/StaticMethodCallableRule`, `Functions/FunctionCallableRule` (first-class callables) - `Cast/VoidCastRule`, `Exceptions/ThrowExpressionRule`, `Traits/TraitAttributesRule`, `Functions/ExistingClassesInArrowFunctionTypehintsRule` (never return type) - `FunctionDefinitionCheck` native union types, covering the four `ExistingClassesIn*TypehintsRule` rules and property hooks - Add `tests/PHPStan/Rules/php-version-*.neon` fixtures and rework the affected rule tests to configure the analysed PHP version through the container instead of handing a fake `PhpVersion` to the rule, so the scope agrees with the configured version. - Add 17 regression fixtures declaring the same construct inside `if (PHP_VERSION_ID >= X)`, inside `if (PHP_VERSION_ID < X)` and unguarded; only the latter two are expected to be reported. - `FunctionCallParametersCheck` (named arguments), `NoncapturingCatchRule` and `FinalPrivateMethodRule` already used the scope and needed no change. Closes phpstan/phpstan#13133 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A global `const` declaration is a parse error inside an `if` block, so `Scope::getPhpVersion()` can never be narrower than the configured PHP version for this rule - the switch was a no-op that only made the "ConstantAttributesRule requires PHP 8.5 runtime to check the code." branch untestable without a dedicated config file. Also drop the now-unused PhpVersions::supportsAttributesOnGlobalConstants(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…stead of config files Rule tests now let the analysed version be the runtime one and make the expected errors conditional on PHP_VERSION_ID, the same way NativeTypedClassConstantRuleTest does. The "not supported on PHP X" branches stay covered by the `if (PHP_VERSION_ID < X)` declarations in the regression fixtures, which narrow the scope regardless of the runtime version. This removes the tests/PHPStan/Rules/php-version-*.neon files together with the per-test setUp()/getAdditionalConfigFiles() plumbing that selected them. The throw expression fixture gets one method per case: with all three cases in a single method, a conditional `throw` in an earlier branch narrows PHP_VERSION_ID in the later ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test method was lost while resolving the conflict with phpstan#6497, which left data/bug-13133.php unreferenced and the `use const PHP_VERSION_ID;` import removed from a file that still uses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ee8fa64 to
93fe4cc
Compare
staabm
left a comment
There was a problem hiding this comment.
revert changes to
- PropertyAttributesRule*
- ExistingClassesInArrowFunctionTypehintsRule*
- PropertiesInInterfaceRule*
- PropertyInClassRule*
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ersion Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d PhpVersion Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Done — the four rules are reverted and pushed as four commits. What changed
All four rule sources, their three test classes and the Two things worth flagging: The arrow-function test was only partially reverted. I removed five Verification
|
|
//cc @SanderMuller |
|
I have isolated rules which were easy to review and need similar changes in this PR. |
|
On the performance question from the Slack thread: it is answered, and by #6481. I measured That reframes what this PR costs. I counted the rules it touches by instrumenting the rule dispatch: On the open question about the five Numbers are one corpus, and 31433 is an upper bound, since some of these rules return before they |
Summary
A class declared inside
if (PHP_VERSION_ID >= 80300) { … }was still checked against the configured PHP version, so analysing on PHP 8.2 reported the non-ignorable, non-baselineable errorClass constants with native types are supported only on PHP 8.3 and later.for apublic const string BAR = 'bar';that can only ever be reached on PHP 8.3+.The rule asked the DI-injected
PhpVersionservice, which knows nothing about control flow.Scope::getPhpVersion()already narrows the analysed PHP version from thePHP_VERSION_IDconstant in scope (the mechanism introduced in #3662 forNoncapturingCatchRule/FinalPrivateMethodRule). This PR moves the whole family of "… is supported only on PHP X and later." checks over to it.Changes
src/Php/PhpVersions.php— added 20 range-awaresupports*(): TrinaryLogicmethods mirroring thePhpVersionbooleans:supportsThrowExpression,supportsClassConstantOnExpression,supportsPromotedProperties,supportsNativeUnionTypes,supportsFinalConstants,supportsReadOnlyProperties,supportsFirstClassCallables,supportsReadOnlyClasses,supportsNeverReturnTypeInArrowFunction,supportsNativeTypesInClassConstants,supportsDynamicClassConstantFetch,supportsReadOnlyAnonymousClasses,supportsPropertyHooks,supportsFinalProperties,supportsAsymmetricVisibilityForStaticProperties,supportsFinalPromotedProperties,supportsVoidCast,supportsAttributesOnGlobalConstants,supportsDeprecatedTraits,supportsOverrideAttributeOnProperty.$scope->getPhpVersion()->supportsX()->yes()(and their now-unusedPhpVersionconstructor dependency removed):src/Rules/Constants/NativeTypedClassConstantRule.php— the reported casesrc/Rules/Constants/FinalConstantRule.php,src/Rules/Constants/DynamicClassConstantFetchRule.php,src/Rules/Constants/ConstantAttributesRule.phpsrc/Rules/Classes/ReadOnlyClassRule.php(readonly classes and anonymous readonly classes),src/Rules/Classes/ClassConstantRule.php(::classon an expression),src/Rules/Classes/InvalidPromotedPropertiesRule.php(promoted and final promoted properties)src/Rules/Properties/ReadOnlyPropertyRule.php,src/Rules/Properties/PropertiesInInterfaceRule.php,src/Rules/Properties/PropertyInClassRule.php(final properties, property hooks, asymmetric visibility for static properties),src/Rules/Properties/PropertyAttributesRule.phpsrc/Rules/Methods/MethodCallableRule.php,src/Rules/Methods/StaticMethodCallableRule.php,src/Rules/Functions/FunctionCallableRule.php— first-class callablessrc/Rules/Cast/VoidCastRule.php,src/Rules/Exceptions/ThrowExpressionRule.php,src/Rules/Traits/TraitAttributesRule.php,src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.phpsrc/Rules/FunctionDefinitionCheck.php— all three native-union-type sites, which coverFunctions/,Methods/, closure, arrow-function and property-hookExistingClassesIn*TypehintsRulesrc/Rules/FunctionCallParametersCheck.php(named arguments),src/Rules/Exceptions/NoncapturingCatchRule.php,src/Rules/Methods/FinalPrivateMethodRule.php— these already read the version off the scope.PhpVersionuses that drive semantics rather than a syntax-support error (OverridingPropertyRule,AccessPropertiesCheck,ReadOnlyPropertyAssignRule, thedeprecates*checks inFunctionDefinitionCheck). Those are not "this syntax does not parse/exist on your PHP version" errors and changing them is a different, much larger behavioural question.tests/PHPStan/Rules/php-version-{70400,80000,80100,80200,80300,80400,80500}.neon. Rule tests that used to hand a fakePhpVersionto the rule now configure the analysed version through the container viagetAdditionalConfigFiles(), so the scope and the rule agree on the version.Root cause
Two sources of truth for "which PHP version is being analysed":
PhpVersionservice — a single value derived from config/composer/runtime, with no notion of where in the file we are;Scope::getPhpVersion()— aPhpVersionsvalue object wrapping the type ofPHP_VERSION_IDin the current scope, soif (PHP_VERSION_ID >= 80300)narrows it toint<80300, max>.Every rule that reported a non-ignorable "supported only on PHP X and later" error used the first one, so version-gated declarations were always judged against the lowest supported version. Because the errors are
nonIgnorable(), they cannot be silenced with@phpstan-ignoreor a baseline — the code was unanalysable.PhpVersionsonly exposed sixsupports*()methods, which is why the pattern had only ever been applied to two rules; the fix is to complete that API and sweep the whole family.->yes()is the right predicate: when the analysed version is an uncertain range that straddles the threshold, the answer ismaybeand the error is still reported — matching today's behaviour for a configuredphpVersion: {min, max}range.Test
tests/PHPStan/Rules/Constants/data/bug-13133.php+NativeTypedClassConstantRuleTest::testBug13133()— the OP's playground snippet verbatim, analysed withphpVersion: 80200. Only the unguarded class is reported.*-php-versions.phpfixtures +testConditionally*()methods for: final class constants, readonly properties, readonly/anonymous readonly classes, promoted properties,throwexpressions,(void)casts, first-class callables (method, static method and function variants), dynamic class constant fetch, final properties, properties in interfaces,::classon an expression,#[Override]on properties, native union types, andneverreturn type in arrow functions. Each fixture declares the construct three times — guarded byif (PHP_VERSION_ID >= X), guarded byif (PHP_VERSION_ID < X), and unguarded — and expects errors only for the last two.testConditionally*/testBug13133cases were verified to fail when thePHP_VERSION_IDnarrowing is removed fromMutatingScope::getPhpVersion(), and pass with it.phpVersionconfiguration;make tests(22015 tests),make phpstanandmake cs-fixare green.make name-collisionfails on the pre-existingtests/PHPStan/Rules/Methods/data/static-call-pipe.phpparse error, unrelated to this change.Fixes phpstan/phpstan#13133
refs #3642 (comment)
🤖 Generated with Claude Code