-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Index signature arguments are not type checked based on the specialized key type they are constrained to #7660
Comments
The first example is an error under The second is by design -- indexing by a number is a subset of indexing by a string, so indexing by a number when a string would have been valid is also valid. |
I'm not sure exactly what the logic is on why the first one errors on I renamed the title to a more accurate one, as the keys are of course checked if they receive something like an I also tested in ES6 mode for type OnlyNumberKeys = { [key: number]: any };
let a: OnlyNumberKeys = {};
a[Symbol("abcd")] = 1; // No error?
let x = a[Symbol("abcd")]; // No error? type OnlyStringKeys = { [key: string]: any };
let b: OnlyStringKeys = {};
b[Symbol("abcd")] = 1; // No error?
let y = b[Symbol("abcd")]; // No error? |
Indexing is special because it's the built-in JS way to perform "reflection", i.e. to access a field dynamically at runtime. type X = { length: number; [key: number]: string };
let some: X;
let len = some['length']; // equivalent to some.length;
let dynamic = "length";
len = some[dynamic]; // works at runtime, treated as any at compile time And because the result of dynamic access has to be I think a similar argument can be made for |
I'm aware that square bracket notation was designed to become a "back door" where anything can be accessed. I do use it myself occasionally. However, I don't remember a case I needed it when a type had an explicit index signature. The reason is very simple: Since an index signature was designed to "open up" access to what is usually a large amount of key space, such as "all the strings", "all the numbers" and even "all the symbols" (though that is not supported yet, I opened issue #7667 for that) or a combination of them. One can always write something like: type OnlyStringOrNumberKeys = { [key: string | number]: any };
type OnlyStringOrSymbolKeys = { [key: string | symbol]: any };
type OnlyNumberOrSymbolKeys = { [key: number | symbol]: any };
type AllPossibleKeys = { [key: string | number | symbol]: any }; I mean what really is the benefit of allowing the programmer to specify a parameter type if it is not actually enforced? There's a very good reason I'm mentioning this at this particular time. The reason is that in light of a proposal like #5683 (also see the duplicate #7656) which is currently in discussion, where the key would be constrained to a finite set of string literals or possibly numbers (with numeric literals), there's not much point of implementing it if key types are not fully checked. E.g. you had something like: type OnlySomeStringLiteralKeys = { [key: "A" | "B" | "C"]: any };
x: OnlySomeStringLiteralKeys = {};
x["D"] = 1; // Error, specified key type is not assignable to "A" | "B" | "C" I can't see how that constraint would be truly realized if you can still, "by design" do things like: x[4385] = "a";
x[Symbol("abcd")] = 12; ? |
You make a good point. But I think disallowing dynamic access completely on those objects is too much collateral damage. In let arr: number[];
let x = arr[4]; // array indexer -> x: number
// dynamically augment the array with metadata or additional infos
arr['priority'] = 'high'; // ok... This is certainly not the best code, and you probably should use an intersection type to describe your array Not sure what the best approach is here.
Perhaps some real-world examples could help better define a solution? |
I'm not sure if internally interface NodeBuffer {
[index: number]: number;
... Lots of properties follow ...
} It would break the ability to "back door" into, say, undocumented properties. The simplest workaround that one would need to either cast it to let buf: Buffer;
(<any> buf)["undocumentedProperty"] = 12; Or use some sort of a intersection type (though I don't think that would be something that would appeal or even be understood by beginners). But I can see this becoming a bit tedious when it is frequently used, and it would break some existing code, so I agree this would be a major problem if key checking was stricter. Unfortunately I don't really have a good alternative solution for that at this point. Anyway, as for a real-world example (other the ones described in #7656 for the string literal keys). In the case of "symbols keys only" signatures I could see an example where I have a function that only cares about the metadata hidden inside some object so ideally it would be nice to have a type that basically sees anything thrown at it as just a bunch of symbols, and no more: function iOnlyCareAboutTheSymbolsInWhateverYouGiveMe(x: any) {
let symbolsOnlyHere: { [key: symbol]: any } = x;
// ... do something with the hidden metadata ...
} |
Nitpicking maybe, but it's not just a "backdoor". It's how you can access members dynamically at runtime. Regarding examples:
Probably the most compelling example so far is this one of yours: type RequestMethod = "GET" | "SET" | "PUT" | "UPDATE" | "DELETE";
type HandlerObject = { [key: RequestMethod]: (requestObject: RequestObject) => boolean; }; I think this is desirable but I'm not sure what the best way to proceed here is. Another thing I haven't seen mentionned yet: how should it work with generics? function dump<T>(obj: T) {
for (let prop in obj)
console.log(obj[prop]); // <-- trouble spot
}
let hi = { greetings: "hello world" };
dump(hi); // sure.
interface SymbolsOnly { [key: symbol]: any };
dump<SymbolsOnly>(hi); // ok or not? there's indexed access in dump, which may not be a symbol. |
I'm trying to figure out what is the actual effect of having index signature key types at the first place, also considering what would happen if string literals are allowed as keys. First: let x: { [key: number] : Array<number> }; There seem to be two different interpretations of what this should mean:
Now let's take this to string literals: let x: { [key: "A" | "B" | "C" ]: Array<number> };
The second option here wouldn't seem to be be very useful in this case as I feel it is not strict enough, so I'm afraid that if the semantics of indexer key types were generally intended to be this way then I would personally recommend against implementing #5683 at this point. I'm also considering the fact that |
@malibuzios
Another aspect of indexers is that JS does not allow you to define your own indexer (like say C#). So using an indexer is in fact always going through the JS associative array design. |
I'm assuming the behavior described for key type checking is by design. Essentially - there is no specialized type checking and any indexer would take E.g: let x: { ["A", "B", "C"]: number } = {};
x["A"] = 1; // OK
x["D"] = 1; // No error
x[12] = 1; // No error
x[Symbol("abc")] = 1; // No error; The alternative - the all optional interface: let x: { A?: number, B?: number, C?: number} = {}; Would have a sufficient level of checking when using the dot notation (e.g. It seems very unlikely (and I'm not sure if desirable at this point of the development of the language) the |
TypeScript Version:
1.8.7
Code
Expected behavior:
Should error
Actual behavior:
Doesn't error
(Sorry if this is a duplicate but a search did not yield anything on this)
The text was updated successfully, but these errors were encountered: