Typescript: noImplicitAny checks don't work with arrays if mutated

Created on 23 Feb 2018  ·  20Comments  ·  Source: microsoft/TypeScript

And duplicate errors should be suppressed.

TypeScript Version: master


Search Terms:

Code

const a = [];
a;
const b = [];
b.push(1);

Expected behavior:

$ node built/local/tsc.js --noEmit index.ts --noImplicitAny
index.ts(1,7): error TS7034: Variable 'a' implicitly has type 'any[]' in some locations where its type cannot be determined.
index.ts(3,7): error TS7034: Variable 'b' implicitly has type 'any[]' in some locations where its type cannot be determined.

Actual behavior:

$ node built/local/tsc.js --noEmit index.ts --noImplicitAny
index.ts(1,7): error TS7034: Variable 'a' implicitly has type 'any[]' in some locations where its type cannot be determined.
index.ts(2,1): error TS7005: Variable 'a' implicitly has an 'any[]' type.

Playground Link:

Related Issues:

Design Limitation

Most helpful comment

Then you shouldn't use such an insufficient type inference in strict mode.

All 20 comments

I think type b can be resolved but still it should be error.

If you put b; after the last line and hover, b will correctly be an Array<string | number>. It shows as any[] if you hover over it earlier though.

I offten forget type declarations. I want to get that my mistake from compiler.

Note: b.push(''); is not intended. So I removed it, sorry.

Can you give a concrete example of when this gives you an issue? You're using a in a manner where control flow analysis hasn't added anything to the type. You also haven't actually used b at all. So it's unclear what sort of bug you want reported.

I found the following case.

const a = [];
const b = [];
b.push(0);
a.push(b);
b.push('');
const c: number[][] = a; // wrong but no error.

BTW, can you also take a look at #22046?

@DanielRosenwasser thoughts?

I think that while it's undesirable, it's a limitation of the current system. I guess the alternative would be that in order to get the type of a, you need to analyze the final type of b. It's hard to imagine TypeScript realistically going down that path to be honest.

Then you shouldn't use such an insufficient type inference in strict mode.

@DanielRosenwasser This case can be detected in the loop as follows. So probably this is just a bug.

const a = [];
const b = [];
while (0) {
    b.push(0);
    a.push(b);
    b.push('');
    const c: number[][] = a; // error correctly
}

The loop case is slightly different since the control flow analysis will go through it twice, and that will cause the detection of the mutation on b.

The other one is a design limitation. the analysis we do does not take into account the change in the type of the argument. it will capture the type of b at the point it is used.

You shouldn't use such a loose type inference in strict mode.

The error happens with only one evolving array if you use it in a way other than direct access:

declare function f(arr: ReadonlyArray<number>): void; // May assign `arr` to something or use it in a closure
const a = [];
a.push(0);
f(a);
a.push("");
// ...run-time error happens later on and is hard to track down...

The error would be detected by freezing the array's type at the call. It seems rare to write code that relies on arr temporarily having a more specific type than it ends up having, but more common to use an array before all elements have been written. We don't really need to time-travel and get the final type of a to detect this error, we just need to prevent the type from changing after use.

we just need to prevent the type from changing after use.

Sounds good.

@DanielRosenwasser @mhegazy Can you confirm?

To be clear, the above was just a comment and this may remain a design limitation if other team members think otherwise.

Agreed.

This is a design limitation, and it is general and not specific to arrays. the compiler does not make any assumptions about mutation for arrays, and object literals. this is not any different in this sense.

The example above should only happen for an array because it is the only thing whose value can change type:

declare function f(a: number[]): void;
const a = [];
a.push(0);
f(a);
a.push(""); // No error

declare function g(o: { x: number }): void;
const o = { x: 0 };
f(g);
o.x = ""; // Error

(A local variable can change type, but the object it points to won't be changed to have a different type -- the local will point to a different object instead.)

Accepting initial implicit any type and type mutation from there are specific to arrays. Empty object value {} doesn't make any but array makes it.

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Was this page helpful?
0 / 5 - 0 ratings