Skip to content

Commit 70508db

Browse files
committed
Handle module field initialization guarantees
1 parent 601e4e8 commit 70508db

2 files changed

Lines changed: 79 additions & 9 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,7 +1760,7 @@ private void checkUninitializedVars(FunctionLike f) {
17601760
* cheap, local check: it does not attempt interprocedural or path-sensitive reasoning.
17611761
*/
17621762
private void checkPotentiallyUninitializedClassFields(FunctionLike function) {
1763-
ClassDef owner = function.attrNearestClassDef();
1763+
ClassOrModule owner = function.attrNearestClassOrModule();
17641764
if (owner == null || function instanceof OnDestroyDef) {
17651765
return;
17661766
}
@@ -1926,12 +1926,12 @@ private boolean hasGuaranteedConstructorAssignment(GlobalVarDef field) {
19261926
if (cached != null) {
19271927
return cached;
19281928
}
1929-
ClassDef declaringClass = field.attrNearestClassDef();
1930-
if (declaringClass == null || declaringClass.getConstructors().isEmpty()) {
1929+
List<ConstructorDef> constructors = constructorsFor(field);
1930+
if (constructors.isEmpty()) {
19311931
guaranteedClassFieldInitCache.put(field, false);
19321932
return false;
19331933
}
1934-
for (ConstructorDef constructor : declaringClass.getConstructors()) {
1934+
for (ConstructorDef constructor : constructors) {
19351935
if (!constructorAssignsField(constructor, field, Collections.newSetFromMap(new IdentityHashMap<>()))) {
19361936
guaranteedClassFieldInitCache.put(field, false);
19371937
return false;
@@ -1965,14 +1965,38 @@ private boolean constructorAssignsField(ConstructorDef constructor, GlobalVarDef
19651965
if (thisCall == null) {
19661966
return false;
19671967
}
1968-
ClassOrModule owner = constructor.attrNearestClassOrModule();
1969-
if (owner == null) {
1970-
return false;
1971-
}
1972-
ConstructorDef target = OverloadingResolver.resolveThisCall(owner.getConstructors(), thisCall);
1968+
ConstructorDef target = OverloadingResolver.resolveThisCall(constructorsFor(constructor), thisCall);
19731969
return target != null && target != constructor && constructorAssignsField(target, field, visiting);
19741970
}
19751971

1972+
private List<ConstructorDef> constructorsFor(GlobalVarDef field) {
1973+
Element current = field;
1974+
while (current != null) {
1975+
if (current instanceof ModuleInstanciation module) {
1976+
return module.getConstructors();
1977+
}
1978+
if (current instanceof ClassOrModule owner) {
1979+
return owner.getConstructors();
1980+
}
1981+
current = current.getParent();
1982+
}
1983+
return Collections.emptyList();
1984+
}
1985+
1986+
private List<ConstructorDef> constructorsFor(ConstructorDef constructor) {
1987+
Element current = constructor;
1988+
while (current != null) {
1989+
if (current instanceof ModuleInstanciation module) {
1990+
return module.getConstructors();
1991+
}
1992+
if (current instanceof ClassOrModule owner) {
1993+
return owner.getConstructors();
1994+
}
1995+
current = current.getParent();
1996+
}
1997+
return Collections.emptyList();
1998+
}
1999+
19762000
private void checkClassFieldInitializerReads(GlobalVarDef field) {
19772001
if (!field.attrIsDynamicClassMember() || !(field.getInitialExpr() instanceof Expr initializer)) {
19782002
return;
@@ -1993,6 +2017,11 @@ private void checkField(NameRef access) {
19932017
+ " Initialize it explicitly before using it.");
19942018
}
19952019

2020+
@Override
2021+
public void visit(ExprClosure closure) {
2022+
// A closure runs later (and may never run), so its body is not field initialization.
2023+
}
2024+
19962025
@Override
19972026
public void visit(ExprVarAccess access) {
19982027
super.visit(access);

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ClassesTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,47 @@ public void warnsWhenFieldInitializerReadsUninitializedField() {
216216
);
217217
}
218218

219+
@Test
220+
public void doesNotWarnWhenModuleConstructorAssignsClassField() {
221+
CompilationResult result = test()
222+
.setStopOnFirstError(false)
223+
.executeProg(false)
224+
.lines(
225+
"package Test",
226+
"module Values",
227+
" int value",
228+
" construct()",
229+
" value = 1",
230+
" function get() returns int",
231+
" return value",
232+
"class Counter",
233+
" use Values"
234+
);
235+
236+
assertFalse(result.getGui().getWarningList().stream()
237+
.anyMatch(w -> w.getMessage().contains("no explicit initializer")),
238+
result.getGui().getWarningList().toString());
239+
}
240+
241+
@Test
242+
public void doesNotTreatDeferredClosureInitializerAsImmediateFieldRead() {
243+
CompilationResult result = test()
244+
.setStopOnFirstError(false)
245+
.executeProg(false)
246+
.lines(
247+
"package Test",
248+
"interface Reader",
249+
" function read() returns int",
250+
"class Counter",
251+
" int value",
252+
" Reader reader = () -> value"
253+
);
254+
255+
assertFalse(result.getGui().getWarningList().stream()
256+
.anyMatch(w -> w.getMessage().contains("read from a field initializer")),
257+
result.getGui().getWarningList().toString());
258+
}
259+
219260
@Test
220261
public void classes1() throws IOException {
221262
testAssertOkFile(new File(TEST_DIR + "Classes_1.wurst"), true);

0 commit comments

Comments
 (0)