Home > OS >  "A type predicate's type must be assignable to its parameter's type" only when u
"A type predicate's type must be assignable to its parameter's type" only when u

Time:04-05

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.

TypeScript playground

  • Related