Skip to content

Commit 5e66a09

Browse files
Infinity & NaN Type-level Support
1 parent d4cf24c commit 5e66a09

File tree

63 files changed

+2490
-1583
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2490
-1583
lines changed

src/compiler/checker.ts

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ import {
870870
ReverseMappedSymbol,
871871
ReverseMappedType,
872872
sameMap,
873+
sameValueZero,
873874
SatisfiesExpression,
874875
ScriptKind,
875876
ScriptTarget,
@@ -1407,6 +1408,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
14071408
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String);
14081409
const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String);
14091410

1411+
const InfinitySymbol = createSymbol(SymbolFlags.Property, "Infinity" as __String);
1412+
InfinitySymbol.declarations = [];
1413+
const NaNSymbol = createSymbol(SymbolFlags.Property, "NaN" as __String);
1414+
NaNSymbol.declarations = [];
1415+
14101416
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
14111417
let apparentArgumentCount: number | undefined;
14121418

@@ -1430,6 +1436,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
14301436
isUndefinedSymbol: symbol => symbol === undefinedSymbol,
14311437
isArgumentsSymbol: symbol => symbol === argumentsSymbol,
14321438
isUnknownSymbol: symbol => symbol === unknownSymbol,
1439+
isInfinitySymbol: symbol => symbol === InfinitySymbol,
1440+
isNaNSymbol: symbol => symbol === NaNSymbol,
14331441
getMergedSymbol,
14341442
getDiagnostics,
14351443
getGlobalDiagnostics,
@@ -2025,7 +2033,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20252033
let deferredGlobalOmitSymbol: Symbol | undefined;
20262034
let deferredGlobalAwaitedSymbol: Symbol | undefined;
20272035
let deferredGlobalBigIntType: ObjectType | undefined;
2028-
let deferredGlobalNaNSymbol: Symbol | undefined;
20292036
let deferredGlobalRecordSymbol: Symbol | undefined;
20302037

20312038
const allPotentiallyUnusedIdentifiers = new Map<Path, PotentiallyUnusedIdentifier[]>(); // key is file name
@@ -2084,6 +2091,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20842091

20852092
const builtinGlobals = createSymbolTable();
20862093
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);
2094+
builtinGlobals.set(InfinitySymbol.escapedName, InfinitySymbol);
2095+
builtinGlobals.set(NaNSymbol.escapedName, NaNSymbol);
20872096

20882097
// Extensions suggested for path imports when module resolution is node16 or higher.
20892098
// The first element of each tuple is the extension a file has.
@@ -6198,7 +6207,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
61986207
if (type.flags & TypeFlags.NumberLiteral) {
61996208
const value = (type as NumberLiteralType).value;
62006209
context.approximateLength += ("" + value).length;
6201-
return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value));
6210+
const expression = isInfinityOrNaNString("" + value) ? isNaN(value) ? factory.createToken(SyntaxKind.NaNKeyword) : factory.createToken(SyntaxKind.InfinityKeyword) : factory.createNumericLiteral(Math.abs(value));
6211+
return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression) : expression);
62026212
}
62036213
if (type.flags & TypeFlags.BigIntLiteral) {
62046214
context.approximateLength += (pseudoBigIntToString((type as BigIntLiteralType).value).length) + 1;
@@ -15397,10 +15407,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1539715407
return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType;
1539815408
}
1539915409

15400-
function getGlobalNaNSymbol(): Symbol | undefined {
15401-
return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false));
15402-
}
15403-
1540415410
function getGlobalRecordSymbol(): Symbol | undefined {
1540515411
deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol;
1540615412
return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol;
@@ -19569,15 +19575,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1956919575
if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true;
1957019576
if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral &&
1957119577
t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) &&
19572-
(source as NumberLiteralType).value === (target as NumberLiteralType).value) return true;
19578+
sameValueZero((source as NumberLiteralType).value, (target as NumberLiteralType).value)) return true;
1957319579
if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true;
1957419580
if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true;
1957519581
if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true;
1957619582
if (s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName &&
1957719583
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
1957819584
if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) {
1957919585
if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
19580-
if (s & TypeFlags.Literal && t & TypeFlags.Literal && (source as LiteralType).value === (target as LiteralType).value &&
19586+
if (s & TypeFlags.Literal && t & TypeFlags.Literal && sameValueZero((source as LiteralType).value, (target as LiteralType).value) &&
1958119587
isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true;
1958219588
}
1958319589
// In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`.
@@ -19593,7 +19599,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1959319599
if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true;
1959419600
if (s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum ||
1959519601
t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral &&
19596-
(source as NumberLiteralType).value === (target as NumberLiteralType).value)) return true;
19602+
sameValueZero((source as NumberLiteralType).value, (target as NumberLiteralType).value))) return true;
1959719603
// Anything is assignable to a union containing undefined, null, and {}
1959819604
if (isUnknownLikeUnionType(target)) return true;
1959919605
}
@@ -33554,8 +33560,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3355433560
case SyntaxKind.PrefixUnaryExpression:
3355533561
const op = (node as PrefixUnaryExpression).operator;
3355633562
const arg = (node as PrefixUnaryExpression).operand;
33557-
return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) ||
33558-
op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral;
33563+
return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral || isInfinityOrNaNSymbol(arg)) ||
33564+
op === SyntaxKind.PlusToken && (arg.kind === SyntaxKind.NumericLiteral || isInfinityOrNaNSymbol(arg));
3355933565
case SyntaxKind.PropertyAccessExpression:
3356033566
case SyntaxKind.ElementAccessExpression:
3356133567
const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression;
@@ -33565,7 +33571,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3356533571
}
3356633572
return !!(symbol && getAllSymbolFlags(symbol) & SymbolFlags.Enum);
3356733573
}
33568-
return false;
33574+
return isInfinityOrNaNSymbol(node);
33575+
}
33576+
33577+
function isInfinityOrNaNSymbol(node: Node): boolean {
33578+
if (!isIdentifier(node)) {
33579+
return false;
33580+
}
33581+
const symbol = getResolvedSymbol(node);
33582+
return symbol === InfinitySymbol || symbol === NaNSymbol;
3356933583
}
3357033584

