You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/CHANGELOG.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -375,6 +375,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
375
375
- **A return type that depends on an argument's value is read from the value passed, or from the argument's default when it is left out.** A conditional `@return` keyed on a value (`($format is 0 ? int : list<string>)`) only ever recognised a quoted string, so an `int` literal decided nothing and an omitted argument decided nothing either, leaving every call to read back the union of every branch and any use of the result in a typed position reported. The literal at the call site now decides the branch, compared by value rather than by spelling, and an omitted argument is decided by the default it declares, since that is the value it takes at runtime. `str_word_count()` is the standard library's example of the shape and now resolves to the count, the word list, or the offset-keyed map according to its `$format`, so returning it from an `int` method is no longer reported. A negated condition is honoured in both directions, and a value the call site cannot pin down still reads back everything the call could return.
376
376
-**A tag written on a docblock's opening line is read.** A docblock that starts its first tag on the `/**` line itself, as in `/** @param 'a'|'b' $key` followed by more tags below, had that first tag ignored for `@param` and `@var`. The same tag moved down a line worked, and so did the fully single-line spelling, so only the shape that shares the opening line was affected. The parameter or variable fell back to its native hint, which is wider than what was declared, so narrowing, argument checks, and hover all read the wide type, and a `@return` further down the same docblock (which was read) could then be reported as incompatible with the body's widened value.
377
377
-**`?->` on a `null` subject is no longer reported as a crash.**`echo $customer?->id;` where `$customer` is `null` was reported as "Cannot access property 'id' on type 'null'", the same diagnostic a plain `->` earns for the same subject. The nullsafe operator exists precisely so that case doesn't crash: it short-circuits to `null` without touching the property. The diagnostic now tells the two operators apart and only reports a `null` subject under a plain `->`, where accessing it is still a real crash.
378
+
- **A replace on a string comes back a string, and one on an array comes back an array.** `preg_replace()`, `preg_replace_callback()`, `preg_filter()`, `str_replace()`, `str_ireplace()` and `substr_replace()` return whatever shape their subject was, but their signatures can only name the flat union of both overloads, so every call was read as `array|string` no matter what it was handed. Passing the result of a replace on a plain string straight into a `string` parameter or returning it from a `string` function was reported for an array branch the call could never take, which was the single largest source of argument and return mismatches in real code. Each of them now resolves against the subject at the call site: a string subject rules the array branch out, an array subject rules the string branch out and keeps the keys it was given, and a subject whose shape is genuinely unknown still reports both, since that is all the call can promise. `preg_replace()`'s `null` error result survives for a string subject, where PHP really can return it, and is dropped for an array subject, where it cannot.
379
+
-**`json_encode()` with `JSON_THROW_ON_ERROR` can no longer be `false`.** The flag is how modern code asks for a `JsonException` instead of a silent `false`, and the declared `string|false` return type has no way to say so, so every such call was still read as possibly `false`: handing the result to a `string` parameter, returning it, or concatenating it were all reported for a branch the flag had already ruled out. The flag is now read at the call site, whether it is passed on its own, OR-ed together with other JSON flags such as `JSON_PRETTY_PRINT`, written as a plain number, or held in a constant. A call that leaves the flag out, or whose flags cannot be read, keeps the failure branch, because there it is real.
380
+
-**A cast argument is read as the type it casts to.**`(string) $customer->mobile` is a `string` whatever the property holds, but an argument written that way resolved to nothing at all, so anything that reads a call's arguments to work out its return type was left guessing: a `@template` bound from that argument stayed unbound, and a return type that depends on the argument fell back to naming every branch at once. Every cast now answers with what it produces, so `str_replace('a', 'b', (string) $value)` is a `string` and not the string-or-array union its signature also allows.
Copy file name to clipboardExpand all lines: docs/todo.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -72,14 +72,14 @@ unlikely to move the needle for most users.
72
72
| C6 |`#[ExpectedValues]` parameter value suggestions | Low | Medium |
73
73
| C10 |[Deprecation markers on class-name completions from all sources](todo/completion.md#c10-deprecation-markers-on-class-name-completions-from-all-sources)| Low | Low |
74
74
||**[Type Inference](todo/type-inference.md)**|||
75
-
| T38 |[Several core builtins have a return type that depends on an argument's value or shape](todo/type-inference.md#t38-several-core-builtins-have-a-return-type-that-depends-on-an-arguments-value-or-shape) (`preg_replace`/`str_replace` family, `json_encode`/`json_decode`) | High | Medium |
| T29 |[Definite vs possible variable existence tracking](todo/type-inference.md#t29-definite-vs-possible-variable-existence-tracking)| Medium | Medium |
79
78
| T3 |[Property hooks (PHP 8.4)](todo/type-inference.md#t3-property-hooks-php-84)| Medium | Medium |
80
79
| T32 |[Audit `is_type_compatible`'s MAYBE escape hatches for core-engine gaps](todo/type-inference.md#t32-audit-is_type_compatibles-maybe-escape-hatches-for-core-engine-gaps)| Medium | Medium |
81
80
| T34 |[`static::CONST` over-narrows to the declaring class's value](todo/type-inference.md#t34-staticconst-over-narrows-to-the-declaring-classs-value)| Medium | Medium |
82
81
| T30 |[Literal type collapse limit](todo/type-inference.md#t30-literal-type-collapse-limit)| Low-Medium | Low |
82
+
| T40 |[`pathinfo()` returns a shape or a string depending on the flags argument](todo/type-inference.md#t40-pathinfo-returns-a-shape-or-a-string-depending-on-the-flags-argument)| Low-Medium | Low |
| D17 |[`docblock_native_mismatch` only judges nullability](todo/diagnostics.md#d17-docblock_native_mismatch-only-judges-nullability)| Low | Medium |
99
99
||**[Bug Fixes](todo/bugs.md)**|||
100
100
| B99 |[Formatting a `.blade.php` file has no extension guard](todo/bugs.md#b99-formatting-a-bladephp-file-has-no-extension-guard)| Medium | Low |
101
+
| B124 |[An argument's type is read from its source text, and several ordinary spellings read as nothing](todo/bugs.md#b124-an-arguments-type-is-read-from-its-source-text-and-several-ordinary-spellings-read-as-nothing)| Medium | Medium |
101
102
||**[Code Actions](todo/actions.md)**|||
102
103
| A40 |[Generate method from call](todo/actions.md#a40-generate-method-from-call)| Medium-High | Medium |
103
104
| A41 |[Create class from non-existing name](todo/actions.md#a41-create-class-from-non-existing-name)| Medium | Medium |
0 commit comments