diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/dynamic.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/dynamic.ts index ddcaf246b2..a0553d2b78 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/dynamic.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/dynamic.ts @@ -46,6 +46,13 @@ export const enum DynamicValueReason { */ UNKNOWN_IDENTIFIER, + /** + * A value could be resolved, but is not an acceptable type for the operation being performed. + * + * For example, attempting to call a non-callable expression. + */ + INVALID_EXPRESSION_TYPE, + /** * A value could not be determined statically for any reason other the above. */ @@ -55,7 +62,7 @@ export const enum DynamicValueReason { /** * Represents a value which cannot be determined statically. */ -export class DynamicValue { +export class DynamicValue { private constructor( readonly node: ts.Node, readonly reason: R, private code: DynamicValueReason) {} @@ -64,7 +71,7 @@ export class DynamicValue { } static fromDynamicString(node: ts.Node): DynamicValue { - return new DynamicValue(node, {}, DynamicValueReason.DYNAMIC_STRING); + return new DynamicValue(node, undefined, DynamicValueReason.DYNAMIC_STRING); } static fromExternalReference(node: ts.Node, ref: Reference): @@ -73,15 +80,19 @@ export class DynamicValue { } static fromUnknownExpressionType(node: ts.Node): DynamicValue { - return new DynamicValue(node, {}, DynamicValueReason.UNKNOWN_EXPRESSION_TYPE); + return new DynamicValue(node, undefined, DynamicValueReason.UNKNOWN_EXPRESSION_TYPE); } static fromUnknownIdentifier(node: ts.Identifier): DynamicValue { - return new DynamicValue(node, {}, DynamicValueReason.UNKNOWN_IDENTIFIER); + return new DynamicValue(node, undefined, DynamicValueReason.UNKNOWN_IDENTIFIER); + } + + static fromInvalidExpressionType(node: ts.Node, value: unknown): DynamicValue { + return new DynamicValue(node, value, DynamicValueReason.INVALID_EXPRESSION_TYPE); } static fromUnknown(node: ts.Node): DynamicValue { - return new DynamicValue(node, {}, DynamicValueReason.UNKNOWN); + return new DynamicValue(node, undefined, DynamicValueReason.UNKNOWN); } isFromDynamicInput(this: DynamicValue): this is DynamicValue { @@ -104,6 +115,10 @@ export class DynamicValue { return this.code === DynamicValueReason.UNKNOWN_IDENTIFIER; } + isFromInvalidExpressionType(this: DynamicValue): this is DynamicValue { + return this.code === DynamicValueReason.INVALID_EXPRESSION_TYPE; + } + isFromUnknown(this: DynamicValue): this is DynamicValue { return this.code === DynamicValueReason.UNKNOWN; } diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts index 8a3f662780..b4fafda882 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts @@ -86,6 +86,7 @@ export class StaticInterpreter { } private visitExpression(node: ts.Expression, context: Context): ResolvedValue { + let result: ResolvedValue; if (node.kind === ts.SyntaxKind.TrueKeyword) { return true; } else if (node.kind === ts.SyntaxKind.FalseKeyword) { @@ -95,38 +96,42 @@ export class StaticInterpreter { } else if (ts.isNoSubstitutionTemplateLiteral(node)) { return node.text; } else if (ts.isTemplateExpression(node)) { - return this.visitTemplateExpression(node, context); + result = this.visitTemplateExpression(node, context); } else if (ts.isNumericLiteral(node)) { return parseFloat(node.text); } else if (ts.isObjectLiteralExpression(node)) { - return this.visitObjectLiteralExpression(node, context); + result = this.visitObjectLiteralExpression(node, context); } else if (ts.isIdentifier(node)) { - return this.visitIdentifier(node, context); + result = this.visitIdentifier(node, context); } else if (ts.isPropertyAccessExpression(node)) { - return this.visitPropertyAccessExpression(node, context); + result = this.visitPropertyAccessExpression(node, context); } else if (ts.isCallExpression(node)) { - return this.visitCallExpression(node, context); + result = this.visitCallExpression(node, context); } else if (ts.isConditionalExpression(node)) { - return this.visitConditionalExpression(node, context); + result = this.visitConditionalExpression(node, context); } else if (ts.isPrefixUnaryExpression(node)) { - return this.visitPrefixUnaryExpression(node, context); + result = this.visitPrefixUnaryExpression(node, context); } else if (ts.isBinaryExpression(node)) { - return this.visitBinaryExpression(node, context); + result = this.visitBinaryExpression(node, context); } else if (ts.isArrayLiteralExpression(node)) { - return this.visitArrayLiteralExpression(node, context); + result = this.visitArrayLiteralExpression(node, context); } else if (ts.isParenthesizedExpression(node)) { - return this.visitParenthesizedExpression(node, context); + result = this.visitParenthesizedExpression(node, context); } else if (ts.isElementAccessExpression(node)) { - return this.visitElementAccessExpression(node, context); + result = this.visitElementAccessExpression(node, context); } else if (ts.isAsExpression(node)) { - return this.visitExpression(node.expression, context); + result = this.visitExpression(node.expression, context); } else if (ts.isNonNullExpression(node)) { - return this.visitExpression(node.expression, context); + result = this.visitExpression(node.expression, context); } else if (this.host.isClass(node)) { - return this.visitDeclaration(node, context); + result = this.visitDeclaration(node, context); } else { return DynamicValue.fromUnknownExpressionType(node); } + if (result instanceof DynamicValue && result.node !== node) { + return DynamicValue.fromDynamicInput(node, result); + } + return result; } private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression, context: Context): @@ -211,6 +216,8 @@ export class StaticInterpreter { this.visitDeclaration(decl.node, {...context, ...joinModuleContext(context, node, decl)}); if (result instanceof Reference) { result.addIdentifier(node); + } else if (result instanceof DynamicValue) { + return DynamicValue.fromDynamicInput(node, result); } return result; } @@ -365,10 +372,9 @@ export class StaticInterpreter { } if (!(lhs instanceof Reference)) { - throw new Error(`attempting to call something that is not a function: ${lhs}`); + return DynamicValue.fromInvalidExpressionType(node.expression, lhs); } else if (!isFunctionOrMethodReference(lhs)) { - throw new Error( - `calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]} (${node.getText()})`); + return DynamicValue.fromInvalidExpressionType(node.expression, lhs); } const fn = this.host.getDefinitionOfFunction(lhs.node); diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/result.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/result.ts index 8f9dcdf56d..bfcc2cd007 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/result.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/result.ts @@ -21,7 +21,7 @@ import {DynamicValue} from './dynamic'; * available statically. */ export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue | - ResolvedValueArray | ResolvedValueMap | BuiltinFn | DynamicValue<{}>; + ResolvedValueArray | ResolvedValueMap | BuiltinFn | DynamicValue; /** * An array of `ResolvedValue`s. diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index ee3b2c83c4..b205fc67ca 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import {Reference} from '../../imports'; import {TypeScriptReflectionHost} from '../../reflection'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; +import {DynamicValue} from '../src/dynamic'; import {PartialEvaluator} from '../src/interface'; import {EnumValue, ResolvedValue} from '../src/result'; @@ -281,6 +282,31 @@ describe('ngtsc metadata', () => { const resolved = evaluator.evaluate(prop.name); expect(resolved).toBe(42); }); + + it('should resolve dynamic values in object literals', () => { + const {program} = makeProgram([ + {name: 'decl.d.ts', contents: 'export declare const fn: any;'}, + { + name: 'entry.ts', + contents: `import {fn} from './decl'; const prop = fn.foo(); const target$ = {value: prop};` + }, + ]); + const checker = program.getTypeChecker(); + const reflectionHost = new TypeScriptReflectionHost(checker); + const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration); + const expr = result.initializer !as ts.ObjectLiteralExpression; + const evaluator = new PartialEvaluator(reflectionHost, checker); + const resolved = evaluator.evaluate(expr); + if (!(resolved instanceof Map)) { + return fail('Should have resolved to a Map'); + } + const value = resolved.get('value') !; + if (!(value instanceof DynamicValue)) { + return fail(`Should have resolved 'value' to a DynamicValue`); + } + const prop = expr.properties[0] as ts.PropertyAssignment; + expect(value.node).toBe(prop.initializer); + }); }); function owningModuleOf(ref: Reference): string|null {