perf(compiler): only emit changed files for incremental compilation

For now, we always create all generated files, but diff them
before we pass them to TypeScript.

For the user files, we compare the programs and only emit changed
TypeScript files.

This also adds more diagnostic messages if the `—diagnostics` flag
is passed to the command line.
This commit is contained in:
Tobias Bosch
2017-09-29 15:02:11 -07:00
committed by Alex Rickabaugh
parent b0868915ae
commit 745b59f49c
15 changed files with 424 additions and 94 deletions

View File

@ -7,7 +7,7 @@
*/
import {sourceUrl} from '../compile_metadata';
import {Statement} from '../output/output_ast';
import {Statement, areAllEquivalent} from '../output/output_ast';
import {TypeScriptEmitter} from '../output/ts_emitter';
export class GeneratedFile {
@ -24,6 +24,21 @@ export class GeneratedFile {
this.stmts = sourceOrStmts;
}
}
isEquivalent(other: GeneratedFile): boolean {
if (this.genFileUrl !== other.genFileUrl) {
return false;
}
if (this.source) {
return this.source === other.source;
}
if (other.stmts == null) {
return false;
}
// Note: the constructor guarantees that if this.source is not filled,
// then this.stmts is.
return areAllEquivalent(this.stmts !, other.stmts !);
}
}
export function toTypeScript(file: GeneratedFile, preamble: string = ''): string {

View File

@ -104,6 +104,27 @@ export enum BinaryOperator {
BiggerEquals
}
export function nullSafeIsEquivalent<T extends{isEquivalent(other: T): boolean}>(
base: T | null, other: T | null) {
if (base == null || other == null) {
return base == other;
}
return base.isEquivalent(other);
}
export function areAllEquivalent<T extends{isEquivalent(other: T): boolean}>(
base: T[], other: T[]) {
const len = base.length;
if (len !== other.length) {
return false;
}
for (let i = 0; i < len; i++) {
if (!base[i].isEquivalent(other[i])) {
return false;
}
}
return true;
}
export abstract class Expression {
public type: Type|null;
@ -116,6 +137,12 @@ export abstract class Expression {
abstract visitExpression(visitor: ExpressionVisitor, context: any): any;
/**
* Calculates whether this expression produces the same value as the given expression.
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
*/
abstract isEquivalent(e: Expression): boolean;
prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr {
return new ReadPropExpr(this, name, null, sourceSpan);
}
@ -222,6 +249,10 @@ export class ReadVarExpr extends Expression {
this.builtin = <BuiltinVar>name;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadVarExpr(this, context);
}
@ -242,6 +273,9 @@ export class WriteVarExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteVarExpr(this, context);
@ -261,6 +295,10 @@ export class WriteKeyExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteKeyExpr(this, context);
}
@ -275,6 +313,10 @@ export class WritePropExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWritePropExpr(this, context);
}
@ -301,6 +343,10 @@ export class InvokeMethodExpr extends Expression {
this.builtin = <BuiltinMethod>method;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeMethodExpr(this, context);
}
@ -313,6 +359,10 @@ export class InvokeFunctionExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) &&
areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeFunctionExpr(this, context);
}
@ -325,6 +375,10 @@ export class InstantiateExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) &&
areAllEquivalent(this.args, e.args);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInstantiateExpr(this, context);
}
@ -332,9 +386,14 @@ export class InstantiateExpr extends Expression {
export class LiteralExpr extends Expression {
constructor(public value: any, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
constructor(
public value: number|string|boolean|null|undefined, type?: Type|null,
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralExpr && this.value === e.value;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralExpr(this, context);
}
@ -347,6 +406,10 @@ export class ExternalExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ExternalExpr && this.value.name === e.value.name &&
this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitExternalExpr(this, context);
}
@ -355,6 +418,7 @@ export class ExternalExpr extends Expression {
export class ExternalReference {
constructor(public moduleName: string|null, public name: string|null, public runtime?: any|null) {
}
// Note: no isEquivalent method here as we use this as an interface too.
}
export class ConditionalExpr extends Expression {
@ -365,6 +429,10 @@ export class ConditionalExpr extends Expression {
super(type || trueCase.type, sourceSpan);
this.trueCase = trueCase;
}
isEquivalent(e: Expression): boolean {
return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) &&
this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitConditionalExpr(this, context);
}
@ -375,6 +443,9 @@ export class NotExpr extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(BOOL_TYPE, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitNotExpr(this, context);
}
@ -384,6 +455,9 @@ export class AssertNotNull extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(condition.type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitAssertNotNullExpr(this, context);
}
@ -393,6 +467,9 @@ export class CastExpr extends Expression {
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CastExpr && this.value.isEquivalent(e.value);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCastExpr(this, context);
}
@ -401,6 +478,8 @@ export class CastExpr extends Expression {
export class FnParam {
constructor(public name: string, public type: Type|null = null) {}
isEquivalent(param: FnParam): boolean { return this.name === param.name; }
}
@ -410,6 +489,10 @@ export class FunctionExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) &&
areAllEquivalent(this.statements, e.statements);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitFunctionExpr(this, context);
}
@ -429,6 +512,10 @@ export class BinaryOperatorExpr extends Expression {
super(type || lhs.type, sourceSpan);
this.lhs = lhs;
}
isEquivalent(e: Expression): boolean {
return e instanceof BinaryOperatorExpr && this.operator === e.operator &&
this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitBinaryOperatorExpr(this, context);
}
@ -441,6 +528,10 @@ export class ReadPropExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadPropExpr(this, context);
}
@ -456,6 +547,10 @@ export class ReadKeyExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadKeyExpr(this, context);
}
@ -471,6 +566,9 @@ export class LiteralArrayExpr extends Expression {
super(type, sourceSpan);
this.entries = entries;
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralArrayExpr(this, context);
}
@ -478,6 +576,9 @@ export class LiteralArrayExpr extends Expression {
export class LiteralMapEntry {
constructor(public key: string, public value: Expression, public quoted: boolean) {}
isEquivalent(e: LiteralMapEntry): boolean {
return this.key === e.key && this.value.isEquivalent(e.value);
}
}
export class LiteralMapExpr extends Expression {
@ -489,6 +590,9 @@ export class LiteralMapExpr extends Expression {
this.valueType = type.valueType;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralMapExpr(this, context);
}
@ -498,6 +602,9 @@ export class CommaExpr extends Expression {
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) {
super(parts[parts.length - 1].type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CommaExpr && areAllEquivalent(this.parts, e.parts);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCommaExpr(this, context);
}
@ -547,6 +654,11 @@ export abstract class Statement {
this.modifiers = modifiers || [];
this.sourceSpan = sourceSpan || null;
}
/**
* Calculates whether this statement produces the same value as the given statement.
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
*/
abstract isEquivalent(stmt: Statement): boolean;
abstract visitStatement(visitor: StatementVisitor, context: any): any;
@ -562,7 +674,10 @@ export class DeclareVarStmt extends Statement {
super(modifiers, sourceSpan);
this.type = type || value.type;
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof DeclareVarStmt && this.name === stmt.name &&
this.value.isEquivalent(stmt.value);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareVarStmt(this, context);
}
@ -576,6 +691,10 @@ export class DeclareFunctionStmt extends Statement {
super(modifiers, sourceSpan);
this.type = type || null;
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof DeclareFunctionStmt && areAllEquivalent(this.params, stmt.params) &&
areAllEquivalent(this.statements, stmt.statements);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareFunctionStmt(this, context);
@ -586,6 +705,9 @@ export class ExpressionStatement extends Statement {
constructor(public expr: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ExpressionStatement && this.expr.isEquivalent(stmt.expr);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitExpressionStmt(this, context);
@ -597,6 +719,9 @@ export class ReturnStatement extends Statement {
constructor(public value: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ReturnStatement && this.value.isEquivalent(stmt.value);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitReturnStmt(this, context);
}
@ -617,6 +742,7 @@ export class ClassField extends AbstractClassPart {
constructor(public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null) {
super(type, modifiers);
}
isEquivalent(f: ClassField) { return this.name === f.name; }
}
@ -626,6 +752,9 @@ export class ClassMethod extends AbstractClassPart {
type?: Type|null, modifiers: StmtModifier[]|null = null) {
super(type, modifiers);
}
isEquivalent(m: ClassMethod) {
return this.name === m.name && areAllEquivalent(this.body, m.body);
}
}
@ -635,6 +764,9 @@ export class ClassGetter extends AbstractClassPart {
modifiers: StmtModifier[]|null = null) {
super(type, modifiers);
}
isEquivalent(m: ClassGetter) {
return this.name === m.name && areAllEquivalent(this.body, m.body);
}
}
@ -646,6 +778,14 @@ export class ClassStmt extends Statement {
sourceSpan?: ParseSourceSpan|null) {
super(modifiers, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof ClassStmt && this.name === stmt.name &&
nullSafeIsEquivalent(this.parent, stmt.parent) &&
areAllEquivalent(this.fields, stmt.fields) &&
areAllEquivalent(this.getters, stmt.getters) &&
this.constructorMethod.isEquivalent(stmt.constructorMethod) &&
areAllEquivalent(this.methods, stmt.methods);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareClassStmt(this, context);
}
@ -658,6 +798,11 @@ export class IfStmt extends Statement {
public falseCase: Statement[] = [], sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof IfStmt && this.condition.isEquivalent(stmt.condition) &&
areAllEquivalent(this.trueCase, stmt.trueCase) &&
areAllEquivalent(this.falseCase, stmt.falseCase);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitIfStmt(this, context);
}
@ -668,6 +813,7 @@ export class CommentStmt extends Statement {
constructor(public comment: string, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean { return stmt instanceof CommentStmt; }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitCommentStmt(this, context);
}
@ -680,6 +826,10 @@ export class TryCatchStmt extends Statement {
sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: Statement): boolean {
return stmt instanceof TryCatchStmt && areAllEquivalent(this.bodyStmts, stmt.bodyStmts) &&
areAllEquivalent(this.catchStmts, stmt.catchStmts);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitTryCatchStmt(this, context);
}
@ -690,6 +840,9 @@ export class ThrowStmt extends Statement {
constructor(public error: Expression, sourceSpan?: ParseSourceSpan|null) {
super(null, sourceSpan);
}
isEquivalent(stmt: ThrowStmt): boolean {
return stmt instanceof TryCatchStmt && this.error.isEquivalent(stmt.error);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitThrowStmt(this, context);
}