interface Test {
a: string;
b: number;
c: boolean;
}
let arr: string[] = []
function test<S extends Pick<Test, 'a' | 'b'>, T extends keyof S>(val: T[]) {
arr = val // it's not ok
}
But, if I don't use function, it's ok:
type S = Pick<Test, 'a' | 'b'>;
type T = keyof S
const val: T[] = []
arr = val // why it is ok?
And, When I declare the generic S separately, there is also no error:
type S = Pick<Test, 'a' | 'b'>;
function test<T extends keyof S>(val: T[]) {
arr = val // it is ok
}
This is because the generic type S
can have more keys than just "a"
and "b"
— that's what the extends
keyword means in that position: that the generic type S
must be constrained by Pick<Test, "a" | "b">
(not that it only has keys "a"
and "b"
).
TypeScript is structurally-typed (more here and here), so supplying a generic type that satisfies Pick<Test, "a" | "b">
that also has an index signature of symbol
keys with unknown
values will be accepted by the compiler…
type Foo = {
a: string;
b: number;
[key: symbol]: unknown;
};
const input: (keyof Foo)[] = [Symbol("foo"), "a", "b"];
test<Foo, keyof Foo>(input); // Ok, but symbol is not assignable to string
…but symbol
is not assignable to string
, so it would be a type error to accept an array of such values (or any other non-string value).
In the second code block example of your question you provided this code and question:
type S = Pick<Test, "a" | "b">;
type T = keyof S;
const val: T[] = [];
arr = val; // why it is ok?
There, S
is a type that looks like { a: string; b: number }
and T is the union of its keys, which is "a" | "b"
. An array of "a"
and "b"
values are assignable to string
, so there's no problem there.
A modified version of your test
function that uses these types would look like this…
function test(val: (keyof Pick<Test, "a" | "b">)[]) {
arr = val;
}
Note that
keyof Pick<Test, "a" | "b">
is just"a" | "b"
.
…and attempting to provide an array of values that includes a non-string will produce a compiler diagnostic error:
type Foo = {
a: string;
b: number;
[key: symbol]: unknown;
};
const input: (keyof Foo)[] = [Symbol("foo"), "a", "b"];
test(input); /* Error
~~~~~
Argument of type '(keyof Foo)[]' is not assignable to parameter of type '("a" | "b")[]'.
Type 'keyof Foo' is not assignable to type '"a" | "b"'.
Type 'symbol' is not assignable to type '"a" | "b"'.(2345) */