Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.9k views
in Technique[技术] by (71.8m points)

typescript - Type of properties in a generic function

I'm trying to transform some fields of my object by providing field names, currently I wrote something like follows:

interface Foo {
    a: number[],
    b: string[],
}

type Bar = { [T in keyof Foo] : (arg : Foo[T]) => Foo[T] }

function test<T extends keyof Foo>(field: T) {
    const foo : Foo = {
        a: [],
        b: [],
    };

    const bar: Bar = {
        a: arg => /* some code */ [],
        b: arg => /* some code */ [],
    };

    foo[field] = bar[field](foo[field]);
}

But I end up with the following error message on bar[field](foo[field]):

Argument of type 'Foo[T]' is not assignable to parameter of type 'number[] & string[]'.
  Type 'number[] | string[]' is not assignable to type 'number[] & string[]'.
    Type 'number[]' is not assignable to type 'number[] & string[]'.
      Type 'number[]' is not assignable to type 'string[]'.
        Type 'number' is not assignable to type 'string'.
          Type 'Foo[T]' is not assignable to type 'number[]'.
            Type 'number[] | string[]' is not assignable to type 'number[]'.
              Type 'string[]' is not assignable to type 'number[]'.
                Type 'string' is not assignable to type 'number'

But shouldn't typescript "know" that with the same T, Foo[T] and Parameters<Bar[T]> should be the same?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Perhaps the compiler should know that, but it doesn't. I tend to call this issue "correlated types" or "correlated records". The compiler sees foo[field] and bar[field] as union-typed things, which is true enough. But it treats their types as independent, meaning that as far as it knows, foo[field] might be number[] while bar[field] might be a function that takes string[]. It doesn't see that the type of foo[field] is correlated to the type of bar[field] in such a way that knowing one fixes the other. There's an open issue, microsoft/TypeScript#30581 (which I filed, fwiw) suggesting that there be some support for correlated types, but it's not clear if that's ever going to happen or how.

All we have right now are workarounds. The two workarounds mentioned in that issue: either use redundant code to force the compiler to walk through the different possibilities and guarantee type safety, or use type assertions to give up on some type safety but maintain brevity. For your code it would look like this:

// redundant code
const f: keyof Foo = field;
switch (f) {
   case "a":
      foo[f] = bar[f](foo[f]);
      break;
   case "b":
      foo[f] = bar[f](foo[f]);
      break;
}

// type assertion
foo[field] = (bar[field] as <T>(arg: T) => T)(foo[field]);

I usually opt for the type assertion. Okay, hope that helps; good luck!

Link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

56.5k users

...