From a23a0bc3a446ec6834de5f2deb162c885292dc71 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 27 Feb 2019 15:32:38 -0800 Subject: [PATCH] feat(ivy): support tracking the provenance of DynamicValue (#29033) DynamicValues are generated whenever a partially evaluated expression is unable to be resolved statically. They contain a reference to the ts.Node which wasn't resolvable. They can also be nested. For example, the expression 'a + b' is resolvable only if 'a' and 'b' are themselves resolvable. If either 'a' or 'b' resolve to a DynamicValue, the whole expression must also resolve to a DynamicValue. Previously, if 'a' resolved to a DynamicValue, the entire expression might have been resolved to the same DynamicValue. This correctly indicated that the expression wasn't resolvable, but didn't return a reference to the shallow node that couldn't be resolved (the expression 'a + b'), only a reference to the deep node that couldn't be resolved ('a'). In certain situations, it's very useful to know the shallow unresolvable node (for example, to use it verbatim in the output). To support this, the partial evaluator is updated to always wrap DynamicValue to point to each unresolvable expression as it's processed, ensuring the receiver can determine exactly which expression node failed to resolve. PR Close #29033 --- .../ngtsc/partial_evaluator/src/dynamic.ts | 25 +++++++++--- .../partial_evaluator/src/interpreter.ts | 40 +++++++++++-------- .../src/ngtsc/partial_evaluator/src/result.ts | 2 +- .../partial_evaluator/test/evaluator_spec.ts | 26 ++++++++++++ 4 files changed, 70 insertions(+), 23 deletions(-) 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 {