Skip to content

Commit 125f146

Browse files
committed
Move field initialization tracking at the flow level
This commit add field initialization at the flow level. It also refactors the error reporting for uninitialized properties.
1 parent ac1939e commit 125f146

File tree

5 files changed

+101
-33
lines changed

5 files changed

+101
-33
lines changed

src/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export enum CommonFlags {
7676
// Other
7777

7878
/** Is quoted. */
79-
QUOTED = 1 << 28,
79+
QUOTED = 1 << 28
8080
}
8181

8282
/** Path delimiter inserted between file system levels. */

src/compiler.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ import {
9090

9191
import {
9292
FlowFlags,
93+
FieldFlags,
9394
Flow,
9495
LocalFlags,
9596
ConditionKind,
@@ -1509,6 +1510,7 @@ export class Compiler extends DiagnosticEmitter {
15091510
}
15101511
}
15111512
this.ensureConstructor(instance, instance.identifierNode);
1513+
this.checkFieldInitialization(instance);
15121514
var instanceMembers = instance.members;
15131515
if (instanceMembers) {
15141516
// TODO: for (let element of instanceMembers.values()) {
@@ -1536,18 +1538,30 @@ export class Compiler extends DiagnosticEmitter {
15361538
return true;
15371539
}
15381540

1541+
checkFieldInitialization(instance: Class): void {
1542+
const flow = instance.constructorInstance!.flow;
1543+
const instanceMembers = instance.members;
1544+
if (instanceMembers) {
1545+
for (let _values = Map_values(instanceMembers), i = 0, k = _values.length; i < k; ++i) {
1546+
const element = unchecked(_values[i]);
1547+
if (element.kind == ElementKind.FIELD) {
1548+
const field = <Field>element;
1549+
if (!flow.isFieldFlag(field.internalName, FieldFlags.INITIALIZED)) {
1550+
this.error(
1551+
DiagnosticCode.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_constructor,
1552+
field.declaration.name.range,
1553+
field.declaration.name.text
1554+
);
1555+
}
1556+
}
1557+
}
1558+
}
1559+
}
1560+
15391561
/** Compiles an instance field to a getter and a setter. */
15401562
compileField(instance: Field): bool {
15411563
this.compileFieldGetter(instance);
15421564
this.compileFieldSetter(instance);
1543-
if (!instance.is(CommonFlags.INITIALIZED)) {
1544-
this.error(
1545-
DiagnosticCode.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_constructor,
1546-
instance.declaration.name.range,
1547-
instance.declaration.name.text
1548-
);
1549-
}
1550-
15511565
return instance.is(CommonFlags.COMPILED);
15521566
}
15531567

@@ -5945,8 +5959,8 @@ export class Compiler extends DiagnosticEmitter {
59455959
return module.unreachable();
59465960
}
59475961

5948-
if (flow.actualFunction.is(CommonFlags.CONSTRUCTOR) && !fieldInstance.is(CommonFlags.INITIALIZED)) {
5949-
fieldInstance.set(CommonFlags.INITIALIZED);
5962+
if (flow.actualFunction.is(CommonFlags.CONSTRUCTOR)) {
5963+
flow.setFieldFlag(fieldInstance.internalName, FieldFlags.INITIALIZED);
59505964
}
59515965

59525966
return this.makeFieldAssignment(fieldInstance,
@@ -8838,6 +8852,7 @@ export class Compiler extends DiagnosticEmitter {
88388852
reportNode: Node
88398853
): ExpressionRef {
88408854
var ctor = this.ensureConstructor(classInstance, reportNode);
8855+
this.checkFieldInitialization(classInstance);
88418856
if (ctor.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(reportNode);
88428857
var expr = this.compileCallDirect( // no need for another autoreleased local
88438858
ctor,
@@ -8850,23 +8865,6 @@ export class Compiler extends DiagnosticEmitter {
88508865
this.currentType = classInstance.type; // important because a super ctor could be called
88518866
}
88528867

8853-
let members = classInstance.members;
8854-
if (members) {
8855-
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
8856-
const field = <Field>unchecked(_values[i]);
8857-
if (
8858-
field.kind === ElementKind.FIELD &&
8859-
!field.is(CommonFlags.INITIALIZED)
8860-
) {
8861-
this.error(
8862-
DiagnosticCode.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_constructor,
8863-
field.declaration.name.range,
8864-
field.declaration.name.text
8865-
);
8866-
}
8867-
}
8868-
}
8869-
88708868
return expr;
88718869
}
88728870

@@ -10075,7 +10073,9 @@ export class Compiler extends DiagnosticEmitter {
1007510073
field.memoryOffset
1007610074
)
1007710075
);
10078-
field.set(CommonFlags.INITIALIZED);
10076+
flow.setFieldFlag(field.internalName, FieldFlags.INITIALIZED)
10077+
} else {
10078+
flow.setFieldFlag(field.internalName, FieldFlags.NONE);
1007910079
}
1008010080
}
1008110081
return stmts;

src/flow.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ export enum LocalFlags {
169169
| CONDITIONALLY_RETAINED
170170
}
171171

172+
/** Flags indicating the current state of an instance field. */
173+
export enum FieldFlags {
174+
NONE = 0,
175+
INITIALIZED = 1 << 0,
176+
}
177+
172178
/** Condition kinds. */
173179
export const enum ConditionKind {
174180
/** Outcome of the condition is unknown */
@@ -200,6 +206,8 @@ export class Flow {
200206
scopedLocals: Map<string,Local> | null = null;
201207
/** Local flags. */
202208
localFlags: LocalFlags[];
209+
/** Field flags. */
210+
fieldFlags: Map<string, FieldFlags>;
203211
/** Function being inlined, when inlining. */
204212
inlineFunction: Function | null;
205213
/** The label we break to when encountering a return statement, when inlining. */
@@ -216,6 +224,7 @@ export class Flow {
216224
flow.returnType = parentFunction.signature.returnType;
217225
flow.contextualTypeArguments = parentFunction.contextualTypeArguments;
218226
flow.localFlags = [];
227+
flow.fieldFlags = new Map();
219228
flow.inlineFunction = null;
220229
flow.inlineReturnLabel = null;
221230
return flow;
@@ -276,6 +285,7 @@ export class Flow {
276285
branch.localFlags = this.localFlags.slice();
277286
branch.inlineFunction = this.inlineFunction;
278287
branch.inlineReturnLabel = this.inlineReturnLabel;
288+
branch.fieldFlags = new Map(this.fieldFlags);
279289
return branch;
280290
}
281291

@@ -519,6 +529,20 @@ export class Flow {
519529
localFlags[index] = flags & ~flag;
520530
}
521531

532+
setFieldFlag(name: string, flag: FieldFlags): void {
533+
let fieldFlags = this.fieldFlags;
534+
const flags = fieldFlags.get(name) || 0;
535+
fieldFlags.set(name, flags | flag);
536+
}
537+
538+
isFieldFlag(name: string, flag: FieldFlags): bool {
539+
const flags = this.fieldFlags.get(name);
540+
if (flags) {
541+
return (flags & flag) == flag;
542+
}
543+
return false;
544+
}
545+
522546
/** Pushes a new break label to the stack, for example when entering a loop that one can `break` from. */
523547
pushBreakLabel(): string {
524548
var parentFunction = this.parentFunction;
@@ -806,6 +830,20 @@ export class Flow {
806830
thisLocalFlags[i] = newFlags;
807831
}
808832
}
833+
834+
// Only the most right flow will have an effect
835+
// on the resulting flow.
836+
const rightFieldFlags = right.fieldFlags;
837+
const rightKeys = Map_keys(rightFieldFlags);
838+
839+
for (let _values = Map_values(rightFieldFlags), i = 0, k = _values.length; i < k; ++i) {
840+
const rightValue = unchecked(_values[i]);
841+
const currentKey = unchecked(rightKeys[i]);
842+
843+
if (rightValue & FieldFlags.INITIALIZED) {
844+
this.setFieldFlag(currentKey, FieldFlags.INITIALIZED);
845+
}
846+
}
809847
}
810848

811849
/** Tests if the specified flows have differing local states. */

tests/compiler/strict-init.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"--runtime none"
44
],
55
"stderr": [
6-
"TS2564: Property b has no initializer and is not definitely assigned in constructor"
6+
"TS2564: Property inlinedProp has no initializer and is not definitely assigned in constructor",
7+
"TS2564: Property b has no initializer and is not definitely assigned in constructor",
8+
"TS2564: Property p has no initializer and is not definitely assigned in constructor"
79
]
810
}

tests/compiler/strict-init.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// OK - Field with explicit initializer
22
export class WithInitializer {
33
public a: i32 = 1;
4-
constructor() {}
54
}
65

76
// ERR - Field b not initialized
@@ -24,8 +23,37 @@ export class ExplicitConstructorInit {
2423
}
2524
}
2625

27-
export class SupressExplicitInit {
28-
p!: f64;
26+
export class NonDefiniteIf {
27+
p: f64;
28+
constructor(a: i32) {
29+
if ((a % 2) == 0) {
30+
this.p = 1.0;
31+
} else if ((a * 2) == 10) {
32+
this.p = 0.0;
33+
}
34+
}
35+
}
36+
37+
export class DefiniteIf {
38+
definite: i32;
39+
40+
constructor(a: i32) {
41+
if ((a % 2) == 0) {
42+
this.definite = 1;
43+
} else if ((a * 2) == 10) {
44+
this.definite = 0;
45+
} else if ((a / 2) == 1) {
46+
this.definite = 8;
47+
} else {
48+
this.definite = 0;
49+
}
50+
}
51+
}
52+
53+
54+
class Inlined {
55+
inlinedProp: i32;
2956
constructor() {}
3057
}
3158

59+
new Inlined();

0 commit comments

Comments
 (0)