Skip to content

Commit 50dea3b

Browse files
committed
Basic initial inheritance
1 parent ae99ade commit 50dea3b

7 files changed

+199
-22
lines changed

src/compiler.ts

+24-13
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,10 @@ export class Compiler extends DiagnosticEmitter {
352352
return false;
353353
} else if (declaration.initializer) {
354354
initExpr = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable
355-
if (this.currentType == Type.void)
355+
if (this.currentType == Type.void) {
356+
this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, declaration.range, this.currentType.toString(), "<auto>");
356357
return false;
358+
}
357359
global.type = this.currentType;
358360
} else {
359361
this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd);
@@ -1010,8 +1012,11 @@ export class Compiler extends DiagnosticEmitter {
10101012
init = this.compileExpression(declaration.initializer, type); // reports and returns unreachable
10111013
} else if (declaration.initializer) { // infer type
10121014
init = this.compileExpression(declaration.initializer, Type.void, ConversionKind.NONE); // reports and returns unreachable
1013-
if ((type = this.currentType) == Type.void)
1015+
if (this.currentType == Type.void) {
1016+
this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, declaration.range, this.currentType.toString(), "<auto>");
10141017
continue;
1018+
}
1019+
type = this.currentType;
10151020
} else {
10161021
this.error(DiagnosticCode.Type_expected, declaration.name.range.atEnd);
10171022
continue;
@@ -1168,8 +1173,8 @@ export class Compiler extends DiagnosticEmitter {
11681173

11691174
// void to any
11701175
if (fromType.kind == TypeKind.VOID) {
1171-
this.error(DiagnosticCode.Operation_not_supported, reportNode.range);
1172-
throw new Error("unexpected conversion from void");
1176+
this.error(DiagnosticCode.Type_0_is_not_assignable_to_type_1, reportNode.range, fromType.toString(), toType.toString());
1177+
return this.module.createUnreachable();
11731178
}
11741179

11751180
// any to void
@@ -2108,6 +2113,7 @@ export class Compiler extends DiagnosticEmitter {
21082113
if (!resolved)
21092114
return this.module.createUnreachable();
21102115

2116+
// to compile just the value, we need to know the target's type
21112117
var element = resolved.element;
21122118
var elementType: Type;
21132119
switch (element.kind) {
@@ -2143,9 +2149,8 @@ export class Compiler extends DiagnosticEmitter {
21432149
this.error(DiagnosticCode.Operation_not_supported, expression.range);
21442150
return this.module.createUnreachable();
21452151
}
2146-
if (!elementType)
2147-
return this.module.createUnreachable();
21482152

2153+
// now compile the value and do the assignment
21492154
this.currentType = elementType;
21502155
return this.compileAssignmentWithValue(expression, this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT), contextualType != Type.void);
21512156
}
@@ -2208,25 +2213,29 @@ export class Compiler extends DiagnosticEmitter {
22082213
if (setterPrototype) {
22092214
var setterInstance = setterPrototype.resolve(); // reports
22102215
if (setterInstance) {
2211-
if (!tee)
2216+
assert(setterInstance.parameters.length == 1);
2217+
if (!tee) {
2218+
this.currentType = Type.void;
22122219
return this.makeCall(setterInstance, [ valueWithCorrectType ]);
2220+
}
22132221
var getterPrototype = (<Property>element).getterPrototype;
22142222
assert(getterPrototype != null);
22152223
var getterInstance = (<FunctionPrototype>getterPrototype).resolve(); // reports
2216-
if (getterInstance)
2224+
if (getterInstance) {
2225+
assert(getterInstance.parameters.length == 0);
2226+
this.currentType = getterInstance.returnType;
22172227
return this.module.createBlock(null, [
22182228
this.makeCall(setterInstance, [ valueWithCorrectType ]),
22192229
this.makeCall(getterInstance)
22202230
], getterInstance.returnType.toNativeType());
2231+
}
22212232
}
22222233
} else
22232234
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Property>element).internalName);
22242235
return this.module.createUnreachable();
2225-
2226-
default:
2227-
this.error(DiagnosticCode.Operation_not_supported, expression.range);
2228-
return this.module.createUnreachable();
22292236
}
2237+
this.error(DiagnosticCode.Operation_not_supported, expression.range);
2238+
return this.module.createUnreachable();
22302239
}
22312240

22322241
compileCallExpression(expression: CallExpression, contextualType: Type): ExpressionRef {
@@ -2527,7 +2536,9 @@ export class Compiler extends DiagnosticEmitter {
25272536
var getterInstance = (<FunctionPrototype>getter).resolve(); // reports
25282537
if (!getterInstance)
25292538
return this.module.createUnreachable();
2530-
return this.compileCall(getterInstance, [], propertyAccess);
2539+
assert(getterInstance.parameters.length == 0);
2540+
this.currentType = getterInstance.returnType;
2541+
return this.makeCall(getterInstance);
25312542
}
25322543
this.error(DiagnosticCode.Operation_not_supported, propertyAccess.range);
25332544
return this.module.createUnreachable();

src/diagnosticMessages.generated.ts

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export enum DiagnosticCode {
88
Cannot_export_a_mutable_global = 104,
99
Compiling_constant_with_non_constant_initializer_as_mutable = 105,
1010
Type_0_cannot_be_changed_to_type_1 = 106,
11+
Structs_cannot_extend_classes_and_vice_versa = 107,
12+
Structs_cannot_implement_interfaces = 108,
1113
Unterminated_string_literal = 1002,
1214
Identifier_expected = 1003,
1315
_0_expected = 1005,
@@ -54,6 +56,7 @@ export enum DiagnosticCode {
5456
Unterminated_Unicode_escape_sequence = 1199,
5557
Decorators_are_not_valid_here = 1206,
5658
_abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration = 1242,
59+
A_class_may_only_extend_another_class = 1311,
5760
Duplicate_identifier_0 = 2300,
5861
Cannot_find_name_0 = 2304,
5962
Module_0_has_no_exported_member_1 = 2305,
@@ -89,6 +92,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
8992
case 104: return "Cannot export a mutable global.";
9093
case 105: return "Compiling constant with non-constant initializer as mutable.";
9194
case 106: return "Type '{0}' cannot be changed to type '{1}'.";
95+
case 107: return "Structs cannot extend classes and vice-versa.";
96+
case 108: return "Structs cannot implement interfaces.";
9297
case 1002: return "Unterminated string literal.";
9398
case 1003: return "Identifier expected.";
9499
case 1005: return "'{0}' expected.";
@@ -135,6 +140,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
135140
case 1199: return "Unterminated Unicode escape sequence.";
136141
case 1206: return "Decorators are not valid here.";
137142
case 1242: return "'abstract' modifier can only appear on a class, method, or property declaration.";
143+
case 1311: return "A class may only extend another class.";
138144
case 2300: return "Duplicate identifier '{0}'.";
139145
case 2304: return "Cannot find name '{0}'.";
140146
case 2305: return "Module '{0}' has no exported member '{1}'.";

src/diagnosticMessages.json

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"Cannot export a mutable global.": 104,
77
"Compiling constant with non-constant initializer as mutable.": 105,
88
"Type '{0}' cannot be changed to type '{1}'.": 106,
9+
"Structs cannot extend classes and vice-versa.": 107,
10+
"Structs cannot implement interfaces.": 108,
911

1012
"Unterminated string literal.": 1002,
1113
"Identifier expected.": 1003,
@@ -53,6 +55,7 @@
5355
"Unterminated Unicode escape sequence.": 1199,
5456
"Decorators are not valid here.": 1206,
5557
"'abstract' modifier can only appear on a class, method, or property declaration.": 1242,
58+
"A class may only extend another class.": 1311,
5659

5760
"Duplicate identifier '{0}'.": 2300,
5861
"Cannot find name '{0}'.": 2304,

src/program.ts

+47-9
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ export class Program extends DiagnosticEmitter {
265265
else
266266
this.elements.set(declaration.name.name, prototype);
267267
}
268+
if (hasDecorator("struct", declaration.decorators)) {
269+
prototype.isStruct = true;
270+
if (declaration.implementsTypes && declaration.implementsTypes.length)
271+
this.error(DiagnosticCode.Structs_cannot_implement_interfaces, Range.join(declaration.name.range, declaration.implementsTypes[declaration.implementsTypes.length - 1].range));
272+
} else if (declaration.implementsTypes.length)
273+
throw new Error("not implemented");
268274

269275
// add as namespace member if applicable
270276
if (namespace) {
@@ -377,6 +383,9 @@ export class Program extends DiagnosticEmitter {
377383
} else
378384
classPrototype.instanceMembers = new Map();
379385
var instancePrototype = new FunctionPrototype(this, name, internalName, declaration, classPrototype);
386+
// if (classPrototype.isStruct && instancePrototype.isAbstract) {
387+
// this.error( Structs cannot declare abstract methods. );
388+
// }
380389
classPrototype.instanceMembers.set(name, instancePrototype);
381390
}
382391
}
@@ -946,10 +955,10 @@ export class Program extends DiagnosticEmitter {
946955
case ElementKind.GLOBAL:
947956
case ElementKind.LOCAL:
948957
targetType = (<VariableLikeElement>target).type;
949-
if (!targetType) // FIXME: are globals always resolved here?
950-
throw new Error("type expected");
951-
if (targetType.classType)
952-
target = targetType.classType;
958+
assert(targetType != null); // FIXME: this is a problem because auto-globals might not be
959+
// resolved (and should not be attempted to be resolved) here
960+
if ((<Type>targetType).classType)
961+
target = <Class>(<Type>targetType).classType;
953962
// fall-through
954963
else
955964
break;
@@ -1104,7 +1113,11 @@ export enum ElementFlags {
11041113
/** Is a protected member. */
11051114
PROTECTED = 1 << 14,
11061115
/** Is a private member. */
1107-
PRIVATE = 1 << 15
1116+
PRIVATE = 1 << 15,
1117+
/** Is an abstract member. */
1118+
ABSTRACT = 1 << 16,
1119+
/** Is a struct-like class with limited capabilites. */
1120+
STRUCT = 1 << 17
11081121
}
11091122

11101123
/** Base class of all program elements. */
@@ -1779,7 +1792,11 @@ export class ClassPrototype extends Element {
17791792
}
17801793
}
17811794

1782-
resolve(typeArguments: Type[] | null, contextualTypeArguments: Map<string,Type> | null = null): Class {
1795+
/** Whether a struct-like class with limited capabilities or not. */
1796+
get isStruct(): bool { return (this.flags & ElementFlags.STRUCT) != 0; }
1797+
set isStruct(is: bool) { if (is) this.flags |= ElementFlags.STRUCT; else this.flags &= ~ElementFlags.STRUCT; }
1798+
1799+
resolve(typeArguments: Type[] | null, contextualTypeArguments: Map<string,Type> | null = null): Class | null {
17831800
var instanceKey = typeArguments ? typesToString(typeArguments) : "";
17841801
var instance = this.instances.get(instanceKey);
17851802
if (instance)
@@ -1796,8 +1813,20 @@ export class ClassPrototype extends Element {
17961813
for (var [inheritedName, inheritedType] of inheritedTypeArguments)
17971814
contextualTypeArguments.set(inheritedName, inheritedType);
17981815

1799-
if (declaration.extendsType) // TODO: base class
1800-
throw new Error("not implemented");
1816+
var baseClass: Class | null = null;
1817+
if (declaration.extendsType) {
1818+
var baseClassType = this.program.resolveType(declaration.extendsType, null); // reports
1819+
if (!baseClassType)
1820+
return null;
1821+
if (!(baseClass = baseClassType.classType)) {
1822+
this.program.error(DiagnosticCode.A_class_may_only_extend_another_class, declaration.extendsType.range);
1823+
return null;
1824+
}
1825+
if (baseClass.prototype.isStruct != this.isStruct) {
1826+
this.program.error(DiagnosticCode.Structs_cannot_extend_classes_and_vice_versa, Range.join(declaration.name.range, declaration.extendsType.range));
1827+
return null;
1828+
}
1829+
}
18011830

18021831
// override call specific contextual type arguments if provided
18031832
var i: i32, k: i32;
@@ -1812,11 +1841,20 @@ export class ClassPrototype extends Element {
18121841
var internalName = this.internalName;
18131842
if (instanceKey.length)
18141843
internalName += "<" + instanceKey + ">";
1815-
instance = new Class(this, internalName, typeArguments, null); // TODO: base class
1844+
instance = new Class(this, internalName, typeArguments, baseClass);
18161845
instance.contextualTypeArguments = contextualTypeArguments;
18171846
this.instances.set(instanceKey, instance);
18181847

18191848
var memoryOffset: i32 = 0;
1849+
if (baseClass) {
1850+
memoryOffset = baseClass.type.byteSize;
1851+
if (baseClass.members) {
1852+
if (!instance.members)
1853+
instance.members = new Map();
1854+
for (var inheritedMember of baseClass.members.values())
1855+
instance.members.set(inheritedMember.simpleName, inheritedMember);
1856+
}
1857+
}
18201858

18211859
if (this.instanceMembers)
18221860
for (var member of this.instanceMembers.values()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
(module
2+
(type $iv (func (param i32)))
3+
(memory $0 1)
4+
(export "test" (func $class-extends/test))
5+
(export "memory" (memory $0))
6+
(func $class-extends/test (; 0 ;) (type $iv) (param $0 i32)
7+
(drop
8+
(i32.load
9+
(get_local $0)
10+
)
11+
)
12+
(drop
13+
(i32.load16_s offset=4
14+
(get_local $0)
15+
)
16+
)
17+
(i32.store
18+
(get_local $0)
19+
(i32.const 2)
20+
)
21+
(i32.store16 offset=4
22+
(get_local $0)
23+
(i32.const 3)
24+
)
25+
)
26+
)

tests/compiler/class-extends.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class A {
2+
a: i32 = 0;
3+
}
4+
5+
class B extends A {
6+
b: i16 = 1;
7+
}
8+
9+
export function test(b: B): void {
10+
b.a;
11+
b.b;
12+
b.a = 2;
13+
b.b = 3;
14+
}

tests/compiler/class-extends.wast

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
(module
2+
(type $iv (func (param i32)))
3+
(global $HEAP_BASE i32 (i32.const 4))
4+
(memory $0 1)
5+
(export "test" (func $class-extends/test))
6+
(export "memory" (memory $0))
7+
(func $class-extends/test (; 0 ;) (type $iv) (param $0 i32)
8+
(drop
9+
(i32.load
10+
(get_local $0)
11+
)
12+
)
13+
(drop
14+
(i32.load16_s offset=4
15+
(get_local $0)
16+
)
17+
)
18+
(i32.store
19+
(get_local $0)
20+
(i32.const 2)
21+
)
22+
(i32.store16 offset=4
23+
(get_local $0)
24+
(i32.const 3)
25+
)
26+
)
27+
)
28+
(;
29+
[program.elements]
30+
GLOBAL: NaN
31+
GLOBAL: Infinity
32+
FUNCTION_PROTOTYPE: isNaN
33+
FUNCTION_PROTOTYPE: isFinite
34+
FUNCTION_PROTOTYPE: clz
35+
FUNCTION_PROTOTYPE: ctz
36+
FUNCTION_PROTOTYPE: popcnt
37+
FUNCTION_PROTOTYPE: rotl
38+
FUNCTION_PROTOTYPE: rotr
39+
FUNCTION_PROTOTYPE: abs
40+
FUNCTION_PROTOTYPE: max
41+
FUNCTION_PROTOTYPE: min
42+
FUNCTION_PROTOTYPE: ceil
43+
FUNCTION_PROTOTYPE: floor
44+
FUNCTION_PROTOTYPE: copysign
45+
FUNCTION_PROTOTYPE: nearest
46+
FUNCTION_PROTOTYPE: reinterpret
47+
FUNCTION_PROTOTYPE: sqrt
48+
FUNCTION_PROTOTYPE: trunc
49+
FUNCTION_PROTOTYPE: load
50+
FUNCTION_PROTOTYPE: store
51+
FUNCTION_PROTOTYPE: sizeof
52+
FUNCTION_PROTOTYPE: select
53+
FUNCTION_PROTOTYPE: unreachable
54+
FUNCTION_PROTOTYPE: current_memory
55+
FUNCTION_PROTOTYPE: grow_memory
56+
FUNCTION_PROTOTYPE: parseInt
57+
FUNCTION_PROTOTYPE: parseFloat
58+
FUNCTION_PROTOTYPE: changetype
59+
FUNCTION_PROTOTYPE: assert
60+
FUNCTION_PROTOTYPE: i8
61+
FUNCTION_PROTOTYPE: i16
62+
FUNCTION_PROTOTYPE: i32
63+
FUNCTION_PROTOTYPE: i64
64+
FUNCTION_PROTOTYPE: u8
65+
FUNCTION_PROTOTYPE: u16
66+
FUNCTION_PROTOTYPE: u32
67+
FUNCTION_PROTOTYPE: u64
68+
FUNCTION_PROTOTYPE: bool
69+
FUNCTION_PROTOTYPE: f32
70+
FUNCTION_PROTOTYPE: f64
71+
FUNCTION_PROTOTYPE: isize
72+
FUNCTION_PROTOTYPE: usize
73+
GLOBAL: HEAP_BASE
74+
CLASS_PROTOTYPE: class-extends/A
75+
CLASS_PROTOTYPE: class-extends/B
76+
FUNCTION_PROTOTYPE: class-extends/test
77+
[program.exports]
78+
FUNCTION_PROTOTYPE: class-extends/test
79+
;)

0 commit comments

Comments
 (0)