Skip to content

Implement calls to 'super()' #445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 31, 2019
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