Skip to content

Ask Scope::getPhpVersion() instead of the injected PhpVersion in rules and expression handlers - #6984

Merged
ondrejmirtes merged 2 commits into
2.3.xfrom
scope-php-version-in-rules
Nov 6, 2026
Merged

ondrejmirtes merged 2 commits into
2.3.xfrom
scope-php-version-in-rules

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Closes phpstan/phpstan#15673

With a phpVersion range (or a composer.json PHP constraint) the injected PhpVersion is only the range's minimum, so e.g. method.tentativeReturnType was not reported for a 7.4–8.5 range. Following the dynamic return type extensions that already ask the scope, rules and expression handlers now ask Scope::getPhpVersion(), which knows the whole range and PHP_VERSION_ID narrowing. New PhpVersions methods back the converted checks.

Semantics

  • Rules reporting a problem report it when any version in the range is affected (!->no()): OverridingMethodRule / MethodPrototypeFinder (tentative return types), FunctionDefinitionCheck (required-after-optional, implicitly nullable), invalid array keys, BacktickRule, InvalidIncDecOperationRule, MissingMagicSerializationMethodsRule, DefineParametersRule, FilterVarRule, PropertyAssignRefRule, UnsetRule.
  • Throw points are added when any version may throw: property hooks (PropertyFetchHandler, AssignHandler), __toString() on string casts (ImplicitToStringCallHelper).
  • Checks that only relax an error need every version (->yes()): ReadOnlyPropertyAssignRule's subclass assignment and __clone reinitialization (and the matching MutatingScope clone tracking).
  • MutatingScope: isReadonlyPropertyFetch() (readonly narrowing carried into closures, kept on invalidation) and canWriteProperty() follow the scope's versions.
  • InitializerExprTypeResolver::getArrayType() takes optional ?PhpVersions (like resolveEqualType()); ArrayHandler passes the scope's, and string keys are kept on unpacking when any version supports it (below 8.1 it is an Error).

The turbo-ext mirrors follow the same changes (shared pt_mutating_scope_php_versions_trinary() helper); now-unused PhpVersion queries were removed.

Tests
Each converted site has a test that fails without it: *ConfigPhpTest classes with a version-range config (7.4–8.5, 7.3–8.5 for string casts, 8.1–8.5 for readonly), type inference asserts in ScopePhpVersionRangeReturnTypeExtensionsTest, and PHP_VERSION_ID-guard cases for explicit property hooks. Full suite passes with and without the extension; turbo smoke, signature parity and side-by-side pass.

Still on the injected version: InitializerExprTypeResolver::getFunctionType() (no scope; MutatingScope overrides variadics), resolveConcatType(), AccessPropertiesCheck / AccessStaticPropertiesCheck.

…s and expression handlers

With a phpVersion range (or a composer.json PHP constraint), the injected
PhpVersion is only the range's minimum. Rules and expression handlers now ask
the scope, which knows the whole range and PHP_VERSION_ID narrowing:

- Rules that report a problem (deprecations, tentative return types, invalid
  array keys, missing magic serialization methods, ...) report it when any
  version in the range is affected.
- Throw points (property hooks, __toString() on string casts) are added when
  any version in the range may throw.
- Checks that only relax an error (readonly assignment from a subclass,
  readonly reinitialization in __clone) need every version in the range.
- MutatingScope's readonly property tracking, canWriteProperty() and the
  clone reinitialization follow the scope's versions too, and array
  unpacking keeps string keys when any version supports it.

The native turbo-ext mirrors follow the same changes.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1b1b9-0a7d-772a-a8bb-331fd76aa9cc
SplFixedArray::setSize() has the tentative return type true only since
PHP 8.4. The SOAP E2E fixture is analysed in a PHP 8.2+ range from
composer.json, so its implicitly nullable parameter is now reported as
deprecated in PHP 8.4; declare it explicitly nullable instead.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1b1b9-0a7d-772a-a8bb-331fd76aa9cc
@ondrejmirtes
ondrejmirtes merged commit c92421b into 2.3.x Nov 6, 2026
542 of 543 checks passed
@ondrejmirtes
ondrejmirtes deleted the scope-php-version-in-rules branch November 6, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

method.tentativeReturnType not reported when phpVersion range starts below PHP 8.1

1 participant