custom type guard, ReturnType, Parameters
Add a type combinator to extract the guarded type of custom type guard function and/or refine the type of a custom type guard to something stronger than (...) => boolean.
I don't think it's currently doable using either conditional type expressions or ambient combinators like ReturnType or Parameters.
Avoid carrying around the type parameter of a generic type.
My specific use case is when a complex generic type is encapsulated in a simpler generic type like the following:
type Message<
RolesT extends Role,
ProtocolIdentifierT extends ProtocolIdentifier,
SourceSelectorT extends ProcessSelector<RolesT>,
DestinationSelectorT extends ProcessSelector<RolesT>,
DirectionT extends Direction,
KindT extends Kind,
PayloadT extends Payload
> = {
uuid: Uuid;
protocol: ProtocolIdentifierT;
source: MatchingProcess<RolesT, SourceSelectorT>;
destination: MatchingProcess<RolesT, DestinationSelectorT>;
direction: DirectionT;
kind: KindT;
payload: PayloadT;
};
type Protocol<
RolesT extends Role,
ProtocolIdentifierT extends ProtocolIdentifier,
MessageT extends Message<RolesT, ProtocolIdentifierT, any, any, any, any, any>
> = {
roles: RolesT[];
identifier: ProtocolIdentifier;
isMessage: (m: AnyMessage) => m is MessageT;
};
Then if I want to define a Server class, right now I need to carry round a complex Message type already embedded in the simpler Protocol type:
class Server<
MessageT extends AnyMessage, // need to carry around T
ProtocolT extends Protocol< // and perform complex type combination
(
| keyof MessageT["source"]["roles"] // need to deep extract types just to correctly reconstruct ProtocolT
| keyof MessageT["destination"]["roles"]) &
Role,
MessageT["protocol"],
MessageT
>
> {
protocol: ProtocolT;
constructor(protocol: ProtocolT) {
this.protocol = protocol;
}
listen = (
listener: (message: MessageT) => Promise<MessageT>,
):=> {
...
};
}
With a combinator like GuardedType, this could be written as:
type MessageOf<ProtocolT extends Protocol<any, any, any>> = GuardedType<ProtocolT["isMessage"]>;
class Server<ProtocolT extends Protocol<any, any, any>> {
...
listen = (listen: (message: MessageOf<ProtocolT>) => Promise<MessageOf<ProtocolT>>) => { ... }
}
In summary, if this combinator were named GuardedType, then this would be used like is:
const isFoo = (x : any): x is Foo => { ... }
type ExtractedFoo = GuardedType<typeof isFoo>;
My suggestion meets these guidelines:
Does this work?
interface Foo {
name: string;
}
declare const foo: Foo;
function isFoo(x: any): x is Foo {
return x === foo;
}
type GuardedType<T> = T extends (x: any) => x is (infer T) ? T : never;
type ExtractedFoo = GuardedType<typeof isFoo>;
This works, thanks!
Maybe adding this to the list of predefined combinators, or improve documentation on infer could be useful though?
Most helpful comment
Does this work?