Hi Rob,

The RFC enforces type-system structure at four places: declaration-site
syntax and variance soundness, link-time inheritance arity and bound
conformance, link-time parametric LSP (including the diamond-merge case I
described), and runtime turbofish arity and bounds. The structural
enforcement is substantial: the variance declared at the parameter (`+T`,
`-T`, invariant by default) constrains how the parameter can be used,
parametric LSP substitutes the bindings into method signatures at
inheritance points, and turbofish forces explicit type arguments at call
sites requiring disambiguation.

What's not enforced is parametric flow analysis: tracking how a `T`-typed
value flows through a method's body, narrowing it through control flow, and
inferring its concrete type at a use site. That's the layer where tools
currently diverge most, and you're right that this RFC doesn't directly
resolve those disagreements.

But the RFC does change the *shape* of the inference-disagreement problem
in a way that helps. Today, tools have to guess what `new Collection([1])`
means because there's no syntax to express the user's intent. The guesses
differ. Once turbofish exists, tools can do two things they can't do now:

1. Simplify inference to only handle the unambiguous cases (where the type
is clearly determinable from context and conventions), and emit a warning
or error when inference would otherwise have to guess.
2. Recommend turbofish at sites where the user's intent is ambiguous, so
the user can disambiguate explicitly with `new Collection::<int>([1])`.

That moves the dev-UX problem from "different tools silently produce
different inferred types" to "tools agree that the type is ambiguous,
recommend turbofish, and the user disambiguates." The convergence point
becomes the user's annotation, not the tool's heuristic. As Mago's
maintainer I can tell you we'd lean into this shift hard. Our current
inference heuristics are messy precisely because there's no other option;
the moment turbofish exists, we'd simplify them to "be sure or ask the
user."I'd expect PHPStan and Psalm to land in similar places eventually too.

> Which means Box<A&B> works either way you want it to work. It could mean
A|B or A&B; the engine happily accepts either reading.

Disagree. The reading of `Box<A&B>` is determined by Box's variance
declaration. If `Box<+T>` declares T as covariant, then `Box<A&B>` is a
subtype of `Box<A>` and `Box<B>` (intersection narrows for covariant
positions). If `Box<-T>` declares T as contravariant, the relationship
inverts: `Box<A&B>` is a supertype, and a `Box<A>` can be passed where
`Box<A&B>` is expected. If T is invariant (the default), neither
relationship holds and the engine treats `Box<A&B>` as a distinct type from
`Box<A>` and `Box<B>`.

So the engine has a definite reading; it just depends on the variance
declared at Box. The interpretation isn't ambiguous, it's compositional.
Tools that disagreed today on what `Box<A&B>` means would have to agree
once variance is declared in syntax, because the variance is then a
property of the language, not a tool-specific interpretation.

I'd grant that for the common case of invariant T (which is the default and
what most generic code will use) the type argument is opaque relative to
subtyping, for example, `Box<A&B>` is not a subtype of `Box<A>` or
`Box<B>`. That opacity is the bound-erasure trade. But it's a determinate
opacity, not a "happily accepts either reading" one.

Cheers,
Seifeddine.

Reply via email to