Hi Michal,

> The receiver in your example is $x, an untyped local, so $x->length()
> doesn't dispatch at all.

Fine, then take the corrected version. Every narrowing you apply to keep
dispatch "locally provable" makes the feature worse, because the property
that matters to a user is the one being trimmed away: if `<expr>` is a
string, `<expr>->trim()` should work. Not "works if you assigned it to a
typed local first," not "works unless it came from a bare function call,"
not "works depending on what's above or below it in the file."

Seeing `$s->trim()` where `$s` is a string, I should know it works. A
construct where identical AST dispatches or doesn't depending on file
position, a redundant cast, or whether a declaration sits above or below
the call is not something people can reason about.

> restrict that form to internal functions, where the compiler and the
> analyser agree unconditionally

This is more fragile than the problem it fixes. A static analyser often
can't tell whether a function is "internal." A function added as a builtin
in 9.1 but provided by a polyfill on 9.0: a project supporting both has no
single answer. The same call is internal on one target and userland on the
other, so the analyser can't soundly decide whether it dispatches. That's a
language construct whose validity depends on the runtime present.

Also, this does not take into consideration unresolved names at compile
time, calling `strlen($s)->multiply($y)` inside namespace `App` with no
imports. How would this be handled? Would it also not work?

> The definitions aren't hidden. They're ordinary .stub.php files in core
> (str.stub.php, ...), which is what PhpStorm, PHPStan and Psalm already
> consume.

This isn't accurate, and I maintain Mago, so I can speak to it directly. We
don't consume PHP's `.stub.php` files. Neither do PHPStan or Psalm; they
ship their own stub sets, because core's stubs aren't expressive enough for
analysis: no conditional return types, no generics, no ranges. Publishing
`str.stub.php` doesn't give analysers what they need.

PHPStan: https://github.com/phpstan/php-8-stubs/tree/main/stubs
Psalm: https://github.com/vimeo/psalm/tree/6.x/stubs
Mago:
https://github.com/carthage-software/mago/tree/main/crates/prelude/assets/extensions

> an analyser has to special-case scalar dispatch regardless, since a
> string isn't an object

It doesn't. If `$s->trim()` works whenever `$s` is a string, the analysis
is clean: I resolve string methods against whatever interface defines them,
as if `$s` were an instance of it. What forces a hack is your design, a
construct valid or invalid for identical AST depending on file order and
surrounding declarations. I don't know how I'd model that soundly. Your
approach creates the special-casing you say is unavoidable.

> That last one is taste

The "should scalars have methods" part is taste. But this already existed,
unrestricted, and worked: Nikita's `scalar_objects` (
https://github.com/nikic/scalar_objects) dispatches on the runtime type, so
`$str->length()` works for any string, regardless of where it came from.

>  so I won't try to talk you out of a no.

You don't have to :P I don't vote.

Cheers,
Seifeddine

Reply via email to