Repository navigation
Unions and intersections of type predicates produce wrong type #17757
Description
Activity
aluanhaddad commented
on Aug 12, 2017 ContributorMore actionsIt appears to just grab the first type predicate and ignore the rest.
Here is a self-contained repro (2.5.0-dev.20170808)
type IsStringOrNumber = ((x: any) => x is number) | ((x: any) => x is string); declare const x: any; if ((((x: any) => true) as IsStringOrNumber)(x)) { x.toFixed(); }
Intersecting type predicates seems to have the same result, only the first signature is considered.
type IsStringAndNumber = ((x: any) => x is number) & ((x: any) => x is string);
Reacted by Sam and Stanislav Panferov- changed the title
[-]Unions of type guard types produce wrong type[/-][+]Unions of type predicates types produce wrong type[/+]on Aug 12, 2017 type predicate
I knew using "type guards" like that wasn't quite right. 😂
Reacted by Aluan HaddadReacted by Aluan HaddadReacted by Aluan Haddad- changed the title
[-]Unions of type predicates types produce wrong type[/-][+]Unions and intersections of type predicates types produce wrong type[/+]on Aug 12, 2017 - changed the title
[-]Unions and intersections of type predicates types produce wrong type[/-][+]Unions and intersections of type predicates produce wrong type[/+]on Aug 12, 2017 aluanhaddad commented
on Aug 13, 2017 ContributorMore actionsBeing able to compose these functions would be highly useful.
Imagine a rather heterogeneous array elements of elements in a scenario such as
import moment from 'moment'; interface Partial { name?: string; id?: number; dob?: moment.Moment } declare const partials: Partial[]; type HasName = (x: Partial) => x is {name: string}; type HasDob = (x: Partial) => x is {dob: moment.Moment}; type HasNameAndDob = HasName & HasDob; const hasNameAndDob: HasNameAndDob = ({name, dob}) => name && dob; const withNamesAndDobs = mayHaveProps.filter(hasNameAndDob);
There is some boilerplate in the example, but throw in a
composehelper and it would make for some very nice patterns.Reacted by SamI was thinking of this more as a bug report than a feature request, but if we're doing demonstrations of value, this was my scenario.
The typescript compiler has lots of
Nodetypes differentiated by akindenum property, but no discriminated union type exists to allow nicely switching on kinds. The compiler itself does lots of ugly and dangerous asserting to get around this. There are, however,ts.isWhileStatement(node: Node)style functions for almost all nodes.I was trying to put together something like this:
function isAnyOf<T extends ts.Node>(node: ts.Node, ...preds: Array<(n: ts.Node) => n is T>): node is T { return preds.some(p => p(node)); } if (isAnyOf(node, ts.isWhileStatement, ts.isIfStatement)) { // node should have type ts.WhileStatement | ts.IfStatement // actually just has type ts.WhileStatement }
But with the current behaviour/bug,
Tjust takes the type of whatever the first predicate is and completely ignores the others.RyanCavanaugh commented
on Aug 16, 2017 MemberMore actionsProbably an easy fix if someone wants to try
charlespierce commented
on Oct 6, 2017 ContributorMore actionsRyan Cavanaugh (@RyanCavanaugh) I'm interested in tackling this issue, can you point me towards what changes will be needed? I was able to find (and fix) an issue where Type Predicates weren't being correctly handled by
getContextualSignatureinchecker.ts, however that didn't seem to change the behavior at all. It seems thatgetContextualSignatureisn't actually used inside ofresolveCallso the Type Predicate still isn't being correctly determined.charlespierce commented
on Oct 6, 2017 ContributorMore actionsAndy (Andrewkraft) (@Andy-MS) Thanks, I'll take a look at the intersection part. I actually had just figured out what I was missing, but I'm glad to see I came up with essentially the same solution as you for the union signatures.
jack-williams commented
on Feb 10, 2019 CollaboratorMore actionsI think this issue can be closed. The union case correctly works by selecting both predicate types:
type IsStringOrNumber = ((x: any) => x is number) | ((x: any) => x is string); declare const x: any; if ((((x: any) => true) as IsStringOrNumber)(x)) { x.toFixed(); // x has type number | string }
The intersection case still selects the first overload, but this is a general problem with intersections of signatures---it is not a particular issue with type predicates.
Reacted by Ryan Cavanaugh- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript Version: 2.5.0-dev.20170803
Code
Expected behavior:
Resulting type should be
(node: ts.Node) => node is (ts.DoStatement | ts.WhileStatement)Actual behavior:
Resulting type is
(node: ts.Node) => node is ts.DoStatementIt seems to just take the first one.