Skip to content

Commit

Permalink
fixup! refactor(compiler-cli): effective narrowing of signal reads in…
Browse files Browse the repository at this point in the history
… templates WIP
  • Loading branch information
JoostK committed Apr 14, 2024
1 parent 14bfaf1 commit be357e0
Show file tree
Hide file tree
Showing 10 changed files with 426 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const FS_NATIVE = 'Native';
const FS_OS_X = 'OS/X';
const FS_UNIX = 'Unix';
const FS_WINDOWS = 'Windows';
const FS_ALL = [FS_OS_X, FS_WINDOWS, FS_UNIX, FS_NATIVE];
const FS_ALL = [FS_NATIVE];

function runInEachFileSystemFn(callback: (os: string) => void) {
FS_ALL.forEach(os => runInFileSystem(os, callback, false));
Expand Down
51 changes: 32 additions & 19 deletions packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@ const BINARY_OPS = new Map<string, ts.BinaryOperator>([

export interface SignalCall {
identifier: ts.Identifier;
initialized: boolean;
}


export interface SignalCallOutlining {
allocateTarget(call: PotentialSignalCall): ts.Identifier|null;

resolveTarget(call: PotentialSignalCall): ts.Identifier|null;
}


export enum EmitMode {
Regular,
Narrowing,
}

/**
Expand All @@ -56,17 +68,17 @@ export interface SignalCall {
*/
export function astToTypescript(
ast: AST, maybeResolve: (ast: AST) => (ts.Expression | null),
identifySignalCall: (ast: PotentialSignalCall) => SignalCall | null,
config: TypeCheckingConfig): ts.Expression {
const translator = new AstTranslator(maybeResolve, identifySignalCall, config);
signalCallOutlining: SignalCallOutlining, config: TypeCheckingConfig,
mode: EmitMode): ts.Expression {
const translator = new AstTranslator(maybeResolve, signalCallOutlining, config, mode);
return translator.translate(ast);
}

class AstTranslator implements AstVisitor {
constructor(
private maybeResolve: (ast: AST) => (ts.Expression | null),
private identifySignalCall: (ast: PotentialSignalCall) => SignalCall | null,
private config: TypeCheckingConfig) {}
private signalCallOutlining: SignalCallOutlining, private config: TypeCheckingConfig,
private mode: EmitMode) {}

translate(ast: AST): ts.Expression {
// Skip over an `ASTWithSource` as its `visit` method calls directly into its ast's `visit`,
Expand Down Expand Up @@ -334,12 +346,14 @@ class AstTranslator implements AstVisitor {

let signalAssignmentTarget: ts.Identifier|null = null;
if (args.length === 0) {
const signalCall = this.identifySignalCall(ast);
if (signalCall !== null) {
if (signalCall.initialized) {
return signalCall.identifier;
}
signalAssignmentTarget = signalCall.identifier;
const target = this.signalCallOutlining.resolveTarget(ast);
if (target !== null) {
// TODO: using the outlined result here means that diagnostics won't be repeated
return target;
}

if (this.mode === EmitMode.Narrowing) {
signalAssignmentTarget = this.signalCallOutlining.allocateTarget(ast);
}
}

Expand Down Expand Up @@ -384,14 +398,13 @@ class AstTranslator implements AstVisitor {
const args = ast.args.map(expr => this.translate(expr));

let signalAssignmentTarget: ts.Identifier|null = null;
if (args.length === 0) {
const signalCall = this.identifySignalCall(ast);
if (signalCall !== null) {
if (signalCall.initialized) {
return signalCall.identifier;
}
signalAssignmentTarget = signalCall.identifier;
if (args.length === 0 && this.mode === EmitMode.Narrowing) {
const target = this.signalCallOutlining.resolveTarget(ast);
if (target !== null) {
// TODO: using the outlined result here means that diagnostics won't be repeated
return target;
}
signalAssignmentTarget = this.signalCallOutlining.allocateTarget(ast);
}

const expr = wrapForDiagnostics(this.translate(ast.receiver));
Expand Down
166 changes: 110 additions & 56 deletions packages/compiler-cli/src/ngtsc/typecheck/src/signal_calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,115 +14,169 @@ export type PotentialSignalCall = Call|SafeCall;

export function computeSignalCallIdentity(
call: PotentialSignalCall,
recurse: (receiverCall: PotentialSignalCall) => string | null): SignalCallIdentity {
const identifier = call.receiver.visit(new SignalCallIdentification(recurse));
recurse: (receiverCall: PotentialSignalCall) => string | null,
identifyImplicitRead: (expr: AST) => string | null,
): SignalCallIdentity {
const identifier =
call.receiver.visit(new SignalCallIdentification(recurse, identifyImplicitRead));
return identifier as SignalCallIdentity;
}

class SignalCallIdentification implements AstVisitor {
constructor(private recurse: (receiverCall: PotentialSignalCall) => string | null) {}
constructor(
private recurse: (receiverCall: PotentialSignalCall) => string | null,
private identifyImplicitRead: (expr: AST) => string | null,
) {}

visitUnary(ast: Unary): string {
return `${ast.operator}${this.forAst(ast.expr)}`;
visitUnary(ast: Unary): string|null {
const expr = this.forAst(ast.expr);
if (expr === null) {
return null;
}
return `${ast.operator}${expr}`;
}

visitBinary(ast: Binary): string {
return `${this.forAst(ast.left)}${ast.operation}${this.forAst(ast.right)}`;
visitBinary(ast: Binary): string|null {
const left = this.forAst(ast.left);
const right = this.forAst(ast.right);
if (left === null || right === null) {
return null;
}
return `${left}${ast.operation}${right}`;
}

visitChain(ast: Chain): string {
return ast.expressions.map(expr => this.forAst(expr)).join(',');
visitChain(ast: Chain): string|null {
return null;
}

visitConditional(ast: Conditional): string {
return `(${this.forAst(ast.condition)} ? ${this.forAst(ast.trueExp)} : ${
this.forAst(ast.falseExp)})`;
visitConditional(ast: Conditional): string|null {
return null;
}

visitThisReceiver(ast: ThisReceiver): string {
visitThisReceiver(ast: ThisReceiver): string|null {
return 'this';
}

visitImplicitReceiver(ast: ImplicitReceiver): string {
visitImplicitReceiver(ast: ImplicitReceiver): string|null {
return 'this';
}

visitInterpolation(ast: Interpolation): string {
return ast.expressions.map(expr => this.forAst(expr)).join(',');
visitInterpolation(ast: Interpolation): string|null {
return null;
}

visitKeyedRead(ast: KeyedRead): string {
return `${this.forAst(ast.receiver)}[${this.forAst(ast.key)}]`;
visitKeyedRead(ast: KeyedRead): string|null {
const receiver = this.forAst(ast.receiver);
const key = this.forAst(ast.key);
if (receiver === null || key === null) {
return null;
}
return `${receiver}[${key}]`;
}

visitKeyedWrite(ast: KeyedWrite): string {
return `${this.forAst(ast.receiver)}[${this.forAst(ast.key)}] = ${this.forAst(ast.value)}`;
visitKeyedWrite(ast: KeyedWrite): string|null {
return null;
}

visitLiteralArray(ast: LiteralArray): string {
const values = ast.expressions.map(expr => this.forAst(expr)).join(',');
return `[${values}]`;
visitLiteralArray(ast: LiteralArray): string|null {
return null;
}

visitLiteralMap(ast: LiteralMap): string {
const values = ast.keys.map((key, i) => `${key}: ${this.forAst(ast.values[i])}`).join(',');
return `{${values}}`;
visitLiteralMap(ast: LiteralMap): string|null {
return null;
}

visitLiteralPrimitive(ast: LiteralPrimitive): string {
visitLiteralPrimitive(ast: LiteralPrimitive): string|null {
return `${ast.value}`;
}

visitPipe(ast: BindingPipe): string {
const args = ast.args.map(expr => this.forAst(expr)).join(' : ');
return `${this.forAst(ast.exp)} | ${ast.name}: ${args}`;
visitPipe(ast: BindingPipe): string|null {
return null;
}

visitPrefixNot(ast: PrefixNot): string {
return `!${this.forAst(ast.expression)}`;
visitPrefixNot(ast: PrefixNot): string|null {
const expression = this.forAst(ast.expression);
if (expression === null) {
return expression;
}
return `!${expression}`;
}

visitNonNullAssert(ast: NonNullAssert): string {
return `${this.forAst(ast.expression)}!`;
visitNonNullAssert(ast: NonNullAssert): string|null {
return this.forAst(ast.expression);
}

visitPropertyRead(ast: PropertyRead): string {
return `${this.forAst(ast.receiver)}.${ast.name}`;
visitPropertyRead(ast: PropertyRead): string|null {
const receiver = this.identifyReceiver(ast);
if (receiver === null) {
return null;
}
return `${receiver}.${ast.name}`;
}

visitPropertyWrite(ast: PropertyWrite): string {
return `${this.forAst(ast.receiver)}.${ast.name} = ${this.forAst(ast.value)}`;
visitPropertyWrite(ast: PropertyWrite): string|null {
return null;
}

visitSafePropertyRead(ast: SafePropertyRead): string {
return `${this.forAst(ast.receiver)}?.${ast.name}`;
visitSafePropertyRead(ast: SafePropertyRead): string|null {
const receiver = this.identifyReceiver(ast);
if (receiver === null) {
return null;
}
return `${receiver}?.${ast.name}`;
}

visitSafeKeyedRead(ast: SafeKeyedRead): string {
return `${this.forAst(ast.receiver)}?.[${this.forAst(ast.key)}]`;
visitSafeKeyedRead(ast: SafeKeyedRead): string|null {
const receiver = this.forAst(ast.receiver)
if (receiver === null) {
return null;
}
return `${receiver}?.[${this.forAst(ast.key)}]`;
}

visitCall(ast: Call): string {
const args = ast.args.map(expr => this.forAst(expr)).join(',');
return `${this.forAst(ast.receiver)}(${args})`;
visitCall(ast: Call): string|null {
if (ast.args.length > 0) {
return null;
}
const receiver = this.forAst(ast.receiver);
if (receiver === null) {
return null;
}
return `${receiver}()`;
}

visitSafeCall(ast: SafeCall): string {
const args = ast.args.map(expr => this.forAst(expr)).join(',');
return `${this.forAst(ast.receiver)}?.(${args})`;
visitSafeCall(ast: SafeCall): string|null {
if (ast.args.length > 0) {
return null;
}
const receiver = this.forAst(ast.receiver);
if (receiver === null) {
return null;
}
return `${receiver}?.()`;
}

visitASTWithSource(ast: ASTWithSource): string {
visitASTWithSource(ast: ASTWithSource): string|null {
return this.forAst(ast.ast);
}

private forAst(ast: AST): string {
// if (ast instanceof Call || ast instanceof SafeCall) {
// const result = this.recurse(ast);
// if (result !== null) {
// return `ɵ${result}`;
// }
// }
private identifyReceiver(ast: PropertyRead|SafePropertyRead): string|null {
if (ast.receiver instanceof ImplicitReceiver && !(ast.receiver instanceof ThisReceiver)) {
const implicitIdentity = this.identifyImplicitRead(ast);
if (implicitIdentity !== null) {
return implicitIdentity;
}
}
return this.forAst(ast.receiver);
}

private forAst(ast: AST): string|null {
if (ast instanceof Call || ast instanceof SafeCall) {
const result = this.recurse(ast);
if (result !== null) {
return ${result}`;
}
}
return ast.visit(this);
}
}
2 changes: 1 addition & 1 deletion packages/compiler-cli/src/ngtsc/typecheck/src/ts_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function tsCreateVariable(
/* initializer */ initializer);
return ts.factory.createVariableStatement(
/* modifiers */ undefined,
/* declarationList */ ts.factory.createVariableDeclarationList([decl], ts.NodeFlags.Const));
/* declarationList */[decl]);
}

/**
Expand Down

0 comments on commit be357e0

Please sign in to comment.