Skip to content

Commit fd388f7

Browse files
Refactor expression evaluator (#57955)
1 parent bc7e538 commit fd388f7

File tree

3 files changed

+140
-113
lines changed

3 files changed

+140
-113
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
createDiagnosticForNodeFromMessageChain,
110110
createDiagnosticMessageChainFromDiagnostic,
111111
createEmptyExports,
112+
createEvaluator,
112113
createFileDiagnostic,
113114
createGetCanonicalFileName,
114115
createGetSymbolWalker,
@@ -1477,6 +1478,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
14771478
var checkBinaryExpression = createCheckBinaryExpression();
14781479
var emitResolver = createResolver();
14791480
var nodeBuilder = createNodeBuilder();
1481+
var evaluate = createEvaluator({
1482+
evaluateElementAccessExpression,
1483+
evaluateEntityNameExpression,
1484+
});
14801485

14811486
var globals = createSymbolTable();
14821487
var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String);
@@ -39331,7 +39336,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3933139336
if (isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType)) {
3933239337
return getTemplateLiteralType(texts, types);
3933339338
}
39334-
const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluateTemplateExpression(node);
39339+
const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluate(node);
3933539340
return evaluated ? getFreshTypeOfLiteralType(getStringLiteralType(evaluated)) : stringType;
3933639341
}
3933739342

@@ -45828,108 +45833,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4582845833
return false;
4582945834
}
4583045835

45831-
function evaluate(expr: Expression, location?: Declaration): string | number | undefined {
45832-
switch (expr.kind) {
45833-
case SyntaxKind.PrefixUnaryExpression:
45834-
const value = evaluate((expr as PrefixUnaryExpression).operand, location);
45835-
if (typeof value === "number") {
45836-
switch ((expr as PrefixUnaryExpression).operator) {
45837-
case SyntaxKind.PlusToken:
45838-
return value;
45839-
case SyntaxKind.MinusToken:
45840-
return -value;
45841-
case SyntaxKind.TildeToken:
45842-
return ~value;
45843-
}
45844-
}
45845-
break;
45846-
case SyntaxKind.BinaryExpression:
45847-
const left = evaluate((expr as BinaryExpression).left, location);
45848-
const right = evaluate((expr as BinaryExpression).right, location);
45849-
if (typeof left === "number" && typeof right === "number") {
45850-
switch ((expr as BinaryExpression).operatorToken.kind) {
45851-
case SyntaxKind.BarToken:
45852-
return left | right;
45853-
case SyntaxKind.AmpersandToken:
45854-
return left & right;
45855-
case SyntaxKind.GreaterThanGreaterThanToken:
45856-
return left >> right;
45857-
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
45858-
return left >>> right;
45859-
case SyntaxKind.LessThanLessThanToken:
45860-
return left << right;
45861-
case SyntaxKind.CaretToken:
45862-
return left ^ right;
45863-
case SyntaxKind.AsteriskToken:
45864-
return left * right;
45865-
case SyntaxKind.SlashToken:
45866-
return left / right;
45867-
case SyntaxKind.PlusToken:
45868-
return left + right;
45869-
case SyntaxKind.MinusToken:
45870-
return left - right;
45871-
case SyntaxKind.PercentToken:
45872-
return left % right;
45873-
case SyntaxKind.AsteriskAsteriskToken:
45874-
return left ** right;
45875-
}
45876-
}
45877-
else if (
45878-
(typeof left === "string" || typeof left === "number") &&
45879-
(typeof right === "string" || typeof right === "number") &&
45880-
(expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken
45881-
) {
45882-
return "" + left + right;
45883-
}
45884-
break;
45885-
case SyntaxKind.StringLiteral:
45886-
case SyntaxKind.NoSubstitutionTemplateLiteral:
45887-
return (expr as StringLiteralLike).text;
45888-
case SyntaxKind.TemplateExpression:
45889-
return evaluateTemplateExpression(expr as TemplateExpression, location);
45890-
case SyntaxKind.NumericLiteral:
45891-
checkGrammarNumericLiteral(expr as NumericLiteral);
45892-
return +(expr as NumericLiteral).text;
45893-
case SyntaxKind.ParenthesizedExpression:
45894-
return evaluate((expr as ParenthesizedExpression).expression, location);
45895-
case SyntaxKind.Identifier: {
45896-
const identifier = expr as Identifier;
45897-
if (isInfinityOrNaNString(identifier.escapedText) && (resolveEntityName(identifier, SymbolFlags.Value, /*ignoreErrors*/ true) === getGlobalSymbol(identifier.escapedText, SymbolFlags.Value, /*diagnostic*/ undefined))) {
45898-
return +(identifier.escapedText);
45899-
}
45900-
// falls through
45836+
function evaluateEntityNameExpression(expr: EntityNameExpression, location?: Declaration) {
45837+
const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true);
45838+
if (!symbol) return undefined;
45839+
45840+
if (expr.kind === SyntaxKind.Identifier) {
45841+
const identifier = expr;
45842+
if (isInfinityOrNaNString(identifier.escapedText) && (symbol === getGlobalSymbol(identifier.escapedText, SymbolFlags.Value, /*diagnostic*/ undefined))) {
45843+
return +(identifier.escapedText);
4590145844
}
45902-
case SyntaxKind.PropertyAccessExpression:
45903-
if (isEntityNameExpression(expr)) {
45904-
const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true);
45905-
if (symbol) {
45906-
if (symbol.flags & SymbolFlags.EnumMember) {
45907-
return location ? evaluateEnumMember(expr, symbol, location) : getEnumMemberValue(symbol.valueDeclaration as EnumMember);
45908-
}
45909-
if (isConstantVariable(symbol)) {
45910-
const declaration = symbol.valueDeclaration;
45911-
if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && (!location || declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location))) {
45912-
return evaluate(declaration.initializer, declaration);
45913-
}
45914-
}
45915-
}
45916-
}
45917-
break;
45918-
case SyntaxKind.ElementAccessExpression:
45919-
const root = (expr as ElementAccessExpression).expression;
45920-
if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) {
45921-
const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true);
45922-
if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) {
45923-
const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text);
45924-
const member = rootSymbol.exports!.get(name);
45925-
if (member) {
45926-
return location ? evaluateEnumMember(expr, member, location) : getEnumMemberValue(member.valueDeclaration as EnumMember);
45927-
}
45928-
}
45845+
}
45846+
45847+
if (symbol.flags & SymbolFlags.EnumMember) {
45848+
return location ? evaluateEnumMember(expr, symbol, location) : getEnumMemberValue(symbol.valueDeclaration as EnumMember);
45849+
}
45850+
if (isConstantVariable(symbol)) {
45851+
const declaration = symbol.valueDeclaration;
45852+
if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && (!location || declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location))) {
45853+
return evaluate(declaration.initializer, declaration);
45854+
}
45855+
}
45856+
}
45857+
45858+
function evaluateElementAccessExpression(expr: ElementAccessExpression, location?: Declaration) {
45859+
const root = expr.expression;
45860+
if (isEntityNameExpression(root) && isStringLiteralLike(expr.argumentExpression)) {
45861+
const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true);
45862+
if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) {
45863+
const name = escapeLeadingUnderscores(expr.argumentExpression.text);
45864+
const member = rootSymbol.exports!.get(name);
45865+
if (member) {
45866+
return location ? evaluateEnumMember(expr, member, location) : getEnumMemberValue(member.valueDeclaration as EnumMember);
4592945867
}
45930-
break;
45868+
}
4593145869
}
45932-
return undefined;
4593345870
}
4593445871

