Reputation: 233
Here are some types I'm using (simplified for this conversation):
export interface NodeId { readonly _nodeId: string }
export interface CellId { readonly _cellId: string }
export type Call = CodeCall | DefinitionCall
export interface CodeCall {
readonly inputs: Array<{
readonly outside: NodeId,
readonly inside: string,
}>,
}
export interface DefinitionCall {
readonly inputs: Array<{
readonly outside: NodeId,
readonly inside: CellId,
}>,
}
Key here: CodeCall
and DefinitionCall
each contain an array of "inputs", with overlapping but different definitions of what an input is.
Here's a useful function for my application:
export function doesCallUseNode1(call: Call, nodeId: NodeId): boolean {
for (let input of call.inputs) {
if (input.outside === nodeId) {
return true;
}
}
return false;
}
This works! But gosh, it would be nice to use a utility function to do the search. Here's the signature of a utility function I like:
declare function findWith<T, K extends keyof T>(arr: T[], key: K, value: T[K]): boolean;
But if I try to use it like this,
export function doesCallUseNode2(call: Call, nodeId: NodeId): boolean {
return findWith(call.inputs, "outside", nodeId)
}
I get an error! In particular, this error:
Argument of type '{ readonly outside: NodeId; readonly inside: string; }[] | { readonly outside: NodeId; readonly inside: CellId; }[]' is not assignable to parameter of type '{ readonly outside: NodeId; readonly inside: string; }[]'.
My analysis: call.inputs
has type {readonly outside: NodeId; readonly inside: string;}[] | {readonly outside: NodeId; readonly inside: CellId;}[]
. findWith
can be called with either:
{readonly outside: NodeId; readonly inside: string;}
, K = 'outside'
{readonly outside: NodeId; readonly inside: CellId;}
, K = 'outside'
But it can't be called with T = the union of these. I guess this is kinda reasonable – TypeScript has no way of knowing that I'm using arrays in a context in which this should make sense.
I'm stuck figuring out how to type findWith
to make this work. Any ideas? (Thanks in advance for any help!)
Update: Thanks to Matt for his helpful answer, below. Just for future reference: I have ended up implementing this as follows (using lodash)...
export function findWith<T>(arr: Array<T>, key: keyof T, value: T[keyof T]): T | undefined {
return _.find(arr, (o) => _.isEqual(o[key], value))
}
export function hasWith<K extends keyof any, V>(arr: {[key in K]: V}[], key: K, value: V): boolean {
return !!findWith(arr, key, value)
}
I am relieved that hasWith
can be implemented (in the flexible way I want) by calling a stricter findWith
, which holds onto more type information for stricter uses.
Upvotes: 1
Views: 511
Reputation: 30919
Try this:
declare function findWith<K extends keyof any, V>(arr: {[P in K]: V}[], key: K, value: V): boolean;
Then, instead of trying to match T[]
against {readonly outside: NodeId; readonly inside: string;}[] | {readonly outside: NodeId; readonly inside: CellId;}[]
and getting two conflicting inferences for T
, you just require that the array have the key you are looking for, which it does for both cases of the union.
Upvotes: 2