Skip to content

Commit b37cf1a

Browse files
authored
Fix recursive extends (#1028)
1 parent 747ebde commit b37cf1a

9 files changed

+115
-5
lines changed

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export enum DiagnosticCode {
125125
The_0_operator_cannot_be_applied_to_type_1 = 2469,
126126
In_const_enum_declarations_member_initializer_must_be_constant_expression = 2474,
127127
Export_declaration_conflicts_with_exported_declaration_of_0 = 2484,
128+
_0_is_referenced_directly_or_indirectly_in_its_own_base_expression = 2506,
128129
Object_is_possibly_null = 2531,
129130
Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property = 2540,
130131
The_target_of_an_assignment_must_be_a_variable_or_a_property_access = 2541,
@@ -268,6 +269,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
268269
case 2469: return "The '{0}' operator cannot be applied to type '{1}'.";
269270
case 2474: return "In 'const' enum declarations member initializer must be constant expression.";
270271
case 2484: return "Export declaration conflicts with exported declaration of '{0}'.";
272+
case 2506: return "'{0}' is referenced directly or indirectly in its own base expression.";
271273
case 2531: return "Object is possibly 'null'.";
272274
case 2540: return "Cannot assign to '{0}' because it is a constant or a read-only property.";
273275
case 2541: return "The target of an assignment must be a variable or a property access.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"The '{0}' operator cannot be applied to type '{1}'.": 2469,
121121
"In 'const' enum declarations member initializer must be constant expression.": 2474,
122122
"Export declaration conflicts with exported declaration of '{0}'.": 2484,
123+
"'{0}' is referenced directly or indirectly in its own base expression.": 2506,
123124
"Object is possibly 'null'.": 2531,
124125
"Cannot assign to '{0}' because it is a constant or a read-only property.": 2540,
125126
"The target of an assignment must be a variable or a property access.": 2541,

src/resolver.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,10 +2761,24 @@ export class Resolver extends DiagnosticEmitter {
27612761
assert(!(typeParameterNodes && typeParameterNodes.length));
27622762
}
27632763

2764+
// There shouldn't be an instance before resolving the base class
2765+
assert(!prototype.getResolvedInstance(instanceKey));
2766+
27642767
// Resolve base class if applicable
27652768
var basePrototype = prototype.basePrototype;
27662769
var baseClass: Class | null = null;
27672770
if (basePrototype) {
2771+
let current: ClassPrototype | null = basePrototype;
2772+
do {
2773+
if (current == prototype) {
2774+
this.error(
2775+
DiagnosticCode._0_is_referenced_directly_or_indirectly_in_its_own_base_expression,
2776+
prototype.identifierNode.range,
2777+
prototype.internalName
2778+
);
2779+
return null;
2780+
}
2781+
} while (current = current.basePrototype);
27682782
let extendsNode = assert(prototype.extendsNode); // must be present if it has a base prototype
27692783
baseClass = this.resolveClassInclTypeArguments(
27702784
basePrototype,
@@ -2778,11 +2792,17 @@ export class Resolver extends DiagnosticEmitter {
27782792
}
27792793

27802794
// Construct the instance and remember that it has been resolved already
2781-
var nameInclTypeParamters = prototype.name;
2782-
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2783-
instance = new Class(nameInclTypeParamters, prototype, typeArguments, baseClass);
2784-
instance.contextualTypeArguments = ctxTypes;
2785-
prototype.setResolvedInstance(instanceKey, instance);
2795+
var recursiveInstanceFromResolvingBase = prototype.getResolvedInstance(instanceKey);
2796+
if (recursiveInstanceFromResolvingBase) {
2797+
// Happens if the base class already triggers resolving this class
2798+
instance = recursiveInstanceFromResolvingBase;
2799+
} else {
2800+
let nameInclTypeParamters = prototype.name;
2801+
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2802+
instance = new Class(nameInclTypeParamters, prototype, typeArguments, baseClass);
2803+
prototype.setResolvedInstance(instanceKey, instance);
2804+
}
2805+
instance.contextualTypeArguments = ctxTypes; // unique (as specified by the caller)
27862806

27872807
// Inherit base class members and set up the initial memory offset for own fields
27882808
var memoryOffset: u32 = 0;

tests/compiler/extends-recursive.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"asc_flags": [
3+
"--runtime none"
4+
]
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
(module
2+
(type $i32_i32_=>_none (func (param i32 i32)))
3+
(type $i32_=>_i32 (func (param i32) (result i32)))
4+
(memory $0 0)
5+
(global $extends-recursive/Child i32 (i32.const 4))
6+
(export "memory" (memory $0))
7+
(export "Child" (global $extends-recursive/Child))
8+
(export "Child#get:child" (func $Child#get:child))
9+
(export "Child#set:child" (func $Child#set:child))
10+
(func $Child#get:child (; 0 ;) (param $0 i32) (result i32)
11+
local.get $0
12+
i32.load
13+
)
14+
(func $Child#set:child (; 1 ;) (param $0 i32) (param $1 i32)
15+
(local $2 i32)
16+
local.get $0
17+
local.get $1
18+
local.tee $0
19+
i32.load
20+
drop
21+
local.get $0
22+
i32.store
23+
)
24+
)

tests/compiler/extends-recursive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Parent {
2+
child: Child | null = null;
3+
}
4+
export class Child extends Parent { }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
(module
2+
(type $i32_=>_i32 (func (param i32) (result i32)))
3+
(type $i32_=>_none (func (param i32)))
4+
(type $i32_i32_=>_none (func (param i32 i32)))
5+
(memory $0 0)
6+
(table $0 1 funcref)
7+
(global $extends-recursive/Child i32 (i32.const 4))
8+
(export "memory" (memory $0))
9+
(export "Child" (global $extends-recursive/Child))
10+
(export "Child#get:child" (func $Child#get:child))
11+
(export "Child#set:child" (func $Child#set:child))
12+
(func $~lib/rt/stub/__retain (; 0 ;) (param $0 i32) (result i32)
13+
local.get $0
14+
)
15+
(func $Child#get:child (; 1 ;) (param $0 i32) (result i32)
16+
local.get $0
17+
i32.load
18+
call $~lib/rt/stub/__retain
19+
)
20+
(func $~lib/rt/stub/__release (; 2 ;) (param $0 i32)
21+
nop
22+
)
23+
(func $Child#set:child (; 3 ;) (param $0 i32) (param $1 i32)
24+
local.get $0
25+
local.get $1
26+
local.tee $0
27+
local.get $0
28+
i32.load
29+
local.tee $1
30+
i32.ne
31+
if
32+
local.get $0
33+
call $~lib/rt/stub/__retain
34+
local.set $0
35+
local.get $1
36+
call $~lib/rt/stub/__release
37+
end
38+
local.get $0
39+
i32.store
40+
)
41+
)

tests/compiler/extends-self.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"asc_flags": [
3+
"--runtime none"
4+
],
5+
"stderr": [
6+
"TS2506",
7+
"EOF"
8+
]
9+
}

tests/compiler/extends-self.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Parent extends Child { }
2+
class Child extends Parent { }
3+
new Child();
4+
ERROR("EOF");

0 commit comments

Comments
 (0)