feat(ivy): static evaluation of TypeScript's __spread helper (#30492)

The usage of array spread syntax in source code may be downleveled to a
call to TypeScript's `__spread` helper function from `tslib`, depending
on the options `downlevelIteration` and `emitHelpers`. This proves
problematic for ngcc when it is processing ES5 formats, as the static
evaluator won't be able to interpret those calls.

A custom foreign function resolver is not sufficient in this case, as
`tslib` may be emitted into the library code itself. In that case, a
helper function can be resolved to an actual function with body, such
that it won't be considered as foreign function. Instead, a reflection
host can now indicate that the definition of a function corresponds with
a certain TypeScript helper, such that it becomes statically evaluable
in ngtsc.

Resolves #30299

PR Close #30492
This commit is contained in:
JoostK
2019-05-15 21:10:47 +02:00
committed by Igor Minar
parent c0386757b1
commit 9d9c9e43e5
10 changed files with 231 additions and 31 deletions

View File

@ -1329,7 +1329,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return null;
}
const declaration = implementation;
const body = this.getDefinitionOfFunction(declaration).body;
const definition = this.getDefinitionOfFunction(declaration);
if (definition === null) {
return null;
}
const body = definition.body;
const lastStatement = body && body[body.length - 1];
const returnExpression =
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
import {getNameText, hasNameIdentifier} from '../utils';
@ -148,8 +148,28 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
* @param node the function declaration to parse.
* @returns an object containing the node, statements and parameters of the function.
*/
getDefinitionOfFunction<T extends ts.FunctionDeclaration|ts.MethodDeclaration|
ts.FunctionExpression>(node: T): FunctionDefinition<T> {
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
if (!ts.isFunctionDeclaration(node) && !ts.isMethodDeclaration(node) &&
!ts.isFunctionExpression(node) && !ts.isVariableDeclaration(node)) {
return null;
}
const tsHelperFn = getTsHelperFn(node);
if (tsHelperFn !== null) {
return {
node,
body: null,
helper: tsHelperFn,
parameters: [],
};
}
// If the node was not identified to be a TypeScript helper, a variable declaration at this
// point cannot be resolved as a function.
if (ts.isVariableDeclaration(node)) {
return null;
}
const parameters =
node.parameters.map(p => ({name: getNameText(p.name), node: p, initializer: null}));
let lookingForParamInitializers = true;
@ -161,7 +181,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return !lookingForParamInitializers;
});
return {node, body: statements || null, parameters};
return {node, body: statements || null, helper: null, parameters};
}
/**
@ -565,6 +585,21 @@ function reflectArrayElement(element: ts.Expression) {
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
}
/**
* Inspects a function declaration to determine if it corresponds with a TypeScript helper function,
* returning its kind if so or null if the declaration does not seem to correspond with such a
* helper.
*/
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
if (name === '__spread') {
return TsHelperFn.Spread;
} else {
return null;
}
}
/**
* A constructor function may have been "synthesized" by TypeScript during JavaScript emit,
* in the case no user-defined constructor exists and e.g. property initializers are used.