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.
