Skip to content

Commit d0244a9

Browse files
committed
Add an error for missing initializers on default params, fixes AssemblyScript#121; Fix detection of terminated switch cases and improve tests, fixes AssemblyScript#122
1 parent 113925f commit d0244a9

File tree

9 files changed

+1366
-148
lines changed

9 files changed

+1366
-148
lines changed

dist/assemblyscript.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,7 @@ export class Compiler extends DiagnosticEmitter {
14681468
let stmt = this.compileStatement(statements[i]);
14691469
if (getExpressionId(stmt) != ExpressionId.Nop) {
14701470
stmts[count++] = stmt;
1471-
if (flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
1471+
if (flow.isAny(FlowFlags.TERMINATED)) break;
14721472
}
14731473
}
14741474
stmts.length = count;
@@ -1774,14 +1774,18 @@ export class Compiler extends DiagnosticEmitter {
17741774
var module = this.module;
17751775
var currentFunction = this.currentFunction;
17761776

1777+
var cases = statement.cases;
1778+
var numCases = cases.length;
1779+
if (!numCases) {
1780+
return this.compileExpression(statement.condition, Type.void, ConversionKind.IMPLICIT, WrapMode.NONE);
1781+
}
1782+
17771783
// Everything within a switch uses the same break context
17781784
var context = currentFunction.enterBreakContext();
17791785

17801786
// introduce a local for evaluating the condition (exactly once)
17811787
var tempLocal = currentFunction.getTempLocal(Type.u32, false);
17821788
var tempLocalIndex = tempLocal.index;
1783-
var cases = statement.cases;
1784-
var numCases = cases.length;
17851789

17861790
// Prepend initializer to inner block. Does not initiate a new branch, yet.
17871791
var breaks = new Array<ExpressionRef>(1 + numCases);
@@ -1833,31 +1837,37 @@ export class Compiler extends DiagnosticEmitter {
18331837
let breakLabel = "break|" + context;
18341838
flow.breakLabel = breakLabel;
18351839

1836-
let fallsThrough = i != numCases - 1;
1837-
let nextLabel = !fallsThrough ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
1840+
let isLast = i == numCases - 1;
1841+
let nextLabel = isLast ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
18381842
let stmts = new Array<ExpressionRef>(1 + numStatements);
18391843
stmts[0] = currentBlock;
18401844
let count = 1;
1845+
let terminated = false;
18411846
for (let j = 0; j < numStatements; ++j) {
18421847
let stmt = this.compileStatement(statements[j]);
18431848
if (getExpressionId(stmt) != ExpressionId.Nop) {
18441849
stmts[count++] = stmt;
1845-
if (flow.is(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
1850+
if (flow.isAny(FlowFlags.TERMINATED)) {
1851+
terminated = true;
1852+
break;
1853+
}
18461854
}
18471855
}
18481856
stmts.length = count;
1849-
if (!(fallsThrough || flow.is(FlowFlags.RETURNS))) alwaysReturns = false; // ignore fall-throughs
1850-
if (!(fallsThrough || flow.is(FlowFlags.RETURNS_WRAPPED))) alwaysReturnsWrapped = false; // ignore fall-throughs
1851-
if (!(fallsThrough || flow.is(FlowFlags.THROWS))) alwaysThrows = false;
1852-
if (!(fallsThrough || flow.is(FlowFlags.ALLOCATES))) alwaysAllocates = false;
1857+
if (terminated || isLast) {
1858+
if (!flow.is(FlowFlags.RETURNS)) alwaysReturns = false;
1859+
if (!flow.is(FlowFlags.RETURNS_WRAPPED)) alwaysReturnsWrapped = false;
1860+
if (!flow.is(FlowFlags.THROWS)) alwaysThrows = false;
1861+
if (!flow.is(FlowFlags.ALLOCATES)) alwaysAllocates = false;
1862+
}
18531863

18541864
// Switch back to the parent flow
1855-
currentFunction.flow = flow.leaveBranchOrScope();
1865+
currentFunction.flow = flow.leaveBranchOrScope(false);
18561866
currentBlock = module.createBlock(nextLabel, stmts, NativeType.None); // must be a labeled block
18571867
}
18581868
currentFunction.leaveBreakContext();
18591869

1860-
// If the switch has a default and always returns, propagate that
1870+
// If the switch has a default (guaranteed to handle any value), propagate common flags
18611871
if (defaultIndex >= 0) {
18621872
let flow = currentFunction.flow;
18631873
if (alwaysReturns) flow.set(FlowFlags.RETURNS);
@@ -5101,7 +5111,7 @@ export class Compiler extends DiagnosticEmitter {
51015111
let stmt = this.compileStatement(statements[i]);
51025112
if (getExpressionId(stmt) != ExpressionId.Nop) {
51035113
body.push(stmt);
5104-
if (flow.is(FlowFlags.RETURNS)) break;
5114+
if (flow.isAny(FlowFlags.TERMINATED)) break;
51055115
}
51065116
}
51075117
} else {
@@ -5122,8 +5132,8 @@ export class Compiler extends DiagnosticEmitter {
51225132
this.currentFunction.flow = previousFlow;
51235133
this.currentType = returnType;
51245134

5125-
// Check that all branches return
5126-
if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) {
5135+
// Check that all branches are terminated
5136+
if (returnType != Type.void && !flow.isAny(FlowFlags.TERMINATED)) {
51275137
this.error(
51285138
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
51295139
declaration.signature.returnType.range
@@ -5224,16 +5234,28 @@ export class Compiler extends DiagnosticEmitter {
52245234
]);
52255235
for (let i = 0; i < numOptional; ++i, ++operandIndex) {
52265236
let type = originalParameterTypes[minArguments + i];
5227-
body = module.createBlock(names[i + 1], [
5228-
body,
5229-
module.createSetLocal(operandIndex,
5237+
let declaration = originalParameterDeclarations[minArguments + i];
5238+
let initializer = declaration.initializer;
5239+
let initExpr: ExpressionRef;
5240+
if (initializer) {
5241+
initExpr = module.createSetLocal(operandIndex,
52305242
this.compileExpression(
5231-
assert(originalParameterDeclarations[minArguments + i].initializer),
5243+
initializer,
52325244
type,
52335245
ConversionKind.IMPLICIT,
52345246
WrapMode.WRAP
52355247
)
5236-
)
5248+
);
5249+
} else {
5250+
this.error(
5251+
DiagnosticCode.Optional_parameter_must_have_an_initializer,
5252+
declaration.range
5253+
);
5254+
initExpr = module.createUnreachable();
5255+
}
5256+
body = module.createBlock(names[i + 1], [
5257+
body,
5258+
initExpr,
52375259
]);
52385260
forwardedOperands[operandIndex] = module.createGetLocal(operandIndex, type.toNativeType());
52395261
}
@@ -5326,9 +5348,10 @@ export class Compiler extends DiagnosticEmitter {
53265348
let parameterNodes = instance.prototype.declaration.signature.parameterTypes;
53275349
let allOptionalsAreConstant = true;
53285350
for (let i = numArguments; i < maxArguments; ++i) {
5329-
let initializer = assert(parameterNodes[i].initializer);
5330-
if (initializer.kind != NodeKind.LITERAL) {
5351+
let initializer = parameterNodes[i].initializer;
5352+
if (!(initializer && initializer.kind == NodeKind.LITERAL)) {
53315353
// TODO: other kinds might be constant as well
5354+
// NOTE: if the initializer is missing this is reported in ensureTrampoline below
53325355
allOptionalsAreConstant = false;
53335356
break;
53345357
}

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export enum DiagnosticCode {
2424
Decorator_0_is_not_valid_here = 212,
2525
Duplicate_decorator = 213,
2626
An_allocator_must_be_declared_to_allocate_memory_Try_importing_allocator_arena_or_allocator_tlsf = 214,
27+
Optional_parameter_must_have_an_initializer = 215,
2728
Unterminated_string_literal = 1002,
2829
Identifier_expected = 1003,
2930
_0_expected = 1005,
@@ -133,6 +134,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
133134
case 212: return "Decorator '{0}' is not valid here.";
134135
case 213: return "Duplicate decorator.";
135136
case 214: return "An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.";
137+
case 215: return "Optional parameter must have an initializer.";
136138
case 1002: return "Unterminated string literal.";
137139
case 1003: return "Identifier expected.";
138140
case 1005: return "'{0}' expected.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"Decorator '{0}' is not valid here.": 212,
1717
"Duplicate decorator.": 213,
1818
"An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.": 214,
19+
"Optional parameter must have an initializer.": 215,
1920

2021
"Unterminated string literal.": 1002,
2122
"Identifier expected.": 1003,

src/program.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3589,7 +3589,10 @@ export const enum FlowFlags {
35893589
/** This branch explicitly requests no bounds checking. */
35903590
UNCHECKED_CONTEXT = 1 << 11,
35913591
/** This branch returns a properly wrapped value. */
3592-
RETURNS_WRAPPED = 1 << 12
3592+
RETURNS_WRAPPED = 1 << 12,
3593+
3594+
/** This branch is terminated if any of these flags is set. */
3595+
TERMINATED = FlowFlags.RETURNS | FlowFlags.THROWS | FlowFlags.BREAKS | FlowFlags.CONTINUES
35933596
}
35943597

35953598
/** A control flow evaluator. */
@@ -3639,7 +3642,7 @@ export class Flow {
36393642
/** Tests if this flow has the specified flag or flags. */
36403643
is(flag: FlowFlags): bool { return (this.flags & flag) == flag; }
36413644
/** Tests if this flow has one of the specified flags. */
3642-
isAny(flag: CommonFlags): bool { return (this.flags & flag) != 0; }
3645+
isAny(flag: FlowFlags): bool { return (this.flags & flag) != 0; }
36433646
/** Sets the specified flag or flags. */
36443647
set(flag: FlowFlags): void { this.flags |= flag; }
36453648
/** Unsets the specified flag or flags. */
@@ -3662,7 +3665,7 @@ export class Flow {
36623665
}
36633666

36643667
/** Leaves the current branch or scope and returns the parent flow. */
3665-
leaveBranchOrScope(): Flow {
3668+
leaveBranchOrScope(propagate: bool = true): Flow {
36663669
var parent = assert(this.parent);
36673670

36683671
// Free block-scoped locals
@@ -3676,22 +3679,23 @@ export class Flow {
36763679
}
36773680

36783681
// Propagate conditionaal flags to parent
3679-
if (this.is(FlowFlags.RETURNS)) {
3680-
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
3681-
}
3682-
if (this.is(FlowFlags.THROWS)) {
3683-
parent.set(FlowFlags.CONDITIONALLY_THROWS);
3684-
}
3685-
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
3686-
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
3687-
}
3688-
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
3689-
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
3690-
}
3691-
if (this.is(FlowFlags.ALLOCATES)) {
3692-
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
3682+
if (propagate) {
3683+
if (this.is(FlowFlags.RETURNS)) {
3684+
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
3685+
}
3686+
if (this.is(FlowFlags.THROWS)) {
3687+
parent.set(FlowFlags.CONDITIONALLY_THROWS);
3688+
}
3689+
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
3690+
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
3691+
}
3692+
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
3693+
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
3694+
}
3695+
if (this.is(FlowFlags.ALLOCATES)) {
3696+
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
3697+
}
36933698
}
3694-
36953699
return parent;
36963700
}
36973701

0 commit comments

Comments
 (0)