Skip to content

Commit 0ca5f73

Browse files
authored
Correctly detect loop assignment as variable read & make loop variable immutable (#1165)
1 parent b45da02 commit 0ca5f73

4 files changed

Lines changed: 48 additions & 11 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ public static void prettyPrint(LocalVarDef e, Spacer spacer, StringBuilder sb, i
876876
printCommentsBefore(sb, e, indent);
877877
printIndent(sb, indent);
878878
if ((e.getOptTyp() instanceof NoTypeExpr)) {
879-
if (e.attrIsConstant()) {
879+
if (e.attrIsConstant() && !(e.getParent() instanceof StmtForRange)) {
880880
sb.append("let");
881881
} else if (!(e.getParent() instanceof StmtForRange)) {
882882
sb.append("var");

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/jurst/AntlrJurstParseTreeTransformer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -782,8 +782,8 @@ private WStatement transformForLoop(StmtForLoopContext s) {
782782

783783
private WStatement transformForRangeLoop(ForRangeLoopContext s) {
784784
WPos source = source(s);
785-
LocalVarDef loopVar = transformLocalVarDef(s.loopVar);
786-
loopVar.setInitialExpr(transformExpr(s.start));
785+
Expr start = transformExpr(s.start);
786+
LocalVarDef loopVar = transformLocalVarDef(s.loopVar, start, true);
787787
Expr to = transformExpr(s.end);
788788
Expr step;
789789
if (s.step == null) {
@@ -800,17 +800,20 @@ private WStatement transformForRangeLoop(ForRangeLoopContext s) {
800800
throw error(s, "for range loop not implemented: " + text(s));
801801
}
802802

803-
private LocalVarDef transformLocalVarDef(LocalVarDefInlineContext v) {
803+
private LocalVarDef transformLocalVarDef(LocalVarDefInlineContext v, OptExpr initialExpr, boolean constant) {
804804
Modifiers modifiers = Ast.Modifiers();
805+
if (constant) {
806+
// Range-loop variables behave like let-variables for user code.
807+
modifiers.add(Ast.ModConstant(source(v)));
808+
}
805809
OptTypeExpr optTyp = transformOptionalType(v.typeExpr());
806810
Identifier name = text(v.name);
807-
OptExpr initialExpr = Ast.NoExpr();
808811
return Ast.LocalVarDef(source(v), modifiers, optTyp, name, initialExpr);
809812
}
810813

811814
private WStatement transformForIteratorLoop(ForIteratorLoopContext s) {
812815
WPos source = source(s);
813-
LocalVarDef loopVar = transformLocalVarDef(s.loopVar);
816+
LocalVarDef loopVar = transformLocalVarDef(s.loopVar, Ast.NoExpr(), false);
814817
Expr in = transformExpr(s.iteratorExpr);
815818
WStatements body = transformStatements(s.statementsBlock());
816819
if (s.iterStyle.getType() == JurstParser.IN) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,8 @@ private WStatement transformForLoop(StmtForLoopContext s) {
946946

947947
private WStatement transformForRangeLoop(ForRangeLoopContext s) {
948948
WPos source = source(s);
949-
LocalVarDef loopVar = transformLocalVarDef(s.loopVar);
950-
loopVar.setInitialExpr(transformExpr(s.start));
949+
Expr start = transformExpr(s.start);
950+
LocalVarDef loopVar = transformLocalVarDef(s.loopVar, start, true);
951951
Expr to = transformExpr(s.end);
952952
Expr step;
953953
if (s.step == null) {
@@ -964,17 +964,20 @@ private WStatement transformForRangeLoop(ForRangeLoopContext s) {
964964
throw error(s, "not implemented: " + text(s));
965965
}
966966

967-
private LocalVarDef transformLocalVarDef(LocalVarDefInlineContext v) {
967+
private LocalVarDef transformLocalVarDef(LocalVarDefInlineContext v, OptExpr initialExpr, boolean constant) {
968968
Modifiers modifiers = Ast.Modifiers();
969+
if (constant) {
970+
// Range-loop variables behave like let-variables for user code.
971+
modifiers.add(Ast.ModConstant(source(v)));
972+
}
969973
OptTypeExpr optTyp = transformOptionalType(v.typeExpr());
970974
Identifier name = text(v.name);
971-
OptExpr initialExpr = Ast.NoExpr();
972975
return Ast.LocalVarDef(source(v), modifiers, optTyp, name, initialExpr);
973976
}
974977

975978
private WStatement transformForIteratorLoop(ForIteratorLoopContext s) {
976979
WPos source = source(s);
977-
LocalVarDef loopVar = transformLocalVarDef(s.loopVar);
980+
LocalVarDef loopVar = transformLocalVarDef(s.loopVar, Ast.NoExpr(), false);
978981
Expr in = transformExpr(s.iteratorExpr);
979982
WStatements body = transformStatements(s.statementsBlock());
980983
if (s.iterStyle.getType() == WurstParser.IN) {

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,37 @@ public void unreadVarWarning3() { // #380
898898
);
899899
}
900900

901+
@Test
902+
public void forRangeStartReadsParameter() {
903+
CompilationResult result = test()
904+
.setStopOnFirstError(false)
905+
.executeProg(false)
906+
.lines(
907+
"package test",
908+
"function foo(int bar)",
909+
" for i = bar to 10",
910+
" skip",
911+
"endpackage"
912+
);
913+
914+
Assert.assertTrue(
915+
result.getGui().getWarningList().stream()
916+
.noneMatch(w -> w.getMessage().contains("The parameter bar is never read")),
917+
"Unexpected unused warning for parameter bar: " + result.getGui().getWarningList()
918+
);
919+
}
920+
921+
@Test
922+
public void forRangeLoopVarIsImmutable() {
923+
testAssertErrorsLines(false, "Cannot assign a new value to constant",
924+
"package test",
925+
"init",
926+
" int stackPointer = 3",
927+
" for i = 0 to stackPointer",
928+
" i--",
929+
"endpackage");
930+
}
931+
901932

902933
@Test
903934
public void unreadVarWarningArrays() { // #813

0 commit comments

Comments
 (0)