Resolve property, trait constant, arrow function never and attribute PHP version checks from Scope::getPhpVersion() - #6510
Conversation
…e PHP version checks from `Scope::getPhpVersion()` * Added `supportsConstantsInTraits()`, `supportsNeverReturnTypeInArrowFunction()`, `supportsArrayUnpackingWithStringKeys()`, `supportsPropertyHooks()`, `supportsFinalProperties()`, `supportsAsymmetricVisibilityForStaticProperties()`, `supportsOverrideAttributeOnProperty()`, `supportsAttributesOnGlobalConstants()` and `supportsUnsetCast()` to `PhpVersions`. * `PropertyInClassRule` now resolves the final-property (8.4), static asymmetric-visibility (8.5) and property-hooks (8.4) checks from the scope. * `PropertiesInInterfaceRule`, `PropertyAttributesRule`, `ConstantsInTraitsRule`, `ExistingClassesInArrowFunctionTypehintsRule`, `ConstantAttributesRule`, `UnsetCastRule` and `ArrayUnpackingRule` do the same for their own version gates. * `UnsetCastRule` reports only when the scope version is definitely >= 8.0 (`supportsUnsetCast()->no()`), which is the inverse-direction member of the family. * Dropped the now-unused `PhpVersion` constructor dependencies from all of these rules. * Probed and deliberately left alone: `supportsPureIntersectionTypes()` in `FunctionDefinitionCheck`/`ExistingClassesInPropertiesRule` and the covariance/visibility/deprecation gates in `OverridingMethodRule`, `MethodParameterComparisonHelper`, `OverridingPropertyRule`, `DeprecatedCastRule` and `BacktickRule` - those pick between two valid behaviours rather than emitting a non-ignorable "supported only on PHP X" error. Closes phpstan/phpstan#13133
9f4e6b9 to
71ffcb4
Compare
|
//cc @SanderMuller |
|
The cast does break on the 8.0 half of that range, and the other rules report in the same
Intended? For a feature that was removed rather than added there is an argument for staying quiet |
There was a problem hiding this comment.
adjust per #6510 (comment)
should be $scope->getPhpVersion()->supportsUnsetCast()->yes()
`UnsetCastRule` was the only rule in the `Scope::getPhpVersion()` sweep using `!...->no()` instead of `...->yes()`, which made it stay silent when the version range straddles 8.0 — even though the cast is a fatal error on the 8.0 half of that range. Align it with the other nine rules: return early only when the cast is definitely still supported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Done — pushed as Change ( Behaviour delta — only the straddling-range case changes, which is exactly what SanderMuller's table flagged:
Test added: Verified per the repo's stash-and-rerun rule: with the rule reverted to |
Summary
Code that is guarded by a
if (PHP_VERSION_ID >= 8xx00)check was still being reported with non-ignorable "… is supported only on PHP X and later." errors, which cannot be baselined or ignored.#6496 converted a first batch of those rules to
Scope::getPhpVersion(). This finishes the sweep: every rule in that family now resolves the version from the scope, so a conditionally declared class/interface/trait/property/expression inside aPHP_VERSION_IDguard is judged against the version range that actually reaches it.Changes
src/Php/PhpVersions.php— new range-aware queries:supportsConstantsInTraits(),supportsNeverReturnTypeInArrowFunction(),supportsArrayUnpackingWithStringKeys(),supportsPropertyHooks(),supportsFinalProperties(),supportsAsymmetricVisibilityForStaticProperties(),supportsOverrideAttributeOnProperty(),supportsAttributesOnGlobalConstants(),supportsUnsetCast().Rules converted to
$scope->getPhpVersion()(and theirPhpVersionconstructor dependency dropped):src/Rules/Properties/PropertyInClassRule.php— all three gates:Final properties are supported only on PHP 8.4 and later.,Asymmetric visibility for static properties is supported only on PHP 8.5 and later.,Property hooks are supported only on PHP 8.4 and later.src/Rules/Properties/PropertiesInInterfaceRule.php—Interfaces can include properties only on PHP 8.4 and later.src/Rules/Properties/PropertyAttributesRule.php—Attribute class Override can be used with properties only on PHP 8.5 and later.src/Rules/Traits/ConstantsInTraitsRule.php—Constant is declared inside a trait but is only supported on PHP 8.2 and later.src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php—Never return type in arrow function is supported only on PHP 8.2 and later.src/Rules/Constants/ConstantAttributesRule.php—Attributes on global constants are supported only on PHP 8.5 and later.src/Rules/Cast/UnsetCastRule.php—The (unset) cast is no longer supported in PHP 8.0 and later.src/Rules/Arrays/ArrayUnpackingRule.php— the 8.1 gate aroundArray unpacking cannot be used on an array with string keys.Probed and found to belong to a different family, so left as-is (they select between two valid behaviours instead of reporting unsupported syntax, and a
Maybeanswer has no obviously correct resolution):supportsPureIntersectionTypes()inFunctionDefinitionCheckandExistingClassesInPropertiesRulesupportsReturnCovariance()/supportsParameterContravariance()/supportsParameterTypeWidening()/supportsLessOverridenParametersWithVariadic()inOverridingMethodRuleandMethodParameterComparisonHelpersupportsPropertyHooks()/supportsOverrideAttributeOnProperty()inOverridingPropertyRule,supportsAsymmetricVisibility*()in the property access checksdeprecates*()gates inDeprecatedCastRuleandBacktickRule(ignorable deprecation reports, not non-ignorable syntax-support errors)Root cause
The pattern is that a rule asked the DI-injected
PhpVersion— a single, file-wide version — whether a language feature exists, instead of askingScope::getPhpVersion(), which returns aPhpVersionsrange narrowed by any surroundingPHP_VERSION_IDcomparison. Because the injected value never reflects control flow, code insideif (PHP_VERSION_ID >= 80300) { … }was judged against the analysed version rather than the version range that can actually reach that branch, producing non-ignorable errors on deliberately version-gated declarations.The fix is mechanical and uniform: query the scope, and treat only a definite
yes()as "supported" (for the inverse-directionUnsetCastRule, only a definiteno()as "must report"), so an uncertain range keeps the previous, conservative outcome.ConstantAttributesRuleis a special member:constcannot appear inside a conditional block in PHP, so its scope narrowing can only come from thephpVersionmin/max NEON configuration — it is covered by a config-driven test instead.Test
The reported case (native typed class constants,
NativeTypedClassConstantRule) is already covered bytests/PHPStan/Rules/Constants/data/bug-13133.php, and the reproducer from the issue produces no errors withphpVersion: 80200.New regression tests, each with a data file declaring the construct three times — inside a supporting
PHP_VERSION_IDbranch, inside a non-supporting branch, and unguarded:tests/PHPStan/Rules/Properties/data/property-in-class-php-versions.php+PropertyInClassRuleTest::testPhpVersionNarrowedScope()tests/PHPStan/Rules/Properties/data/properties-in-interface-php-versions.php+PropertiesInInterfaceRuleTest::testPhpVersionNarrowedScope()tests/PHPStan/Rules/Properties/data/override-attr-on-property-php-versions.php+PropertyAttributesRuleTest::testOverrideAttributePhpVersionNarrowedScope()tests/PHPStan/Rules/Traits/data/constants-in-traits-php-versions.php+ConstantsInTraitsRuleTest::testPhpVersionNarrowedScope()(the guard sits on the consuming class, since a trait body is analysed through itsusesite)tests/PHPStan/Rules/Functions/data/arrow-function-never-php-versions.php+ExistingClassesInArrowFunctionTypehintsRuleTest::testNeverPhpVersionNarrowedScope()tests/PHPStan/Rules/Cast/data/unset-cast-php-versions.php+UnsetCastRuleTest::testPhpVersionNarrowedScope()tests/PHPStan/Rules/Arrays/data/array-unpacking-php-versions.php+ArrayUnpackingRuleTest::testPhpVersionNarrowedScope()tests/PHPStan/Rules/Constants/ConstantAttributesRuleConfigPhpTest+data/constant-attributes-php-version.neon(phpVersionrange instead of an inline guard)All eight fail when the rules are put back on a non-narrowing
PhpVersions, and pass with the change. The existingUnsetCastRuleTest,ConstantsInTraitsRuleTest,ReadOnlyPropertyRuleTest-style per-version data providers were rewritten to key off the runtimePHP_VERSION_ID, matching the convention introduced in #6496.Fixes phpstan/phpstan#13133
refs #3642 (comment)