#65817 Narrow the accepted and returned types for esc_sql() - #12975
#65817 Narrow the accepted and returned types for esc_sql()#12975johnbillion wants to merge 5 commits into
esc_sql()#12975Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
A `@phpstan-template` cannot express what `_escape()` does. PHPStan is unable to prove that a value the method rebuilds (`$data[ $k ] = …`) or reassigns is still the caller's exact `T`, so `@phpstan-return T` fails even when the recursive call is removed entirely. Templates only carry through pass-through functions, which is why `esc_sql()` itself reported no error while `_escape()` did. The recursive branch caused two further errors. Under `treatPhpDocTypesAsCertain: false` the `is_array( $v )` check is still analyzed even though `$v` is a `string` per the narrowed contract, and `$v` narrows to `never` inside it. A template parameter cannot be inferred from `never`, producing both `argument.unresolvableType` and `method.unresolvableReturnType`. Use a conditional return type instead. The `mixed[]` branch absorbs the widening that the recursive call introduces, so the method body is left untouched and nested arrays keep behaving exactly as before. A truly recursive type is not an option: PHPStan rejects recursive type aliases as circular, and any bounded depth is off by one because the recursive call produces one level more than declared. Callers keep the inference the generic was intended to provide: `string` in yields `string` out, and `string[]` in yields `string[]` out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| if ( is_array( $data ) ) { | ||
| foreach ( $data as $k => $v ) { | ||
| if ( is_array( $v ) ) { | ||
| $data[ $k ] = $this->_escape( $v ); |
There was a problem hiding this comment.
Note PHPStan is currently complaining about this line:
Parameter #1 $data of method wpdb::_escape() contains unresolvable type.
I'm working on a solution.
The `mixed[]` branch was not what allowed the method body to type check. The ordering of the conditional cases was. PHPStan narrows `$v` to `never` inside the nested `is_array()` check, and a `never` argument satisfies whichever case is tested first. Testing for `string` first resolves the recursive call to `string`, so nothing widens `$data`. The earlier `( $data is array ? string[] : string )` tested the array case first, resolved the recursive call to an array, and widened `$data` — which is what the `mixed[]` branch was papering over. Removing it also restores verification. Because `mixed[]` accepts any array, PHPStan could not reject a wrong array return type; changing that branch to `int[]` produced no error. With a concrete `array<TKey, string>` return, both a wrong value type and a wrong string case are reported. Add a key template to both functions so the keys of the supplied array survive into the return type. This resolves eight `implode expects array<string>, array<mixed> given` errors in `WP_Site_Query` and `WP_Network_Query`, which call `_escape()` directly. Six `argument.templateType` errors appear in `WP_Comment_Query`, `WP_User_Query`, and `WP_Date_Query`, where the value passed to `esc_sql()` is typed as `mixed` and no key type can be inferred. Those lines already report the argument type for the same reason, so the fix belongs at the call sites rather than in a looser annotation here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🤖 Comment from Claude Opus 5 The Why a template cannot work here
This is not about the recursion. Deleting the recursive branch entirely, leaving a flat loop, leaves the error verbatim. Templates carry through pass-through functions, which is exactly why The other two errors come from the recursive branch. What is committed@phpstan-template TKey of array-key
@phpstan-param string|array<TKey, string> $data
@phpstan-return ( $data is string ? string : array<TKey, string> )on both functions, with the body of Callers get what the template was meant to provide, plus key preservation:
The ordering is load-bearingTesting
There is a comment above the method saying so, because it is not obvious and the failure mode if someone flips it is confusing. Two dead ends worth recording, so nobody re-walks them. A genuinely recursive type is not expressible: Effect on core call sitesMeasured across the ten files that call Six One known limitationPHPStan validates a conditional return type by checking the body against the union of its cases. The While in here: no core call site passes a multi-dimensional array to |
|
@irozum As I mentioned in #13023 (comment), it seems like #12975 (review) was written by AI. When you use AI to add reviews, please disclose how you have done so. Otherwise, it is misleading given that your comment says “I” and “me” when actually it was “it”. Please refer to the AI Guidelines. |
Narrows
arraytostring[]and adds a PHPStan generic.I opted to use the
@phpstan-prefix for the generic. Still need to make a decision on the preferred approach in general for prefixing tags.Trac ticket: Core-65817