3357133585
function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
@@ -34825,6 +34839,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3482534839
base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text)
3482634840
}));
3482734841
}
34842+
break;
34843+
case SyntaxKind.InfinityKeyword:
34844+
switch (node.operator) {
34845+
case SyntaxKind.MinusToken:
34846+
return getFreshTypeOfLiteralType(getNumberLiteralType(-Infinity));
34847+
case SyntaxKind.PlusToken:
34848+
return getFreshTypeOfLiteralType(getNumberLiteralType(Infinity));
34849+
}
34850+
break;
34851+
case SyntaxKind.NaNKeyword:
34852+
switch (node.operator) {
34853+
case SyntaxKind.MinusToken:
34854+
case SyntaxKind.PlusToken:
34855+
return getFreshTypeOfLiteralType(getNumberLiteralType(NaN));
34856+
}
34857+
break;
34858+
case SyntaxKind.Identifier:
34859+
switch (getResolvedSymbol(node.operand as Identifier)) {
34860+
case InfinitySymbol:
34861+
switch (node.operator) {
34862+
case SyntaxKind.MinusToken:
34863+
return getFreshTypeOfLiteralType(getNumberLiteralType(-Infinity));
34864+
case SyntaxKind.PlusToken:
34865+
return getFreshTypeOfLiteralType(getNumberLiteralType(Infinity));
34866+
}
34867+
break;
34868+
case NaNSymbol:
34869+
switch (node.operator) {
34870+
case SyntaxKind.MinusToken:
34871+
case SyntaxKind.PlusToken:
34872+
return getFreshTypeOfLiteralType(getNumberLiteralType(NaN));
34873+
}
34874+
break;
34875+
}
3482834876
}
3482934877
switch (node.operator) {
3483034878
case SyntaxKind.PlusToken:
@@ -35812,8 +35860,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3581235860
}
3581335861

3581435862
function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) {
35815-
const isLeftNaN = isGlobalNaN(skipParentheses(left));
35816-
const isRightNaN = isGlobalNaN(skipParentheses(right));
35863+
const isLeftNaN = isNaNSymbol(skipParentheses(left));
35864+
const isRightNaN = isNaNSymbol(skipParentheses(right));
3581735865
if (isLeftNaN || isRightNaN) {
3581835866
const err = error(errorNode, Diagnostics.This_condition_will_always_return_0,
3581935867
tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword));
@@ -35826,12 +35874,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3582635874
}
3582735875
}
3582835876

35829-
function isGlobalNaN(expr: Expression): boolean {
35830-
if (isIdentifier(expr) && expr.escapedText === "NaN") {
35831-
const globalNaNSymbol = getGlobalNaNSymbol();
35832-
return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr);
35833-
}
35834-
return false;
35877+
function isNaNSymbol(expr: Expression): boolean {
35878+
return isIdentifier(expr) && getResolvedSymbol(expr) === NaNSymbol;
3583535879
}
3583635880
}
3583735881

