From babeba79c8ea92d6f7a209d516f5d8f77e5a3780 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 10 Jun 2020 00:17:48 +0200 Subject: [PATCH 1/2] Remember failed globals --- src/compiler.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 059cf752e4..e2dcb8bff7 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -361,6 +361,8 @@ export class Compiler extends DiagnosticEmitter { pendingClassInstanceOf: Set = new Set(); /** Functions potentially involving a virtual call. */ virtualCalls: Set = new Set(); + /** Set of globals that failed to compile. */ + failedGlobals: Set = new Set(); /** Compiles a {@link Program} to a {@link Module} using the specified options. */ static compile(program: Program): Module { @@ -993,9 +995,14 @@ export class Compiler extends DiagnosticEmitter { /** Compiles a global variable. */ compileGlobal(global: Global): bool { - if (global.is(CommonFlags.COMPILED)) return true; + var failedGlobals = this.failedGlobals; + if (global.is(CommonFlags.COMPILED)) return !failedGlobals.has(global); global.set(CommonFlags.COMPILED); + // Assume that compilation will fail, and undo once we succeed to compile + // it. Means: Referencing a global in its initializer becomes a `false`. + failedGlobals.add(global); + var module = this.module; var initExpr: ExpressionRef = 0; var typeNode = global.typeNode; @@ -1050,6 +1057,7 @@ export class Compiler extends DiagnosticEmitter { if (global.is(CommonFlags.AMBIENT) && global.hasDecorator(DecoratorFlags.BUILTIN)) { if (global.internalName == BuiltinNames.heap_base) this.runtimeFeatures |= RuntimeFeatures.HEAP; else if (global.internalName == BuiltinNames.rtti_base) this.runtimeFeatures |= RuntimeFeatures.RTTI; + failedGlobals.delete(global); return true; } @@ -1072,7 +1080,7 @@ export class Compiler extends DiagnosticEmitter { nativeType, !isDeclaredConstant ); - global.set(CommonFlags.COMPILED); + failedGlobals.delete(global); return true; // Importing mutable globals is not supported in the MVP @@ -1200,6 +1208,7 @@ export class Compiler extends DiagnosticEmitter { } else if (!isDeclaredInline) { // compile normally module.addGlobal(internalName, nativeType, !isDeclaredConstant, initExpr); } + failedGlobals.delete(global); return true; } From a9e52b36840cb679defcc5e3162cd55e47d0fd1e Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 11 Jun 2020 04:15:44 +0200 Subject: [PATCH 2/2] tackle self-reference in initializer on top --- src/common.ts | 14 ++- src/compiler.ts | 106 ++++++++++++++---- src/diagnosticMessages.generated.ts | 2 + src/diagnosticMessages.json | 1 + src/flow.ts | 22 ++++ src/program.ts | 17 +++ .../variable-access-in-initializer.json | 10 ++ .../variable-access-in-initializer.ts | 8 ++ 8 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 tests/compiler/variable-access-in-initializer.json create mode 100644 tests/compiler/variable-access-in-initializer.ts diff --git a/src/common.ts b/src/common.ts index dc0a90daf5..f2f128775c 100644 --- a/src/common.ts +++ b/src/common.ts @@ -62,21 +62,23 @@ export enum CommonFlags { RESOLVED = 1 << 21, /** Is compiled. */ COMPILED = 1 << 22, + /** Did error. */ + ERRORED = 1 << 23, /** Has a constant value and is therefore inlined. */ - INLINED = 1 << 23, + INLINED = 1 << 24, /** Is scoped. */ - SCOPED = 1 << 24, + SCOPED = 1 << 25, /** Is a stub. */ - STUB = 1 << 25, + STUB = 1 << 26, /** Is a virtual method. */ - VIRTUAL = 1 << 26, + VIRTUAL = 1 << 27, /** Is (part of) a closure. */ - CLOSURE = 1 << 27, + CLOSURE = 1 << 28, // Other /** Is quoted. */ - QUOTED = 1 << 28 + QUOTED = 1 << 29 } /** Path delimiter inserted between file system levels. */ diff --git a/src/compiler.ts b/src/compiler.ts index e2dcb8bff7..55eb876ca0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -361,8 +361,8 @@ export class Compiler extends DiagnosticEmitter { pendingClassInstanceOf: Set = new Set(); /** Functions potentially involving a virtual call. */ virtualCalls: Set = new Set(); - /** Set of globals that failed to compile. */ - failedGlobals: Set = new Set(); + /** Elements currently undergoing compilation. */ + pendingElements: Set = new Set(); /** Compiles a {@link Program} to a {@link Module} using the specified options. */ static compile(program: Program): Module { @@ -995,13 +995,11 @@ export class Compiler extends DiagnosticEmitter { /** Compiles a global variable. */ compileGlobal(global: Global): bool { - var failedGlobals = this.failedGlobals; - if (global.is(CommonFlags.COMPILED)) return !failedGlobals.has(global); + if (global.is(CommonFlags.COMPILED)) return !global.is(CommonFlags.ERRORED); global.set(CommonFlags.COMPILED); - // Assume that compilation will fail, and undo once we succeed to compile - // it. Means: Referencing a global in its initializer becomes a `false`. - failedGlobals.add(global); + var pendingElements = this.pendingElements; + pendingElements.add(global); var module = this.module; var initExpr: ExpressionRef = 0; @@ -1013,12 +1011,18 @@ export class Compiler extends DiagnosticEmitter { // Resolve type if annotated if (typeNode) { let resolvedType = this.resolver.resolveType(typeNode, global.parent); // reports - if (!resolvedType) return false; + if (!resolvedType) { + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); + return false; + } if (resolvedType == Type.void) { this.error( DiagnosticCode.Type_expected, typeNode.range ); + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); return false; } global.setType(resolvedType); @@ -1039,6 +1043,8 @@ export class Compiler extends DiagnosticEmitter { DiagnosticCode.Type_0_is_not_assignable_to_type_1, initializerNode.range, this.currentType.toString(), "" ); + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); return false; } global.setType(this.currentType); @@ -1049,6 +1055,8 @@ export class Compiler extends DiagnosticEmitter { DiagnosticCode.Type_expected, global.identifierNode.range.atEnd ); + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); return false; } } @@ -1057,7 +1065,7 @@ export class Compiler extends DiagnosticEmitter { if (global.is(CommonFlags.AMBIENT) && global.hasDecorator(DecoratorFlags.BUILTIN)) { if (global.internalName == BuiltinNames.heap_base) this.runtimeFeatures |= RuntimeFeatures.HEAP; else if (global.internalName == BuiltinNames.rtti_base) this.runtimeFeatures |= RuntimeFeatures.RTTI; - failedGlobals.delete(global); + pendingElements.delete(global); return true; } @@ -1080,16 +1088,17 @@ export class Compiler extends DiagnosticEmitter { nativeType, !isDeclaredConstant ); - failedGlobals.delete(global); + pendingElements.delete(global); return true; + } // Importing mutable globals is not supported in the MVP - } else { - this.error( - DiagnosticCode.Feature_0_is_not_enabled, - global.declaration.range, "mutable-globals" - ); - } + this.error( + DiagnosticCode.Feature_0_is_not_enabled, + global.declaration.range, "mutable-globals" + ); + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); return false; } @@ -1175,6 +1184,8 @@ export class Compiler extends DiagnosticEmitter { } default: { assert(false); + global.set(CommonFlags.ERRORED); + pendingElements.delete(global); return false; } } @@ -1208,7 +1219,7 @@ export class Compiler extends DiagnosticEmitter { } else if (!isDeclaredInline) { // compile normally module.addGlobal(internalName, nativeType, !isDeclaredConstant, initExpr); } - failedGlobals.delete(global); + pendingElements.delete(global); return true; } @@ -1216,9 +1227,12 @@ export class Compiler extends DiagnosticEmitter { /** Compiles an enum. */ compileEnum(element: Enum): bool { - if (element.is(CommonFlags.COMPILED)) return true; + if (element.is(CommonFlags.COMPILED)) return !element.is(CommonFlags.ERRORED); element.set(CommonFlags.COMPILED); + var pendingElements = this.pendingElements; + pendingElements.add(element); + var module = this.module; var previousParent = this.currentParent; this.currentParent = element; @@ -1314,6 +1328,7 @@ export class Compiler extends DiagnosticEmitter { } } this.currentParent = previousParent; + pendingElements.delete(element); return true; } @@ -1326,7 +1341,8 @@ export class Compiler extends DiagnosticEmitter { /** Force compilation of stdlib alternative if a builtin. */ forceStdAlternative: bool = false ): bool { - if (instance.is(CommonFlags.COMPILED)) return true; + if (instance.is(CommonFlags.COMPILED)) return !instance.is(CommonFlags.ERRORED); + if (!forceStdAlternative) { if (instance.hasDecorator(DecoratorFlags.BUILTIN)) return true; if (instance.hasDecorator(DecoratorFlags.LAZY)) { @@ -1335,9 +1351,11 @@ export class Compiler extends DiagnosticEmitter { } } - var previousType = this.currentType; instance.set(CommonFlags.COMPILED); + var pendingElements = this.pendingElements; + pendingElements.add(instance); + var previousType = this.currentType; var module = this.module; var signature = instance.signature; var bodyNode = instance.prototype.bodyNode; @@ -1452,10 +1470,12 @@ export class Compiler extends DiagnosticEmitter { instance.identifierNode.range ); funcRef = 0; // TODO? + instance.set(CommonFlags.ERRORED); } instance.finalize(module, funcRef); this.currentType = previousType; + pendingElements.delete(instance); return true; } @@ -2982,18 +3002,29 @@ export class Compiler extends DiagnosticEmitter { this.checkTypeSupported(type, typeNode); if (initializerNode) { + let pendingElements = this.pendingElements; + let dummy = flow.addScopedDummyLocal(name, type); // pending dummy + pendingElements.add(dummy); initExpr = this.compileExpression(initializerNode, type, // reports Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN ); initAutoreleaseSkipped = this.skippedAutoreleases.has(initExpr); + pendingElements.delete(dummy); + flow.freeScopedDummyLocal(name); } // Otherwise infer type from initializer } else if (initializerNode) { + let pendingElements = this.pendingElements; + let temp = flow.addScopedDummyLocal(name, Type.auto); // pending dummy + pendingElements.add(temp); initExpr = this.compileExpression(initializerNode, Type.auto, Constraints.WILL_RETAIN ); // reports initAutoreleaseSkipped = this.skippedAutoreleases.has(initExpr); + pendingElements.delete(temp); + flow.freeScopedDummyLocal(name); + if (this.currentType == Type.void) { this.error( DiagnosticCode.Type_0_is_not_assignable_to_type_1, @@ -5925,6 +5956,14 @@ export class Compiler extends DiagnosticEmitter { } case ElementKind.LOCAL: case ElementKind.FIELD: { + if (this.pendingElements.has(target)) { + this.error( + DiagnosticCode.Variable_0_used_before_its_declaration, + expression.range, + target.internalName + ); + return this.module.unreachable(); + } targetType = (target).type; if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; @@ -8180,6 +8219,15 @@ export class Compiler extends DiagnosticEmitter { let local = target; let localType = local.type; assert(localType != Type.void); + if (this.pendingElements.has(local)) { + this.error( + DiagnosticCode.Variable_0_used_before_its_declaration, + expression.range, + local.internalName + ); + this.currentType = localType; + return module.unreachable(); + } if (local.is(CommonFlags.INLINED)) { return this.compileInlineConstant(local, contextualType, constraints); } @@ -8207,6 +8255,15 @@ export class Compiler extends DiagnosticEmitter { return module.unreachable(); } let globalType = global.type; + if (this.pendingElements.has(global)) { + this.error( + DiagnosticCode.Variable_0_used_before_its_declaration, + expression.range, + global.internalName + ); + this.currentType = globalType; + return module.unreachable(); + } assert(globalType != Type.void); if (global.is(CommonFlags.INLINED)) { return this.compileInlineConstant(global, contextualType, constraints); @@ -9275,6 +9332,15 @@ export class Compiler extends DiagnosticEmitter { if (!this.compileGlobal(global)) return module.unreachable(); // reports let globalType = global.type; assert(globalType != Type.void); + if (this.pendingElements.has(global)) { + this.error( + DiagnosticCode.Variable_0_used_before_its_declaration, + expression.range, + global.internalName + ); + this.currentType = globalType; + return module.unreachable(); + } if (global.is(CommonFlags.INLINED)) { return this.compileInlineConstant(global, ctxType, constraints); } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index f80bf17c68..aa2cf67cf8 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -147,6 +147,7 @@ export enum DiagnosticCode { A_class_can_only_implement_an_interface = 2422, A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434, Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses = 2445, + Variable_0_used_before_its_declaration = 2448, The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly = 2453, Type_0_has_no_property_1 = 2460, The_0_operator_cannot_be_applied_to_type_1 = 2469, @@ -321,6 +322,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2422: return "A class can only implement an interface."; case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged."; case 2445: return "Property '{0}' is protected and only accessible within class '{1}' and its subclasses."; + case 2448: return "Variable '{0}' used before its declaration."; case 2453: return "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly."; case 2460: return "Type '{0}' has no property '{1}'."; case 2469: return "The '{0}' operator cannot be applied to type '{1}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index d7a8750a76..00e3bea6ec 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -143,6 +143,7 @@ "A class can only implement an interface.": 2422, "A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434, "Property '{0}' is protected and only accessible within class '{1}' and its subclasses.": 2445, + "Variable '{0}' used before its declaration.": 2448, "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453, "Type '{0}' has no property '{1}'.": 2460, "The '{0}' operator cannot be applied to type '{1}'.": 2469, diff --git a/src/flow.ts b/src/flow.ts index 1c88ab3a28..5de4c8129c 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -336,6 +336,7 @@ export class Flow { var parentFunction = this.parentFunction; var temps: Local[]; assert(local.type != null); // internal error + local.resetTemporaryName(); switch (local.type.toNativeType()) { case NativeType.I32: { let tempI32s = parentFunction.tempI32s; @@ -395,6 +396,7 @@ export class Flow { /** Adds a new scoped local of the specified name. */ addScopedLocal(name: string, type: Type, except: Set | null = null): Local { var scopedLocal = this.getTempLocal(type, except); + scopedLocal.setTemporaryName(name); var scopedLocals = this.scopedLocals; if (!scopedLocals) this.scopedLocals = scopedLocals = new Map(); else assert(!scopedLocals.has(name)); @@ -403,6 +405,17 @@ export class Flow { return scopedLocal; } + /** Adds a new scoped dummy local of the specified name. */ + addScopedDummyLocal(name: string, type: Type): Local { + var scopedDummy = new Local(name, -1, type, this.parentFunction); + var scopedLocals = this.scopedLocals; + if (!scopedLocals) this.scopedLocals = scopedLocals = new Map(); + else assert(!scopedLocals.has(name)); + scopedDummy.set(CommonFlags.SCOPED); + scopedLocals.set(name, scopedDummy); + return scopedDummy; + } + /** Adds a new scoped alias for the specified local. For example `super` aliased to the `this` local. */ addScopedAlias(name: string, type: Type, index: i32, reportNode: Node | null = null): Local { var scopedLocals = this.scopedLocals; @@ -450,6 +463,15 @@ export class Flow { return false; } + /** Frees a single scoped local by its name. */ + freeScopedDummyLocal(name: string): void { + var scopedLocals = assert(this.scopedLocals); + assert(scopedLocals.has(name)); + let local = assert(scopedLocals.get(name)); + assert(local.index == -1); + scopedLocals.delete(name); + } + /** Frees this flow's scoped variables and returns its parent flow. */ freeScopedLocals(): void { var scopedLocals = this.scopedLocals; diff --git a/src/program.ts b/src/program.ts index ab7ec8056e..f3bf0dafb1 100644 --- a/src/program.ts +++ b/src/program.ts @@ -3267,6 +3267,9 @@ export class Parameter { /** A local variable. */ export class Local extends VariableLikeElement { + /** Original name of the (temporary) local. */ + private originalName: string; + /** Constructs a new local variable. */ constructor( /** Simple name. */ @@ -3286,10 +3289,24 @@ export class Local extends VariableLikeElement { parent, declaration ); + this.originalName = name; this.index = index; assert(type != Type.void); this.setType(type); } + + /** Sets the temporary name of this local. */ + setTemporaryName(name: string): void { + this.name = name; + this.internalName = mangleInternalName(name, this.parent, false); + } + + /** Resets the temporary name of this local. */ + resetTemporaryName(): void { + var name = this.originalName; + this.name = name; + this.internalName = mangleInternalName(name, this.parent, false); + } } /** A yet unresolved function prototype. */ diff --git a/tests/compiler/variable-access-in-initializer.json b/tests/compiler/variable-access-in-initializer.json new file mode 100644 index 0000000000..500017b069 --- /dev/null +++ b/tests/compiler/variable-access-in-initializer.json @@ -0,0 +1,10 @@ +{ + "asc_flags": [ + "--runtime none" + ], + "stderr": [ + "TS2448: Variable 'variable-access-in-initializer/a' used before its declaration.", + "TS2448: Variable 'variable-access-in-initializer/test~b' used before its declaration.", + "EOF" + ] +} \ No newline at end of file diff --git a/tests/compiler/variable-access-in-initializer.ts b/tests/compiler/variable-access-in-initializer.ts new file mode 100644 index 0000000000..3d3ce749e4 --- /dev/null +++ b/tests/compiler/variable-access-in-initializer.ts @@ -0,0 +1,8 @@ +var a = (a = 4, 3); // TS2448 + +function test(): void { + let b = (b = 4, 3); // TS2448 +} +test(); + +ERROR("EOF");