-
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
Suggestion: option to include undefined in index signatures #13778
Comments
With the exception of strictNullChecks, we do not have flags that change the type system behavior. flags usually enable/disable error reporting. |
@mhegazy That's an interesting idea. Any guidance on how to override the type signatures for array/object? |
There is |
Interesting, so I tried this: {
// https://github.com/Microsoft/TypeScript/blob/1f92bacdc81e7ae6706ad8776121e1db986a8b27/lib/lib.d.ts#L1300
declare global {
interface Array<T> {
[n: number]: T | undefined;
}
}
const xs = [1,2,3]
const x = xs[100]
x // still number :-(
} Any ideas? |
copy |
Cool, thanks for that. The issue with the suggested fix here is it requires forking and maintaining a separate I wonder if this feature is demanded enough to warrant some sort of option out of the box. On a side note, it's interesting that the type signature for the |
this is a conscious decision. it would be very annoying for this code to be an error: var a = [];
for (var i =0; i< a.length; i++) {
a[i]+=1; // a[i] is possibly undefined
} and it would be unreasonable to ask every user to use var a = [];
for (var i =0; i< a.length; i++) {
if (a[i]) {
a[i]+=1; // a[i] is possibly undefined
}
} For map this is not the case generally. Similarly for your types, you can specify I do not think adding a flag to change the shape of a declaration is something we would do. |
@mhegazy but for arrays with holes
Output is:
|
We remain quite skeptical that anyone would get any benefit from this flag in practice. Maps and maplike things can already opt in to If someone wants to modify their lib.d.ts and fix all the downstream breaks in their own code and show what the overall diff looks like to show that this has some value proposition, we're open to that data. Alternatively if lots of people are really excited to use postfix |
Isn't one of the goals of TypeScript to allow errors to be caught at "compile" time rather than rely on the user to remember/know to do something specific? This seems to go against that goal; requiring the user to do something in order to avoid crashes. The same could be said for many other features; they're not needed if the developer always does x. The goal of TypeScript is (presumably) to make the job easier and eliminate these things. I came across this bug because I was enabling |
Actually the goal is:
What is being discussed here the likelyhood of an error (low in the opinion of the TypeScript team) and the common productive usability of the language. Some of the early change to CFA have been to be less alarmist or improve the CFA analysis to more intelligently determine these things. I think the question from the TypeScript team is that instead of arguing the strictly correctness of it, to provide examples of where this sort of strictness, in common usage would actually identify an error that should be guarded against. |
I went into the reasoning a bit more at this comment #11238 (comment) Think of the two types of keys in the world: Those which you know do have a corresponding property in some object (safe), those which you don't know to have a corresponding property in some object (dangerous). You get the first kind of key, a "safe" key, by writing correct code like for (let i = 0; i < arr.length; i++) {
// arr[i] is T, not T | undefined or
You get the second kind from key, the "dangerous" kind, from things like user inputs, or random JSON files from disk, or some list of keys which may be present but might not be. So if you have a key of the dangerous kind and index by it, it'd be nice to have for (let i = 0; i < arr.length; i++) {
console.log(arr[i].name); and TypeScript is complaining at you that for (let i = 0; i < arr.length; i++) {
// TypeScript makes me use ! with my arrays, sad.
console.log(arr[i]!.name); Or maybe you write code like this: function doSomething(myObj: T, yourObj: T) {
for (const k of Object.keys(myObj)) {
console.log(yourObj[k].name);
}
} and TypeScript says "Hey, that index expression might be function doSomething(myObj: T, yourObj: T) {
for (const k of Object.keys(myObj)) {
console.log(yourObj[k]!.name); // Shut up TypeScript I know what I'm doing
}
} But you didn't fix the bug. You meant to write I think of the old "Are you sure you want to delete this file?" dialog. If that dialog appeared every time you tried to delete a file, you would very quickly learn to hit |
Perhaps this needs to be amended to say "Statically identify constructs that are more likely than others to be errors." 😉. I'm reminded of when we get bugs that are essentially "I used |
I understand, but index out of range is a real and common issue; forcing people to enumerate arrays in a way that they can't do this would not be a bad thing. The fix with You already narrow types based on code so in theory couldn't you could spot patterns that are safe narrow the type to remove That said, I only use TS on one project and that will ultimately be migrated to Dart so it's unlikely to make any real difference to me. I'm just sad that the general quality of software is bad and there's an opportunity to help eliminate errors here that is seemingly being ignored for the sake of convenience. I'm sure the type system could be made solid and the common annoyances addressed in a way that doesn't introduce these holes. Anyway, that's just my 2 cents.. I don't want to drag this out - I'm sure you understand where we're coming from and you're far better placed to make decisions on this than me :-) |
I think there are a few things to consider. There are a lot of patterns for iterating over arrays in common use that account for the number of elements. While it is a possible pattern to just randomly access indexes on arrays, in the wild that is a very uncommon pattern and is not likely to be a statical error. While there are modern ways to iterate, the most common would be something like: for (let i = 0; i < a.length; i++) {
const value = a[i];
} If you assume spare arrays are uncommon (they are) it is of little help to have I think there has been conversation before about improving CFA so that there is a way to express the co-dependancy of values (e.g. Array.prototype.length relates to the index value) so that things like index out of bounds could be statically analysed. Obviously that is a significant piece of work, wrought with all sorts of edge cases and considerations I wouldn't like to fathom (though it is likely Anders wakes up in a cold sweat over some things like this). So it becomes a trade off... Without CFA improvements, complicate 90% of code with red herrings to catch potentially 10% bad code. Otherwise it is investing in major CFA improvements, which might be wrought with their own consequences of stability and issues against again, finding what would be unsafe code. There is only so much TypeScript can do to save us from ourselves. |
All this focus is on arrays and I agree it's less likely to be an issue there, but most of the original issues raised (like mine) were about maps where I don't think the common case is always-existing keys at all? |
If this is your type, add |
i rewrote all definitions of Arrays and Maps to make index signatures returning would be great if TypeScript could control flow the checks like C# does (to eliminate index range checks to save some processor time), for example: declare var values: number[];
for (let index = 0, length = values.length; index< length; index ++) {
const value = value[index]; // always defined, because index is within array range and only controlled by it
} (to those who uses sparse arrays - kill yourself with hot burning fire) as for |
I think this would be a good option to have, because right now we are essentially lying about the type of the indexing operation, and it can be easy to forget to add |
I think the best solution would be to throw an exception when trying to access a non-existent element. This is in line with normal programming practice in statically typed languages. And if you want to get a nullable value without throwing an exception, then a method like "tryGet" is used. |
@Perfectoff As a principle, TypeScript never changes the runtime behavior of JavaScript code: What you are proposing are run-time assertions, and that could be implemented as a separate library. |
It seems to me that @Perfectoff just wants to use a language that isn't Javascript. JS arrays have never had a concept of "out of bounds", Arrays have always been sparse, and Objects have always allowed arbitrary indexing. That's the whole reason |
I see your point but almost nobody use the optional sparse capabilities of Arrays in their production applications. If types can help alleviate the historicaly poor technical choices of JS, why not do it. |
That's exactly what this issue was about! One could argue that sparse arrays are "one of the bad parts", sure. The fix for that, IMHO, is forcing you to check your indexed access, or avoid it altogether. |
@OliverJAsh Coul dyou edit the issue description to mention that The thread is so long and still continuously edited that the solution is hard to find. |
Update: fixed by
--noUncheckedIndexedAccess
in TypeScript 4.1Update: for my latest proposal see comment #13778 (comment)
With
strictNullChecks
enabled, TypeScript does not includeundefined
in index signatures (e.g. on an object or array). This is a well known caveat and discussed in several issues, namely #9235, #13161, #12287, and #7140 (comment).Example:
However, it appears from reading the above issues that many TypeScript users wish this wasn't the case. Granted, if index signatures did include
undefined
, code will likely require much more guarding, but—for some—this is an acceptable trade off for increased type safety.Example of index signatures including
undefined
:I would like to know whether this behaviour could be considered as an extra compiler option on top of
strictNullChecks
. This way, we are able to satisfy all groups of users: those who want strict null checks with or without undefined in their index signatures.The text was updated successfully, but these errors were encountered: