Skip to content

Commit 5ac5841

Browse files
authored
Remember pending elements in compilation so we don't hit assertions (#1321)
1 parent 64ec8b9 commit 5ac5841

8 files changed

+155
-18
lines changed

src/common.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,23 @@ export enum CommonFlags {
6262
RESOLVED = 1 << 21,
6363
/** Is compiled. */
6464
COMPILED = 1 << 22,
65+
/** Did error. */
66+
ERRORED = 1 << 23,
6567
/** Has a constant value and is therefore inlined. */
66-
INLINED = 1 << 23,
68+
INLINED = 1 << 24,
6769
/** Is scoped. */
68-
SCOPED = 1 << 24,
70+
SCOPED = 1 << 25,
6971
/** Is a stub. */
70-
STUB = 1 << 25,
72+
STUB = 1 << 26,
7173
/** Is a virtual method. */
72-
VIRTUAL = 1 << 26,
74+
VIRTUAL = 1 << 27,
7375
/** Is (part of) a closure. */
74-
CLOSURE = 1 << 27,
76+
CLOSURE = 1 << 28,
7577

7678
// Other
7779

7880
/** Is quoted. */
79-
QUOTED = 1 << 28
81+
QUOTED = 1 << 29
8082
}
8183

8284
/** Path delimiter inserted between file system levels. */

src/compiler.ts

+87-12
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ export class Compiler extends DiagnosticEmitter {
361361
pendingClassInstanceOf: Set<ClassPrototype> = new Set();
362362
/** Functions potentially involving a virtual call. */
363363
virtualCalls: Set<Function> = new Set();
364+
/** Elements currently undergoing compilation. */
365+
pendingElements: Set<Element> = new Set();
364366

365367
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
366368
static compile(program: Program): Module {
@@ -993,9 +995,12 @@ export class Compiler extends DiagnosticEmitter {
993995

994996
/** Compiles a global variable. */
995997
compileGlobal(global: Global): bool {
996-
if (global.is(CommonFlags.COMPILED)) return true;
998+
if (global.is(CommonFlags.COMPILED)) return !global.is(CommonFlags.ERRORED);
997999
global.set(CommonFlags.COMPILED);
9981000

1001+
var pendingElements = this.pendingElements;
1002+
pendingElements.add(global);
1003+
9991004
var module = this.module;
10001005
var initExpr: ExpressionRef = 0;
10011006
var typeNode = global.typeNode;
@@ -1006,12 +1011,18 @@ export class Compiler extends DiagnosticEmitter {
10061011
// Resolve type if annotated
10071012
if (typeNode) {
10081013
let resolvedType = this.resolver.resolveType(typeNode, global.parent); // reports
1009-
if (!resolvedType) return false;
1014+
if (!resolvedType) {
1015+
global.set(CommonFlags.ERRORED);
1016+
pendingElements.delete(global);
1017+
return false;
1018+
}
10101019
if (resolvedType == Type.void) {
10111020
this.error(
10121021
DiagnosticCode.Type_expected,
10131022
typeNode.range
10141023
);
1024+
global.set(CommonFlags.ERRORED);
1025+
pendingElements.delete(global);
10151026
return false;
10161027
}
10171028
global.setType(resolvedType);
@@ -1032,6 +1043,8 @@ export class Compiler extends DiagnosticEmitter {
10321043
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
10331044
initializerNode.range, this.currentType.toString(), "<auto>"
10341045
);
1046+
global.set(CommonFlags.ERRORED);
1047+
pendingElements.delete(global);
10351048
return false;
10361049
}
10371050
global.setType(this.currentType);
@@ -1042,6 +1055,8 @@ export class Compiler extends DiagnosticEmitter {
10421055
DiagnosticCode.Type_expected,
10431056
global.identifierNode.range.atEnd
10441057
);
1058+
global.set(CommonFlags.ERRORED);
1059+
pendingElements.delete(global);
10451060
return false;
10461061
}
10471062
}
@@ -1050,6 +1065,7 @@ export class Compiler extends DiagnosticEmitter {
10501065
if (global.is(CommonFlags.AMBIENT) && global.hasDecorator(DecoratorFlags.BUILTIN)) {
10511066
if (global.internalName == BuiltinNames.heap_base) this.runtimeFeatures |= RuntimeFeatures.HEAP;
10521067
else if (global.internalName == BuiltinNames.rtti_base) this.runtimeFeatures |= RuntimeFeatures.RTTI;
1068+
pendingElements.delete(global);
10531069
return true;
10541070
}
10551071

@@ -1072,16 +1088,17 @@ export class Compiler extends DiagnosticEmitter {
10721088
nativeType,
10731089
!isDeclaredConstant
10741090
);
1075-
global.set(CommonFlags.COMPILED);
1091+
pendingElements.delete(global);
10761092
return true;
1093+
}
10771094

10781095
// Importing mutable globals is not supported in the MVP
1079-
} else {
1080-
this.error(
1081-
DiagnosticCode.Feature_0_is_not_enabled,
1082-
global.declaration.range, "mutable-globals"
1083-
);
1084-
}
1096+
this.error(
1097+
DiagnosticCode.Feature_0_is_not_enabled,
1098+
global.declaration.range, "mutable-globals"
1099+
);
1100+
global.set(CommonFlags.ERRORED);
1101+
pendingElements.delete(global);
10851102
return false;
10861103
}
10871104

@@ -1167,6 +1184,8 @@ export class Compiler extends DiagnosticEmitter {
11671184
}
11681185
default: {
11691186
assert(false);
1187+
global.set(CommonFlags.ERRORED);
1188+
pendingElements.delete(global);
11701189
return false;
11711190
}
11721191
}
@@ -1200,16 +1219,20 @@ export class Compiler extends DiagnosticEmitter {
12001219
} else if (!isDeclaredInline) { // compile normally
12011220
module.addGlobal(internalName, nativeType, !isDeclaredConstant, initExpr);
12021221
}
1222+
pendingElements.delete(global);
12031223
return true;
12041224
}
12051225

