Skip to content

Commit

Permalink
use global NaN symbol for NaN equality comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Sep 15, 2022
1 parent fb44a1d commit 5ca49bb
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 6 deletions.
21 changes: 15 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ namespace ts {
let deferredGlobalOmitSymbol: Symbol | undefined;
let deferredGlobalAwaitedSymbol: Symbol | undefined;
let deferredGlobalBigIntType: ObjectType | undefined;
let deferredGlobalNaNSymbol: Symbol | undefined;

const allPotentiallyUnusedIdentifiers = new Map<Path, PotentiallyUnusedIdentifier[]>(); // key is file name

Expand Down Expand Up @@ -14287,6 +14288,10 @@ namespace ts {
return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType;
}

function getGlobalNaNSymbol(): Symbol | undefined {
return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false));
}

/**
* Instantiates a global type that is generic with some element type, and returns that instantiation.
*/
Expand Down Expand Up @@ -34386,7 +34391,7 @@ namespace ts {
const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken;
error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true");
}
checkNaNEquality(errorNode, operator, left, right, leftType, rightType);
checkNaNEquality(errorNode, operator, left, right);
reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left));
return booleanType;

Expand Down Expand Up @@ -34620,9 +34625,9 @@ namespace ts {
}
}

function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression, leftType: Type, rightType: Type) {
const isLeftNaN = (leftType.flags & TypeFlags.Number) && isNaN(skipParentheses(left));
const isRightNaN = (rightType.flags & TypeFlags.Number) && isNaN(skipParentheses(right));
function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) {
const isLeftNaN = isGlobalNaN(skipParentheses(left));
const isRightNaN = isGlobalNaN(skipParentheses(right));
if (isLeftNaN || isRightNaN) {
const err = error(errorNode, Diagnostics.This_condition_will_always_return_0,
tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword));
Expand All @@ -34635,8 +34640,12 @@ namespace ts {
}
}

function isNaN(expr: Expression): boolean {
return isIdentifier(expr) && expr.escapedText === "NaN";
function isGlobalNaN(expr: Expression): boolean {
if (isIdentifier(expr) && expr.escapedText === "NaN") {
const globalNaNSymbol = getGlobalNaNSymbol();
return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr);
}
return false;
}
}

Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/nanEquality.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,16 @@ tests/cases/compiler/nanEquality.ts(29,5): error TS2845: This condition will alw
~~~~~~~~~~~~~~~
!!! error TS2845: This condition will always return 'false'.
!!! related TS1369 tests/cases/compiler/nanEquality.ts:29:13: Did you mean 'Number.isNaN(...)'?

function t1(value: number, NaN: number) {
return value === NaN; // ok
}

function t2(value: number, NaN: number) {
return NaN == value; // ok
}

function t3(NaN: number) {
return NaN === NaN; // ok
}

21 changes: 21 additions & 0 deletions tests/baselines/reference/nanEquality.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ if (NaN != NaN) {}
// ...
declare let y: any;
if (NaN === y[0][1]) {}

function t1(value: number, NaN: number) {
return value === NaN; // ok
}

function t2(value: number, NaN: number) {
return NaN == value; // ok
}

function t3(NaN: number) {
return NaN === NaN; // ok
}


//// [nanEquality.js]
Expand All @@ -48,3 +60,12 @@ if (NaN !== NaN) { }
if (NaN == NaN) { }
if (NaN != NaN) { }
if (NaN === y[0][1]) { }
function t1(value, NaN) {
return value === NaN; // ok
}
function t2(value, NaN) {
return NaN == value; // ok
}
function t3(NaN) {
return NaN === NaN; // ok
}
29 changes: 29 additions & 0 deletions tests/baselines/reference/nanEquality.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,32 @@ if (NaN === y[0][1]) {}
>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(nanEquality.ts, 27, 11))

function t1(value: number, NaN: number) {
>t1 : Symbol(t1, Decl(nanEquality.ts, 28, 23))
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))

return value === NaN; // ok
>value : Symbol(value, Decl(nanEquality.ts, 30, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 30, 26))
}

function t2(value: number, NaN: number) {
>t2 : Symbol(t2, Decl(nanEquality.ts, 32, 1))
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))

return NaN == value; // ok
>NaN : Symbol(NaN, Decl(nanEquality.ts, 34, 26))
>value : Symbol(value, Decl(nanEquality.ts, 34, 12))
}

function t3(NaN: number) {
>t3 : Symbol(t3, Decl(nanEquality.ts, 36, 1))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))

return NaN === NaN; // ok
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
>NaN : Symbol(NaN, Decl(nanEquality.ts, 38, 12))
}

32 changes: 32 additions & 0 deletions tests/baselines/reference/nanEquality.types
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,35 @@ if (NaN === y[0][1]) {}
>0 : 0
>1 : 1

function t1(value: number, NaN: number) {
>t1 : (value: number, NaN: number) => boolean
>value : number
>NaN : number

return value === NaN; // ok
>value === NaN : boolean
>value : number
>NaN : number
}

function t2(value: number, NaN: number) {
>t2 : (value: number, NaN: number) => boolean
>value : number
>NaN : number

return NaN == value; // ok
>NaN == value : boolean
>NaN : number
>value : number
}

function t3(NaN: number) {
>t3 : (NaN: number) => boolean
>NaN : number

return NaN === NaN; // ok
>NaN === NaN : boolean
>NaN : number
>NaN : number
}

12 changes: 12 additions & 0 deletions tests/cases/compiler/nanEquality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,15 @@ if (NaN != NaN) {}
// ...
declare let y: any;
if (NaN === y[0][1]) {}

function t1(value: number, NaN: number) {
return value === NaN; // ok
}

function t2(value: number, NaN: number) {
return NaN == value; // ok
}

function t3(NaN: number) {
return NaN === NaN; // ok
}

0 comments on commit 5ca49bb

Please sign in to comment.