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:
@ -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;
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user