fix(compiler): resolve enum values in binary operations (#36461)
During static evaluation of expressions, the partial evaluator may come across a binary + operator for which it needs to evaluate its operands. Any of these operands may be a reference to an enum member, in which case the enum member's value needs to be used as literal value, not the enum member reference itself. This commit fixes the behavior by resolving an `EnumValue` when used as a literal value. Fixes #35584 Resolves FW-1951 PR Close #36461
This commit is contained in:
parent
f9f6e2e1b3
commit
64022f51d4
|
@ -203,19 +203,13 @@ export class StaticInterpreter {
|
||||||
const pieces: string[] = [node.head.text];
|
const pieces: string[] = [node.head.text];
|
||||||
for (let i = 0; i < node.templateSpans.length; i++) {
|
for (let i = 0; i < node.templateSpans.length; i++) {
|
||||||
const span = node.templateSpans[i];
|
const span = node.templateSpans[i];
|
||||||
let value = this.visit(span.expression, context);
|
const value = literal(
|
||||||
if (value instanceof EnumValue) {
|
this.visit(span.expression, context),
|
||||||
value = value.resolved;
|
() => DynamicValue.fromDynamicString(span.expression));
|
||||||
}
|
if (value instanceof DynamicValue) {
|
||||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ||
|
|
||||||
value == null) {
|
|
||||||
pieces.push(`${value}`);
|
|
||||||
} else if (value instanceof DynamicValue) {
|
|
||||||
return DynamicValue.fromDynamicInput(node, value);
|
return DynamicValue.fromDynamicInput(node, value);
|
||||||
} else {
|
|
||||||
return DynamicValue.fromDynamicInput(node, DynamicValue.fromDynamicString(span.expression));
|
|
||||||
}
|
}
|
||||||
pieces.push(span.literal.text);
|
pieces.push(`${value}`, span.literal.text);
|
||||||
}
|
}
|
||||||
return pieces.join('');
|
return pieces.join('');
|
||||||
}
|
}
|
||||||
|
@ -520,8 +514,12 @@ export class StaticInterpreter {
|
||||||
const opRecord = BINARY_OPERATORS.get(tokenKind)!;
|
const opRecord = BINARY_OPERATORS.get(tokenKind)!;
|
||||||
let lhs: ResolvedValue, rhs: ResolvedValue;
|
let lhs: ResolvedValue, rhs: ResolvedValue;
|
||||||
if (opRecord.literal) {
|
if (opRecord.literal) {
|
||||||
lhs = literal(this.visitExpression(node.left, context), node.left);
|
lhs = literal(
|
||||||
rhs = literal(this.visitExpression(node.right, context), node.right);
|
this.visitExpression(node.left, context),
|
||||||
|
value => DynamicValue.fromInvalidExpressionType(node.left, value));
|
||||||
|
rhs = literal(
|
||||||
|
this.visitExpression(node.right, context),
|
||||||
|
value => DynamicValue.fromInvalidExpressionType(node.right, value));
|
||||||
} else {
|
} else {
|
||||||
lhs = this.visitExpression(node.left, context);
|
lhs = this.visitExpression(node.left, context);
|
||||||
rhs = this.visitExpression(node.right, context);
|
rhs = this.visitExpression(node.right, context);
|
||||||
|
@ -585,12 +583,16 @@ function isFunctionOrMethodReference(ref: Reference<ts.Node>):
|
||||||
ts.isFunctionExpression(ref.node);
|
ts.isFunctionExpression(ref.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
function literal(value: ResolvedValue, node: ts.Node): any {
|
function literal(
|
||||||
|
value: ResolvedValue, reject: (value: ResolvedValue) => ResolvedValue): ResolvedValue {
|
||||||
|
if (value instanceof EnumValue) {
|
||||||
|
value = value.resolved;
|
||||||
|
}
|
||||||
if (value instanceof DynamicValue || value === null || value === undefined ||
|
if (value instanceof DynamicValue || value === null || value === undefined ||
|
||||||
typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
return DynamicValue.fromInvalidExpressionType(node, value);
|
return reject(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVariableDeclarationDeclared(node: ts.VariableDeclaration): boolean {
|
function isVariableDeclarationDeclared(node: ts.VariableDeclaration): boolean {
|
||||||
|
|
|
@ -448,6 +448,31 @@ runInEachFileSystem(() => {
|
||||||
expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345');
|
expect(evaluate('const a = 2, b = 4;', '`1${a}3${b}5`')).toEqual('12345');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('template expressions should resolve enums', () => {
|
||||||
|
expect(evaluate('enum Test { VALUE = "test" };', '`a.${Test.VALUE}.b`')).toBe('a.test.b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('string concatenation should resolve enums', () => {
|
||||||
|
expect(evaluate('enum Test { VALUE = "test" };', '"a." + Test.VALUE + ".b"'))
|
||||||
|
.toBe('a.test.b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve non-literals as dynamic string', () => {
|
||||||
|
const value = evaluate(`const a: any = [];`, '`a.${a}.b`');
|
||||||
|
|
||||||
|
if (!(value instanceof DynamicValue)) {
|
||||||
|
return fail(`Should have resolved to a DynamicValue`);
|
||||||
|
}
|
||||||
|
expect(value.node.getText()).toEqual('`a.${a}.b`');
|
||||||
|
|
||||||
|
if (!value.isFromDynamicInput()) {
|
||||||
|
return fail('Should originate from dynamic input');
|
||||||
|
} else if (!value.reason.isFromDynamicString()) {
|
||||||
|
return fail('Should refer to a dynamic string part');
|
||||||
|
}
|
||||||
|
expect(value.reason.node.getText()).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
it('enum resolution works', () => {
|
it('enum resolution works', () => {
|
||||||
const result = evaluate(
|
const result = evaluate(
|
||||||
`
|
`
|
||||||
|
@ -512,12 +537,6 @@ runInEachFileSystem(() => {
|
||||||
expect(value.node).toBe(prop.initializer);
|
expect(value.node).toBe(prop.initializer);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve enums in template expressions', () => {
|
|
||||||
const value =
|
|
||||||
evaluate(`enum Test { VALUE = 'test', } const value = \`a.\${Test.VALUE}.b\`;`, 'value');
|
|
||||||
expect(value).toBe('a.test.b');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not attach identifiers to FFR-resolved values', () => {
|
it('should not attach identifiers to FFR-resolved values', () => {
|
||||||
const value = evaluate(
|
const value = evaluate(
|
||||||
`
|
`
|
||||||
|
|
Loading…
Reference in New Issue