From 50dea3b1df93b1ae2206fdbff9ef208aae7bc30e Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Thu, 4 Jan 2018 01:36:26 +0100 Subject: [PATCH] Basic initial inheritance --- src/compiler.ts | 37 ++++++---- src/diagnosticMessages.generated.ts | 6 ++ src/diagnosticMessages.json | 3 + src/program.ts | 56 ++++++++++++--- tests/compiler/class-extends.optimized.wast | 26 +++++++ tests/compiler/class-extends.ts | 14 ++++ tests/compiler/class-extends.wast | 79 +++++++++++++++++++++ 7 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 tests/compiler/class-extends.optimized.wast create mode 100644 tests/compiler/class-extends.ts create mode 100644 tests/compiler/class-extends.wast diff --git a/src/compiler.ts b/src/compiler.ts index eba1c222..b591c145 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -352,8 +352,10 @@ export class Compiler extends DiagnosticEmitter { return false; } else if (declaration.initializer) { initExpr = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable - if (this.currentType == Type.void) + if (this.currentType == Type.void) { + this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, declaration.range, this.currentType.toString(), ""); return false; + } global.type = this.currentType; } else { this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd); @@ -1010,8 +1012,11 @@ export class Compiler extends DiagnosticEmitter { init = this.compileExpression(declaration.initializer, type); // reports and returns unreachable } else if (declaration.initializer) { // infer type init = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable - if ((type = this.currentType) == Type.void) + if (this.currentType == Type.void) { + this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, declaration.range, this.currentType.toString(), ""); continue; + } + type = this.currentType; } else { this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd); continue; @@ -1168,8 +1173,8 @@ export class Compiler extends DiagnosticEmitter { // void to any if (fromType.kind == TypeKind.VOID) { - this.error(DiagnosticCode.Operation_not_supported, reportNode.range); - throw new Error("unexpected conversion from void"); + this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, reportNode.range, fromType.toString(), toType.toString()); + return this.module.createUnreachable(); } // any to void @@ -2108,6 +2113,7 @@ export class Compiler extends DiagnosticEmitter { if (!resolved) return this.module.createUnreachable(); + // to compile just the value, we need to know the target's type var element = resolved.element; var elementType: Type; switch (element.kind) { @@ -2143,9 +2149,8 @@ export class Compiler extends DiagnosticEmitter { this.error(DiagnosticCode.Operation_not_supported, expression.range); return this.module.createUnreachable(); } - if (!elementType) - return this.module.createUnreachable(); + // now compile the value and do the assignment this.currentType = elementType; return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT), contextualType != Type.void); } @@ -2208,25 +2213,29 @@ export class Compiler extends DiagnosticEmitter { if (setterPrototype) { var setterInstance = setterPrototype.resolve(); // reports if (setterInstance) { - if (!tee) + assert(setterInstance.parameters.length == 1); + if (!tee) { + this.currentType = Type.void; return this.makeCall(setterInstance, [ valueWithCorrectType ]); + } var getterPrototype = (element).getterPrototype; assert(getterPrototype != null); var getterInstance = (getterPrototype).resolve(); // reports - if (getterInstance) + if (getterInstance) { + assert(getterInstance.parameters.length == 0); + this.currentType = getterInstance.returnType; return this.module.createBlock(null, [ this.makeCall(setterInstance, [ valueWithCorrectType ]), this.makeCall(getterInstance) ], getterInstance.returnType.toNativeType()); + } } } else this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (element).internalName); return this.module.createUnreachable(); - - default: - this.error(DiagnosticCode.Operation_not_supported, expression.range); - return this.module.createUnreachable(); } + this.error(DiagnosticCode.Operation_not_supported, expression.range); + return this.module.createUnreachable(); } compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef { @@ -2527,7 +2536,9 @@ export class Compiler extends DiagnosticEmitter { var getterInstance = (getter).resolve(); // reports if (!getterInstance) return this.module.createUnreachable(); - return this.compileCall(getterInstance, [], propertyAccess); + assert(getterInstance.parameters.length == 0); + this.currentType = getterInstance.returnType; + return this.makeCall(getterInstance); } this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range); return this.module.createUnreachable(); diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index beaa340a..ab25f974 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -8,6 +8,8 @@ export enum DiagnosticCode { Cannot_export_a_mutable_global = 104, Compiling_constant_with_non_constant_initializer_as_mutable = 105, Type_0_cannot_be_changed_to_type_1 = 106, + Structs_cannot_extend_classes_and_vice_versa = 107, + Structs_cannot_implement_interfaces = 108, Unterminated_string_literal = 1002, Identifier_expected = 1003, _0_expected = 1005, @@ -54,6 +56,7 @@ export enum DiagnosticCode { Unterminated_Unicode_escape_sequence = 1199, Decorators_are_not_valid_here = 1206, _abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration = 1242, + A_class_may_only_extend_another_class = 1311, Duplicate_identifier_0 = 2300, Cannot_find_name_0 = 2304, Module_0_has_no_exported_member_1 = 2305, @@ -89,6 +92,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 104: return "Cannot export a mutable global."; case 105: return "Compiling constant with non-constant initializer as mutable."; case 106: return "Type '{0}' cannot be changed to type '{1}'."; + case 107: return "Structs cannot extend classes and vice-versa."; + case 108: return "Structs cannot implement interfaces."; case 1002: return "Unterminated string literal."; case 1003: return "Identifier expected."; case 1005: return "'{0}' expected."; @@ -135,6 +140,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 1199: return "Unterminated Unicode escape sequence."; case 1206: return "Decorators are not valid here."; case 1242: return "'abstract' modifier can only appear on a class, method, or property declaration."; + case 1311: return "A class may only extend another class."; case 2300: return "Duplicate identifier '{0}'."; case 2304: return "Cannot find name '{0}'."; case 2305: return "Module '{0}' has no exported member '{1}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 339825e4..a0563670 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -6,6 +6,8 @@ "Cannot export a mutable global.": 104, "Compiling constant with non-constant initializer as mutable.": 105, "Type '{0}' cannot be changed to type '{1}'.": 106, + "Structs cannot extend classes and vice-versa.": 107, + "Structs cannot implement interfaces.": 108, "Unterminated string literal.": 1002, "Identifier expected.": 1003, @@ -53,6 +55,7 @@ "Unterminated Unicode escape sequence.": 1199, "Decorators are not valid here.": 1206, "'abstract' modifier can only appear on a class, method, or property declaration.": 1242, + "A class may only extend another class.": 1311, "Duplicate identifier '{0}'.": 2300, "Cannot find name '{0}'.": 2304, diff --git a/src/program.ts b/src/program.ts index 98b15e54..d8e0278c 100644 --- a/src/program.ts +++ b/src/program.ts @@ -265,6 +265,12 @@ export class Program extends DiagnosticEmitter { else this.elements.set(declaration.name.name, prototype); } + if (hasDecorator("struct", declaration.decorators)) { + prototype.isStruct = true; + if (declaration.implementsTypes && declaration.implementsTypes.length) + this.error(DiagnosticCode.Structs_cannot_implement_interfaces, Range.join(declaration.name.range, declaration.implementsTypes[declaration.implementsTypes.length - 1].range)); + } else if (declaration.implementsTypes.length) + throw new Error("not implemented"); // add as namespace member if applicable if (namespace) { @@ -377,6 +383,9 @@ export class Program extends DiagnosticEmitter { } else classPrototype.instanceMembers = new Map(); var instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype); + // if (classPrototype.isStruct && instancePrototype.isAbstract) { + // this.error( Structs cannot declare abstract methods. ); + // } classPrototype.instanceMembers.set(name, instancePrototype); } } @@ -946,10 +955,10 @@ export class Program extends DiagnosticEmitter { case ElementKind.GLOBAL: case ElementKind.LOCAL: targetType = (target).type; - if (!targetType) // FIXME: are globals always resolved here? - throw new Error("type expected"); - if (targetType.classType) - target = targetType.classType; + assert(targetType != null); // FIXME: this is a problem because auto-globals might not be + // resolved (and should not be attempted to be resolved) here + if ((targetType).classType) + target = (targetType).classType; // fall-through else break; @@ -1104,7 +1113,11 @@ export enum ElementFlags { /** Is a protected member. */ PROTECTED = 1 << 14, /** Is a private member. */ - PRIVATE = 1 << 15 + PRIVATE = 1 << 15, + /** Is an abstract member. */ + ABSTRACT = 1 << 16, + /** Is a struct-like class with limited capabilites. */ + STRUCT = 1 << 17 } /** Base class of all program elements. */ @@ -1779,7 +1792,11 @@ export class ClassPrototype extends Element { } } - resolve(typeArguments: Type[] | null, contextualTypeArguments: Map | null = null): Class { + /** Whether a struct-like class with limited capabilities or not. */ + get isStruct(): bool { return (this.flags & ElementFlags.STRUCT) != 0; } + set isStruct(is: bool) { if (is) this.flags |= ElementFlags.STRUCT; else this.flags &= ~ElementFlags.STRUCT; } + + resolve(typeArguments: Type[] | null, contextualTypeArguments: Map | null = null): Class | null { var instanceKey = typeArguments ? typesToString(typeArguments) : ""; var instance = this.instances.get(instanceKey); if (instance) @@ -1796,8 +1813,20 @@ export class ClassPrototype extends Element { for (var [inheritedName, inheritedType] of inheritedTypeArguments) contextualTypeArguments.set(inheritedName, inheritedType); - if (declaration.extendsType) // TODO: base class - throw new Error("not implemented"); + var baseClass: Class | null = null; + if (declaration.extendsType) { + var baseClassType = this.program.resolveType(declaration.extendsType, null); // reports + if (!baseClassType) + return null; + if (!(baseClass = baseClassType.classType)) { + this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range); + return null; + } + if (baseClass.prototype.isStruct != this.isStruct) { + this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range)); + return null; + } + } // override call specific contextual type arguments if provided var i: i32, k: i32; @@ -1812,11 +1841,20 @@ export class ClassPrototype extends Element { var internalName = this.internalName; if (instanceKey.length) internalName += "<" + instanceKey + ">"; - instance = new Class(this, internalName, typeArguments, null); // TODO: base class + instance = new Class(this, internalName, typeArguments, baseClass); instance.contextualTypeArguments = contextualTypeArguments; this.instances.set(instanceKey, instance); var memoryOffset: i32 = 0; + if (baseClass) { + memoryOffset = baseClass.type.byteSize; + if (baseClass.members) { + if (!instance.members) + instance.members = new Map(); + for (var inheritedMember of baseClass.members.values()) + instance.members.set(inheritedMember.simpleName, inheritedMember); + } + } if (this.instanceMembers) for (var member of this.instanceMembers.values()) { diff --git a/tests/compiler/class-extends.optimized.wast b/tests/compiler/class-extends.optimized.wast new file mode 100644 index 00000000..bb1cfa98 --- /dev/null +++ b/tests/compiler/class-extends.optimized.wast @@ -0,0 +1,26 @@ +(module + (type $iv (func (param i32))) + (memory $0 1) + (export "test" (func $class-extends/test)) + (export "memory" (memory $0)) + (func $class-extends/test (; 0 ;) (type $iv) (param $0 i32) + (drop + (i32.load + (get_local $0) + ) + ) + (drop + (i32.load16_s offset=4 + (get_local $0) + ) + ) + (i32.store + (get_local $0) + (i32.const 2) + ) + (i32.store16 offset=4 + (get_local $0) + (i32.const 3) + ) + ) +) diff --git a/tests/compiler/class-extends.ts b/tests/compiler/class-extends.ts new file mode 100644 index 00000000..a96800ab --- /dev/null +++ b/tests/compiler/class-extends.ts @@ -0,0 +1,14 @@ +class A { + a: i32 = 0; +} + +class B extends A { + b: i16 = 1; +} + +export function test(b: B): void { + b.a; + b.b; + b.a = 2; + b.b = 3; +} diff --git a/tests/compiler/class-extends.wast b/tests/compiler/class-extends.wast new file mode 100644 index 00000000..4a5d89eb --- /dev/null +++ b/tests/compiler/class-extends.wast @@ -0,0 +1,79 @@ +(module + (type $iv (func (param i32))) + (global $HEAP_BASE i32 (i32.const 4)) + (memory $0 1) + (export "test" (func $class-extends/test)) + (export "memory" (memory $0)) + (func $class-extends/test (; 0 ;) (type $iv) (param $0 i32) + (drop + (i32.load + (get_local $0) + ) + ) + (drop + (i32.load16_s offset=4 + (get_local $0) + ) + ) + (i32.store + (get_local $0) + (i32.const 2) + ) + (i32.store16 offset=4 + (get_local $0) + (i32.const 3) + ) + ) +) +(; +[program.elements] + GLOBAL: NaN + GLOBAL: Infinity + FUNCTION_PROTOTYPE: isNaN + FUNCTION_PROTOTYPE: isFinite + FUNCTION_PROTOTYPE: clz + FUNCTION_PROTOTYPE: ctz + FUNCTION_PROTOTYPE: popcnt + FUNCTION_PROTOTYPE: rotl + FUNCTION_PROTOTYPE: rotr + FUNCTION_PROTOTYPE: abs + FUNCTION_PROTOTYPE: max + FUNCTION_PROTOTYPE: min + FUNCTION_PROTOTYPE: ceil + FUNCTION_PROTOTYPE: floor + FUNCTION_PROTOTYPE: copysign + FUNCTION_PROTOTYPE: nearest + FUNCTION_PROTOTYPE: reinterpret + FUNCTION_PROTOTYPE: sqrt + FUNCTION_PROTOTYPE: trunc + FUNCTION_PROTOTYPE: load + FUNCTION_PROTOTYPE: store + FUNCTION_PROTOTYPE: sizeof + FUNCTION_PROTOTYPE: select + FUNCTION_PROTOTYPE: unreachable + FUNCTION_PROTOTYPE: current_memory + FUNCTION_PROTOTYPE: grow_memory + FUNCTION_PROTOTYPE: parseInt + FUNCTION_PROTOTYPE: parseFloat + FUNCTION_PROTOTYPE: changetype + FUNCTION_PROTOTYPE: assert + FUNCTION_PROTOTYPE: i8 + FUNCTION_PROTOTYPE: i16 + FUNCTION_PROTOTYPE: i32 + FUNCTION_PROTOTYPE: i64 + FUNCTION_PROTOTYPE: u8 + FUNCTION_PROTOTYPE: u16 + FUNCTION_PROTOTYPE: u32 + FUNCTION_PROTOTYPE: u64 + FUNCTION_PROTOTYPE: bool + FUNCTION_PROTOTYPE: f32 + FUNCTION_PROTOTYPE: f64 + FUNCTION_PROTOTYPE: isize + FUNCTION_PROTOTYPE: usize + GLOBAL: HEAP_BASE + CLASS_PROTOTYPE: class-extends/A + CLASS_PROTOTYPE: class-extends/B + FUNCTION_PROTOTYPE: class-extends/test +[program.exports] + FUNCTION_PROTOTYPE: class-extends/test +;)