You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Remove type parameter introduced by 'expression dependent' during exposure
Summary:
Typing escape implements exposure - inside a lambda, any type parameter which is introduced should not be able to escape it's scope. Type parameters appearing only co- orcontravariantly can be replaced with their lower / upper bounds and errors are raised for those which appear invariantly. However, the code deliberately avoids dealing with 'bogus' type parameters which are introduced as part of the implementation of type constant access.
This gap has an unfortunate interaction with the more performant version of the common 'fluent builder' pattern:
```
interface IRepBuilderMethods<+TChainableBuilder as IRepBuilder> {
public function setField<TValue>(
HH\EnumClass\Label<RepAccessors, IRepField<
RepFieldSettings with { type TValue super TValue },
>> $label,
TValue $value,
): TChainableBuilder;
}
interface IRepBuilder extends IRepBuilderMethods<this::TChainableBuilder> {
abstract const type TChainableBuilder as
IRepBuilder with { type TChainableBuilder = this::TChainableBuilder };
}
```
Suppose we have a series of lambda each of which take a parameter of type `IRepBuilder` and call `setField`.
On exit from the lambda we still have the 'expression dependent' type parameter (`<expr#N>::...`) and the 'bogus' type parameter (`IRepBuilder::TChainableBuilder`) builder in the global `tpenv`:
```
<expr#1>::TCB upper: {\IRB::TCB}
\IRB::TCB lower: {<expr#1>::TCB}
upper: {IRB with { type TCB = <expr#1>::TCB }
```
In the next lambda, we get a fresh 'expression dependent' type parameter which will also appear in the bounds of the 'bogus' type paramter `IRepBuilder::TChainableBuilder':
```
\IRB::TCB lower: {<expr#1>::TCB, <expr#2>::TCB}
upper: {IRB with { type TCB = <expr#1>::TCB },
IRB with { type TCB = <expr#2>::TCB }}
```
Now, for each method call on an receiver expression with type `<expr#2>::TCB`, or *any* subsequent expression dependent type which shares it's upperbound, we are actually resolving the method on the intersection of the upper bounds so we have linear growth in the number of lambda. This is worsened when we have multiple method calls since each is paying the cost of access a method through an intersection. Altogether we end up with a cubic cost.
This didn't happen for the non-recursive case since each call to `setField` gives us back a fresh type (the ever expanding access path).
This diff addresses the issue by removing type parameters introduced for expression dependent types inside lambdas during the exposure step and also removing them from the bounds of the 'bogus' type parameters.
Reviewed By: andrewjkennedy
Differential Revision: D95847716
fbshipit-source-id: 2974c2ccb795e6af162a8df0c04a3456da6440a7
0 commit comments