On Mon, 29 Jun 2026 at 23:33, Michal Kral <[email protected]> wrote:
>
> Hi all,
>
> Per Seifeddine's suggestion to keep this out of the karma-request
> thread, I'm opening a pre-RFC discussion for scalar object methods --
> calling a small, curated set of methods directly on scalar values, e.g.
> $str->trim(), (3)->pow(2). There's a complete, tested implementation and
> a full write-up (links below); I'd like to surface the strongest
> objections before I write the formal RFC.

Hi Michal,

Thanks for the detailed write-up. I'll be upfront: I'm against this
feature. I have two concrete objections to the approach and one
broader objection to the idea itself.

> The idea: dispatch only on receivers the compiler already knows are
> scalar. The method call is rewritten at compile time to an ordinary call
> into an internal backing class -- no runtime type dispatch, no new
> opcode, the object method-call path is untouched. A receiver qualifies
> only if its type is guaranteed syntactically: a literal, a
> (string)/(int) cast, a concatenation/interpolation, a non-nullable
> scalar-typed property, or a call with a declared non-nullable scalar
> return type.

This is my main objection, and I think it's a fatal one.

Restricting dispatch to receivers "the compiler already knows are
scalar" sounds safe, but in practice it covers almost no real code.
PHP is compiled one file at a time, with no view into other files. The
compiler does not perform whole-program analysis; when compiling a
given file, it generally has no knowledge of declarations in other
files and haven't been autoloaded yet.

So take your own qualifying rule, "a call with a declared non-nullable
scalar return type":

    class Example {
        public static function getStr(): string { return "x"; }
    }

    $x = Example::getStr();
    $x->length();

A human reads this and knows `$x` is a string. But the PHP *compiler*,
when compiling the file that contains `$x = Example::getStr()`, only
knows `getStr()` returns `string` if `Example` happens to be declared
in the same file. Move `Example` into its own autoloaded file (which
is how essentially all real code is organised) and the compiler has no
idea what `getStr()` returns at compile time. So `$x->length()` would
*not* dispatch, even though the type is completely determined.

The result is that the same expression works or fails depending on
whether a class is in the same file or autoloaded from another one.
That's not a predictable rule a developer can hold in their head.
Real-world values emerge from call chains, conditionals, and
cross-file boundaries, The cases the compiler can't prove
syntactically. The feature ends up usable only on literals and casts
and almost nothing else.

This also forces special handling in static-analysis tools because the
compiler and the analyser will disagree about the same line. Given
`$x->length()`, PHPStan/Psalm/Mago will happily infer `$x` is a string
and accept it, while the compiler rejects it.

> - The backing classes are internal-only (NUL-prefixed name, like
> anonymous classes): class_exists('Str') is false, no Reflection,
> userland "class Str {}" can't collide.

My second objection. If `$s->length()` is sugar for `Str::length($s)`,
then `Str` (or whatever backs it) *must* be visible to userland in
some form. Static analysers (PHPStan, Psalm, Mago, Phan, PhpStorm,
...) need a definition describing which methods exist, to type-check
calls, support "go to definition," report wrong arity, and the
PHP-based ones need it to be reflectable.

The collision concern you're solving with NUL-prefixing isn't worth
that cost. Userland already has thousands of `Str` classes; the clean
fix is to namespace the backing classes (or simply pick non-colliding
names), not to hide them from the entire ecosystem. Solve the naming
problem with naming, not by blinding the tooling.

(Related, and unaddressed in the write-up: what happens with methods
that take arguments, e.g. `$s->indexOf($y)`? Does an arity mismatch
fail at compile time, or at runtime with `ArgumentCountError` like a
normal method call?)

Finally, the broader point. Even if both of the above were fully
resolved, I'd still be against this. PHP has an established way of
doing these operations and scalar methods don't remove that.
`trim($s)`, `mb_trim($s)`, and `$s->trim()` would all coexist, with
the method form available only sometimes. To me that's too disruptive
to the norm for what it buys: it doesn't replace anything, it doesn't
compose cleanly, and it introduces a method-call syntax on values that
carry no object identity. I don't think the language is better for it.

Cheers,
Seifeddine.

Reply via email to