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
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,6 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
124
124
125
125
- **The type engine itself now knows that a class is an `object` and that a `Traversable` is `iterable`.** Those facts, together with the equivalence between the ways one array type can be written (`array<int, Cat>`, `list<Cat>`, and `Cat[]` all describe the same values), used to be reachable only from the argument-type check, so everything else reasoned without them: to completion filtering and hover a `Collection<User>` was not an `object` and an `ArrayIterator` was not `iterable`, and an array whose element type only matched through a parent class did not match at all once the two sides were spelled at different arities. They live in the shared subtype check now, so every feature reads the same answer. This does tighten one case: a value that may be `null` no longer satisfies an `object` or `iterable` parameter, matching both what PHP does at runtime and how a nullable value has always been judged against every other parameter type.
126
126
- **A ternary inside a `throw` narrows its branches.** `throw new RuntimeException($model ? get_class($model) : '')` still read `$model` as nullable inside the arm the check proves it is not, so whatever that arm passed on was reported against the wider type. The identical ternary in a `return`, an assignment, or a call argument narrowed correctly. A thrown value is walked like any other expression now.
127
+
- **A plain function's `@return` docblock is checked against its body.** `/** @return array<string, int> */ function bad(): array { return ['a' => 'x']; }` went unreported, and so did every other return that satisfied the native hint but not the docblock behind it. The check read the `array` written in the signature and stopped there, so the type that says what the array actually holds was never compared to anything, while the same mistake passed to a parameter was reported as it should be. The docblock and the hint are now merged the way they are everywhere else, so a function is held to the type it documents rather than the weaker one it declares. Methods were never affected, which is what made the gap look like an array-shape problem rather than a function one.
127
128
- **A generic call keeps the alternatives its argument's return type declares.** `takesCarbon(passthrough(Carbon::create(2024)))` was reported as fine even though `Carbon::create()` returns `?Carbon`. The pass that works out what a `@template` binds to reads its argument as text, and that reading answers with the classes an expression can be, so the `null` arm was dropped and the template bound a plain `Carbon`. Every use of the substituted type then claimed the value could never be null: a missed report where the result is consumed, and an invented one where a parameter is checked against it. Writing the call to a variable first bound the nullable correctly, which is what made the two forms disagree. The alternatives a call's return type declares and the class walk cannot name, `null` and scalars alike, are now put back, so both forms bind the same thing. A call a check has already narrowed keeps the narrowed type rather than having the declared one restored over it.
128
129
- **A one-line function no longer inherits the previous function's `@param`.** `function g3(array $s): void { foreach ($s as $x) { doThing($x); } }` opens and closes its body on the signature line, and the backward scan that looks for an enclosing docblock relied on watching brace depth rise and fall to spot where a sibling function ends; a body written entirely on one line never moves that depth, so the scan walked straight past `g3` into its docblock and handed `g4(Status $s)` the `@param array<Status> $s` written for a different function entirely, reporting a type error against a type the parameter never had. The scan now also recognises a `function` keyword sitting at a depth it has already fully backed out of, which catches a body collapsed onto one line the same as one spread across several.
129
130
- **A guard on a call narrows the same call written again.** `if (currentUser()) { render(currentUser()); }` is the shape a nullable accessor is written for, and it was reported as passing a `?User`. A check on `$holder->get()` already carried to the next `$holder->get()`; a plain function call and a static call did not, because the scope had no entry for either to narrow. Both are now recorded under the call's own text like a method call is, so a repeated `currentUser()` or `Session::current()` inside the guard reads what the guard proved. A call that takes arguments is keyed with them, so checking one call still says nothing about a different one.
Copy file name to clipboardExpand all lines: docs/todo.md
-1Lines changed: 0 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,7 +38,6 @@ contributor even though it's short.
38
38
| B78 |[A standalone `@var` cast above `return` is ignored](todo/bugs.md#b78-a-standalone-var-cast-above-return-is-ignored)| Medium | Medium |
39
39
| B82 |[A nullsafe chain compared `===` to a non-nullable value does not narrow the receiver](todo/bugs.md#b82-a-nullsafe-chain-compared--to-a-non-nullable-value-does-not-narrow-the-receiver)| Medium | Medium |
40
40
| B83 |[A `match (true)` arm's condition does not narrow inside the arm's result](todo/bugs.md#b83-a-match-true-arms-condition-does-not-narrow-inside-the-arms-result)| Medium | Medium |
41
-
| B84 |[Return-position compatibility ignores an array shape's value types](todo/bugs.md#b84-return-position-compatibility-ignores-an-array-shapes-value-types)| Medium | Medium |
42
41
| B79 |[`array_filter()` without a callback keeps `null` on values that share the array with a `?bool`](todo/bugs.md#b79-array_filter-without-a-callback-keeps-null-on-values-that-share-the-array-with-a-bool)| Low-Medium | Medium |
43
42
| B81 |[Foreach element extraction widens `false` to `bool`](todo/bugs.md#b81-foreach-element-extraction-widens-false-to-bool)| Low-Medium | Medium |
44
43
| B76 |[Blade variables typed from a component class are immune to condition narrowing](todo/bugs.md#b76-blade-variables-typed-from-a-component-class-are-immune-to-condition-narrowing)| Medium | Medium-High |
0 commit comments