4593545872
function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) {
@@ -45945,19 +45882,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4594545882
return getEnumMemberValue(declaration as EnumMember);
4594645883
}
4594745884

45948-
function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration) {
45949-
let result = expr.head.text;
45950-
for (const span of expr.templateSpans) {
45951-
const value = evaluate(span.expression, location);
45952-
if (value === undefined) {
45953-
return undefined;
45954-
}
45955-
result += value;
45956-
result += span.literal.text;
45957-
}
45958-
return result;
45959-
}
45960-
4596145885
function checkEnumDeclaration(node: EnumDeclaration) {
4596245886
addLazyDiagnostic(() => checkEnumDeclarationWorker(node));
4596345887
}

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10065,3 +10065,9 @@ export interface Queue<T> {
1006510065
dequeue(): T;
1006610066
isEmpty(): boolean;
1006710067
}
10068+
10069+
/** @internal */
10070+
export interface EvaluationResolver {
10071+
evaluateEntityNameExpression(expr: EntityNameExpression, location: Declaration | undefined): string | number | undefined;
10072+
evaluateElementAccessExpression(expr: ElementAccessExpression, location: Declaration | undefined): string | number | undefined;
10073+
}

src/compiler/utilities.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import {
119119
EqualsToken,
120120
equateValues,
121121
escapeLeadingUnderscores,
122+
EvaluationResolver,
122123
every,
123124
ExportAssignment,
124125
ExportDeclaration,
@@ -511,6 +512,7 @@ import {
511512
SyntaxKind,
512513
SyntaxList,
513514
TaggedTemplateExpression,
515+
TemplateExpression,
514516
TemplateLiteral,
515517
TemplateLiteralLikeNode,
516518
TemplateLiteralToken,
@@ -10664,3 +10666,98 @@ export function isSyntacticallyString(expr: Expression): boolean {
1066410666
}
1066510667
return false;
1066610668
}
10669+
10670+
/** @internal */
10671+
export function createEvaluator({ evaluateElementAccessExpression, evaluateEntityNameExpression }: EvaluationResolver) {
10672+
function evaluate(expr: TemplateExpression, location?: Declaration): string;
10673+
function evaluate(expr: Expression, location?: Declaration): string | number | undefined;
10674+
function evaluate(expr: Expression, location?: Declaration): string | number | undefined {
10675+
switch (expr.kind) {
10676+
case SyntaxKind.PrefixUnaryExpression:
10677+
const value = evaluate((expr as PrefixUnaryExpression).operand, location);
10678+
if (typeof value === "number") {
10679+
switch ((expr as PrefixUnaryExpression).operator) {
10680+
case SyntaxKind.PlusToken:
10681+
return value;
10682+
case SyntaxKind.MinusToken:
10683+
return -value;
10684+
case SyntaxKind.TildeToken:
10685+
return ~value;
10686+
}
10687+
}
10688+
break;
10689+
case SyntaxKind.BinaryExpression:
10690+
const left = evaluate((expr as BinaryExpression).left, location);
10691+
const right = evaluate((expr as BinaryExpression).right, location);
10692+
if (typeof left === "number" && typeof right === "number") {
10693+
switch ((expr as BinaryExpression).operatorToken.kind) {
10694+
case SyntaxKind.BarToken:
10695+
return left | right;
10696+
case SyntaxKind.AmpersandToken:
10697+
return left & right;
10698+
case SyntaxKind.GreaterThanGreaterThanToken:
10699+
return left >> right;
10700+
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
10701+
return left >>> right;
10702+
case SyntaxKind.LessThanLessThanToken:
10703+
return left << right;
10704+
case SyntaxKind.CaretToken:
10705+
return left ^ right;
10706+
case SyntaxKind.AsteriskToken:
10707+
return left * right;
10708+
case SyntaxKind.SlashToken:
10709+
return left / right;
10710+
case SyntaxKind.PlusToken:
10711+
return left + right;
10712+
case SyntaxKind.MinusToken:
10713+
return left - right;
10714+
case SyntaxKind.PercentToken:
10715+
return left % right;
10716+
case SyntaxKind.AsteriskAsteriskToken:
10717+
return left ** right;
10718+
}
10719+
}
10720+
else if (
10721+
(typeof left === "string" || typeof left === "number") &&
10722+
(typeof right === "string" || typeof right === "number") &&
10723+
(expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken
10724+
) {
10725+
return "" + left + right;
10726+
}
10727+
break;
10728+
case SyntaxKind.StringLiteral:
10729+
case SyntaxKind.NoSubstitutionTemplateLiteral:
10730+
return (expr as StringLiteralLike).text;
10731+
case SyntaxKind.TemplateExpression:
10732+
return evaluateTemplateExpression(expr as TemplateExpression, location);
10733+
case SyntaxKind.NumericLiteral:
10734+
return +(expr as NumericLiteral).text;
10735+
case SyntaxKind.ParenthesizedExpression:
10736+
return evaluate((expr as ParenthesizedExpression).expression, location);
10737+
case SyntaxKind.Identifier:
10738+
return evaluateEntityNameExpression(expr as Identifier, location);
10739+
case SyntaxKind.PropertyAccessExpression:
10740+
if (isEntityNameExpression(expr)) {
10741+
return evaluateEntityNameExpression(expr, location);
10742+
}
10743+
break;
10744+
case SyntaxKind.ElementAccessExpression:
10745+
return evaluateElementAccessExpression(expr as ElementAccessExpression, location);
10746+
}
10747+
return undefined;
10748+
}
10749+
10750+
function evaluateTemplateExpression(expr: TemplateExpression, location?: Declaration) {
10751+
let result = expr.head.text;
10752+
for (const span of expr.templateSpans) {
10753+
const value = evaluate(span.expression, location);
10754+
if (value === undefined) {
10755+
return undefined;
10756+
}
10757+
result += value;
10758+
result += span.literal.text;
10759+
}
10760+
return result;
10761+
}
10762+
return evaluate;
10763+
}

0 commit comments

Comments
 (0)