I'm trying to create a type guard for narrowing Partial to non-Partial. For some reason I cannot compile it when it's generic, but it works without the generic type.
Suppose there's following setup:
type Foo = {
a: number
b?: string
c: string
}
let partialFoo: Partial<Foo> = {}
const doSomething = (foo: Foo) => {}
Meanwhile, somewhere else, I want to call function doSomething(..)
with argument partialFoo
while knowing partialFoo
is assignable to Foo
and I want to make compiler believe me. Normally it would complain about Argument of type 'Partial<Foo>' is not assignable to parameter of type 'Foo'
, so I created a type guard in order to narrow the type of variable partialFoo
.
type DefinedProp<T, TProp extends keyof T> = { [P in TProp]-?: T[P] } & Omit<T, TProp>
const hasDefinedProps = <TProp extends keyof Partial<Foo>>(
partial: Partial<Foo>,
propNames: TProp[]
): partial is DefinedProp<Partial<Foo>, TProp> => {
return propNames.every(x => partial[x] !== undefined)
}
if(!hasDefinedProps(partialFoo,['a','c']))
{
throw new Error('props must be defined here')
}
doSomething(partialFoo) // fine, because partialFoo is DefinedProp<Partial<Foo>, 'a' | 'c'>, which is same as type { a: number, b?: string, c: string }
EDIT 1: (Added missing type DefinedProp<..> above)
The problem happens, when I want to get rid of dependency Foo
in hasDefinedProps
by making it more generic:
const hasDefinedProps = <T, TProp extends keyof T>(
partial: T,
propNames: TProp[]
): partial is DefinedProp<T, TProp> => { // Error: A type predicate's type must be assignable to its parameter's type.
return propNames.every(x => partial[x] !== undefined)
}
This fails to compile saying A type predicate's type must be assignable to its parameter's type.
EDIT 2: TypeScript playground
Any helps would be appreciated. Thanks
CodePudding user response:
The definition for DefinedProp
was not in the question, so I used this one:
type DefinedProp<T, Key extends keyof T> = T & Required<Pick<T, Key>>
after which hasDefinedProps
type checks and works as expected:
const test = (foo: Foo) => {
if (hasDefinedProps(foo, [ 'b'])) {
const t = foo.b // string
} else {
const t = foo.b // string | undefined
}
}
So I guess the problem was in the definition of DefinedProp
.