-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bring back return type narrowing + fixes #61359
base: main
Are you sure you want to change the base?
Conversation
This reverts commit c3ae7c4.
@typescript-bot pack this |
@typescript-bot test it |
Hey @gabritto, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
Hey @gabritto, the results of running the DT tests are ready. Everything looks the same! |
@gabritto Here are the results of running the user tests with tsc comparing Everything looks good! |
@gabritto Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
@gabritto Here are the results of running the top 400 repos with tsc comparing Everything looks good! |
@typescript-bot perf test this faster |
@gabritto Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
Note to self:
|
Fixes #33014.
Fixes #33912.
This PR brings back conditional and indexed access return type narrowing from #56941, but with a few updates.
The main motivation behind those updates is described in https://gist.github.com/gabritto/b6ebd5f9fc2bb3cfc305027609e66bca,
but to put it shortly, type narrowing very often cannot distinguish between two non-primitive types,
and since return type narrowing depends on type narrowing to work, it often didn't.
Update 1: non-primitive restriction
To deal with those problematic type narrowing scenarios, the first update is to disallow narrowing of conditional return types that
attempt to distinguish between two non-primitive types.
So this example will not work:
That's because the conditional return type
QuickPickReturn
has one branch with type{ canSelectMultiple: true }
, which is non-primitive,and another branch with type
{ canSelectMultiple: false }
, which is also non-primitive.However, the following will work:
Distinguishing between two primitive types or between a primitive and non-primitive type in the conditional type's branches is allowed.
Update 2: type parameter embedding
We can now detect that a type parameter is a candidate for being used in return type narrowing (i.e. it's a narrowable type parameter) in
cases where the type parameter is indirectly used as a type of a parameter or property.
Before, only this type of usage of
T
in a parameter type annotation would be recognized:Now, the following also work:
Combined with the non-primitive restriction mentioned above, this enables users to place narrowable type parameters inside object types
at the exact property that is used for narrowing, so instead of writing this:
function fun<T extends { prop: true } | { prop: false }>(param: T): ...
,users can write this:
function fun<T extends true | false>(param: { prop: T }): ...
.Note that the analysis done to decide if a type parameter is used the parameter in a way that allows it to be narrowed is a syntactical one.
We want to avoid resolving and inspecting actual types during the analysis, because a lot of types are lazy in some sense, and we don't
want this analysis to cause unintended side effects, e.g. circularity errors.
But this means that any usage of a type parameter that requires semantically resolving types to validate it is not going to work.
For a more complete list of what's currently supported here in terms of usages of type parameters, see test
tests\cases\compiler\dependentReturnType11.ts
.Update 3: relax extends type restriction
This is a small improvement unrelated to the previous updates.
In the original PR, this was disallowed because we required that a conditional return type's extends types be identical to the types
in the type parameter constraint:
Note that in
NormalizedRet
, the first branch's extends type is{}
, notDog
, so this wasn't allowed because those types are not identical.With this PR, we only require that a type in the constraint is assignable to the extends type, i.e. that
Dog
is assignable to{}
,so the code above is now allowed for return type narrowing.