Repository: TypeScript
Issue: Weird behaviour with an "evolving any" and a non-null assertion operator
Labels: Bug,Help Wanted

### 🔎 Search Terms

"evolving any", "non-null assertion", "compiler api"

### 🕗 Version & Regression Information

- This changed in version 5.1


### ⏯ Playground Link

https://typescript-eslint.io/play/#ts=5.5.2&fileType=.tsx&code=CYUwxgNghgTiAEAzArgOzAFwJYHtVJxwAoBKALnlWQFsAjEGeAH3jVES1RGAFgAoUJFgIU6bHni1YpClToN%2B-UZlz5qUTgEZS8AN7948CCAzwAHgG4D5%2BAF5J0klb6GzdgsSf8A9N4B6APzWxqYAnpp21r6GgdaG8QmWUd4J8bEuRibwoQBMkRnR8OmpJWYAhM6GhanFmWEAzPlVKUVBGSXxblAAzpQ09DCV8NUJ6QC%2B-EA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3QAacDUhFYASSSomyPAEEiATwphR6KQF8Q%2BoA&tokens=false

NOTE: I linked to the typescript-eslint playground just because it both supports `// ^?` and also provides access to the TS types so you can easily see the issue. You can ofc reproduce this in the [TS playground too](https://www.typescriptlang.org/play/?ts=5.6.3#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVJxwAoBKALnlWQFsAjEGeAH3jVES1RGAChRJYCFOmx54tWKQpU6DHj2GZc+alE4BGUvADePePAggM8AB4BuPafgBecZJIX9JmwWIOeAeg8A9APyXDYwBPdRtLL30-S30Y2PNwj1iYqP1A+CCAJjD9CPgUpKSTAEJHeFyk-LSggGZsssS8-wLC+CgAZ0oaehhS8tiogF8eIA).

### 💻 Code

```ts
declare function foo(): number | undefined
declare function bar(): number

function main1() {
  let x;
  x = bar();
  x = foo();
//^?
  let y1 =
  //  ^?
           x;
  //       ^?
  let y2 =
  //  ^?
           x!;
  //       ^?
  let y3 =
  //  ^?
           x as number;
  //       ^?
}
```


### 🙁 Actual behavior

```ts
declare function foo(): number | undefined
declare function bar(): number

function main1() {
  let x;
  x = bar();
  x = foo();
//^? any ✅
  let y1 =
  //  ^? number | undefined ✅
           x;
  //       ^? number | undefined ✅
  let y2 =
  //  ^? number ✅
           x!;
  //       ^? number ❌
  let y3 =
  //  ^? number ✅
           x as number;
  //       ^? number | undefined ✅
}
```


### 🙂 Expected behavior

```ts
declare function foo(): number | undefined
declare function bar(): number

function main1() {
  let x;
  x = bar();
  x = foo();
//^? any ✅
  let y1 =
  //  ^? number | undefined ✅
           x;
  //       ^? number | undefined ✅
  let y2 =
  //  ^? number ✅
           x!;
  //       ^? number | undefined ❓❓❓❓
  let y3 =
  //  ^? number ✅
           x as number;
  //       ^? number | undefined ✅
}
```

### Additional information about the issue

Reference: https://github.com/typescript-eslint/typescript-eslint/issues/10334

[TypeScript-ESLint has a rule called `no-unnecessary-type-assertion`](https://typescript-eslint.io/rules/no-unnecessary-type-assertion/) which, as the name implies, flags unnecessary type assertions to help keep the code clean.

One of the checks done by the rule involves flagging unnecessary non-null assertions.
The basic logic for this check is "if the type of the thing does not include `null | undefined` then the non-null assertion is unnecessary".
By "the thing" I am referring to the expression being non-null asserted, eg the `x` in `x!`.

In the case of an "evolving any" the type of the variable is shown to be incorrect by the TS APIs.
As shown in the example code (specifically the `y2` case) - the type of `x` is presented as `number` - even though it should be presented as `number | undefined`. It appears that for some reason the non-null assertion modifies the type of the variable to remove the `| undefined`, rather than just emitting a non-nullish type from the non-null assertion expression.

This is made even weirder from the fact that writing `x as number` behaves correctly and the type of `x` is correctly presented as `number | undefined`.

This discrepancy is problematic for our lint rule because it means we currently false-positive on this case because when we query for the type of `x` it looks non-nullish -- which suggests the non-null assertion is unnecessary. But if the user removes the non-null assertion then the type of `x` changes to `number | undefined` and this causes errors.