-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
type has no index signature #14951
Comments
Interesting point -- a fresh object literal type certainly could have an index signature derived from its properties. Not sure how common this pattern is in practice, though (do you really want to allocate a new object every time this function gets called?). |
The function is to illustrate my point. I found this pattern more elegant than a switch/case for example. Is it as performant? I don't know but I don't think I care most of the time. const nameMap = {
1: 'one',
2: 'two',
};
function getName(id: number) { // inferred return type should be string
return nameMap[id] || 'something else';
} |
The implicit any there is by design -- we don't allow arbitrary indexing into objects that don't include an index signature in their type. Once an object has been "indirected" we no longer trust that it isn't an alias to some other object that might have non-string properties (in this case). |
Ok, makes sense. Supporting my first example would already be nice for me. |
I'd go so far as to expect the return type from the first example to be const nameMap: { [key: number]: string } = {
1: 'one',
2: 'two'
};
function getName(id: number) { // inferred return type is string
return nameMap[id] || 'something else';
} |
@PyroVortex we have no way to know that the line |
@RyanCavanaugh return {
1: 'one',
2: 'two'
}[id]; for which the object is for all intents and purposes completely immutable by virtue of scoping. With this particular construction I would want the compiler to be as specific as possible in the typing. Otherwise I would fall back to the pattern of specifying a type for the constant. |
👍 for this feature. Lack of the index signature often makes auto-inherited types useless, so I need to copy-paste type inherited by typescript compiler and add index signature to it. The fix is really simple and clear in my opinion - for objects like this: const randomMapping = {
stringProp: "1",
numberProp: 2
}; Instead of the following type: interface RandomMappingWithoutIndexSignature {
stringProp: string;
numberProp: number;
} Generate type with the index signature: interface RandomMappingWithIndexSignature {
stringProp: string;
numberProp: number;
[propName: string]: string | number | undefined;
} Am I missing something? |
We have a use case where this would be nice as well. The simple Knex.js setup code below won't work without declaring the type of
However in the case @vladimir-tikhonov shows, I think that index signature can't be reasonably inferred. Even if it's So it seems to me the design @RyanCavanaugh is referring to is correct and inevitable - there's no way to guarantee you're going to get specific types (i.e. not |
Also allow union types for keys please but I do believe that some sort of inference could be done here |
I'm not sure if this is the same issue, but I'm getting a similar Error:
Here's a simplified example: enum eFoobar {
foo = 'foo',
bar = 'bar',
}
type tFooBar = { // this is `iAbilityModifiers` from the error
[fb in keyof eFoobar]: number;
}; const character = {
// …
foobars: _.reduce(eFoobar,
(acc, key) => _.merge(acc, {[key]: 10}),
{} as tFooBar
),
// …
}, {_.map(someOptions, ({ text: foobar, value: key }) => (
<input value={character.foobars[key]} /> {/* error references this line */}
)} According to the Mapped Types documentation, adding
|
Not sure if this is helpful in terms of adding a use-case, but I encountered what appears to be the same (or related?) issue by using a type annotation that uses an type EnumIndexedType =
{
// Note: entries are optional.
[TKey in SomeList]?: string; // <-- is this not an index signature?
} See the full example at the TypeScript playground here (issue on line 27). If someone can point out a better or otherwise "more correct" way of doing what I'm attempting here that works as TypeScript expects, I'd very much appreciate it! Regardless, the error as it stands is very confusing: "No Index Signature?! Isn't that, like, the only type annotation I wrote?" |
@ericdrobinson, yes, the error message is extremely confusing. However, what it is actually complaining about is not the What you wrote is called a Mapped Type (horrible name for trying to google it). You've got 2 choices: type Hashmap<K, V> = {
[k in K]: V;
} I usually use this with enum eCharacterClass {
mage = 'mage',
rogue = 'rogue',
warrior = 'warrior',
}
class CharacterClass {
health: number = 1;
// …
}
type tCharacterClassSet = HashMap<eCharacterClass, CharacterClass>;
/*
{
[eCharacterClass.mage]: CharacterClass{},
[eCharacterClass.rogue]: CharacterClass{},
[eCharacterClass.warrior]: CharacterClass{},
}
*/ So you could do: type EnumIndexedType = Hashmap<SomeList, string>; Alternatively, you could create a weak relationship: interface WeakHashmap<V> {
[key: string]: V[keyof V];
} type tCharacterClassWeakSet = WeakHashmap<CharacterClass>;
/*
{
mage: CharacterClass{},
rogue: CharacterClass{},
warrior: CharacterClass{},
}
*/ In this second one, the "index signature" will be string, which means there is a weak link between your Mapped Type and your enum: You can bypass the enum by supplying any value that happens to be in it ( |
@jshado1 Your Option 2 makes total sense to me but doesn't provide the same restrictions I'm going for, as you point out. With Option 1, I'm not able to get things to work in my actual environment. First, in order to remove errors, I had to modify the type HashMap<K extends string, V> = {
[k in K]: V;
} Notice the
At the end of the day, the
To be clear, viewing the issue in my previous comment requires enabling the noImplicitAny option in the Options dropdown. Indeed, the code suggested in Option 2 appears to result in the same error when the noImplicitAny option is set. Please see this merged example and enable the noImplicitAny option to see the issue. |
@ericdrobinson are you using an enum with string values? I just tried it without, and I get that error, but when I switch it to the string enum, it works (Playground). You cannot cast an enum (a collection of options) to a string, but you can cast its options to strings if need-be. I don't know the TS syntax for doing that in a Mapped Type (I'd guess either |
@jshado1 I take it you didn't actually look at the Playground example I provided and follow the directions about setting the noImplicitAny option, yes? The enum I used does indeed have I have adjusted the Playground example that you provided to include an actual triggering use of the issue. Instructions:
You should see an error appear no line 27, one built from your code. Line 34, on the other hand, has a workaround. As the comment above the for-in loop suggests, the error is confusing and does not suggest that the workaround is to explicitly cast the type of |
The error message is truly confusing - I just spend good few hours before looking elsewhere before realizing this... (TS 2.8 / 2.9@rc)
The Error here is, that var Did I get it right? - If yes, then this error message should definitively be tweaked, because it's plain wrong, not just confusing... |
If you look at the type of To me, this part feels like a bug. That said, the error you encounter drops away entirely if you add an explicit type declaration to your definition of const key: PaletteColors = 'primarry' Rather, what you get now is a far more helpful error that says:
At the end of the day, more explicit declaration of expected types appears to appease the compiler. However, the compiler's current error is extremely confusing as both you (@vadistic) and myself have encountered. |
Yes, TypeScript is very prone to throwing "has no index signature" whenever the wind blows (it seems to be the TS version of "something went wrong"). @ericdrobinson sorry before, I missed the enable |
Object literals don't have index signatures. They are assignable to types with index signatures if they have compatible properties and are fresh (i.e. provably do not have properties we don't know about) but never have index signatures implicitly or explicitly. Nothing in this example has an index signature. |
@RyanCavanaugh @ericdrobinson Thanks for answering. I thought I knew how they worked, but it looks I was wrong and made two false assumptions:
I'll think over it, thanks! |
@RyanCavanaugh I ran across this when using the JavaScript const palette = {
primary: 'hotpink',
secondary: 'green',
danger: 'red'
}
for (let elt in palette)
{
console.log(palette[elt]); // ... type {...} has no index signature.
} Here is a playground showcasing the issue. Be sure to turn on the This is the standard affair use-case for the var string1 = "";
var object1 = {a: 1, b: 2, c: 3};
for (var property1 in object1) {
string1 = string1 + object1[property1];
}
console.log(string1); Copy the MDN example over into the TypeScript playground and you get the same error (with the The enum case that lead me here is simply a variation on this theme. Given that this is a standard method for iterating over properties of an object in JavaScript (and theoretically TypeScript), what is the TypeScript Way™ of handling the implicit any that appears due to object literals not having a standard index signature?
@RyanCavanaugh If That aside, I see now that what the |
@ericdrobinson Regarding the first example, we have to put aside the fact that we (from inspection) can see the runtime behavior of this code by executing it in our heads. If Of course, we can always add more special cases to the type system to detect the specific case of iterating over a known-bounded object literal, but this just leads to the "Why does this code not behave like this other nearly-identical code?" problem when we fail to properly exhaust all the special cases. The "standard" way would be to write Regarding mapped types, remember that |
@RyanCavanaugh Fair enough! That certainly is a fair point. I considered adding a default Perhaps consider adjusting the error to be more informative? Looking for info on this (while admittedly being under the false assumption that the Mapped Type was a restrictive index signature of sorts) didn't turn up much helpful information. Alternatively (or in addition), could you expand upon index signatures in the documentation to provide an explanation as to why they're not provided by default (especially with object literals)? I'd guess I'm not the only one to be tripped up by the
Mind blown.
Got it. I will say that when I tested out the "standard" way you suggested above, I immediately found myself trying to adjust it to |
Here's my little example that caused me to run into this issue (mostly typed in IDE to reduce syntax errors):
Based on other comments, I sort of see why this is happening. However, unless my feeble brain is feeble, this looks like a valid use case and I might not be alone. |
I think the problem is two-fold.
This results in the following error:
Note that I also think that when the indexer type is greater than the key type ( So, Typescript is too quick to derive |
Accepting PRs for a solution that special-cases property access expressions where the thing being accessed is an object literal. Use the type of the index to filter to matching properties (the relevant indexing type here could be a string, number, or union of literals thereof) and return a union of the matching property types along with |
It looks like there may be a fix in for this? But in the mean time I'm going to post a suggestion for others who came here like me. My use case was I had a mapped type that I could use when I had a bunch of identical reducers for different namespaces/domains (just a combo of Record and Readonly) and it worked in most cases but I ran into an issue where it gave me an
to this:
hope that might help others who have my same issue . . . |
I entended the color pallete exercise. May be of interest to some. Playground - TURN ON STRICT MODE
Addendum: You can make an 'inline' typeguard like this (in the situation that not all properties on your 'type' are of the same exact type)
|
This was the "aha" moment in this thread which made the error message make sense. It would be really helpful if the error message when doing index access on a mapped type explained this, or if the mapped type documentation mentioned this. It would be even better if indexing over mapped types would produce the value, which i think is what #29528 is asking for. |
@RyanCavanaugh I'm a bit confused here. This particular error is frequent and annoying for me, but this example shouldn't be an exception.
The fact that the object is static is irrelevant: the problem here is the key is a plain The correct type should be: function getName(id: 1 | 2) {
return {
1: 'one',
2: 'two',
}[id] || 'something else';
} Fix/improve the |
@fregante I'm not really sure what you're getting at |
Maybe I misread your solution proposal. Did you say that in this case only the error “Type has no index signature” will be dropped? Because I don’t think this is a special case. However if my example (which doesn’t cause the error) returns |
@RyanCavanaugh |
TypeScript Version: 2.2.2
Code
Expected behavior:
{1: 'one'}[id]
type should bestring | undefined
So I would expect the return type to be inferred as string.
Actual behavior:
The text was updated successfully, but these errors were encountered: