A fluent method returning self tells the analyser the base class, so every chained call on a subclass loses its type after the first link.
abstract class Query
{
public function where(string $c): static
{
$this->conditions[] = $c;
return $this;
}
}
final class OrderQuery extends Query
{
public function paid(): static { /* ... */ }
}
(new OrderQuery())->where('total > 0')->paid();
// with self: the analyser says Query has no method paid()
static as a return type is late static binding expressed in the signature, and it is only valid as a return type — never for a parameter or a property, because there is no meaningful binding at those positions. The runtime already behaved this way; what changed in 8.0 is that the type system can say so, which turns a docblock that everybody wrote into something checked.