π 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.
π» Code
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
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
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: typescript-eslint/typescript-eslint#10334
TypeScript-ESLint has a rule called 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.
π Search Terms
"evolving any", "non-null assertion", "compiler api"
π Version & Regression Information
β― 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.π» Code
π Actual behavior
π Expected behavior
Additional information about the issue
Reference: typescript-eslint/typescript-eslint#10334
TypeScript-ESLint has a rule called
no-unnecessary-type-assertionwhich, 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 | undefinedthen the non-null assertion is unnecessary".By "the thing" I am referring to the expression being non-null asserted, eg the
xinx!.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
y2case) - the type ofxis presented asnumber- even though it should be presented asnumber | 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 numberbehaves correctly and the type ofxis correctly presented asnumber | 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
xit looks non-nullish -- which suggests the non-null assertion is unnecessary. But if the user removes the non-null assertion then the type ofxchanges tonumber | undefinedand this causes errors.