#Type is assignable to the constraint of type but could be instantiated with a different subtype

14 messages · Page 1 of 1 (latest)

acoustic thorn
#

Having trouble understanding this error in my specific case despite reading a lot about this problem.

thorny hamletBOT
#
subtrohs#0708

Preview:```ts
type FooArgs<
A,
B extends {[k: string]: unknown}

= B & {
a: A
fn: (a: A, b: B) => void
}

function foo<A, B extends {[k: string]: unknown}>({
a,
fn,
...b
}: FooArgs<A, B>) {
fn(a, b)
// Is there a way to do this without casting?
// fn(a, b as B);
...```

ebon cloud
#

Take this example:

function test(a: string, b: { a: string }) {}

foo({ a: 'test', fn: test })
#

test would be called with ('test', {}), and that would be a type error.

#

On a side note, your generic constraints on B aren't necessary.

acoustic thorn
#

Is there a way to infer B from fn's args or vis versa?

ebon cloud
#

The problem isn't your type, it's inferred correctly.

#

It's your implementation, when you destructure FooArgs<A, B> into { a, fn, ...b }, b doesn't have a or fn anymore.

#

If B doesn't have a or fn, that's fine; but if it does like the example I gave, then it's a type error.

#

No, I want test to accept b: { a: string }

acoustic thorn
#

oh i see

#

so

function foo<A, B>(args: FooArgs<A, B>) {
  const { fn, a } = args;
  fn(a, args);
}

would be the solution to that issue

ebon cloud
#

Yep, although you have to live with fn's args having excess a and fn properties if it didn't ask for them.

#

Depends on if that's acceptable to you, there are some alternatives if you are willing to change your API:

// change foo to
foo({ fn, a, options })
// change foo to
foo(fn, a, {
    ...options,
})
// change fn to accept { a, ...options }

// change foo to
foo(fn, {
    a,
    ...options,
})