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

@ -1240,7 +1240,7 @@ describe('Esm2015ReflectionHost', () => {
const fooNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
const fooDef = host.getDefinitionOfFunction(fooNode);
const fooDef = host.getDefinitionOfFunction(fooNode) !;
expect(fooDef.node).toBe(fooNode);
expect(fooDef.body !.length).toEqual(1);
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
@ -1250,7 +1250,7 @@ describe('Esm2015ReflectionHost', () => {
const barNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
const barDef = host.getDefinitionOfFunction(barNode);
const barDef = host.getDefinitionOfFunction(barNode) !;
expect(barDef.node).toBe(barNode);
expect(barDef.body !.length).toEqual(1);
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
@ -1263,7 +1263,7 @@ describe('Esm2015ReflectionHost', () => {
const bazNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
const bazDef = host.getDefinitionOfFunction(bazNode);
const bazDef = host.getDefinitionOfFunction(bazNode) !;
expect(bazDef.node).toBe(bazNode);
expect(bazDef.body !.length).toEqual(3);
expect(bazDef.parameters.length).toEqual(1);
@ -1272,7 +1272,7 @@ describe('Esm2015ReflectionHost', () => {
const quxNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
const quxDef = host.getDefinitionOfFunction(quxNode);
const quxDef = host.getDefinitionOfFunction(quxNode) !;
expect(quxDef.node).toBe(quxNode);
expect(quxDef.body !.length).toEqual(2);
expect(quxDef.parameters.length).toEqual(1);
@ -1281,14 +1281,14 @@ describe('Esm2015ReflectionHost', () => {
const mooNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'moo', isNamedFunctionDeclaration) !;
const mooDef = host.getDefinitionOfFunction(mooNode);
const mooDef = host.getDefinitionOfFunction(mooNode) !;
expect(mooDef.node).toBe(mooNode);
expect(mooDef.body !.length).toEqual(3);
expect(mooDef.parameters).toEqual([]);
const juuNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'juu', isNamedFunctionDeclaration) !;
const juuDef = host.getDefinitionOfFunction(juuNode);
const juuDef = host.getDefinitionOfFunction(juuNode) !;
expect(juuDef.node).toBe(juuNode);
expect(juuDef.body !.length).toEqual(2);
expect(juuDef.parameters).toEqual([]);

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript';
import {ClassMemberKind, CtorParameter, Decorator, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, CtorParameter, Decorator, Import, TsHelperFn, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
import {MockLogger} from '../helpers/mock_logger';
@ -1409,7 +1409,7 @@ describe('Esm5ReflectionHost', () => {
const fooNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
const fooDef = host.getDefinitionOfFunction(fooNode);
const fooDef = host.getDefinitionOfFunction(fooNode) !;
expect(fooDef.node).toBe(fooNode);
expect(fooDef.body !.length).toEqual(1);
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
@ -1419,7 +1419,7 @@ describe('Esm5ReflectionHost', () => {
const barNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
const barDef = host.getDefinitionOfFunction(barNode);
const barDef = host.getDefinitionOfFunction(barNode) !;
expect(barDef.node).toBe(barNode);
expect(barDef.body !.length).toEqual(1);
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
@ -1432,7 +1432,7 @@ describe('Esm5ReflectionHost', () => {
const bazNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
const bazDef = host.getDefinitionOfFunction(bazNode);
const bazDef = host.getDefinitionOfFunction(bazNode) !;
expect(bazDef.node).toBe(bazNode);
expect(bazDef.body !.length).toEqual(3);
expect(bazDef.parameters.length).toEqual(1);
@ -1441,13 +1441,51 @@ describe('Esm5ReflectionHost', () => {
const quxNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
const quxDef = host.getDefinitionOfFunction(quxNode);
const quxDef = host.getDefinitionOfFunction(quxNode) !;
expect(quxDef.node).toBe(quxNode);
expect(quxDef.body !.length).toEqual(2);
expect(quxDef.parameters.length).toEqual(1);
expect(quxDef.parameters[0].name).toEqual('x');
expect(quxDef.parameters[0].initializer).toBe(null);
});
it('should recognize TypeScript __spread helper function declaration', () => {
const file = {
name: 'declaration.d.ts',
contents: `export declare function __spread(...args: any[]): any[];`,
};
const program = makeTestProgram(file);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const node = getDeclaration(program, file.name, '__spread', isNamedFunctionDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Spread);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __spread helper function implementation', () => {
const file = {
name: 'implementation.js',
contents: `
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};`,
};
const program = makeTestProgram(file);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const node = getDeclaration(program, file.name, '__spread', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Spread);
expect(definition.parameters.length).toEqual(0);
});
});
describe('getImportOfIdentifier()', () => {

View File

@ -1380,7 +1380,7 @@ describe('UmdReflectionHost', () => {
const fooNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
const fooDef = host.getDefinitionOfFunction(fooNode);
const fooDef = host.getDefinitionOfFunction(fooNode) !;
expect(fooDef.node).toBe(fooNode);
expect(fooDef.body !.length).toEqual(1);
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
@ -1390,7 +1390,7 @@ describe('UmdReflectionHost', () => {
const barNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
const barDef = host.getDefinitionOfFunction(barNode);
const barDef = host.getDefinitionOfFunction(barNode) !;
expect(barDef.node).toBe(barNode);
expect(barDef.body !.length).toEqual(1);
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
@ -1403,7 +1403,7 @@ describe('UmdReflectionHost', () => {
const bazNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
const bazDef = host.getDefinitionOfFunction(bazNode);
const bazDef = host.getDefinitionOfFunction(bazNode) !;
expect(bazDef.node).toBe(bazNode);
expect(bazDef.body !.length).toEqual(3);
expect(bazDef.parameters.length).toEqual(1);
@ -1412,7 +1412,7 @@ describe('UmdReflectionHost', () => {
const quxNode =
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
const quxDef = host.getDefinitionOfFunction(quxNode);
const quxDef = host.getDefinitionOfFunction(quxNode) !;
expect(quxDef.node).toBe(quxNode);
expect(quxDef.body !.length).toEqual(2);
expect(quxDef.parameters.length).toEqual(1);