-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Type 'string' cannot be used to index type 'T'
when indexing a generic function parameter
#47357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The error is correct. You're not extending objects only having |
The error can't be correct, since it can be used to index type T. It's at least misleading. |
We can argue that the error message is misleading and bad. But that an error happens is definitely correct. As my example demonstrates, assigning an empty string would be invalid. Getting is not an issue here, because you're not using the value for anything. But in your setter you try to assign a potentially incompatible type. |
I'm not sure why |
The type of the property type Test = { val: "a" | "b" }
const t: Test = { val: "a" }
t.val = "b" // is okay
t.val = "" // is not okay |
Oh thanks, now I see. The value type can be anything. (Modified OP as such) |
But: function foo<T extends Record<string, any>>(target: T, p: string) {
target[p];
target[p] = "hello"; // error
}
function foo2(target: Record<string, any>, p: string) {
target[p];
target[p] = "hello"; // no error
}
type Test = { val: "a" | "b" }
const t: Test = { val: "a" }
// no error at all
foo(t, "foo");
foo2(t, "foo"); Maybe this is just what we should expect by using |
A useful way to think about generics is that you're writing a function that works for all possible types that Technically the call of |
If you don't want your random object types to be assignable to function foo(target: Record<string, unknown>, p: string) {
target[p];
target[p] = "hello"; // no error
}
interface Test { val: "a" | "b" }
const t: Test = { val: "a" }
// type error, `Test` has no index signature
foo(t, "foo"); Note that using |
@fatcerberus Even though I went through the examples and discussion above, I'm not sure I understand exactly what is the root cause of this error. Check the following example: interface Foo {
[key: string]: number;
}
function test<T extends Foo>() {
const a = {} as T;
a['test'] = 1234; // <------------ error I get is: "test can't be used to index type T"
} but why can't |
@Tinadim interface Foo {
[key: string]: number;
}
function test<T extends Foo>(a: T) {
a['test'] = 1234; // error
}
const x = { foo: 1, bar: 2 };
test(x); // no error, valid call
|
got it, thanks @fatcerberus! |
So, what's the good practice to index the generic type T inside the function? |
@Terence625 Depends on what you want to do, but generally the answer is: Don't. Also, questions like this are better suited for platforms like StackOverflow. |
So , it need a graceful way to fix it . |
Just a thought for anyone who finds themselves here. I ended up using a one liner taking into account what @MartinJohns said about "extending".
be aware target is entirely replaced with a new object. |
what i find confusing is that i'm using a basic type like maybe, what i would like to have is to be able to say something like |
I keep hitting this error over and over... Can I just say that if the workaround is to use My current workaround is to say screw you to the compiler and |
@fatcerberus my question is really simple... How can I say that |
|
What human intuitively expects when writing |
It would be nice if an
But that code still errors with I don't want to add a |
You can use function foo(target: Record<"only this", any>, p: string) {
// We don't know that `p` is an acceptable key -- namely, `"only this"`.
target[p] = "";
// You also can't read with `p`, for the same reason.
target[p]; // You can
} If you want to treat the argument as any record with string keys, you don't need a generic at all. You can just use that type: function foo(target: Record<string, any>, p: string) {
// Now `p` is just fine as a key.
target[p] = "";
target[p];
} Now, there are two things I still don't understand, and which look like bugs to me on the face of them, but I haven't thought about these long: function foo1<T extends Record<string, "value in the record">>(target: T, p: string) {
// `v` is typed as `"value in the record"`, even though `p` may not be a valid key for a particular `T`.
const v = target[p];
}
function foo2<T extends Record<string, string>>(target: T, p: keyof T) {
// This assignment isn't allowed, even though `p` is typed as a valid key for any given `T`.
target[p] = "";
} |
Here goes a sort of personal summary of the situation, based on my recent investigation of this issue. Now, why is the error happening? function foo<T extends Record<string, string>>(target: T, p: string) {
target[p]; // This is okay because we're reading
target[p] = ""; // This errors because we're writing to `target[p]`
delete target[p]; // Confusingly, this doesn't error
} If we think about it, writing Ok, so we have a justification for not using the index signature of the constraint // Indirect assignment to index signature
function foo3<T extends Record<string, { a: string }>>(target: T, p: string) {
target[p] = { a: "hi" }; // Error
target[p].a = "hi"; // Same as above, but no error!
}
// Direct assignment with regular property declaration
function foo4<T extends { a: string }>(target: T) {
target.a = "wow";
}
foo4<{ a: "not wow" }>({ a: "not wow" }); // Bad, will write "wow" to `a` when we expect "not wow". So, from In conclusion, the rules are not very consistent, and I don't have a precise explanation of why it works the way it does, but it probably has to do with how convenient it is to allow the potentially unsafe pattern vs disallowing patterns that are likely to cause bugs. |
@gabritto I mean, I would like to argue that |
Yes, the index signature rule is consistent with our definition of read, but that's a limited definition in the first place. I was trying to make a point that the two statements accomplish a very similar thing, and yet we disallow one and not the other. In the end, even with the index signature rule in place, you might still mutate something whose type was dictated by the index signature. Maybe that's better expressed as "the rule is incomplete". And yes, there are other rules at play as well that cause this to be permitted. |
@gabritto Similarly, this hole exists: const a: Record<string, "hello"> = {};
const b: Record<string, string> = a;
b.uhOh = "goodbye"
a.uhOh // == "goodbye"
// ^? "hello" |
If I correctly understand, I can say the following, (Please anyone can correct me if there's something wrong): Regarding to the above example, what purpose we have here to add types to the
Let's start from [level 3] - because here's the point. TS has no idea about the literal/union type of So for a reason we should do one of the following:
I don't see any reason here for a generic interface Test { val: "a" | "b", bar: 'foo' | 'baz' }
const t: Test = { val: "a", bar: 'baz' }
// level 3
function foo<K extends keyof Test, V extends Test[K]>(target: Record<K, V>, p: K, value: V) {
target[p]; // okay
target[p] = value // okay
return target
}
console.log(foo(t, "val", 'a')) // p constrained to "val"|"bar", and target[p] constrained to the provided p.
// level 2
function _foo<K extends string,V extends string>(target: Record<K,V>, p: K) {
target[p]; // okay
target[p] = "" as V; // okay - assertion as V, regardless of type Test { val: "a" | "b", bar: 'foo' | 'baz' }
return target
}
console.log(_foo<keyof Test, Test[keyof Test]>(t, 'val')) // constrained p "val"|"bar"
// level 1
function __foo(target: {[k: string]: any}, _p: string) {
const p = _p as keyof typeof target // cast - assertion p is a key in target
target[p]; // ok
target[p] = "abc"; // ok
return target
}
console.log(__foo(t, 'def')) // non-constrained p And that's exactly what I'm (as an old school) waiting from TS, while I'm learning :) |
Bug Report
π Search Terms
Type 'string' cannot be used to index type 'T'.
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
π Expected behavior
Either:
The text was updated successfully, but these errors were encountered: