Skip to content

[6.x] Support PHP 8.5 clone-with-properties#11893

Draft
alies-dev wants to merge 3 commits into
vimeo:6.xfrom
alies-dev:feat/php85-clone-with-properties
Draft

[6.x] Support PHP 8.5 clone-with-properties#11893
alies-dev wants to merge 3 commits into
vimeo:6.xfrom
alies-dev:feat/php85-clone-with-properties

Conversation

@alies-dev

Copy link
Copy Markdown
Contributor

PHP 8.5 adds a function form of clone, clone(object $object, array $withProperties = []), that returns a copy with some properties replaced. php-parser routes it as a regular clone(...) function call rather than a Clone_ node, so it never reached CloneAnalyzer: the cloned type collapsed to the flat object from the CallMap_85 entry, none of the usual clone-validity checks ran, and the withProperties array went unchecked.

This routes the clone(...) call (positional or named arguments) into CloneAnalyzer. The bare-clone validity core is extracted into a reusable method shared by both forms, so clone($o, [...]) now keeps the type of $o, still reports InvalidClone/PossiblyInvalidClone/MixedClone and __clone visibility, and carries the same immutability flags. The withProperties array is validated against the cloned class: each literal-string key must name an accessible property, and each value must be assignable to that property's declared type. Replacing readonly properties is allowed, which is the whole point of the RFC, so that validation is replicated locally rather than routed through InstancePropertyAssignmentAnalyzer, whose readonly-write guard requires $context->self and would reject a top-level clone-with. Generic, intersection, union, dynamic-array and magic-__set targets are handled conservatively so they do not produce false positives.

The second commit gates the function form to PHP 8.5: analysing it against an older target reports a ParseError, since the two-argument call does not compile before 8.5 (this mirrors how ClassConstAnalyzer gates dynamic class-constant fetches to 8.3). This is a deliberate addition that is stricter than #11884 and reverses an earlier decision to leave version-gating out of a first cut.

Relationship to #11884: that PR solves the type-preservation half declaratively with a templated stub in stubs/Php85.phpstub. This PR is the superset, since it also performs the clone-validity and withProperties checks that a stub cannot express (unknown keys, value-type mismatches, visibility, readonly exemption). The two are alternatives rather than complementary: the intercept resolves before CallMap/stub resolution, so if this lands the stub is not needed for the two-argument form.

PHP 8.5 adds the function form of clone, clone(object $object,
array $withProperties), which php-parser routes as a FuncCall rather than a
Clone_ node. It never reached CloneAnalyzer, so the cloned type was lost and
reported as a flat object by the CallMap entry, and the property array went
unchecked.

Extract CloneAnalyzer's clone-validity core into a reusable method and route the
clone() call (positional or named arguments) through it. The cloned type is now
preserved and the bare-clone checks (InvalidClone/PossiblyInvalidClone/
MixedClone, __clone visibility, immutability) apply. The withProperties array is
validated against the cloned class: each literal key must be an accessible
property and each value assignable to its declared type, while replacing
readonly properties is permitted per the RFC.
The function form of clone only exists on PHP 8.5+; on an older target it is a
compile error rather than a clone of an object with replaced properties. Emit a
ParseError when the analysis target is below 8.5, mirroring how ClassConstAnalyzer
gates dynamic class-constant fetches, while still analysing the call so tooling
keeps the inferred type.
@alies-dev alies-dev changed the title Support PHP 8.5 clone-with-properties [6.x] Support PHP 8.5 clone-with-properties Jun 23, 2026
@alies-dev alies-dev marked this pull request as draft June 23, 2026 14:20

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit bb10c15. Configure here.

$with_properties_arg,
$object_type,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped withProperties type check

Medium Severity

analyzeWithProperties runs only when both object and withProperties are present, so a call like clone(withProperties: 5) never validates the second argument and misses InvalidArgument, unlike clone($o, 5) which is checked.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bb10c15. Configure here.

…alues

The clone() intercept resolves arguments itself, so the standard call validation
never ran: extra positional arguments, unknown named arguments, and an argument
passed both by position and by name were accepted silently. Report
TooManyArguments / InvalidNamedArgument for those. Also check the value assigned
to a declared @property-write key against its write type, which a regular
property assignment already validates but the clone-with path was skipping.
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.

1 participant