Add an error for missing initializers on default params, fixes #121; Fix detection of terminated switch cases and improve tests, fixes #122

This commit is contained in:
dcodeIO
2018-05-27 12:24:16 +02:00
parent 113925fa7e
commit d0244a9b0f
9 changed files with 1366 additions and 148 deletions

View File

@ -1468,7 +1468,7 @@ export class Compiler extends DiagnosticEmitter {
let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt;
if (flow.isAny(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
if (flow.isAny(FlowFlags.TERMINATED)) break;
}
}
stmts.length = count;
@ -1774,14 +1774,18 @@ export class Compiler extends DiagnosticEmitter {
var module = this.module;
var currentFunction = this.currentFunction;
var cases = statement.cases;
var numCases = cases.length;
if (!numCases) {
return this.compileExpression(statement.condition, Type.void, ConversionKind.IMPLICIT, WrapMode.NONE);
}
// Everything within a switch uses the same break context
var context = currentFunction.enterBreakContext();
// introduce a local for evaluating the condition (exactly once)
var tempLocal = currentFunction.getTempLocal(Type.u32, false);
var tempLocalIndex = tempLocal.index;
var cases = statement.cases;
var numCases = cases.length;
// Prepend initializer to inner block. Does not initiate a new branch, yet.
var breaks = new Array<ExpressionRef>(1 + numCases);
@ -1833,31 +1837,37 @@ export class Compiler extends DiagnosticEmitter {
let breakLabel = "break|" + context;
flow.breakLabel = breakLabel;
let fallsThrough = i != numCases - 1;
let nextLabel = !fallsThrough ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
let isLast = i == numCases - 1;
let nextLabel = isLast ? breakLabel : "case" + (i + 1).toString(10) + "|" + context;
let stmts = new Array<ExpressionRef>(1 + numStatements);
stmts[0] = currentBlock;
let count = 1;
let terminated = false;
for (let j = 0; j < numStatements; ++j) {
let stmt = this.compileStatement(statements[j]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
stmts[count++] = stmt;
if (flow.is(FlowFlags.BREAKS | FlowFlags.CONTINUES | FlowFlags.RETURNS)) break;
if (flow.isAny(FlowFlags.TERMINATED)) {
terminated = true;
break;
}
}
}
stmts.length = count;
if (!(fallsThrough || flow.is(FlowFlags.RETURNS))) alwaysReturns = false; // ignore fall-throughs
if (!(fallsThrough || flow.is(FlowFlags.RETURNS_WRAPPED))) alwaysReturnsWrapped = false; // ignore fall-throughs
if (!(fallsThrough || flow.is(FlowFlags.THROWS))) alwaysThrows = false;
if (!(fallsThrough || flow.is(FlowFlags.ALLOCATES))) alwaysAllocates = false;
if (terminated || isLast) {
if (!flow.is(FlowFlags.RETURNS)) alwaysReturns = false;
if (!flow.is(FlowFlags.RETURNS_WRAPPED)) alwaysReturnsWrapped = false;
if (!flow.is(FlowFlags.THROWS)) alwaysThrows = false;
if (!flow.is(FlowFlags.ALLOCATES)) alwaysAllocates = false;
}
// Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope();
currentFunction.flow = flow.leaveBranchOrScope(false);
currentBlock = module.createBlock(nextLabel, stmts, NativeType.None); // must be a labeled block
}
currentFunction.leaveBreakContext();
// If the switch has a default and always returns, propagate that
// If the switch has a default (guaranteed to handle any value), propagate common flags
if (defaultIndex >= 0) {
let flow = currentFunction.flow;
if (alwaysReturns) flow.set(FlowFlags.RETURNS);
@ -5101,7 +5111,7 @@ export class Compiler extends DiagnosticEmitter {
let stmt = this.compileStatement(statements[i]);
if (getExpressionId(stmt) != ExpressionId.Nop) {
body.push(stmt);
if (flow.is(FlowFlags.RETURNS)) break;
if (flow.isAny(FlowFlags.TERMINATED)) break;
}
}
} else {
@ -5122,8 +5132,8 @@ export class Compiler extends DiagnosticEmitter {
this.currentFunction.flow = previousFlow;
this.currentType = returnType;
// Check that all branches return
if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) {
// Check that all branches are terminated
if (returnType != Type.void && !flow.isAny(FlowFlags.TERMINATED)) {
this.error(
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
declaration.signature.returnType.range
@ -5224,16 +5234,28 @@ export class Compiler extends DiagnosticEmitter {
]);
for (let i = 0; i < numOptional; ++i, ++operandIndex) {
let type = originalParameterTypes[minArguments + i];
body = module.createBlock(names[i + 1], [
body,
module.createSetLocal(operandIndex,
let declaration = originalParameterDeclarations[minArguments + i];
let initializer = declaration.initializer;
let initExpr: ExpressionRef;
if (initializer) {
initExpr = module.createSetLocal(operandIndex,
this.compileExpression(
assert(originalParameterDeclarations[minArguments + i].initializer),
initializer,
type,
ConversionKind.IMPLICIT,
WrapMode.WRAP
)
)
);
} else {
this.error(
DiagnosticCode.Optional_parameter_must_have_an_initializer,
declaration.range
);
initExpr = module.createUnreachable();
}
body = module.createBlock(names[i + 1], [
body,
initExpr,
]);
forwardedOperands[operandIndex] = module.createGetLocal(operandIndex, type.toNativeType());
}
@ -5326,9 +5348,10 @@ export class Compiler extends DiagnosticEmitter {
let parameterNodes = instance.prototype.declaration.signature.parameterTypes;
let allOptionalsAreConstant = true;
for (let i = numArguments; i < maxArguments; ++i) {
let initializer = assert(parameterNodes[i].initializer);
if (initializer.kind != NodeKind.LITERAL) {
let initializer = parameterNodes[i].initializer;
if (!(initializer && initializer.kind == NodeKind.LITERAL)) {
// TODO: other kinds might be constant as well
// NOTE: if the initializer is missing this is reported in ensureTrampoline below
allOptionalsAreConstant = false;
break;
}

View File

@ -24,6 +24,7 @@ export enum DiagnosticCode {
Decorator_0_is_not_valid_here = 212,
Duplicate_decorator = 213,
An_allocator_must_be_declared_to_allocate_memory_Try_importing_allocator_arena_or_allocator_tlsf = 214,
Optional_parameter_must_have_an_initializer = 215,
Unterminated_string_literal = 1002,
Identifier_expected = 1003,
_0_expected = 1005,
@ -133,6 +134,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 212: return "Decorator '{0}' is not valid here.";
case 213: return "Duplicate decorator.";
case 214: return "An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.";
case 215: return "Optional parameter must have an initializer.";
case 1002: return "Unterminated string literal.";
case 1003: return "Identifier expected.";
case 1005: return "'{0}' expected.";

View File

@ -16,6 +16,7 @@
"Decorator '{0}' is not valid here.": 212,
"Duplicate decorator.": 213,
"An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.": 214,
"Optional parameter must have an initializer.": 215,
"Unterminated string literal.": 1002,
"Identifier expected.": 1003,

View File

@ -3589,7 +3589,10 @@ export const enum FlowFlags {
/** This branch explicitly requests no bounds checking. */
UNCHECKED_CONTEXT = 1 << 11,
/** This branch returns a properly wrapped value. */
RETURNS_WRAPPED = 1 << 12
RETURNS_WRAPPED = 1 << 12,
/** This branch is terminated if any of these flags is set. */
TERMINATED = FlowFlags.RETURNS | FlowFlags.THROWS | FlowFlags.BREAKS | FlowFlags.CONTINUES
}
/** A control flow evaluator. */
@ -3639,7 +3642,7 @@ export class Flow {
/** Tests if this flow has the specified flag or flags. */
is(flag: FlowFlags): bool { return (this.flags & flag) == flag; }
/** Tests if this flow has one of the specified flags. */
isAny(flag: CommonFlags): bool { return (this.flags & flag) != 0; }
isAny(flag: FlowFlags): bool { return (this.flags & flag) != 0; }
/** Sets the specified flag or flags. */
set(flag: FlowFlags): void { this.flags |= flag; }
/** Unsets the specified flag or flags. */
@ -3662,7 +3665,7 @@ export class Flow {
}
/** Leaves the current branch or scope and returns the parent flow. */
leaveBranchOrScope(): Flow {
leaveBranchOrScope(propagate: bool = true): Flow {
var parent = assert(this.parent);
// Free block-scoped locals
@ -3676,22 +3679,23 @@ export class Flow {
}
// Propagate conditionaal flags to parent
if (this.is(FlowFlags.RETURNS)) {
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
if (propagate) {
if (this.is(FlowFlags.RETURNS)) {
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
}
if (this.is(FlowFlags.THROWS)) {
parent.set(FlowFlags.CONDITIONALLY_THROWS);
}
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
}
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
}
if (this.is(FlowFlags.ALLOCATES)) {
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
}
}
if (this.is(FlowFlags.THROWS)) {
parent.set(FlowFlags.CONDITIONALLY_THROWS);
}
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
}
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
}
if (this.is(FlowFlags.ALLOCATES)) {
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
}
return parent;
}