Skip to content

Prototype: Numeric Literal Types #7480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
183 changes: 174 additions & 9 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,10 @@
"category": "Error",
"code": 2533
},
"Numeric literal expected.": {
"category": "Error",
"code": 2540
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
Expand Down
1 change: 1 addition & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5939,6 +5939,7 @@ const _super = (function (geti, seti) {
return;

case SyntaxKind.NumberKeyword:
case SyntaxKind.NumericLiteralType:
write("Number");
return;

Expand Down
34 changes: 33 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ namespace ts {
case SyntaxKind.TypeReference:
return visitNode(cbNode, (<TypeReferenceNode>node).typeName) ||
visitNodes(cbNodes, (<TypeReferenceNode>node).typeArguments);
case SyntaxKind.TypeUnaryPrefix:
return visitNode(cbNode, (<TypeUnaryPrefix>node).operand);
case SyntaxKind.TypePredicate:
return visitNode(cbNode, (<TypePredicateNode>node).parameterName) ||
visitNode(cbNode, (<TypePredicateNode>node).type);
Expand Down Expand Up @@ -1903,11 +1905,23 @@ namespace ts {
return <TemplateLiteralFragment>parseLiteralLikeNode(token, /*internName*/ false);
}

function parseNumericLiteralTypeNode(): NumericLiteralTypeNode {
const node = createNode(SyntaxKind.NumericLiteralType) as NumericLiteralTypeNode;
// Get token text for node text rather than value, this way number formatting is preserved
node.text = scanner.getTokenText();
// But store the number on the node because we need it for comparisons later
node.number = +(scanner.getTokenValue());
return finishLiteralLikeNode(node);
}

function parseLiteralLikeNode(kind: SyntaxKind, internName: boolean): LiteralLikeNode {
const node = <LiteralExpression>createNode(kind);
const text = scanner.getTokenValue();
node.text = internName ? internIdentifier(text) : text;
return finishLiteralLikeNode(node);
}

function finishLiteralLikeNode<T extends LiteralLikeNode>(node: T) {
if (scanner.hasExtendedUnicodeEscape()) {
node.hasExtendedUnicodeEscape = true;
}
Expand All @@ -1926,7 +1940,7 @@ namespace ts {
// never get a token like this. Instead, we would get 00 and 9 as two separate tokens.
// We also do not need to check for negatives because any prefix operator would be part of a
// parent unary expression.
if (node.kind === SyntaxKind.NumericLiteral
if ((node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.NumericLiteralType)
&& sourceText.charCodeAt(tokenPos) === CharacterCodes._0
&& isOctalDigit(sourceText.charCodeAt(tokenPos + 1))) {

Expand Down Expand Up @@ -2360,6 +2374,19 @@ namespace ts {
return token === SyntaxKind.DotToken ? undefined : node;
}

// We can't just add a `-` to the text of the literal, as this breaks on hex and octal literals (and returns NaN)
function parseSignedNumericLiteral(): TypeUnaryPrefix {
const node = createNode(SyntaxKind.TypeUnaryPrefix) as TypeUnaryPrefix;
node.operator = token;
nextToken();
// Since we don't allow reference types after a `-` or `+` (other than Infinity), we special case it here
if (!(token == SyntaxKind.Identifier && scanner.getTokenText() === (Infinity).toString())) {
parseExpected(SyntaxKind.NumericLiteral, Diagnostics.Numeric_literal_expected, /*shouldAdvance*/false);
}
node.operand = parseNumericLiteralTypeNode();
return finishNode(node);
}

function parseNonArrayType(): TypeNode {
switch (token) {
case SyntaxKind.AnyKeyword:
Expand All @@ -2373,6 +2400,8 @@ namespace ts {
return node || parseTypeReference();
case SyntaxKind.StringLiteral:
return parseStringLiteralTypeNode();
case SyntaxKind.NumericLiteral:
return parseNumericLiteralTypeNode();
case SyntaxKind.VoidKeyword:
case SyntaxKind.NullKeyword:
return parseTokenNode<TypeNode>();
Expand All @@ -2393,6 +2422,9 @@ namespace ts {
return parseTupleType();
case SyntaxKind.OpenParenToken:
return parseParenthesizedType();
case SyntaxKind.PlusToken:
case SyntaxKind.MinusToken:
return parseSignedNumericLiteral();
default:
return parseTypeReference();
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ namespace ts {
"&=": SyntaxKind.AmpersandEqualsToken,
"|=": SyntaxKind.BarEqualsToken,
"^=": SyntaxKind.CaretEqualsToken,
"@": SyntaxKind.AtToken,
"@": SyntaxKind.AtToken
};

/*
Expand Down
26 changes: 25 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ namespace ts {
ParenthesizedType,
ThisType,
StringLiteralType,
NumericLiteralType,
TypeUnaryPrefix,
// Binding patterns
ObjectBindingPattern,
ArrayBindingPattern,
Expand Down Expand Up @@ -782,6 +784,12 @@ namespace ts {
_stringLiteralTypeBrand: any;
}

// @kind(SyntaxKind.NumericLiteralTypeNode)
export interface NumericLiteralTypeNode extends LiteralLikeNode, TypeNode {
_numericLiteralTypeBrand: any;
number: number; // The value of the number
}

// @kind(SyntaxKind.StringLiteral)
export interface StringLiteral extends LiteralExpression {
_stringLiteralBrand: any;
Expand Down Expand Up @@ -812,10 +820,18 @@ namespace ts {

// @kind(SyntaxKind.PrefixUnaryExpression)
export interface PrefixUnaryExpression extends IncrementExpression {
_prefixUnaryExpressionBrand: any;
operator: SyntaxKind;
operand: UnaryExpression;
}

// @kind(SyntaxKind.TypeUnaryPrefix)
export interface TypeUnaryPrefix extends TypeNode {
_typeUnaryPrefixBrand: any;
operator: SyntaxKind;
operand: NumericLiteralTypeNode;
}

// @kind(SyntaxKind.PostfixUnaryExpression)
export interface PostfixUnaryExpression extends IncrementExpression {
operand: LeftHandSideExpression;
Expand Down Expand Up @@ -1826,6 +1842,7 @@ namespace ts {
writePunctuation(text: string): void;
writeSpace(text: string): void;
writeStringLiteral(text: string): void;
writeNumericLiteral(text: string): void;
writeParameter(text: string): void;
writeSymbol(text: string, symbol: Symbol): void;
writeLine(): void;
Expand Down Expand Up @@ -2163,6 +2180,7 @@ namespace ts {
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
ThisType = 0x02000000, // This type
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties
NumericLiteral = 0x80000000, // Numeric literal types are specific numbers - just as string literal types are specific strings

/* @internal */
Nullable = Undefined | Null,
Expand All @@ -2171,7 +2189,7 @@ namespace ts {
/* @internal */
Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | StringLiteral | Enum,
StringLike = String | StringLiteral,
NumberLike = Number | Enum,
NumberLike = Number | Enum | NumericLiteral,
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,
Expand Down Expand Up @@ -2203,6 +2221,12 @@ namespace ts {
text: string; // Text of string literal
}

// Numeric literal types (TypeFlags.NumericLiteral)
export interface NumericLiteralType extends Type {
number: number;
text: string;
}

// Object types (TypeFlags.ObjectType)
export interface ObjectType extends Type { }

Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace ts {
writePunctuation: writeText,
writeSpace: writeText,
writeStringLiteral: writeText,
writeNumericLiteral: writeText,
writeParameter: writeText,
writeSymbol: writeText,

Expand Down
3 changes: 0 additions & 3 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
/// ECMAScript APIs
/////////////////////////////

declare const NaN: number;
declare const Infinity: number;

/**
* Evaluates JavaScript code and executes it.
* @param x A String value that contains valid JavaScript code.
Expand Down
1 change: 1 addition & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ namespace ts {
writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation),
writeSpace: text => writeKind(text, SymbolDisplayPartKind.space),
writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral),
writeNumericLiteral: text => writeKind(text, SymbolDisplayPartKind.numericLiteral),
writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName),
writeSymbol,
writeLine,
Expand Down
3 changes: 0 additions & 3 deletions tests/baselines/reference/1.0lib-noErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ and limitations under the License.
/// ECMAScript APIs
/////////////////////////////

declare var NaN: number;
declare var Infinity: number;

/**
* Evaluates JavaScript code and executes it.
* @param x A String value that contains valid JavaScript code.
Expand Down
Loading