From 6e3c348b7fb8d30d84e57e11ad056cb888bb5815 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 15:30:02 +0100 Subject: [PATCH 01/12] Implement calls to 'super()' --- src/ast.ts | 3 +- src/compiler.ts | 44 +++++ src/resolver.ts | 10 +- tests/compiler/call-super.optimized.wat | 173 ++++++++++++++++++ tests/compiler/call-super.ts | 24 +++ tests/compiler/call-super.untouched.wat | 232 ++++++++++++++++++++++++ 6 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 tests/compiler/call-super.optimized.wat create mode 100644 tests/compiler/call-super.ts create mode 100644 tests/compiler/call-super.untouched.wat diff --git a/src/ast.ts b/src/ast.ts index 1b2a839a5c..a3290e11b5 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -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; } diff --git a/src/compiler.ts b/src/compiler.ts index c8d6bbd292..f6726eeb62 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -5230,6 +5230,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(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( @@ -6658,6 +6671,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 diff --git a/src/resolver.ts b/src/resolver.ts index 33f5f883c8..e24e54fd07 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -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 = (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) { diff --git a/tests/compiler/call-super.optimized.wat b/tests/compiler/call-super.optimized.wat new file mode 100644 index 0000000000..25a75bc106 --- /dev/null +++ b/tests/compiler/call-super.optimized.wat @@ -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 + ) +) diff --git a/tests/compiler/call-super.ts b/tests/compiler/call-super.ts new file mode 100644 index 0000000000..dab4214d25 --- /dev/null +++ b/tests/compiler/call-super.ts @@ -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(); diff --git a/tests/compiler/call-super.untouched.wat b/tests/compiler/call-super.untouched.wat new file mode 100644 index 0000000000..fe799c8c1b --- /dev/null +++ b/tests/compiler/call-super.untouched.wat @@ -0,0 +1,232 @@ +(module + (type $v (func)) + (type $ii (func (param i32) (result i32))) + (type $iiiiv (func (param i32 i32 i32 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\00") + (table $0 1 anyfunc) + (elem (i32.const 0) $null) + (global $~lib/internal/allocator/AL_BITS i32 (i32.const 3)) + (global $~lib/internal/allocator/AL_SIZE i32 (i32.const 8)) + (global $~lib/internal/allocator/AL_MASK i32 (i32.const 7)) + (global $~lib/internal/allocator/MAX_SIZE_32 i32 (i32.const 1073741824)) + (global $~lib/allocator/arena/startOffset (mut i32) (i32.const 0)) + (global $~lib/allocator/arena/offset (mut i32) (i32.const 0)) + (global $HEAP_BASE i32 (i32.const 40)) + (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) + (local $4 i32) + (local $5 i32) + (local $6 i32) + get_local $0 + get_global $~lib/internal/allocator/MAX_SIZE_32 + i32.gt_u + if + unreachable + end + get_global $~lib/allocator/arena/offset + set_local $1 + get_local $1 + get_local $0 + tee_local $2 + i32.const 1 + tee_local $3 + get_local $2 + get_local $3 + i32.gt_u + select + i32.add + get_global $~lib/internal/allocator/AL_MASK + i32.add + get_global $~lib/internal/allocator/AL_MASK + i32.const -1 + i32.xor + i32.and + set_local $4 + current_memory + set_local $5 + get_local $4 + get_local $5 + i32.const 16 + i32.shl + i32.gt_u + if + get_local $4 + get_local $1 + i32.sub + i32.const 65535 + i32.add + i32.const 65535 + i32.const -1 + i32.xor + i32.and + i32.const 16 + i32.shr_u + set_local $2 + get_local $5 + tee_local $3 + get_local $2 + tee_local $6 + get_local $3 + get_local $6 + i32.gt_s + select + set_local $3 + get_local $3 + grow_memory + i32.const 0 + i32.lt_s + if + get_local $2 + grow_memory + i32.const 0 + i32.lt_s + if + unreachable + end + end + end + get_local $4 + set_global $~lib/allocator/arena/offset + get_local $1 + ) + (func $~lib/memory/memory.allocate (; 2 ;) (type $ii) (param $0 i32) (result i32) + get_local $0 + call $~lib/allocator/arena/__memory_allocate + return + ) + (func $call-super/A#constructor (; 3 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + get_local $0 + if (result i32) + get_local $0 + else + block (result i32) + i32.const 4 + call $~lib/memory/memory.allocate + set_local $1 + get_local $1 + i32.const 1 + i32.store + get_local $1 + end + tee_local $0 + end + tee_local $0 + ) + (func $call-super/B#constructor (; 4 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + block (result i32) + i32.const 8 + call $~lib/memory/memory.allocate + set_local $1 + get_local $1 + i32.const 1 + i32.store + get_local $1 + i32.const 2 + i32.store offset=4 + get_local $1 + end + call $call-super/A#constructor + set_local $0 + get_local $0 + if (result i32) + get_local $0 + else + block (result i32) + i32.const 8 + call $~lib/memory/memory.allocate + set_local $1 + get_local $1 + i32.const 1 + i32.store + get_local $1 + i32.const 2 + i32.store offset=4 + get_local $1 + end + tee_local $0 + end + tee_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + 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.eq + i32.eqz + 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 (; 5 ;) (type $v) + (local $0 i32) + i32.const 0 + call $call-super/B#constructor + set_local $0 + get_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + 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.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 21 + i32.const 2 + call $~lib/env/abort + unreachable + end + ) + (func $start (; 6 ;) (type $v) + get_global $HEAP_BASE + get_global $~lib/internal/allocator/AL_MASK + i32.add + get_global $~lib/internal/allocator/AL_MASK + i32.const -1 + i32.xor + i32.and + set_global $~lib/allocator/arena/startOffset + get_global $~lib/allocator/arena/startOffset + set_global $~lib/allocator/arena/offset + call $call-super/test + ) + (func $null (; 7 ;) (type $v) + ) +) From a749af919b14cc70623bf9000833f7e6d5edffa7 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 17:45:01 +0100 Subject: [PATCH 02/12] check that super() is the first statement in constructors of derived classes --- src/ast.ts | 6 ++++++ src/compiler.ts | 14 +++++++++++++- src/diagnosticMessages.generated.ts | 2 ++ src/diagnosticMessages.json | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ast.ts b/src/ast.ts index a3290e11b5..7b57aa90f1 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -135,6 +135,12 @@ export function nodeIsGenericCallable(kind: NodeKind): bool { return false; } +export function nodeIsSuperCall(node: Node): bool { + if (node.kind == NodeKind.EXPRESSION) node = (node).expression; + return node.kind == NodeKind.CALL + && (node).expression.kind == NodeKind.SUPER; +} + /** Base class of all nodes. */ export abstract class Node { diff --git a/src/compiler.ts b/src/compiler.ts index f6726eeb62..56cf6ba229 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -144,6 +144,7 @@ import { FieldDeclaration, nodeIsConstantValue, + nodeIsSuperCall, isLastStatement, findDecorator } from "./ast"; @@ -1075,7 +1076,18 @@ export class Compiler extends DiagnosticEmitter { flow.finalize(); } else { assert(body.kind == NodeKind.BLOCK); - let stmts = this.compileStatements((body).statements); + let statements = (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 ((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( diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 85c5b678f5..75b9822bc1 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -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, @@ -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."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 3594958b40..4aa25dc04f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -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, From 92ac40616f6635024c568f1467c1df5fc58fdeb2 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 18:14:47 +0100 Subject: [PATCH 03/12] also check that super calls are the first statement within their constructor when compiling them --- src/ast.ts | 8 ++++++++ src/compiler.ts | 9 ++++++++- src/diagnosticMessages.generated.ts | 2 ++ src/diagnosticMessages.json | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ast.ts b/src/ast.ts index 7b57aa90f1..94e6aa6fbf 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1824,6 +1824,14 @@ export class FunctionDeclaration extends DeclarationStatement { var typeParameters = this.typeParameters; return typeParameters != null && typeParameters.length > 0; } + + get firstStatement(): Statement | null { + var body = this.body; + if (!(body && body.kind == NodeKind.BLOCK)) return null; + var statements = (body).statements; + if (statements.length < 0) return null; + return statements[0]; + } } /** Represents an `if` statement. */ diff --git a/src/compiler.ts b/src/compiler.ts index 56cf6ba229..252a2160b0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -5244,7 +5244,14 @@ export class Compiler extends DiagnosticEmitter { // call to `super()` case ElementKind.CLASS: { - if (expression.expression.kind == NodeKind.SUPER) { + if (expression.expression.kind == NodeKind.SUPER && currentFunction.is(CommonFlags.CONSTRUCTOR)) { + if (expression.parent != currentFunction.prototype.declaration.firstStatement) { + this.error( + DiagnosticCode.A_super_call_must_be_the_first_statement_in_the_constructor, + expression.range + ); + return module.createUnreachable(); + } let classInstance = assert(currentFunction.parent); assert(classInstance.kind == ElementKind.CLASS); let expr = this.compileSuperInstantiate(classInstance, expression.arguments, expression); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 75b9822bc1..c769c38dfe 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -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, + A_super_call_must_be_the_first_statement_in_the_constructor = 2376, Constructors_for_derived_classes_must_call_super_first = 2377, _get_and_set_accessor_must_have_the_same_type = 2380, Constructor_implementation_is_missing = 2390, @@ -225,6 +226,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 2376: return "A 'super' call must be the first statement in the constructor."; 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."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 4aa25dc04f..bcd801c98f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -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, + "A 'super' call must be the first statement in the constructor.": 2376, "Constructors for derived classes must call 'super' first.": 2377, "'get' and 'set' accessor must have the same type.": 2380, "Constructor implementation is missing.": 2390, From c5fc6c63ac8016101d4438f0eee0fbb040734d4a Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 18:17:15 +0100 Subject: [PATCH 04/12] fix --- src/ast.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast.ts b/src/ast.ts index 94e6aa6fbf..cf2bd6bd37 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1829,7 +1829,7 @@ export class FunctionDeclaration extends DeclarationStatement { var body = this.body; if (!(body && body.kind == NodeKind.BLOCK)) return null; var statements = (body).statements; - if (statements.length < 0) return null; + if (!statements.length) return null; return statements[0]; } } From 8b065bfd484febbc6eb9d8709103529d92b87f2f Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 18:33:55 +0100 Subject: [PATCH 05/12] add diagnostic for when trying to call super outside of a ctor --- src/compiler.ts | 12 ++++++++++-- src/diagnosticMessages.generated.ts | 2 ++ src/diagnosticMessages.json | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 252a2160b0..7336cbf682 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -5242,9 +5242,17 @@ export class Compiler extends DiagnosticEmitter { break; } - // call to `super()` case ElementKind.CLASS: { - if (expression.expression.kind == NodeKind.SUPER && currentFunction.is(CommonFlags.CONSTRUCTOR)) { + + // call to `super()` + if (expression.expression.kind == NodeKind.SUPER) { + if (!currentFunction.is(CommonFlags.CONSTRUCTOR)) { + this.error( + DiagnosticCode.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors, + expression.range + ); + return module.createUnreachable(); + } if (expression.parent != currentFunction.prototype.declaration.firstStatement) { this.error( DiagnosticCode.A_super_call_must_be_the_first_statement_in_the_constructor, diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index c769c38dfe..6cbb34d373 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -94,6 +94,7 @@ export enum DiagnosticCode { Index_signature_is_missing_in_type_0 = 2329, _this_cannot_be_referenced_in_current_location = 2332, _super_can_only_be_referenced_in_a_derived_class = 2335, + Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors = 2337, Property_0_does_not_exist_on_type_1 = 2339, Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures = 2349, Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature = 2351, @@ -219,6 +220,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2329: return "Index signature is missing in type '{0}'."; case 2332: return "'this' cannot be referenced in current location."; case 2335: return "'super' can only be referenced in a derived class."; + case 2337: return "Super calls are not permitted outside constructors or in nested functions inside constructors."; case 2339: return "Property '{0}' does not exist on type '{1}'."; case 2349: return "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures."; case 2351: return "Cannot use 'new' with an expression whose type lacks a construct signature."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index bcd801c98f..24bc5677ff 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -88,6 +88,7 @@ "Index signature is missing in type '{0}'.": 2329, "'this' cannot be referenced in current location.": 2332, "'super' can only be referenced in a derived class.": 2335, + "Super calls are not permitted outside constructors or in nested functions inside constructors.": 2337, "Property '{0}' does not exist on type '{1}'.": 2339, "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": 2349, "Cannot use 'new' with an expression whose type lacks a construct signature.": 2351, From 891d654ed8cab2b3cbbee7bf87872d87e5c6e7f8 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 19:17:57 +0100 Subject: [PATCH 06/12] utilize flow to check for super conditions --- src/compiler.ts | 43 ++++++++++++++----------- src/diagnosticMessages.generated.ts | 8 +++-- src/diagnosticMessages.json | 5 +-- src/program.ts | 16 +++++---- tests/compiler/call-super.optimized.wat | 12 ------- tests/compiler/call-super.untouched.wat | 18 ----------- 6 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 7336cbf682..e8ace960a5 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1077,16 +1077,6 @@ export class Compiler extends DiagnosticEmitter { } else { assert(body.kind == NodeKind.BLOCK); let statements = (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 ((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)); @@ -1107,6 +1097,8 @@ export class Compiler extends DiagnosticEmitter { if (isConstructor) { let nativeSizeType = this.options.nativeSizeType; assert(instance.is(CommonFlags.INSTANCE)); + let parent = assert(instance.parent); + assert(parent.kind == ElementKind.CLASS); // implicitly return `this` if the constructor doesn't always return on its own if (!flow.is(FlowFlags.RETURNS)) { @@ -1117,14 +1109,20 @@ export class Compiler extends DiagnosticEmitter { // if not all branches are guaranteed to allocate, also append a conditional allocation } else { - let parent = assert(instance.parent); - assert(parent.kind == ElementKind.CLASS); stmts.push(module.createTeeLocal(0, this.makeConditionalAllocate(parent, declaration.name) )); } } + // check that super has been called if this is a derived class + if ((parent).base && !flow.is(FlowFlags.CALLS_SUPER)) { + this.error( + DiagnosticCode.Constructors_for_derived_classes_must_contain_a_super_call, + instance.prototype.declaration.range + ); + } + // make sure all branches return } else if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) { this.error( @@ -5253,17 +5251,26 @@ export class Compiler extends DiagnosticEmitter { ); return module.createUnreachable(); } - if (expression.parent != currentFunction.prototype.declaration.firstStatement) { + + let classInstance = assert(currentFunction.parent); + assert(classInstance.kind == ElementKind.CLASS); + let expr = this.compileSuperInstantiate(classInstance, expression.arguments, expression); + this.currentType = Type.void; + + // check that super() is called before allocation is performed (incl. in super arguments) + let flow = currentFunction.flow; + if (flow.isAny( + FlowFlags.ALLOCATES | + FlowFlags.CONDITIONALLY_ALLOCATES + )) { this.error( - DiagnosticCode.A_super_call_must_be_the_first_statement_in_the_constructor, + DiagnosticCode._super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class, expression.range ); return module.createUnreachable(); } - let classInstance = assert(currentFunction.parent); - assert(classInstance.kind == ElementKind.CLASS); - let expr = this.compileSuperInstantiate(classInstance, expression.arguments, expression); - this.currentType = Type.void; + flow.set(FlowFlags.ALLOCATES | FlowFlags.CALLS_SUPER); + let thisLocal = assert(this.currentFunction.flow.getScopedLocal("this")); return module.createSetLocal(thisLocal.index, expr); } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 6cbb34d373..4c76e59bf5 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -103,7 +103,7 @@ export enum DiagnosticCode { 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, A_super_call_must_be_the_first_statement_in_the_constructor = 2376, - Constructors_for_derived_classes_must_call_super_first = 2377, + Constructors_for_derived_classes_must_contain_a_super_call = 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, @@ -127,7 +127,8 @@ export enum DiagnosticCode { Required_type_parameters_may_not_follow_optional_type_parameters = 2706, File_0_not_found = 6054, Numeric_separators_are_not_allowed_here = 6188, - Multiple_consecutive_numeric_separators_are_not_permitted = 6189 + Multiple_consecutive_numeric_separators_are_not_permitted = 6189, + _super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class = 17009 } /** Translates a diagnostic code to its respective string. */ @@ -229,7 +230,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { 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 2376: return "A 'super' call must be the first statement in the constructor."; - case 2377: return "Constructors for derived classes must call 'super' first."; + case 2377: return "Constructors for derived classes must contain a 'super' call."; 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."; @@ -254,6 +255,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 6054: return "File '{0}' not found."; case 6188: return "Numeric separators are not allowed here."; case 6189: return "Multiple consecutive numeric separators are not permitted."; + case 17009: return "'super' must be called before accessing 'this' in the constructor of a derived class."; default: return ""; } } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 24bc5677ff..85006c6824 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -97,7 +97,7 @@ "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, "A 'super' call must be the first statement in the constructor.": 2376, - "Constructors for derived classes must call 'super' first.": 2377, + "Constructors for derived classes must contain a 'super' call.": 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, @@ -122,5 +122,6 @@ "File '{0}' not found.": 6054, "Numeric separators are not allowed here.": 6188, - "Multiple consecutive numeric separators are not permitted.": 6189 + "Multiple consecutive numeric separators are not permitted.": 6189, + "'super' must be called before accessing 'this' in the constructor of a derived class.": 17009 } diff --git a/src/program.ts b/src/program.ts index c916acf2a6..eda4f2cf40 100644 --- a/src/program.ts +++ b/src/program.ts @@ -3041,26 +3041,28 @@ export const enum FlowFlags { CONTINUES = 1 << 4, /** This branch always allocates. Constructors only. */ ALLOCATES = 1 << 5, + /** This branch always calls super. Constructors only. */ + CALLS_SUPER = 1 << 6, // conditional /** This branch conditionally returns in a child branch. */ - CONDITIONALLY_RETURNS = 1 << 6, + CONDITIONALLY_RETURNS = 1 << 7, /** This branch conditionally throws in a child branch. */ - CONDITIONALLY_THROWS = 1 << 7, + CONDITIONALLY_THROWS = 1 << 8, /** This branch conditionally breaks in a child branch. */ - CONDITIONALLY_BREAKS = 1 << 8, + CONDITIONALLY_BREAKS = 1 << 9, /** This branch conditionally continues in a child branch. */ - CONDITIONALLY_CONTINUES = 1 << 9, + CONDITIONALLY_CONTINUES = 1 << 10, /** This branch conditionally allocates in a child branch. Constructors only. */ - CONDITIONALLY_ALLOCATES = 1 << 10, + CONDITIONALLY_ALLOCATES = 1 << 11, // special /** This branch is part of inlining a function. */ - INLINE_CONTEXT = 1 << 11, + INLINE_CONTEXT = 1 << 12, /** This branch explicitly requests no bounds checking. */ - UNCHECKED_CONTEXT = 1 << 12, + UNCHECKED_CONTEXT = 1 << 13, // masks diff --git a/tests/compiler/call-super.optimized.wat b/tests/compiler/call-super.optimized.wat index 25a75bc106..61c9388357 100644 --- a/tests/compiler/call-super.optimized.wat +++ b/tests/compiler/call-super.optimized.wat @@ -95,18 +95,6 @@ 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 diff --git a/tests/compiler/call-super.untouched.wat b/tests/compiler/call-super.untouched.wat index fe799c8c1b..7e1fb52d89 100644 --- a/tests/compiler/call-super.untouched.wat +++ b/tests/compiler/call-super.untouched.wat @@ -137,24 +137,6 @@ call $call-super/A#constructor set_local $0 get_local $0 - if (result i32) - get_local $0 - else - block (result i32) - i32.const 8 - call $~lib/memory/memory.allocate - set_local $1 - get_local $1 - i32.const 1 - i32.store - get_local $1 - i32.const 2 - i32.store offset=4 - get_local $1 - end - tee_local $0 - end - tee_local $0 i32.load i32.const 1 i32.eq From 2af8c229db71263f16e3b8d77a7c7f32ef178252 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 19:19:31 +0100 Subject: [PATCH 07/12] fix --- src/program.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/program.ts b/src/program.ts index eda4f2cf40..f021900843 100644 --- a/src/program.ts +++ b/src/program.ts @@ -3078,7 +3078,8 @@ export const enum FlowFlags { | FlowFlags.THROWS | FlowFlags.BREAKS | FlowFlags.CONTINUES - | FlowFlags.ALLOCATES, + | FlowFlags.ALLOCATES + | FlowFlags.CALLS_SUPER, /** Any conditional flag. */ ANY_CONDITIONAL = FlowFlags.CONDITIONALLY_RETURNS From 50f2d6af543d82a84f845aacf74c03768af560e5 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 19:32:47 +0100 Subject: [PATCH 08/12] check that super has been called before accessing it otherwise --- src/ast.ts | 8 -------- src/compiler.ts | 8 ++++++++ src/diagnosticMessages.generated.ts | 4 +++- src/diagnosticMessages.json | 3 ++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index cf2bd6bd37..7b57aa90f1 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1824,14 +1824,6 @@ export class FunctionDeclaration extends DeclarationStatement { var typeParameters = this.typeParameters; return typeParameters != null && typeParameters.length > 0; } - - get firstStatement(): Statement | null { - var body = this.body; - if (!(body && body.kind == NodeKind.BLOCK)) return null; - var statements = (body).statements; - if (!statements.length) return null; - return statements[0]; - } } /** Represents an `if` statement. */ diff --git a/src/compiler.ts b/src/compiler.ts index e8ace960a5..0a110a8dcb 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6074,6 +6074,14 @@ export class Compiler extends DiagnosticEmitter { return module.createUnreachable(); } case NodeKind.SUPER: { + if (currentFunction.is(CommonFlags.CONSTRUCTOR)) { + if (!currentFunction.flow.is(FlowFlags.CALLS_SUPER)) { + this.error( + DiagnosticCode._super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class, + expression.range + ); + } + } let flow = currentFunction.flow; if (flow.is(FlowFlags.INLINE_CONTEXT)) { let scopedThis = flow.getScopedLocal("this"); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 4c76e59bf5..1ef62a1822 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -128,7 +128,8 @@ export enum DiagnosticCode { File_0_not_found = 6054, Numeric_separators_are_not_allowed_here = 6188, Multiple_consecutive_numeric_separators_are_not_permitted = 6189, - _super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class = 17009 + _super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class = 17009, + _super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class = 17011 } /** Translates a diagnostic code to its respective string. */ @@ -256,6 +257,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 6188: return "Numeric separators are not allowed here."; case 6189: return "Multiple consecutive numeric separators are not permitted."; case 17009: return "'super' must be called before accessing 'this' in the constructor of a derived class."; + case 17011: return "'super' must be called before accessing a property of 'super' in the constructor of a derived class."; default: return ""; } } diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 85006c6824..9e3d82a5ba 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -123,5 +123,6 @@ "File '{0}' not found.": 6054, "Numeric separators are not allowed here.": 6188, "Multiple consecutive numeric separators are not permitted.": 6189, - "'super' must be called before accessing 'this' in the constructor of a derived class.": 17009 + "'super' must be called before accessing 'this' in the constructor of a derived class.": 17009, + "'super' must be called before accessing a property of 'super' in the constructor of a derived class.": 17011 } From f2f03fc5373b14764ab3f991e7dbdf107b757b39 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 19:38:35 +0100 Subject: [PATCH 09/12] validate super syntax in parser --- src/compiler.ts | 1 + src/diagnosticMessages.generated.ts | 2 ++ src/diagnosticMessages.json | 1 + src/parser.ts | 6 ++++++ 4 files changed, 10 insertions(+) diff --git a/src/compiler.ts b/src/compiler.ts index 0a110a8dcb..2802c5383a 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6076,6 +6076,7 @@ export class Compiler extends DiagnosticEmitter { case NodeKind.SUPER: { if (currentFunction.is(CommonFlags.CONSTRUCTOR)) { if (!currentFunction.flow.is(FlowFlags.CALLS_SUPER)) { + // TS1034 in the parser effectively limits this to property accesses this.error( DiagnosticCode._super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class, expression.range diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 1ef62a1822..831dc9a4ea 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -66,6 +66,7 @@ export enum DiagnosticCode { Unexpected_end_of_text = 1126, Invalid_character = 1127, _case_or_default_expected = 1130, + _super_must_be_followed_by_an_argument_list_or_member_access = 1034, A_declare_modifier_cannot_be_used_in_an_already_ambient_context = 1038, Type_argument_expected = 1140, String_literal_expected = 1141, @@ -194,6 +195,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1126: return "Unexpected end of text."; case 1127: return "Invalid character."; case 1130: return "'case' or 'default' expected."; + case 1034: return "'super' must be followed by an argument list or member access."; case 1038: return "A 'declare' modifier cannot be used in an already ambient context."; case 1140: return "Type argument expected."; case 1141: return "String literal expected."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 9e3d82a5ba..c1830de6d8 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -59,6 +59,7 @@ "Unexpected end of text.": 1126, "Invalid character.": 1127, "'case' or 'default' expected.": 1130, + "'super' must be followed by an argument list or member access.": 1034, "A 'declare' modifier cannot be used in an already ambient context.": 1038, "Type argument expected.": 1140, "String literal expected.": 1141, diff --git a/src/parser.ts b/src/parser.ts index e19cf38a80..75b24ded2d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3337,6 +3337,12 @@ export class Parser extends DiagnosticEmitter { return Node.createConstructorExpression(tn.range(startPos, tn.pos)); } case Token.SUPER: { + if (tn.peek() != Token.DOT && tn.nextToken != Token.OPENPAREN) { + this.error( + DiagnosticCode._super_must_be_followed_by_an_argument_list_or_member_access, + tn.range() + ); + } return Node.createSuperExpression(tn.range(startPos, tn.pos)); } case Token.STRINGLITERAL: { From 94fbbc7ede4ac840db1d1e98ab7e42b4b0092fdf Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 30 Jan 2019 20:19:24 +0100 Subject: [PATCH 10/12] minor cleanup --- src/ast.ts | 6 ------ src/compiler.ts | 4 +--- src/program.ts | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 7b57aa90f1..a3290e11b5 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -135,12 +135,6 @@ export function nodeIsGenericCallable(kind: NodeKind): bool { return false; } -export function nodeIsSuperCall(node: Node): bool { - if (node.kind == NodeKind.EXPRESSION) node = (node).expression; - return node.kind == NodeKind.CALL - && (node).expression.kind == NodeKind.SUPER; -} - /** Base class of all nodes. */ export abstract class Node { diff --git a/src/compiler.ts b/src/compiler.ts index 2802c5383a..0723c31ade 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -144,7 +144,6 @@ import { FieldDeclaration, nodeIsConstantValue, - nodeIsSuperCall, isLastStatement, findDecorator } from "./ast"; @@ -1076,8 +1075,7 @@ export class Compiler extends DiagnosticEmitter { flow.finalize(); } else { assert(body.kind == NodeKind.BLOCK); - let statements = (body).statements; - let stmts = this.compileStatements(statements); + let stmts = this.compileStatements((body).statements); if (instance.is(CommonFlags.MAIN)) { module.addGlobal("~started", NativeType.I32, true, module.createI32(0)); stmts.unshift( diff --git a/src/program.ts b/src/program.ts index f021900843..54ff52923e 100644 --- a/src/program.ts +++ b/src/program.ts @@ -2931,6 +2931,21 @@ export class Class extends Element { } } + /** Gets the first applicable constructor. */ + getfirstConstructor(includingThis: bool = true): Function | null { + if (includingThis && this.constructorInstance) return this.constructorInstance; + + // traverse to the top-most derived constructor + var currentBase: Class | null = this.base; + var instance: Function | null = null; + while (currentBase) { + instance = currentBase.constructorInstance; + if (instance) return instance; + currentBase = currentBase.base; + } + return null; + } + /** Tests if a value of this class type is assignable to a target of the specified class type. */ isAssignableTo(target: Class): bool { var current: Class | null = this; From ef573508ce499b6c3c2fd241502544eee884eb21 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 31 Jan 2019 07:26:17 +0100 Subject: [PATCH 11/12] remove tinkering --- src/program.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/program.ts b/src/program.ts index 54ff52923e..f021900843 100644 --- a/src/program.ts +++ b/src/program.ts @@ -2931,21 +2931,6 @@ export class Class extends Element { } } - /** Gets the first applicable constructor. */ - getfirstConstructor(includingThis: bool = true): Function | null { - if (includingThis && this.constructorInstance) return this.constructorInstance; - - // traverse to the top-most derived constructor - var currentBase: Class | null = this.base; - var instance: Function | null = null; - while (currentBase) { - instance = currentBase.constructorInstance; - if (instance) return instance; - currentBase = currentBase.base; - } - return null; - } - /** Tests if a value of this class type is assignable to a target of the specified class type. */ isAssignableTo(target: Class): bool { var current: Class | null = this; From 5883ec9a0e8abac34f510fdf0c54219dc0c7918f Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 31 Jan 2019 10:25:03 +0100 Subject: [PATCH 12/12] more tests --- tests/compiler/call-super.optimized.wat | 145 +++++++++++++++++++--- tests/compiler/call-super.ts | 46 ++++++- tests/compiler/call-super.untouched.wat | 157 ++++++++++++++++++++++-- 3 files changed, 323 insertions(+), 25 deletions(-) diff --git a/tests/compiler/call-super.optimized.wat b/tests/compiler/call-super.optimized.wat index 61c9388357..2b3f00cd94 100644 --- a/tests/compiler/call-super.optimized.wat +++ b/tests/compiler/call-super.optimized.wat @@ -75,7 +75,31 @@ set_global $~lib/allocator/arena/offset get_local $1 ) - (func $call-super/B#constructor (; 2 ;) (type $FUNCSIG$i) (result i32) + (func $call-super/A#constructor (; 2 ;) (type $ii) (param $0 i32) (result i32) + 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.load + i32.const 1 + i32.ne + if + i32.const 0 + i32.const 8 + i32.const 6 + i32.const 4 + call $~lib/env/abort + unreachable + end + get_local $0 + ) + (func $call-super/B#constructor (; 3 ;) (type $FUNCSIG$i) (result i32) (local $0 i32) i32.const 8 call $~lib/allocator/arena/__memory_allocate @@ -86,14 +110,71 @@ i32.const 2 i32.store offset=4 get_local $0 - i32.eqz + call $call-super/A#constructor + tee_local $0 + i32.load + i32.const 1 + i32.ne + if + i32.const 0 + i32.const 8 + i32.const 15 + 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 16 i32.const 4 - call $~lib/allocator/arena/__memory_allocate - tee_local $0 - i32.const 1 - i32.store + call $~lib/env/abort + unreachable + end + get_local $0 + ) + (func $call-super/test1 (; 4 ;) (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 22 + 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 23 + i32.const 2 + call $~lib/env/abort + unreachable end + ) + (func $call-super/D#constructor (; 5 ;) (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.load i32.const 1 @@ -101,7 +182,7 @@ if i32.const 0 i32.const 8 - i32.const 13 + i32.const 36 i32.const 4 call $~lib/env/abort unreachable @@ -113,16 +194,16 @@ if i32.const 0 i32.const 8 - i32.const 14 + i32.const 37 i32.const 4 call $~lib/env/abort unreachable end get_local $0 ) - (func $call-super/test (; 3 ;) (type $v) + (func $call-super/test2 (; 6 ;) (type $v) (local $0 i32) - call $call-super/B#constructor + call $call-super/D#constructor tee_local $0 i32.load i32.const 1 @@ -130,7 +211,7 @@ if i32.const 0 i32.const 8 - i32.const 20 + i32.const 43 i32.const 2 call $~lib/env/abort unreachable @@ -142,20 +223,54 @@ if i32.const 0 i32.const 8 - i32.const 21 + i32.const 44 i32.const 2 call $~lib/env/abort unreachable end ) - (func $start (; 4 ;) (type $v) + (func $call-super/E#constructor (; 7 ;) (type $FUNCSIG$i) (result i32) + (local $0 i32) + i32.const 4 + call $~lib/allocator/arena/__memory_allocate + tee_local $0 + i32.const 1 + i32.store + get_local $0 + i32.load + i32.const 1 + i32.ne + if + i32.const 0 + i32.const 8 + i32.const 52 + i32.const 4 + call $~lib/env/abort + unreachable + end + get_local $0 + ) + (func $start (; 8 ;) (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 + call $call-super/test1 + call $call-super/test2 + call $call-super/E#constructor + i32.load + i32.const 1 + i32.ne + if + i32.const 0 + i32.const 8 + i32.const 62 + i32.const 2 + call $~lib/env/abort + unreachable + end ) - (func $null (; 5 ;) (type $v) + (func $null (; 9 ;) (type $v) nop ) ) diff --git a/tests/compiler/call-super.ts b/tests/compiler/call-super.ts index dab4214d25..939226ea18 100644 --- a/tests/compiler/call-super.ts +++ b/tests/compiler/call-super.ts @@ -3,10 +3,12 @@ import "allocator/arena"; class A { a: i32 = 1; constructor() { + assert(this.a == 1); } } class B extends A { + // a: i32 = 3; // FIXME: currently duplicate identifier b: i32 = 2; constructor() { super(); @@ -15,10 +17,50 @@ class B extends A { } } -function test(): void { +function test1(): void { var b = new B(); assert(b.a == 1); assert(b.b == 2); } -test(); +test1(); + +class C { + a: i32 = 1; +} + +class D extends C { + b: i32 = 2; + constructor() { + super(); + assert(this.a == 1); + assert(this.b == 2); + } +} + +function test2(): void { + var d = new D(); + assert(d.a == 1); + assert(d.b == 2); +} + +test2(); + +class E { + a: i32 = 1; + constructor() { + assert(this.a == 1); + } +} + +class F extends E { + b: i32 = 2; +} + +function test3(): void { + var f = new F(); + assert(f.a == 1); + // assert(f.b == 2); // FIXME: uses E#constructor, not initializing fields +} + +test3(); diff --git a/tests/compiler/call-super.untouched.wat b/tests/compiler/call-super.untouched.wat index 7e1fb52d89..b7e3eb9c7f 100644 --- a/tests/compiler/call-super.untouched.wat +++ b/tests/compiler/call-super.untouched.wat @@ -119,6 +119,19 @@ tee_local $0 end tee_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 6 + i32.const 4 + call $~lib/env/abort + unreachable + end + get_local $0 ) (func $call-super/B#constructor (; 4 ;) (type $ii) (param $0 i32) (result i32) (local $1 i32) @@ -144,7 +157,7 @@ if i32.const 0 i32.const 8 - i32.const 13 + i32.const 15 i32.const 4 call $~lib/env/abort unreachable @@ -157,14 +170,14 @@ if i32.const 0 i32.const 8 - i32.const 14 + i32.const 16 i32.const 4 call $~lib/env/abort unreachable end get_local $0 ) - (func $call-super/test (; 5 ;) (type $v) + (func $call-super/test1 (; 5 ;) (type $v) (local $0 i32) i32.const 0 call $call-super/B#constructor @@ -177,8 +190,50 @@ if i32.const 0 i32.const 8 - i32.const 20 + i32.const 22 + i32.const 2 + call $~lib/env/abort + unreachable + end + get_local $0 + i32.load offset=4 + i32.const 2 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 23 + i32.const 2 + call $~lib/env/abort + unreachable + end + ) + (func $call-super/D#constructor (; 6 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + block (result i32) + i32.const 8 + call $~lib/memory/memory.allocate + set_local $1 + get_local $1 + i32.const 1 + i32.store + get_local $1 i32.const 2 + i32.store offset=4 + get_local $1 + end + set_local $0 + get_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 36 + i32.const 4 call $~lib/env/abort unreachable end @@ -190,13 +245,97 @@ if i32.const 0 i32.const 8 - i32.const 21 + i32.const 37 + i32.const 4 + call $~lib/env/abort + unreachable + end + get_local $0 + ) + (func $call-super/test2 (; 7 ;) (type $v) + (local $0 i32) + i32.const 0 + call $call-super/D#constructor + set_local $0 + get_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 43 + i32.const 2 + call $~lib/env/abort + unreachable + end + get_local $0 + i32.load offset=4 + i32.const 2 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 44 + i32.const 2 + call $~lib/env/abort + unreachable + end + ) + (func $call-super/E#constructor (; 8 ;) (type $ii) (param $0 i32) (result i32) + (local $1 i32) + get_local $0 + if (result i32) + get_local $0 + else + block (result i32) + i32.const 4 + call $~lib/memory/memory.allocate + set_local $1 + get_local $1 + i32.const 1 + i32.store + get_local $1 + end + tee_local $0 + end + tee_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 52 + i32.const 4 + call $~lib/env/abort + unreachable + end + get_local $0 + ) + (func $call-super/test3 (; 9 ;) (type $v) + (local $0 i32) + i32.const 0 + call $call-super/E#constructor + set_local $0 + get_local $0 + i32.load + i32.const 1 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 8 + i32.const 62 i32.const 2 call $~lib/env/abort unreachable end ) - (func $start (; 6 ;) (type $v) + (func $start (; 10 ;) (type $v) get_global $HEAP_BASE get_global $~lib/internal/allocator/AL_MASK i32.add @@ -207,8 +346,10 @@ set_global $~lib/allocator/arena/startOffset get_global $~lib/allocator/arena/startOffset set_global $~lib/allocator/arena/offset - call $call-super/test + call $call-super/test1 + call $call-super/test2 + call $call-super/test3 ) - (func $null (; 7 ;) (type $v) + (func $null (; 11 ;) (type $v) ) )