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 thatvlsProcess
is of typeChildProcess & { pid: number; }
, meaning it has to be aChildProcess
where thepid
property is known to exist and be of typenumber
. ButvlsProcess
is declared aChildProcess
whosepid
property is optional. So you need to do something with thatpid
property.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.pid
fromnumber | undefined
tonumber
, it has no effect on the apparent type ofvlsProcess
itself. In general it would be too expensive to have a check of some subproperty likea.b.c.d.e.f
have 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
, thenobj
will be narrowed from its original type to a subtype which is known to have a defined property at thek
key. This is written as an intersection ofT
and{ [P in K]: {} | null }
, the latter being equivalent toRecord<K, {} | null>
using theRecord
utility type. The type{ [P in K]: {} | null }
is known to have a property of type{} | null
at every key of typeK
, and{} | null
essentially allows every value except forundefined
. Intersecting a type with{} | null
will serve to eliminateundefined
from its domain, as introduced in TypeScript 4.8.Note that the implementation uses the type assertion
obj as any
to 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 thepid
property of the former is(number | undefined) & ({} | null)
which isnumber
.And if, for some reason,
hasDefinedProp()
returnsfalse
, then you don't want to callterminate(vlsProcess)
becausevlsProcess
has 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