I'm trying to write a vscode extension in typescript, I have a ChildProcess and I want to terminate/kill it.
export declare function terminate(process: ChildProcess & {
pid: number;
}, cwd?: string): boolean;
...
let vlsProcess: ChildProcess
and then I tried to call
terminate(vlsProcess);
but I have this error:
Argument of type 'ChildProcess' is not assignable to parameter of type 'ChildProcess & { pid: number; }'. Type 'ChildProcess' is not assignable to type '{ pid: number; }'. Property 'pid' is optional in type 'ChildProcess' but required in type '{ pid: number; }'
the function terminate is expecting an "intersection type" of
ChildProcess & {
pid: number;
}
but I currently only have a ChildProcess, how can I convert a ChildProcess into ChildProcess & { pid: number;}?
I checked the ChildProcess, it has
readonly pid?: number | undefined;
so to me, it seems to be able to convert to a ChildProcess & { pid: number;} but the typescript compiler says:
Property 'pid' is optional in type 'ChildProcess' but required in type '{ pid: number; }'
How can I do this convertion?
The way for
terminate(vlsProcess)to compile without error is to convince the compiler thatvlsProcessis of typeChildProcess & { pid: number; }, meaning it has to be aChildProcesswhere thepidproperty is known to exist and be of typenumber. ButvlsProcessis declared aChildProcesswhosepidproperty is optional. So you need to do something with thatpidproperty.One approach would be to write a check that
typeof vlsProcess.pid === "number"before you callterminate(vlsProcess), in the hopes that such a check would narrow the apparent type ofvlsProcess. Unfortunately that doesn't work:This is essentially a missing feature of TypeScript, as described in microsoft/TypeScript#42384 While the check
typeof vlsProcess.pid === "number"can narrow the apparent type ofvlsProcess.pidfromnumber | undefinedtonumber, it has no effect on the apparent type ofvlsProcessitself. In general it would be too expensive to have a check of some subproperty likea.b.c.d.e.fhave effects on all the parent objects, since the compiler would need to spend time synthesizing all the relevant types, most of which would be completely useless for most calls.Until and unless something better happens there, we can luckily emulate this sort of narrowing by implementing a custom type guard function. Like this:
If
hasDefinedProp(obj, k)returnstrue, thenobjwill be narrowed from its original type to a subtype which is known to have a defined property at thekkey. This is written as an intersection ofTand{ [P in K]: {} | null }, the latter being equivalent toRecord<K, {} | null>using theRecordutility type. The type{ [P in K]: {} | null }is known to have a property of type{} | nullat every key of typeK, and{} | nullessentially allows every value except forundefined. Intersecting a type with{} | nullwill serve to eliminateundefinedfrom its domain, as introduced in TypeScript 4.8.Note that the implementation uses the type assertion
obj as anyto allow us to index intoobj[key]without complaint.Okay, now let's try it:
That works. TypeScript sees the narrowed type
ChildProcess & { pid: {} | null }to be assignable toChildProcess & { pid: number }, because thepidproperty of the former is(number | undefined) & ({} | null)which isnumber.And if, for some reason,
hasDefinedProp()returnsfalse, then you don't want to callterminate(vlsProcess)becausevlsProcesshas nopid. What you should do in such a situation depends on your use case. In the above it just skips theterminate()call, but you might want to throw an exception or something.Playground link to code