Skip to content

Add generic return type for the PHP 8.5 clone() function#11884

Open
delolmo wants to merge 3 commits into
vimeo:6.xfrom
delolmo:generic-clone-function
Open

Add generic return type for the PHP 8.5 clone() function#11884
delolmo wants to merge 3 commits into
vimeo:6.xfrom
delolmo:generic-clone-function

Conversation

@delolmo

@delolmo delolmo commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

PHP 8.5 adds a function form of clone, clone(object $object, array $withProperties = []), which PR #11604 registered in CallMap_85 with a plain object return type. That loses the concrete type of the cloned object, so clone($user, [...]) is inferred as object rather than User.

This adds a templated stub to stubs/Php85.phpstub so the function form returns the same type as its first argument:

/**
 * @psalm-template T of object
 * @param T $object
 * @param array<string, mixed> $withProperties
 * @return T
 */
function clone(object $object, array $withProperties = []): object {}

This mirrors what CloneAnalyzer already does for the clone $x operator form (it preserves the operand's type).

Tests

Adds a case to tests/CloneTest.php (pinned to php_version: 8.5) asserting clone($a, [...]) is inferred as the argument's class.

Notes

I split this out from the array_first/array_last/array_any/array_all generics (separate PR) because clone is a reserved keyword and this needs your eyes.

RoutingCloneAnalyzer handles the Clone_ AST node (the clone $x operator). I'm assuming the two-argument function form clone($x, [...]) is parsed as a regular call and resolves against the CallMap/stub (consistent with #11604 adding clone to CallMap_85). If it is instead routed through Clone_/CloneAnalyzer, this stub won't apply and the fix belongs there or in a return-type provider instead.

Placement — the stub lives in stubs/Php85.phpstub, which is only registered when analysis_php_version_id >= 8_05_00. This gates the function to PHP 8.5+ so Psalm doesn't treat clone(...) as a valid call when analyzing code targeting earlier versions (an earlier revision placed it in CoreGenericFunctions.phpstub, which is loaded unconditionally — fixed per Cursor Bugbot's review).

@delolmo delolmo marked this pull request as ready for review June 18, 2026 10:12
@delolmo

delolmo commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Heads up for reviewers: the failing ci/circleci: test-with-real-projects check is not related to this change. It fails at the Analyse PHPUnit step on a pre-existing baseline drift (psalm dev reporting ImpureFunctionCall/ImpureMethodCall on get_loaded_extensions(), Duration::fromSecondsAndNanoseconds, TestMethod::hasDataFromDataProvider, etc., plus unused-baseline entries) — none of which touch clone. The same step is currently red on unrelated PRs (e.g. #11879, #11878). The macOS build job is also an unrelated shivammathur/setup-php infra flake ("Could not setup PHP 8.5"); the Linux build (Psalm self-analysis) and the full unit-test matrix including PHP 8.5 pass.

Two open questions for maintainers are noted in the PR description: (1) whether the two-argument clone($x, [...]) form resolves against the CallMap/stub rather than being routed through Clone_/CloneAnalyzer, and (2) preferred placement (CoreGenericFunctions.phpstub vs version-gated stubs/Php85.phpstub).

Comment thread stubs/CoreGenericFunctions.phpstub Outdated
The clone() function only exists in PHP 8.5+. Defining its stub in
CoreGenericFunctions.phpstub registered it unconditionally for all
analysis target versions, so Psalm would silently accept clone($x, [...])
even when analyzing code targeting PHP < 8.5. Php85.phpstub is only
loaded when analysis_php_version_id >= 8_05_00.

@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.

✅ Bugbot reviewed your changes and found no new issues!

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

Reviewed by Cursor Bugbot for commit e9d13ed. Configure here.

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