12061226
// === Enums ====================================================================================
12071227

12081228
/** Compiles an enum. */
12091229
compileEnum(element: Enum): bool {
1210-
if (element.is(CommonFlags.COMPILED)) return true;
1230+
if (element.is(CommonFlags.COMPILED)) return !element.is(CommonFlags.ERRORED);
12111231
element.set(CommonFlags.COMPILED);
12121232

1233+
var pendingElements = this.pendingElements;
1234+
pendingElements.add(element);
1235+
12131236
var module = this.module;
12141237
var previousParent = this.currentParent;
12151238
this.currentParent = element;
@@ -1305,6 +1328,7 @@ export class Compiler extends DiagnosticEmitter {
13051328
}
13061329
}
13071330
this.currentParent = previousParent;
1331+
pendingElements.delete(element);
13081332
return true;
13091333
}
13101334

@@ -1317,7 +1341,8 @@ export class Compiler extends DiagnosticEmitter {
13171341
/** Force compilation of stdlib alternative if a builtin. */
13181342
forceStdAlternative: bool = false
13191343
): bool {
1320-
if (instance.is(CommonFlags.COMPILED)) return true;
1344+
if (instance.is(CommonFlags.COMPILED)) return !instance.is(CommonFlags.ERRORED);
1345+
13211346
if (!forceStdAlternative) {
13221347
if (instance.hasDecorator(DecoratorFlags.BUILTIN)) return true;
13231348
if (instance.hasDecorator(DecoratorFlags.LAZY)) {
@@ -1326,9 +1351,11 @@ export class Compiler extends DiagnosticEmitter {
13261351
}
13271352
}
13281353

1329-
var previousType = this.currentType;
13301354
instance.set(CommonFlags.COMPILED);
1355+
var pendingElements = this.pendingElements;
1356+
pendingElements.add(instance);
13311357

1358+
var previousType = this.currentType;
13321359
var module = this.module;
13331360
var signature = instance.signature;
13341361
var bodyNode = instance.prototype.bodyNode;
@@ -1443,10 +1470,12 @@ export class Compiler extends DiagnosticEmitter {
14431470
instance.identifierNode.range
14441471
);
14451472
funcRef = 0; // TODO?
1473+
instance.set(CommonFlags.ERRORED);
14461474
}
14471475

14481476
instance.finalize(module, funcRef);
14491477
this.currentType = previousType;
1478+
pendingElements.delete(instance);
14501479
return true;
14511480
}
14521481

