Skip to content
9 changes: 8 additions & 1 deletion src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export function nodeIsCallable(kind: NodeKind): bool {
case NodeKind.CALL:
case NodeKind.ELEMENTACCESS:
case NodeKind.PARENTHESIZED:
case NodeKind.PROPERTYACCESS: return true;
case NodeKind.PROPERTYACCESS:
case NodeKind.SUPER: return true;
}
return false;
}
Expand All @@ -134,6 +135,12 @@ export function nodeIsGenericCallable(kind: NodeKind): bool {
return false;
}

export function nodeIsSuperCall(node: Node): bool {
if (node.kind == NodeKind.EXPRESSION) node = (<ExpressionStatement>node).expression;
return node.kind == NodeKind.CALL
&& (<CallExpression>node).expression.kind == NodeKind.SUPER;
}

/** Base class of all nodes. */
export abstract class Node {

Expand Down
58 changes: 57 additions & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import {
FieldDeclaration,

nodeIsConstantValue,
nodeIsSuperCall,
isLastStatement,
findDecorator
} from "./ast";
Expand Down Expand Up @@ -1075,7 +1076,18 @@ export class Compiler extends DiagnosticEmitter {
flow.finalize();
} else {
assert(body.kind == NodeKind.BLOCK);
let stmts = this.compileStatements((<BlockStatement>body).statements);
let statements = (<BlockStatement>body).statements;
if (isConstructor) { // make sure super() is called first if this is a derived class
let parent = assert(instance.parent);
assert(parent.kind == ElementKind.CLASS);
if ((<Class>parent).base && !(statements.length >= 1 && nodeIsSuperCall(statements[0]))) {
this.error(
DiagnosticCode.Constructors_for_derived_classes_must_call_super_first,
statements[0].range.atStart
);
}
}
let stmts = this.compileStatements(statements);
if (instance.is(CommonFlags.MAIN)) {
module.addGlobal("~started", NativeType.I32, true, module.createI32(0));
stmts.unshift(
Expand Down Expand Up @@ -5230,6 +5242,19 @@ export class Compiler extends DiagnosticEmitter {
break;
}

// call to `super()`
case ElementKind.CLASS: {
if (expression.expression.kind == NodeKind.SUPER) {
let classInstance = assert(currentFunction.parent);
assert(classInstance.kind == ElementKind.CLASS);
let expr = this.compileSuperInstantiate(<Class>classInstance, expression.arguments, expression);
this.currentType = Type.void;
let thisLocal = assert(this.currentFunction.flow.getScopedLocal("this"));
return module.createSetLocal(thisLocal.index, expr);
}
// otherwise fall-through
}

// not supported
default: {
this.error(
Expand Down Expand Up @@ -6658,6 +6683,37 @@ export class Compiler extends DiagnosticEmitter {
return expr;
}

compileSuperInstantiate(classInstance: Class, argumentExpressions: Expression[], reportNode: Node): ExpressionRef {
// traverse to the top-most visible constructor (except the current one)
var currentClassInstance: Class | null = classInstance.base;
var constructorInstance: Function | null = null;
while (currentClassInstance) {
constructorInstance = currentClassInstance.constructorInstance;
if (constructorInstance) break; // TODO: check visibility
currentClassInstance = currentClassInstance.base;
}

// if a constructor is present, allocate the necessary memory for `this` and call it
var expr: ExpressionRef;
if (constructorInstance) {
expr = this.compileCallDirect(constructorInstance, argumentExpressions, reportNode,
this.makeAllocate(classInstance, reportNode)
);

// otherwise simply allocate a new instance and initialize its fields
} else {
if (argumentExpressions.length) {
this.error(
DiagnosticCode.Expected_0_arguments_but_got_1,
reportNode.range, "0", argumentExpressions.length.toString(10)
);
}
expr = this.makeAllocate(classInstance, reportNode);
}
this.currentType = classInstance.type;
return expr;
}

compileParenthesizedExpression(
expression: ParenthesizedExpression,
contextualType: Type
Expand Down
2 changes: 2 additions & 0 deletions src/diagnosticMessages.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export enum DiagnosticCode {
The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access = 2357,
The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access = 2364,
Operator_0_cannot_be_applied_to_types_1_and_2 = 2365,
Constructors_for_derived_classes_must_call_super_first = 2377,
_get_and_set_accessor_must_have_the_same_type = 2380,
Constructor_implementation_is_missing = 2390,
Function_implementation_is_missing_or_not_immediately_following_the_declaration = 2391,
Expand Down Expand Up @@ -224,6 +225,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2357: return "The operand of an increment or decrement operator must be a variable or a property access.";
case 2364: return "The left-hand side of an assignment expression must be a variable or a property access.";
case 2365: return "Operator '{0}' cannot be applied to types '{1}' and '{2}'.";
case 2377: return "Constructors for derived classes must call 'super' first.";
case 2380: return "'get' and 'set' accessor must have the same type.";
case 2390: return "Constructor implementation is missing.";
case 2391: return "Function implementation is missing or not immediately following the declaration.";
Expand Down
1 change: 1 addition & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"The operand of an increment or decrement operator must be a variable or a property access.": 2357,
"The left-hand side of an assignment expression must be a variable or a property access.": 2364,
"Operator '{0}' cannot be applied to types '{1}' and '{2}'.": 2365,
"Constructors for derived classes must call 'super' first.": 2377,
"'get' and 'set' accessor must have the same type.": 2380,
"Constructor implementation is missing.": 2390,
"Function implementation is missing or not immediately following the declaration.": 2391,
Expand Down
10 changes: 9 additions & 1 deletion src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1242,8 +1242,16 @@ export class Resolver extends DiagnosticEmitter {

// Lay out fields in advance
case ElementKind.FIELD_PROTOTYPE: {
if (!instance.members) instance.members = new Map();
let fieldDeclaration = (<FieldPrototype>member).declaration;
if (!instance.members) instance.members = new Map();
else if (instance.members.has(member.simpleName)) {
this.error(
DiagnosticCode.Duplicate_identifier_0,
fieldDeclaration.name.range,
member.simpleName
);
break;
}
let fieldType: Type | null = null;
// TODO: handle duplicate non-private fields
if (!fieldDeclaration.type) {
Expand Down
173 changes: 173 additions & 0 deletions tests/compiler/call-super.optimized.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
(module
(type $v (func))
(type $ii (func (param i32) (result i32)))
(type $iiiiv (func (param i32 i32 i32 i32)))
(type $FUNCSIG$i (func (result i32)))
(import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32)))
(memory $0 1)
(data (i32.const 8) "\0d\00\00\00c\00a\00l\00l\00-\00s\00u\00p\00e\00r\00.\00t\00s")
(table $0 1 anyfunc)
(elem (i32.const 0) $null)
(global $~lib/allocator/arena/startOffset (mut i32) (i32.const 0))
(global $~lib/allocator/arena/offset (mut i32) (i32.const 0))
(export "memory" (memory $0))
(export "table" (table $0))
(start $start)
(func $~lib/allocator/arena/__memory_allocate (; 1 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
get_local $0
i32.const 1073741824
i32.gt_u
if
unreachable
end
get_global $~lib/allocator/arena/offset
tee_local $1
get_local $0
i32.const 1
get_local $0
i32.const 1
i32.gt_u
select
i32.add
i32.const 7
i32.add
i32.const -8
i32.and
tee_local $2
current_memory
tee_local $3
i32.const 16
i32.shl
i32.gt_u
if
get_local $3
get_local $2
get_local $1
i32.sub
i32.const 65535
i32.add
i32.const -65536
i32.and
i32.const 16
i32.shr_u
tee_local $0
get_local $3
get_local $0
i32.gt_s
select
grow_memory
i32.const 0
i32.lt_s
if
get_local $0
grow_memory
i32.const 0
i32.lt_s
if
unreachable
end
end
end
get_local $2
set_global $~lib/allocator/arena/offset
get_local $1
)
(func $call-super/B#constructor (; 2 ;) (type $FUNCSIG$i) (result i32)
(local $0 i32)
i32.const 8
call $~lib/allocator/arena/__memory_allocate
tee_local $0
i32.const 1
i32.store
get_local $0
i32.const 2
i32.store offset=4
get_local $0
i32.eqz
if
i32.const 4
call $~lib/allocator/arena/__memory_allocate
tee_local $0
i32.const 1
i32.store
end
get_local $0
i32.eqz
if
i32.const 8
call $~lib/allocator/arena/__memory_allocate
tee_local $0
i32.const 1
i32.store
get_local $0
i32.const 2
i32.store offset=4
end
get_local $0
i32.load
i32.const 1
i32.ne
if
i32.const 0
i32.const 8
i32.const 13
i32.const 4
call $~lib/env/abort
unreachable
end
get_local $0
i32.load offset=4
i32.const 2
i32.ne
if
i32.const 0
i32.const 8
i32.const 14
i32.const 4
call $~lib/env/abort
unreachable
end
get_local $0
)
(func $call-super/test (; 3 ;) (type $v)
(local $0 i32)
call $call-super/B#constructor
tee_local $0
i32.load
i32.const 1
i32.ne
if
i32.const 0
i32.const 8
i32.const 20
i32.const 2
call $~lib/env/abort
unreachable
end
get_local $0
i32.load offset=4
i32.const 2
i32.ne
if
i32.const 0
i32.const 8
i32.const 21
i32.const 2
call $~lib/env/abort
unreachable
end
)
(func $start (; 4 ;) (type $v)
i32.const 40
set_global $~lib/allocator/arena/startOffset
get_global $~lib/allocator/arena/startOffset
set_global $~lib/allocator/arena/offset
call $call-super/test
)
(func $null (; 5 ;) (type $v)
nop
)
)
24 changes: 24 additions & 0 deletions tests/compiler/call-super.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "allocator/arena";

class A {
a: i32 = 1;
constructor() {
}
}

class B extends A {
b: i32 = 2;
constructor() {
super();
assert(this.a == 1);
assert(this.b == 2);
}
}

function test(): void {
var b = new B();
assert(b.a == 1);
assert(b.b == 2);
}

test();
Loading