-
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
Object is possibly undefined` error in TypeScript 4.1.2 #41612
Comments
Duplicate of #10530. Type narrowing does not occur for indexed access forms |
@MartinJohns Sorry for the duplicate, but I would not have found it and really looked. I just wanted to add that this specific use case of the problem is the main cause for an immense number of "false errors" when activating the new |
This was one of the caveats that kept the flag from being implemented for so long. We don't have any way to ascertain whether function main(values: Array<string>) {
for (let i = 0; i < values.length; i++) {
const el = values[i];
if (typeof el === 'string' && el.length > 0) {
console.log(i);
}
}
} |
@RyanCavanaugh this should work as well function main(values: ReadonlyArray<string>) {
for (let i = 0; i < values.length; i++) {
if (typeof values[i] === 'string' && values[i].length > 0) {
console.log(i);
}
}
} since |
@thecotne Just to make sure: it should but it does not. Correct? |
@doberkofler sorry for confusion. it should but it does not. |
Have you considered using a for of or forEach loop? function main(values: Array<string>) {
for (const value of values){
if (value) {
console.log(value);
}
}
}
function main(values: Array<string>) {
values.forEach(value => {
if (value) {
console.log(value);
}
})
} |
@Skillz4Killz You are absolutely correct. The code snipped is just an example for the generic problem where the array element is first tested and then used if available. |
@doberkofler Consider the following (contrived) example though
values[i].length is no longer guaranteed to be defined, and TS has no good way to detecting that change. |
This isn't true. A function fn(cb: (arr: ReadonlyArray<number>, clear: () => boolean) => void) {
let arr = [1, 2, 3];
cb(arr, () => {
while(arr.length) arr.pop();
return true;
})
};
// Crashes
fn((arr, clear) => {
let i = 0;
if (arr[i] !== undefined && clear() && arr[i].toFixed() === "1") {
}
}) |
But doesn't narrowing on all object properties work in the "unsafe way" in TypeScript anyway? E.g. type X = { word: string|undefined }
function bad(obj: X) {
if (obj.word !== undefined) {
mutate(obj)
return obj.word.length
}
return undefined
}
function mutate(obj: X) {
obj.word = undefined
}
console.log(bad({ word: 'hello' })) Produces a run-time error, but no TypeScript errors. Of course noone should ever write code like this, but it's the same principle. It's a shame that JS doesn't have better native support for immutable data. The "edge case" of mutability is preventing such a useful feature from working conveniently in the huge percentage of codebases that never mutate most stuff anyway (especially non-local stuff). |
The entire point of the flag was to be more safe than TS's normal behavior, so it being more restrictive than the default is a not undesirable aspect. |
I know TS doesn't recognize this pattern, even for normal optional properties: interface Foo {
bar: string | undefined;
}
declare const foo: Foo;
if (foo.bar) {
foo.bar.toLowerCase(); // OK
}
const bar = 'bar';
if (foo[bar]) {
foo[bar].toLowerCase(); // Object is possibly 'undefined'.
} However I can't understand why: If it's because a variable ( If you say the object ( |
I understand that this option is restrictive and the only reason for my SR was to better understand how to take advantage of it. At least in my "real world" scenario, it seems almost impossible using it, as it leads to reject thousands of code lines that are actually valid. I'm still wondering, if maybe just be identifying some common use cases that are legal, the usability of this interesting new option might be dramatically improved. |
@yume-chan It's not enough for const bar = { toString: Math.random };
({ [bar]: 1 }) // { '0.9271219051493171': 1 }
({ [bar]: 1 }) // { '0.07489693725883417': 1 } It may work for |
I appreciate having that option, but it mixes together two different concerns in an inseparable way
I think there is a good chunk of people that would like having option 2 without also being forced into option 1 (as it is much more rare, while 2. is very common). Even so, it is a valuable improvement, just not as useful as it could be. |
@peterholak Option two would require a way to represent immutable types in TypeScript. This is currently not possible. It's something I personally really want, but it's nothing in the foreseeable future. Issues to follow would be #14909, #16317, #17181. |
@MartinJohns By option 2 I just meant assuming that the array and index don't change within a block (maybe unless there is an explicit assignment directly within the same block), and doing the narrowing anyway, just like it works for non-indexed access (#41612 (comment)). So not really "immutable", but "not mutated in practice"—which covers a lot of cases and is reasonably easy to manually keep in check by humans (depending on the programming style of the project I guess). It would be unsafe if the data changes in other ways, but that's a good trade-off for many projects—especially compared to not adding Basically an option to still add |
|
@yume-chan I just wanted to emphasize that being |
@RyanCavanaugh why was this closed? Was there something that resolve this? Edit: Ah just seeing the "Design Limitation" label on this now - I guess this means that this is by design and TS will not change this behavior? |
For what it's worth, I still think this is 2 separate features mixed under the same flag. One is "stricter narrowing" (but not everywhere) and the other is "adding |undefined to indexed access". |
TypeScript Version: 4.1.2
Search Terms: Object is possibly undefined
Code
Expected behavior:
No error
Actual behavior:
TypeScript 4.1.2 reports a
Object is possibly undefined
error invalues[i].length
but given the first condition the object must be defined as it is of type 'string'Playground Link: https://www.typescriptlang.org/play?noUncheckedIndexedAccess=true&ts=4.1.0-beta#code/GYVwdgxgLglg9mABAWwIYzACgG6oDYgCmAzgFyICCATlagJ4A8xUVGA5gHwCUiA3gFCIhiYHCqJMeQlEQxEAXkQAGANyzEDRLgIkAdFLBsoACzUwA1OZ4Dht2cAlQ6AB0JwH2osQDaMALoK8ooA5MyshsGIAGRRWvhevn76hIYmiBzK1oJ2ORAIxHBS+nBsmDBcKtk5AL5ViLW1QA
Related Issues: no
The text was updated successfully, but these errors were encountered: