Skip to content

Commit f4091b8

Browse files
authored
fix: Fix constructor parameter initialization order (#1934)
1 parent a7f7226 commit f4091b8

15 files changed

+566
-144
lines changed

src/compiler.ts

+62-35
Original file line numberDiff line numberDiff line change
@@ -6717,6 +6717,8 @@ export class Compiler extends DiagnosticEmitter {
67176717
// Compile omitted arguments with final argument locals blocked. Doesn't need to take care of
67186718
// side-effects within earlier expressions because these already happened on set.
67196719
this.currentFlow = flow;
6720+
var isConstructor = instance.is(CommonFlags.CONSTRUCTOR);
6721+
if (isConstructor) flow.set(FlowFlags.CTORPARAM_CONTEXT);
67206722
for (let i = numArguments; i < numParameters; ++i) {
67216723
let initType = parameterTypes[i];
67226724
let initExpr = this.compileExpression(
@@ -6729,12 +6731,13 @@ export class Compiler extends DiagnosticEmitter {
67296731
this.makeLocalAssignment(argumentLocal, initExpr, initType, false)
67306732
);
67316733
}
6734+
flow.unset(FlowFlags.CTORPARAM_CONTEXT);
67326735

67336736
// Compile the called function's body in the scope of the inlined flow
67346737
this.compileFunctionBody(instance, body);
67356738

67366739
// If a constructor, perform field init checks on its flow directly
6737-
if (instance.is(CommonFlags.CONSTRUCTOR)) {
6740+
if (isConstructor) {
67386741
let parent = instance.parent;
67396742
assert(parent.kind == ElementKind.CLASS);
67406743
this.checkFieldInitializationInFlow(<Class>parent, flow);
@@ -6815,6 +6818,7 @@ export class Compiler extends DiagnosticEmitter {
68156818
// accounting for additional locals and a proper `this` context.
68166819
var previousFlow = this.currentFlow;
68176820
var flow = stub.flow;
6821+
if (original.is(CommonFlags.CONSTRUCTOR)) flow.set(FlowFlags.CTORPARAM_CONTEXT);
68186822
this.currentFlow = flow;
68196823

68206824
// create a br_table switching over the number of optional parameters provided
@@ -7641,21 +7645,32 @@ export class Compiler extends DiagnosticEmitter {
76417645
this.currentType = this.options.usizeType;
76427646
return module.unreachable();
76437647
}
7644-
if (actualFunction.is(CommonFlags.CONSTRUCTOR) && !(constraints & Constraints.IS_THIS)) {
7645-
let parent = actualFunction.parent;
7646-
assert(parent.kind == ElementKind.CLASS);
7647-
this.checkFieldInitialization(<Class>parent, expression);
7648+
if (actualFunction.is(CommonFlags.CONSTRUCTOR)) {
7649+
if (flow.is(FlowFlags.CTORPARAM_CONTEXT)) {
7650+
this.error(
7651+
DiagnosticCode._this_cannot_be_referenced_in_constructor_arguments,
7652+
expression.range
7653+
);
7654+
}
7655+
if (!(constraints & Constraints.IS_THIS)) {
7656+
let parent = actualFunction.parent;
7657+
assert(parent.kind == ElementKind.CLASS);
7658+
this.checkFieldInitialization(<Class>parent, expression);
7659+
}
76487660
}
76497661
let thisLocal = assert(flow.lookupLocal(CommonNames.this_));
76507662
flow.set(FlowFlags.ACCESSES_THIS);
76517663
this.currentType = thisType;
76527664
return module.local_get(thisLocal.index, thisType.toRef());
76537665
}
76547666
case NodeKind.SUPER: {
7655-
let flow = this.currentFlow;
7656-
let actualFunction = flow.actualFunction;
76577667
if (actualFunction.is(CommonFlags.CONSTRUCTOR)) {
7658-
if (!flow.is(FlowFlags.CALLS_SUPER)) {
7668+
if (flow.is(FlowFlags.CTORPARAM_CONTEXT)) {
7669+
this.error(
7670+
DiagnosticCode._super_cannot_be_referenced_in_constructor_arguments,
7671+
expression.range
7672+
);
7673+
} else if (!flow.is(FlowFlags.CALLS_SUPER)) {
76597674
// TS1034 in the parser effectively limits this to property accesses
76607675
this.error(
76617676
DiagnosticCode._super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class,
@@ -10292,10 +10307,9 @@ export class Compiler extends DiagnosticEmitter {
1029210307
var module = this.module;
1029310308
var flow = this.currentFlow;
1029410309
var isInline = flow.isInline;
10295-
var thisLocalIndex = isInline
10296-
? flow.lookupLocal(CommonNames.this_)!.index
10297-
: 0;
10310+
var thisLocalIndex = isInline ? flow.lookupLocal(CommonNames.this_)!.index : 0;
1029810311
var sizeTypeRef = this.options.sizeTypeRef;
10312+
var nonParameterFields: Field[] | null = null;
1029910313

1030010314
// TODO: for (let member of members.values()) {
1030110315
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
@@ -10304,44 +10318,57 @@ export class Compiler extends DiagnosticEmitter {
1030410318
member.kind != ElementKind.FIELD || // not a field
1030510319
member.parent != classInstance // inherited field
1030610320
) continue;
10307-
1030810321
let field = <Field>member;
1030910322
assert(!field.isAny(CommonFlags.CONST));
10310-
let fieldType = field.type;
10311-
let fieldTypeRef = fieldType.toRef();
1031210323
let fieldPrototype = field.prototype;
10313-
let initializerNode = fieldPrototype.initializerNode;
1031410324
let parameterIndex = fieldPrototype.parameterIndex;
10315-
let initExpr: ExpressionRef;
10316-
let typeNode = field.typeNode;
10317-
if (typeNode) this.checkTypeSupported(fieldType, typeNode);
10318-
10319-
// if declared as a constructor parameter, use its value
10320-
if (parameterIndex >= 0) {
10321-
initExpr = module.local_get(
10322-
isInline
10323-
? flow.lookupLocal(field.name)!.index
10324-
: 1 + parameterIndex, // this is local 0
10325-
fieldTypeRef
10326-
);
10327-
10328-
// fall back to use initializer if present
10329-
} else if (initializerNode) {
10330-
initExpr = this.compileExpression(initializerNode, fieldType, Constraints.CONV_IMPLICIT);
1033110325

10332-
// otherwise initialize with zero
10333-
} else {
10334-
initExpr = this.makeZero(fieldType, fieldPrototype.declaration);
10326+
// Defer non-parameter fields until parameter fields are initialized
10327+
if (parameterIndex < 0) {
10328+
if (!nonParameterFields) nonParameterFields = new Array();
10329+
nonParameterFields.push(field);
10330+
continue;
1033510331
}
1033610332

10333+
// Initialize constructor parameter field
10334+
let fieldType = field.type;
10335+
let fieldTypeRef = fieldType.toRef();
10336+
assert(!fieldPrototype.initializerNode);
1033710337
this.compileFieldSetter(field);
1033810338
stmts.push(
1033910339
module.call(field.internalSetterName, [
1034010340
module.local_get(thisLocalIndex, sizeTypeRef),
10341-
initExpr
10341+
module.local_get(
10342+
isInline
10343+
? flow.lookupLocal(field.name)!.index
10344+
: 1 + parameterIndex, // `this` is local 0
10345+
fieldTypeRef
10346+
)
1034210347
], TypeRef.None)
1034310348
);
1034410349
}
10350+
10351+
// Initialize deferred non-parameter fields
10352+
if (nonParameterFields) {
10353+
for (let i = 0, k = nonParameterFields.length; i < k; ++i) {
10354+
let field = unchecked(nonParameterFields[i]);
10355+
let fieldType = field.type;
10356+
let fieldPrototype = field.prototype;
10357+
let initializerNode = fieldPrototype.initializerNode;
10358+
assert(fieldPrototype.parameterIndex < 0);
10359+
this.compileFieldSetter(field);
10360+
stmts.push(
10361+
module.call(field.internalSetterName, [
10362+
module.local_get(thisLocalIndex, sizeTypeRef),
10363+
initializerNode // use initializer if present, otherwise initialize with zero
10364+
? this.compileExpression(initializerNode, fieldType, Constraints.CONV_IMPLICIT)
10365+
: this.makeZero(fieldType, fieldPrototype.declaration)
10366+
], TypeRef.None)
10367+
);
10368+
}
10369+
}
10370+
10371+
this.currentType = Type.void;
1034510372
return stmts;
1034610373
}
1034710374

src/diagnosticMessages.generated.ts

+4
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ export enum DiagnosticCode {
124124
Type_0_is_not_assignable_to_type_1 = 2322,
125125
Index_signature_is_missing_in_type_0 = 2329,
126126
_this_cannot_be_referenced_in_current_location = 2332,
127+
_this_cannot_be_referenced_in_constructor_arguments = 2333,
127128
_super_can_only_be_referenced_in_a_derived_class = 2335,
129+
_super_cannot_be_referenced_in_constructor_arguments = 2336,
128130
Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors = 2337,
129131
Property_0_does_not_exist_on_type_1 = 2339,
130132
Property_0_is_private_and_only_accessible_within_class_1 = 2341,
@@ -304,7 +306,9 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
304306
case 2322: return "Type '{0}' is not assignable to type '{1}'.";
305307
case 2329: return "Index signature is missing in type '{0}'.";
306308
case 2332: return "'this' cannot be referenced in current location.";
309+
case 2333: return "'this' cannot be referenced in constructor arguments.";
307310
case 2335: return "'super' can only be referenced in a derived class.";
311+
case 2336: return "'super' cannot be referenced in constructor arguments.";
308312
case 2337: return "Super calls are not permitted outside constructors or in nested functions inside constructors.";
309313
case 2339: return "Property '{0}' does not exist on type '{1}'.";
310314
case 2341: return "Property '{0}' is private and only accessible within class '{1}'.";

src/diagnosticMessages.json

+2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@
122122
"Type '{0}' is not assignable to type '{1}'.": 2322,
123123
"Index signature is missing in type '{0}'.": 2329,
124124
"'this' cannot be referenced in current location.": 2332,
125+
"'this' cannot be referenced in constructor arguments.": 2333,
125126
"'super' can only be referenced in a derived class.": 2335,
127+
"'super' cannot be referenced in constructor arguments.": 2336,
126128
"Super calls are not permitted outside constructors or in nested functions inside constructors.": 2337,
127129
"Property '{0}' does not exist on type '{1}'.": 2339,
128130
"Property '{0}' is private and only accessible within class '{1}'.": 2341,

src/flow.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ export const enum FlowFlags {
129129

130130
/** This is a flow with explicitly disabled bounds checking. */
131131
UNCHECKED_CONTEXT = 1 << 15,
132+
/** This is a flow compiling a constructor parameter. */
133+
CTORPARAM_CONTEXT = 1 << 16,
132134

133135
// masks
134136

@@ -757,7 +759,7 @@ export class Flow {
757759
newFlags |= FlowFlags.TERMINATES;
758760
}
759761

760-
this.flags = newFlags | (thisFlags & FlowFlags.UNCHECKED_CONTEXT);
762+
this.flags = newFlags | (thisFlags & (FlowFlags.UNCHECKED_CONTEXT | FlowFlags.CTORPARAM_CONTEXT));
761763

762764
// local flags
763765
var thisLocalFlags = this.localFlags;
@@ -869,7 +871,7 @@ export class Flow {
869871
newFlags |= FlowFlags.TERMINATES;
870872
}
871873

872-
this.flags = newFlags | (this.flags & FlowFlags.UNCHECKED_CONTEXT);
874+
this.flags = newFlags | (this.flags & (FlowFlags.UNCHECKED_CONTEXT | FlowFlags.CTORPARAM_CONTEXT));
873875

874876
// local flags
875877
var thisLocalFlags = this.localFlags;

tests/compiler/class.optimized.wat

+4-4
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,7 @@
17271727
call $~lib/memory/memory.fill
17281728
local.get $1
17291729
)
1730-
(func $~lib/array/Array<i32>#set:buffer (param $0 i32) (param $1 i32)
1730+
(func $class/GenericInitializer<i32>#set:foo (param $0 i32) (param $1 i32)
17311731
local.get $0
17321732
local.get $1
17331733
i32.store
@@ -1826,7 +1826,7 @@
18261826
i32.store
18271827
local.get $0
18281828
i32.const 0
1829-
call $~lib/array/Array<i32>#set:buffer
1829+
call $class/GenericInitializer<i32>#set:foo
18301830
local.get $0
18311831
i32.const 0
18321832
i32.store offset=4
@@ -1847,7 +1847,7 @@
18471847
call $~lib/memory/memory.fill
18481848
local.get $0
18491849
local.get $1
1850-
call $~lib/array/Array<i32>#set:buffer
1850+
call $class/GenericInitializer<i32>#set:foo
18511851
local.get $0
18521852
local.get $1
18531853
i32.store offset=4
@@ -1863,7 +1863,7 @@
18631863
global.set $~lib/memory/__stack_pointer
18641864
local.get $2
18651865
local.get $0
1866-
call $~lib/array/Array<i32>#set:buffer
1866+
call $class/GenericInitializer<i32>#set:foo
18671867
global.get $~lib/memory/__stack_pointer
18681868
i32.const 4
18691869
i32.add

tests/compiler/class.untouched.wat

+9-9
Original file line numberDiff line numberDiff line change
@@ -2525,6 +2525,15 @@
25252525
end
25262526
end
25272527
)
2528+
(func $class/GenericInitializer<i32>#set:foo (param $0 i32) (param $1 i32)
2529+
local.get $0
2530+
local.get $1
2531+
i32.store
2532+
local.get $0
2533+
local.get $1
2534+
i32.const 0
2535+
call $~lib/rt/itcms/__link
2536+
)
25282537
(func $~lib/array/Array<i32>#set:buffer (param $0 i32) (param $1 i32)
25292538
local.get $0
25302539
local.get $1
@@ -2549,15 +2558,6 @@
25492558
local.get $1
25502559
i32.store offset=12
25512560
)
2552-
(func $class/GenericInitializer<i32>#set:foo (param $0 i32) (param $1 i32)
2553-
local.get $0
2554-
local.get $1
2555-
i32.store
2556-
local.get $0
2557-
local.get $1
2558-
i32.const 0
2559-
call $~lib/rt/itcms/__link
2560-
)
25612561
(func $class/testGenericInitializer
25622562
i32.const 0
25632563
call $class/GenericInitializer<i32>#constructor
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"asc_flags": [
3+
],
4+
"stderr": [
5+
"TS2333: 'this' cannot be referenced in constructor arguments.",
6+
"b: i32 = this.a, // TS2333",
7+
"TS2333: 'this' cannot be referenced in constructor arguments.",
8+
"c: i32 = this.foo() // TS2333",
9+
"TS2333: 'this' cannot be referenced in constructor arguments.",
10+
"e: i32 = this.d, // TS2333",
11+
"TS2333: 'this' cannot be referenced in constructor arguments.",
12+
"f: i32 = this.bar() // TS2333",
13+
"TS2336: 'super' cannot be referenced in constructor arguments.",
14+
"h: i32 = super.g, // TS2336",
15+
"TS2336: 'super' cannot be referenced in constructor arguments.",
16+
"i: i32 = super.baz() // TS2336",
17+
"TS2336: 'super' cannot be referenced in constructor arguments.",
18+
"j: i32 = super.g, // TS2336",
19+
"TS2336: 'super' cannot be referenced in constructor arguments.",
20+
"k: i32 = super.baz() // TS2336",
21+
"EOF"
22+
]
23+
}

tests/compiler/constructor-errors.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
class CtorAccessThis {
2+
a: i32 = 0;
3+
constructor(
4+
public b: i32 = this.a, // TS2333
5+
public c: i32 = this.foo() // TS2333
6+
) {}
7+
foo(): i32 { return 0; }
8+
}
9+
10+
new CtorAccessThis();
11+
12+
class CtorAccessThisInline {
13+
d: i32 = 0;
14+
@inline
15+
constructor(
16+
public e: i32 = this.d, // TS2333
17+
public f: i32 = this.bar() // TS2333
18+
) {}
19+
bar(): i32 { return 0; }
20+
}
21+
22+
new CtorAccessThisInline();
23+
24+
class CtorSuper {
25+
g: i32 = 0;
26+
baz(): i32 { return 0; }
27+
}
28+
29+
class CtorAccessSuper extends CtorSuper {
30+
constructor(
31+
public h: i32 = super.g, // TS2336
32+
public i: i32 = super.baz() // TS2336
33+
) {
34+
super();
35+
}
36+
}
37+
38+
new CtorAccessSuper();
39+
40+
class CtorAccessSuperInline extends CtorSuper {
41+
@inline
42+
constructor(
43+
public j: i32 = super.g, // TS2336
44+
public k: i32 = super.baz() // TS2336
45+
) {
46+
super();
47+
}
48+
}
49+
50+
new CtorAccessSuperInline();
51+
52+
ERROR("EOF");

0 commit comments

Comments
 (0)