@@ -2973,18 +3002,29 @@ export class Compiler extends DiagnosticEmitter {
29733002
this.checkTypeSupported(type, typeNode);
29743003

29753004
if (initializerNode) {
3005+
let pendingElements = this.pendingElements;
3006+
let dummy = flow.addScopedDummyLocal(name, type); // pending dummy
3007+
pendingElements.add(dummy);
29763008
initExpr = this.compileExpression(initializerNode, type, // reports
29773009
Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN
29783010
);
29793011
initAutoreleaseSkipped = this.skippedAutoreleases.has(initExpr);
3012+
pendingElements.delete(dummy);
3013+
flow.freeScopedDummyLocal(name);
29803014
}
29813015

29823016
// Otherwise infer type from initializer
29833017
} else if (initializerNode) {
3018+
let pendingElements = this.pendingElements;
3019+
let temp = flow.addScopedDummyLocal(name, Type.auto); // pending dummy
3020+
pendingElements.add(temp);
29843021
initExpr = this.compileExpression(initializerNode, Type.auto,
29853022
Constraints.WILL_RETAIN
29863023
); // reports
29873024
initAutoreleaseSkipped = this.skippedAutoreleases.has(initExpr);
3025+
pendingElements.delete(temp);
3026+
flow.freeScopedDummyLocal(name);
3027+
29883028
if (this.currentType == Type.void) {
29893029
this.error(
29903030
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
@@ -5916,6 +5956,14 @@ export class Compiler extends DiagnosticEmitter {
59165956
}
59175957
case ElementKind.LOCAL:
59185958
case ElementKind.FIELD: {
5959+
if (this.pendingElements.has(target)) {
5960+
this.error(
5961+
DiagnosticCode.Variable_0_used_before_its_declaration,
5962+
expression.range,
5963+
target.internalName
5964+
);
5965+
return this.module.unreachable();
5966+
}
59195967
targetType = (<VariableLikeElement>target).type;
59205968
if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
59215969
break;
@@ -8171,6 +8219,15 @@ export class Compiler extends DiagnosticEmitter {
81718219
let local = <Local>target;
81728220
let localType = local.type;
81738221
assert(localType != Type.void);
8222+
if (this.pendingElements.has(local)) {
8223+
this.error(
8224+
DiagnosticCode.Variable_0_used_before_its_declaration,
8225+
expression.range,
8226+
local.internalName
8227+
);
8228+
this.currentType = localType;
8229+
return module.unreachable();
8230+
}
81748231
if (local.is(CommonFlags.INLINED)) {
81758232
return this.compileInlineConstant(local, contextualType, constraints);
81768233
}
@@ -8198,6 +8255,15 @@ export class Compiler extends DiagnosticEmitter {
81988255
return module.unreachable();
81998256
}
82008257
let globalType = global.type;
8258+
if (this.pendingElements.has(global)) {
8259+
this.error(
8260+
DiagnosticCode.Variable_0_used_before_its_declaration,
8261+
expression.range,
8262+
global.internalName
8263+
);
8264+
this.currentType = globalType;
8265+
return module.unreachable();
8266+
}
82018267
assert(globalType != Type.void);
82028268
if (global.is(CommonFlags.INLINED)) {
82038269
return this.compileInlineConstant(global, contextualType, constraints);
@@ -9266,6 +9332,15 @@ export class Compiler extends DiagnosticEmitter {
92669332
if (!this.compileGlobal(global)) return module.unreachable(); // reports
92679333
let globalType = global.type;
92689334
assert(globalType != Type.void);
9335+
if (this.pendingElements.has(global)) {
9336+
this.error(
9337+
DiagnosticCode.Variable_0_used_before_its_declaration,
9338+
expression.range,
9339+
global.internalName
9340+
);
9341+
this.currentType = globalType;
9342+
return module.unreachable();
9343+
}
92699344
if (global.is(CommonFlags.INLINED)) {
92709345
return this.compileInlineConstant(global, ctxType, constraints);
92719346
}

src/diagnosticMessages.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export enum DiagnosticCode {
147147
A_class_can_only_implement_an_interface = 2422,
148148
A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434,
149149
Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses = 2445,
150+
Variable_0_used_before_its_declaration = 2448,
150151
The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly = 2453,
151152
Type_0_has_no_property_1 = 2460,
152153
The_0_operator_cannot_be_applied_to_type_1 = 2469,
@@ -321,6 +322,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
321322
case 2422: return "A class can only implement an interface.";
322323
case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged.";
323324
case 2445: return "Property '{0}' is protected and only accessible within class '{1}' and its subclasses.";
325+
case 2448: return "Variable '{0}' used before its declaration.";
324326
case 2453: return "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.";
325327
case 2460: return "Type '{0}' has no property '{1}'.";
326328
case 2469: return "The '{0}' operator cannot be applied to type '{1}'.";

src/diagnosticMessages.json

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
"A class can only implement an interface.": 2422,
144144
"A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434,
145145
"Property '{0}' is protected and only accessible within class '{1}' and its subclasses.": 2445,
146+
"Variable '{0}' used before its declaration.": 2448,
146147
"The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453,
147148
"Type '{0}' has no property '{1}'.": 2460,
148149
"The '{0}' operator cannot be applied to type '{1}'.": 2469,

src/flow.ts

+22
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ export class Flow {
336336
var parentFunction = this.parentFunction;
337337
var temps: Local[];
338338
assert(local.type != null); // internal error
339+
local.resetTemporaryName();
339340
switch (<u32>local.type.toNativeType()) {
340341
case <u32>NativeType.I32: {
341342
let tempI32s = parentFunction.tempI32s;
@@ -395,6 +396,7 @@ export class Flow {
395396
/** Adds a new scoped local of the specified name. */
396397
addScopedLocal(name: string, type: Type, except: Set<i32> | null = null): Local {
397398
var scopedLocal = this.getTempLocal(type, except);
399+
scopedLocal.setTemporaryName(name);
398400
var scopedLocals = this.scopedLocals;
399401
if (!scopedLocals) this.scopedLocals = scopedLocals = new Map();
400402
else assert(!scopedLocals.has(name));
@@ -403,6 +405,17 @@ export class Flow {
403405
return scopedLocal;
404406
}
405407

408+
/** Adds a new scoped dummy local of the specified name. */
409+
addScopedDummyLocal(name: string, type: Type): Local {
410+
var scopedDummy = new Local(name, -1, type, this.parentFunction);
411+
var scopedLocals = this.scopedLocals;
412+
if (!scopedLocals) this.scopedLocals = scopedLocals = new Map();
413+
else assert(!scopedLocals.has(name));
414+
scopedDummy.set(CommonFlags.SCOPED);
415+
scopedLocals.set(name, scopedDummy);
416+
return scopedDummy;
417+
}
418+
406419
/** Adds a new scoped alias for the specified local. For example `super` aliased to the `this` local. */
407420
addScopedAlias(name: string, type: Type, index: i32, reportNode: Node | null = null): Local {
408421
var scopedLocals = this.scopedLocals;
@@ -450,6 +463,15 @@ export class Flow {
450463
return false;
451464
}
452465

466+
/** Frees a single scoped local by its name. */
467+
freeScopedDummyLocal(name: string): void {
468+
var scopedLocals = assert(this.scopedLocals);
469+
assert(scopedLocals.has(name));
470+
let local = assert(scopedLocals.get(name));
471+
assert(local.index == -1);
472+
scopedLocals.delete(name);
473+
}
474+
453475
/** Frees this flow's scoped variables and returns its parent flow. */
454476
freeScopedLocals(): void {
455477
var scopedLocals = this.scopedLocals;

0 commit comments

Comments
 (0)