2

I usually have the following code:

class Foo {
    foo: SomeType[];

    doSomething() {
        const a = this.foo = [];
    }
}

In this case, a would be any[] or never[] (depends on environment) instead of SomeType[]. If I specify noImplicitAny on those that imply any[], the compiler would throw an error.

I know the below cast fixes the problem, but why can't TypeScript deduce the type from this.foo?

        const a: SomeType[] = this.foo = []; // Have to repeat the type again

Reproducible code:

tsconfig.json:

{
    "compilerOptions": {
        "noImplicitAny": true
    }
}

test.ts:

class Foo {

    foo: number[];

    doSomething() {
        const a = this.foo = [];
    }

}

TypeScript complaint (at least in VS Code): enter image description here

Luke Vo
  • 17,859
  • 21
  • 105
  • 181
  • [I can't reproduce](https://tsplay.dev/Na0zEw) any such implicit any error; can you make sure you've got a [mre] that demonstrates what you're talking about? – jcalz Oct 23 '21 at 03:44
  • @jcalz Hi, the code in my original question should work, but I added more details. Probably the problem is in VS Code only (I do know VS has this problem as well). In your playground, you don't have an error, but `a` is still `never[]`, which is the main problem. – Luke Vo Oct 23 '21 at 03:49
  • Can confirm from ts playgroud, ```a``` is type ```never[]```...interesting [playground](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMoHsC2EAqBPABxQG8BfAbgFgAoGhAGzgGcnkAxddZYm5P5GJwBcaLLkIQA2gF0qtav2QJ0IJmCgBXBGHRQAFAEpuvRcjAALYEwB0grgF5kMuYtIm+75ABN0GbBdAAc0NjBVM+ZVUwZDhkRwCbOzinWU83alIgA) – bloo Oct 23 '21 at 03:51
  • What version of TypeScript are you using? If the issue does turn out to be specific to a particular IDE (this is highly unlikely) then you should probably tag it for that IDE. I understand that `never[]` is also not desired, but until I can get reproducible behavior that both you and I agree is happening then I'm not inclined to spend a lot of effort on researching it. If you want to change the question to be about `never[]` that's fine; otherwise you should provide enough info for someone to reproduce it (the playground is also using `--noImplicitAny` so that's not the issue) – jcalz Oct 23 '21 at 03:52
  • Okay, I clarified the question. The problem is not about `noImplicitAny`, it's about not getting the desired array type. Neither `never[]` nor `any[]` is good. – Luke Vo Oct 23 '21 at 03:56
  • I think I need to go to sleep soon, but I don't know if I can find a lot of canonical discussion around why assignment expressions behave as they do in TypeScript; I see [ms/TS#37883](https://github.com/microsoft/TypeScript/issues/37883) but that's not the same issue. The actual behavior seems to be that the expression `(x = y)` evaluates to be the inferred type of `y` regardless of the type of `x`, which isn't wrong *per se* but is not currently your intent. So I'm not sure if someone will have an authoritative answer for "why can't TypeScript deduce the type from `this.foo`". – jcalz Oct 23 '21 at 04:04
  • Interesting this only happens with arrays. If foo is any primitive type or an object, the type is inferred correctly. Correction: The type inferred by typescript for the expression `a = (x = [])` is always `never[]`, but if we just do `let x = []` or `const x = []`, the type is inferred as `any[]` – smac89 Oct 23 '21 at 04:07
  • I found this interesting answer [here](https://stackoverflow.com/questions/1758576/multiple-left-hand-assignment-with-javascript#answer-1758912). This is a ```javascript``` related solution, but it highlights how assignments are right associated, which would explain the ```never[]``` type. Also assigning a type never to some other type is ok. – bloo Oct 23 '21 at 04:16
  • @ash.io I was also looking at the same question lol – smac89 Oct 23 '21 at 04:20
  • It would make sense if typescript is doing some runtime type checking, but even if `this.foo` is defined (in a constructor for example), the result is still the same, and we still end up with `never[]` – smac89 Oct 23 '21 at 04:22

1 Answers1

2

The type being inferred as any[] makes sense because Javascript is right-associative wrt the assignment operator.

See this question: Multiple left-hand assignment with JavaScript

This means that the expression:

const a = this.foo = [];

Is interpreted as:

this.foo = [];
const a = [];

As you can see, the type information isn't associated with empty array, so any[] is the most correct type.


You can prove this is in fact what is happening with a simple example:

let t: number;
const a = t = 5;

The inferred type for a would be the literal number 5, not number (which is the type for t).


It seems that the typescript playground may be wrong in this case, which is why many (including myself) are reporting never[] as the inferred type.

smac89
  • 39,374
  • 15
  • 132
  • 179
  • Thanks, I lean towards this explanation too. I am going to send an issue to TypeScript repo. Hopefully they allow this case to make our lives easier. Just that I work on code like this a lot and it's inconvenient to declare the types again. – Luke Vo Oct 23 '21 at 07:46
  • 1
    Also the playground works correctly. If you turn off `strictNullChecks`, it would act like VS Code, i.e. compile error due to `a` being implicit `any[]`. – Luke Vo Oct 27 '21 at 08:05
  • @LukeVo good to know – smac89 Oct 27 '21 at 14:31