Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ivy): avoid infinite recursion when evaluation source files #33772

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {isDeclaration} from '../../util/src/typescript';
import {ArrayConcatBuiltinFn, ArraySliceBuiltinFn} from './builtin';
import {DynamicValue} from './dynamic';
import {DependencyTracker, ForeignFunctionResolver} from './interface';
import {BuiltinFn, EnumValue, ResolvedValue, ResolvedValueArray, ResolvedValueMap} from './result';
import {BuiltinFn, EnumValue, ResolvedModule, ResolvedValue, ResolvedValueArray, ResolvedValueMap} from './result';
import {evaluateTsHelperInline} from './ts_helpers';


Expand Down Expand Up @@ -183,11 +183,14 @@ export class StaticInterpreter {
const spread = this.visitExpression(property.expression, context);
if (spread instanceof DynamicValue) {
return DynamicValue.fromDynamicInput(node, spread);
} else if (!(spread instanceof Map)) {
} else if (spread instanceof Map) {
spread.forEach((value, key) => map.set(key, value));
} else if (spread instanceof ResolvedModule) {
spread.getExports().forEach((value, key) => map.set(key, value));
} else {
return DynamicValue.fromDynamicInput(
node, DynamicValue.fromInvalidExpressionType(property, spread));
}
spread.forEach((value, key) => map.set(key, value));
} else {
return DynamicValue.fromUnknown(node);
}
Expand Down Expand Up @@ -323,19 +326,18 @@ export class StaticInterpreter {
if (declarations === null) {
return DynamicValue.fromUnknown(node);
}
const map = new Map<string, ResolvedValue>();
declarations.forEach((decl, name) => {

return new ResolvedModule(declarations, decl => {
const declContext = {
...context, ...joinModuleContext(context, node, decl),
};

// Visit both concrete and inline declarations.
// TODO(alxhub): remove cast once TS is upgraded in g3.
const value = decl.node !== null ?
return decl.node !== null ?
this.visitDeclaration(decl.node, declContext) :
this.visitExpression((decl as InlineDeclaration).expression, declContext);
map.set(name, value);
});
return map;
}

private accessHelper(
Expand All @@ -348,6 +350,8 @@ export class StaticInterpreter {
} else {
return undefined;
}
} else if (lhs instanceof ResolvedModule) {
return lhs.getExport(strIndex);
} else if (Array.isArray(lhs)) {
if (rhs === 'length') {
return lhs.length;
Expand Down
37 changes: 31 additions & 6 deletions packages/compiler-cli/src/ngtsc/partial_evaluator/src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import * as ts from 'typescript';

import {Reference} from '../../imports';
import {Declaration} from '../../reflection';

import {DynamicValue} from './dynamic';

Expand All @@ -21,23 +22,47 @@ import {DynamicValue} from './dynamic';
* available statically.
*/
export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue |
ResolvedValueArray | ResolvedValueMap | BuiltinFn | DynamicValue<unknown>;
ResolvedValueArray | ResolvedValueMap | ResolvedModule | BuiltinFn | DynamicValue<unknown>;

/**
* An array of `ResolvedValue`s.
*
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueArray`
* ->
* `ResolvedValue`.
* -> `ResolvedValue`.
*/
export interface ResolvedValueArray extends Array<ResolvedValue> {}

/**
* A map of strings to `ResolvedValue`s.
*
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueMap` ->
* `ResolvedValue`.
*/ export interface ResolvedValueMap extends Map<string, ResolvedValue> {}
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueMap`
* -> `ResolvedValue`.
*/
export interface ResolvedValueMap extends Map<string, ResolvedValue> {}

/**
* A collection of publicly exported declarations from a module. Each declaration is evaluated
* lazily upon request.
*/
export class ResolvedModule {
constructor(
private exports: Map<string, Declaration>,
private evaluate: (decl: Declaration) => ResolvedValue) {}

getExport(name: string): ResolvedValue {
if (!this.exports.has(name)) {
return undefined;
}

return this.evaluate(this.exports.get(name) !);
}

getExports(): ResolvedValueMap {
const map = new Map<string, ResolvedValue>();
this.exports.forEach((decl, name) => { map.set(name, this.evaluate(decl)); });
return map;
}
}

/**
* A value member of an enumeration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,38 @@ runInEachFileSystem(() => {
});
});

it('module spread works', () => {
const map = evaluate<Map<string, number>>(
`import * as mod from './module'; const c = {...mod, c: 3};`, 'c', [
{name: _('/module.ts'), contents: `export const a = 1; export const b = 2;`},
]);

const obj: {[key: string]: number} = {};
map.forEach((value, key) => obj[key] = value);
expect(obj).toEqual({
a: 1,
b: 2,
c: 3,
});
});

it('evaluates module exports lazily to avoid infinite recursion', () => {
const value = evaluate(`import * as mod1 from './mod1';`, 'mod1.primary', [
{
name: _('/mod1.ts'),
contents: `
import * as mod2 from './mod2';
export const primary = mod2.indirection;
export const secondary = 2;`
},
{
name: _('/mod2.ts'),
contents: `import * as mod1 from './mod1'; export const indirection = mod1.secondary;`
},
]);
expect(value).toEqual(2);
});

it('indirected-via-object function call works', () => {
expect(evaluate(
`
Expand Down