Hi Ilija,
> From my testing on your branch, the type inference is very limited.
> [...]
> PHP static analyzers are necessarily very capable at static type
> inference, so this is at least a significant discrepancy between
> your implementation and a type erasure approach.
A reified implementation should require turbofish at every call site
rather than try to do engine-level inference. The two paths land in
different places here.
For an erased model (this RFC), inference belongs in the
static-analysis layer. The runtime doesn't need to know what T is.
It's been erased to its bound by the time the call happens, so `new
Box(42)` produces a Box object with value typed as `mixed` at runtime.
PHPStan, Psalm, Phan, or Mago can independently decide whether to
infer `Box<int>`, `Box<int|string>`, Box<scalar>, or leave T open at
the static layer, and their disagreement doesn't affect runtime
semantics. That's why this RFC leaves turbofish optional. Forcing it
would be work the engine has no use for. The only callers that need
turbofish under erasure are SA tools when they can't figure out user
intent, same as TypeScript saying "needs explicit type argument" when
nothing in the call site constrains T.
For a reified model, inference becomes an engine problem because the
runtime actually carries the type argument forward. Now `new Box(42)`
has to decide on a concrete T at construction. The clean fix is to
require turbofish at every call site that takes a generic-parametric
type, and reject the call as a compile or runtime error otherwise. No
engine-side inference, ever. The user states intent explicitly. The
engine doesn't guess.
That sidesteps the question of what `new Box(42)` means: under
reification it becomes invalid PHP, and the user writes `new
Box::<int>(42)`.
> In other words, whether a(42) is safe to call for signature
> `function a<T>(T $value)` depends on its implementation, i.e.
> whether it makes use of T. This is introduces a variation of the
> function coloring problem, where now all callers must specify
> generic types recursively.
Function coloring is a different problem. Coloring (in the async/await
sense) means a property of one function forces the same property on
every function that calls it. If you call an async function, you have
to be async yourself, and so on transitively up the call graph.
Mandatory turbofish doesn't propagate that way. Consider:
```php
function id<T>(T $v): T { return $v; }
function str_id(string $v): string {
return id::<string>($v);
}
```
`str_id` is not generic. It supplies the type argument at the call
site and that's the end of it. The "color" doesn't travel upward.
`str_id`'s own signature stays exactly as the author wrote it, with no
generic parameters of its own required. Turbofish is a property of the
call site. The calling function's own signature is unaffected.
The case where you'd add a generic parameter to `str_id` is when
`str_id` legitimately needs to be parametric, forwarding `T` through.
That's a design choice the author makes. The language doesn't impose
it.
The issue you found in Rob's branch is real: half-done inference makes
call-site safety depend on the callee's implementation. That's a
problem with partial inference, not with the call-site syntax. The fix
is the same: require turbofish at every call site, no engine-side
inference. Once the engine stops guessing, call-site safety stops
depending on what the body of the callee does.
TypeScript-style inference for T belongs in the SA layer, where it can
be tool-specific and revisable. The engine should not be in that
business.
Cheers,
Seifeddine