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
{{ message }}
Repository navigation
Type assertions & type predicates can have incorrect narrowing logic #57436
This is the behavior in every version I tried, and I reviewed the FAQ for entries about assertion functions, type predicates, and narrowing. I failed to find anything covering type predicates and assertion functions.
typeAnimal={legs: number};typeCat=Animal&{legs: 4,meow: ()=>void};typeBird=Animal&{legs: 2,chirp: ()=>void};// This function doesn't have sufficient logic to narrow from `Bird | Cat` to `Bird`functionisBird(pet: Bird|Cat): pet is Bird{returntrue;}// This function doesn't have sufficient logic to narrow from `Bird | Cat` to `Bird`functionisBirdAssertion(pet: Bird|Cat): asserts pet is Bird{}functioninsufficientAssertions(pet: Bird|Cat){if(isBird(pet)){pet.chirp();}isBirdAssertion(pet);pet.chirp()}// The typescript compiler clearly has the means to handle narrowing between these types:functionvalidNarrowing(pet: Bird|Cat){// the same logic as `isBird` is known to be insufficient to narrow the type of `pet`if(true){// @ts-expect-error: 2339pet.chirp();}if(pet.legs===2){// With proper narrowing logic, the compiler knows the type can be narrowed to `Bird`pet.chirp();}}
🙁 Actual behavior
An assertion function or type predicate which contains insufficient logic to narrow the input type generates no errors and can be used to narrow a type in the calling code, despite not having certainty the type is narrower.
🙂 Expected behavior
an assertion function or type predicate which doesn't sufficiently narrow the input type before the return or end of function should result in an error.
The primary reason to write a UDTG is to do type checks that the compiler isn't capable of doing itself, so it's very much intentional that the implementation isn't checked. Otherwise you'd get spurious errors in cases where the logic is sufficient for narrowing but the compiler isn't able to prove it.
The primary reason to write a UDTG is to do type checks that the compiler isn't capable of doing itself, so it's very much intentional that the implementation isn't checked. Otherwise you'd get spurious errors in cases where the logic is sufficient for narrowing but the compiler isn't able to prove it.
Could you share an example? The TS handbook doesn't seem to imply that, and in fact gives an example similar to mine that the compiler definitely was capable of doing itself. See "using type predicates" for example.
I have yet to encounter a scenario where it is more useful for type predicates to not also have the compiler perform the checks it can. I've also seen many cases of people having the same mistaken understanding I do, so perhaps it would be beneficial to update the handbook to use such an example. I'd be happy to make that PR if I had such an example to propose.
I have yet to encounter a scenario where it is more useful for type predicates to not also have the compiler perform the checks it can.
You’re basically asking TS to spit out an error whenever it thinks that you “haven’t done enough” to narrow the value according to the function signature, via the checks it’s able to do itself. Lots of non-trivial type guards would be broken by such an error; see #29980 (comment).
tl;dr: The proposed behavior would error on any type predicate where the same check wouldn’t already work to narrow the value inline. In other words, almost all of them.
Reacted by Andrii Dieiev, Martin Johns, snarbies and Ryan Cavanaugh
interfaceSuccessStatus{code: 0;message: string;}interfaceFailureStatus{/* in failure case code will be anything other than 0 */code: number;reason: string;}typeProcessStatus=SuccessStatus|FailureStatus;functionisSuccess(response: ProcessStatus): response is SuccessStatus{returnresponse.code===0;}declareconsta: ProcessStatus;if(a.code===0){// this doesn't work since typescript can't determine that failure case can't be 0console.log(a.message);}if(isSuccess(a)){console.log(a.message);// works as intended.}
Reacted by Ryan Cavanaugh, snarbies and Bruce Pascoe
interfaceSuccessStatus{code: 0;message: string;}interfaceFailureStatus{/* in failure case code will be anything other than 0 */code: number;reason: string;}typeProcessStatus=SuccessStatus|FailureStatus;functionisSuccess(response: ProcessStatus): response is SuccessStatus{returnresponse.code===0;}declareconsta: ProcessStatus;if(a.code===0){// this doesn't work since typescript can't determine that failure case can't be 0console.log(a.message);}if(isSuccess(a)){console.log(a.message);// works as intended.}
interfaceSuccessStatus{code: 0;message: string;}interfaceFailureStatus{/* in failure case code will be anything other than 0 */code: number;reason: string;}typeProcessStatus=SuccessStatus|FailureStatus;functionisSuccess(response: ProcessStatus): response is SuccessStatus{return'message'inresponse;}declareconsta: ProcessStatus;if(a.code===0){// this doesn't work since typescript can't determine that failure case can't be 0console.log(a.message);}if('message'ina){// this works and is verifiable by the compilerconsole.log(a.message);}if(isSuccess(a)){console.log(a.message);// works as intended.}
It seems like it would be preferable to have the narrowing occur safely, and that seems to be the standard expectation of TS developers who are unfamiliar with the current behavior.
Allan Deutsch (@Masstronaut) then consider a case where message and reason are both optional. (Playground Link) I do think my original example is sufficient to get the point across but I respect your need for an example that has no alternative.
Activity
fatcerberus commentedon Feb 19, 2024
The primary reason to write a UDTG is to do type checks that the compiler isn't capable of doing itself, so it's very much intentional that the implementation isn't checked. Otherwise you'd get spurious errors in cases where the logic is sufficient for narrowing but the compiler isn't able to prove it.
Masstronaut commentedon Feb 19, 2024
Could you share an example? The TS handbook doesn't seem to imply that, and in fact gives an example similar to mine that the compiler definitely was capable of doing itself. See "using type predicates" for example.
I have yet to encounter a scenario where it is more useful for type predicates to not also have the compiler perform the checks it can. I've also seen many cases of people having the same mistaken understanding I do, so perhaps it would be beneficial to update the handbook to use such an example. I'd be happy to make that PR if I had such an example to propose.
fatcerberus commentedon Feb 19, 2024
You’re basically asking TS to spit out an error whenever it thinks that you “haven’t done enough” to narrow the value according to the function signature, via the checks it’s able to do itself. Lots of non-trivial type guards would be broken by such an error; see #29980 (comment).
tl;dr: The proposed behavior would error on any type predicate where the same check wouldn’t already work to narrow the value inline. In other words, almost all of them.
RyanCavanaugh commentedon Feb 20, 2024
The docs should probably be clearer about this
tadhgmister commentedon Feb 20, 2024
tadhgmister commentedon Feb 20, 2024
example that may be useful for docs:
typescript-bot commentedon Feb 23, 2024
This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Masstronaut commentedon May 12, 2024
Tadhg McDonald-Jensen (@tadhgmister) In the example you shared the type could be narrowed safely:
It seems like it would be preferable to have the narrowing occur safely, and that seems to be the standard expectation of TS developers who are unfamiliar with the current behavior.
tadhgmister commentedon May 12, 2024
Allan Deutsch (@Masstronaut) then consider a case where message and reason are both optional. (Playground Link) I do think my original example is sufficient to get the point across but I respect your need for an example that has no alternative.