@@ -36448,6 +36492,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3644836492
negative: false,
3644936493
base10Value: parsePseudoBigInt((node as BigIntLiteral).text)
3645036494
}));
36495+
case SyntaxKind.InfinityKeyword:
36496+
return getFreshTypeOfLiteralType(getNumberLiteralType(Infinity));
36497+
case SyntaxKind.NaNKeyword:
36498+
return getFreshTypeOfLiteralType(getNumberLiteralType(NaN));
3645136499
case SyntaxKind.TrueKeyword:
3645236500
return trueType;
3645336501
case SyntaxKind.FalseKeyword:
@@ -42099,6 +42147,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4209942147
return +(expr as NumericLiteral).text;
4210042148
case SyntaxKind.ParenthesizedExpression:
4210142149
return evaluate((expr as ParenthesizedExpression).expression, location);
42150+
case SyntaxKind.InfinityKeyword:
42151+
return Infinity;
42152+
case SyntaxKind.NaNKeyword:
42153+
return NaN;
4210242154
case SyntaxKind.Identifier:
4210342155
if (isInfinityOrNaNString((expr as Identifier).escapedText)) {
4210442156
return +((expr as Identifier).escapedText);
@@ -42854,7 +42906,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4285442906
// find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases)
4285542907
const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
4285642908
/*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
42857-
if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
42909+
if (symbol && (symbol === undefinedSymbol || symbol === InfinitySymbol || symbol === NaNSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
4285842910
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName));
4285942911
}
4286042912
else {
@@ -45205,6 +45257,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4520545257
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true);
4520645258
getSymbolLinks(unknownSymbol).type = errorType;
4520745259
getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol);
45260+
getSymbolLinks(InfinitySymbol).type = getFreshTypeOfLiteralType(getNumberLiteralType(Infinity));
45261+
getSymbolLinks(NaNSymbol).type = getFreshTypeOfLiteralType(getNumberLiteralType(NaN));
4520845262

4520945263
// Initialize special types
4521045264
globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true);

src/compiler/core.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,6 +2203,11 @@ export function equateValues<T>(a: T, b: T) {
22032203
return a === b;
22042204
}
22052205

2206+
/** @internal */
2207+
export function sameValueZero<T>(a: T, b: T) {
2208+
return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
2209+
}
2210+
22062211
/**
22072212
* Compare the equality of two strings using a case-sensitive ordinal comparison.
22082213
*

src/compiler/factory/nodeFactory.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ import {
144144
IndexedAccessTypeNode,
145145
IndexSignatureDeclaration,
146146
InferTypeNode,
147+
InfinityExpression,
147148
InputFiles,
148149
InterfaceDeclaration,
149150
IntersectionTypeNode,
@@ -322,6 +323,7 @@ import {
322323
NamespaceExport,
323324
NamespaceExportDeclaration,
324325
NamespaceImport,
326+
NaNExpression,
325327
NewExpression,
326328
Node,
327329
NodeArray,
@@ -1472,6 +1474,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
14721474
function createToken(token: SyntaxKind.NullKeyword): NullLiteral;
14731475
function createToken(token: SyntaxKind.TrueKeyword): TrueLiteral;
14741476
function createToken(token: SyntaxKind.FalseKeyword): FalseLiteral;
1477+
function createToken(token: SyntaxKind.InfinityKeyword): InfinityExpression;
1478+
function createToken(token: SyntaxKind.NaNKeyword): NaNExpression;
14751479
function createToken<TKind extends PunctuationSyntaxKind>(token: TKind): PunctuationToken<TKind>;
14761480
function createToken<TKind extends KeywordTypeSyntaxKind>(token: TKind): KeywordTypeNode<TKind>;
14771481
function createToken<TKind extends ModifierSyntaxKind>(token: TKind): ModifierToken<TKind>;

src/compiler/factory/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,8 @@ export function isLiteralTypeLikeExpression(node: Node): node is NullLiteral | B
11511151
return kind === SyntaxKind.NullKeyword
11521152
|| kind === SyntaxKind.TrueKeyword
11531153
|| kind === SyntaxKind.FalseKeyword
1154+
|| kind === SyntaxKind.InfinityKeyword
1155+
|| kind === SyntaxKind.NaNKeyword
11541156
|| isLiteralExpression(node)
11551157
|| isPrefixUnaryExpression(node);
11561158
}

src/compiler/parser.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ import {
125125
IndexedAccessTypeNode,
126126
IndexSignatureDeclaration,
127127
InferTypeNode,
128+
InfinityOrNaNExpression,
128129
InterfaceDeclaration,
129130
IntersectionTypeNode,
130131
isArray,
@@ -4397,8 +4398,8 @@ namespace Parser {
43974398
nextToken();
43984399
}
43994400
let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression =
4400-
token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ?
4401-
parseTokenNode<BooleanLiteral | NullLiteral>() :
4401+
token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword || token() === SyntaxKind.InfinityKeyword || token() === SyntaxKind.NaNKeyword ?
4402+
parseTokenNode<BooleanLiteral | NullLiteral | InfinityOrNaNExpression>() :
44024403
parseLiteralLikeNode(token()) as LiteralExpression;
44034404
if (negative) {
44044405
expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos);
@@ -4450,7 +4451,7 @@ namespace Parser {
44504451

44514452
function nextTokenIsNumericOrBigIntLiteral() {
44524453
nextToken();
4453-
return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral;
4454+
return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.InfinityKeyword || token() === SyntaxKind.NaNKeyword;
44544455
}
44554456

44564457
function parseNonArrayType(): TypeNode {
@@ -4490,6 +4491,8 @@ namespace Parser {
44904491
case SyntaxKind.TrueKeyword:
44914492
case SyntaxKind.FalseKeyword:
44924493
case SyntaxKind.NullKeyword:
4494+
case SyntaxKind.InfinityKeyword:
4495+
case SyntaxKind.NaNKeyword:
44934496
return parseLiteralTypeNode();
44944497
case SyntaxKind.MinusToken:
44954498
return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference();

0 commit comments

Comments
 (0)