Skip to content

Commit d92077a

Browse files
committed
more testing, better validation
1 parent af27e37 commit d92077a

6 files changed

Lines changed: 145 additions & 6 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrClosureCapturedVariables.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ private static void collect(Builder<Element, VarDef> result, ExprClosure closure
6262

6363
private static boolean isLocalVariable(NameDef def) {
6464
return def instanceof LocalVarDef
65-
|| def instanceof WParameter && !(def.getParent().getParent() instanceof TupleDef);
65+
|| def instanceof WParameter && !(def.getParent().getParent() instanceof TupleDef)
66+
|| def instanceof WShortParameter;
6667

6768
}
6869

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFunctionSignature.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import de.peeeq.wurstscript.types.FunctionSignature;
66
import de.peeeq.wurstscript.types.VariableBinding;
77
import de.peeeq.wurstscript.types.WurstType;
8+
import de.peeeq.wurstscript.types.WurstTypeCode;
89
import de.peeeq.wurstscript.types.WurstTypeUnknown;
910
import de.peeeq.wurstscript.utils.Utils;
1011
import org.jetbrains.annotations.NotNull;
@@ -57,9 +58,38 @@ public static FunctionSignature calculate(StmtCall fc) {
5758
fc.addError("Cannot infer type for type parameter " + mapping.printUnboundTypeVars());
5859
}
5960

61+
checkCodeClosureCaptures(fc, sig);
62+
6063
return sig;
6164
}
6265

66+
private static void checkCodeClosureCaptures(StmtCall fc, FunctionSignature sig) {
67+
for (int i = 0; i < fc.getArgs().size(); i++) {
68+
Expr arg = fc.getArgs().get(i);
69+
if (!(arg instanceof ExprClosure)) {
70+
continue;
71+
}
72+
if (!(sig.getParamType(i) instanceof WurstTypeCode)) {
73+
continue;
74+
}
75+
ExprClosure closure = (ExprClosure) arg;
76+
if (!closure.attrCapturedVariables().isEmpty()) {
77+
String codeLambdaContext = codeLambdaContext(fc);
78+
closure.attrCapturedVariables().entries().forEach(entry ->
79+
entry.getKey().addError("Cannot capture local variable '" + entry.getValue().getName()
80+
+ "' in anonymous function" + codeLambdaContext + ". This is only possible with closures."));
81+
}
82+
}
83+
}
84+
85+
private static String codeLambdaContext(StmtCall stmtCall) {
86+
String funcName = stmtCall instanceof FunctionCall fc ? fc.getFuncName() : "<unknown>";
87+
if (stmtCall instanceof ExprMemberMethod) {
88+
return " passed as code to ." + funcName + "() ->";
89+
}
90+
return " passed as code to " + funcName + "() ->";
91+
}
92+
6393
private static FunctionSignature filterSigs(
6494
Collection<FunctionSignature> sigs,
6595
List<WurstType> argTypes, StmtCall location) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrPossibleFunctionSignatures.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,14 @@ private static ImmutableCollection<FunctionSignature> findBestSignature(StmtCall
8585
fc.getErrorHandler().sendError(c);
8686
}
8787
}
88-
89-
return ImmutableList.copyOf(inferred);
88+
ImmutableList.Builder<FunctionSignature> result = ImmutableList.builder();
89+
for (int i = 0; i < n; i++) {
90+
var r = sigs.get(i).tryMatchAgainstArgs(argTypes, argsNode, fc);
91+
if (r.getBadness() == bestBad) {
92+
result.add(inferred[i]);
93+
}
94+
}
95+
return result.build();
9096
}
9197

9298

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ClosureTranslator.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import de.peeeq.wurstscript.translation.imtojass.TypeRewriteMatcher;
1111
import de.peeeq.wurstscript.translation.imtojass.TypeRewriter;
1212
import de.peeeq.wurstscript.types.*;
13+
import de.peeeq.wurstscript.parser.WPos;
14+
import de.peeeq.wurstscript.utils.Utils;
1315

1416
import java.util.Collections;
1517
import java.util.LinkedHashMap;
@@ -123,20 +125,59 @@ private void verifyTranslatedAnonfunc(ImExpr translated) {
123125
public void visit(ImVarAccess va) {
124126
super.visit(va);
125127
if (isLocalToOtherFunc(va.getVar())) {
126-
throw new CompileError(va.attrTrace().attrSource(), "Anonymous functions used as 'code' cannot capture variables. Captured " + va.getVar().getName());
128+
throw new CompileError(bestCaptureErrorPos(va),
129+
"Anonymous functions used as 'code' cannot capture variables. Captured "
130+
+ va.getVar().getName() + closureDebugContext());
127131
}
128132
}
129133

130134
@Override
131135
public void visit(ImSet s) {
132136
super.visit(s);
133137
if (isLocalToOtherFunc(s.getLeft())) {
134-
throw new CompileError(s.attrTrace().attrSource(), "Anonymous functions used as 'code' cannot capture variables. Captured " + s.getLeft());
138+
throw new CompileError(bestCaptureErrorPos(s),
139+
"Anonymous functions used as 'code' cannot capture variables. Captured "
140+
+ s.getLeft() + closureDebugContext());
135141
}
136142
}
137143
});
138144
}
139145

146+
private String closureDebugContext() {
147+
String pos = "<unknown>";
148+
WPos p = e.attrErrorPos();
149+
if (isUsableSource(p)) {
150+
pos = p.printShort();
151+
}
152+
String nearestFunc = e.attrNearestFuncDef() == null ? "<none>" : e.attrNearestFuncDef().getName();
153+
String nearestPkg = e.attrNearestPackage() == null ? "<none>" : Utils.printElement(e.attrNearestPackage());
154+
return " [closure=" + pos
155+
+ ", expectedType=" + e.attrExpectedTypAfterOverloading()
156+
+ ", closureType=" + e.attrTyp()
157+
+ ", nearestFunc=" + nearestFunc
158+
+ ", nearestPackage=" + nearestPkg + "]";
159+
}
160+
161+
private WPos bestCaptureErrorPos(ImStmt s) {
162+
WPos src = s.attrTrace().attrSource();
163+
if (isUsableSource(src)) {
164+
return src;
165+
}
166+
return e.attrErrorPos();
167+
}
168+
169+
private WPos bestCaptureErrorPos(ImVarAccess va) {
170+
WPos src = va.attrTrace().attrSource();
171+
if (isUsableSource(src)) {
172+
return src;
173+
}
174+
return e.attrErrorPos();
175+
}
176+
177+
private boolean isUsableSource(WPos pos) {
178+
return pos != null && pos.getLine() > 0 && pos.getFile() != null && !pos.getFile().isEmpty();
179+
}
180+
140181

141182
private ImClass createClass() {
142183
ImClassType superClass = getSuperClass();

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1012,10 +1012,11 @@ private void checkClosure(ExprClosure e) {
10121012
if (expectedTyp instanceof WurstTypeCode) {
10131013
// TODO check if no vars are captured
10141014
if (!e.attrCapturedVariables().isEmpty()) {
1015+
String codeLambdaContext = codeLambdaContext(e);
10151016
for (Map.Entry<Element, VarDef> elem : e.attrCapturedVariables().entries()) {
10161017

10171018
elem.getKey().addError("Cannot capture local variable '" + elem.getValue().getName()
1018-
+ "' in anonymous function. This is only possible with closures.");
1019+
+ "' in anonymous function" + codeLambdaContext + ". This is only possible with closures.");
10191020
}
10201021
}
10211022
} else if (expectedTyp instanceof WurstTypeUnknown || expectedTyp instanceof WurstTypeClosure) {
@@ -1062,6 +1063,21 @@ private void checkClosure(ExprClosure e) {
10621063

10631064
}
10641065

1066+
private static String codeLambdaContext(ExprClosure e) {
1067+
Element parent = e.getParent();
1068+
if (parent instanceof Arguments args) {
1069+
Element call = args.getParent();
1070+
if (call instanceof StmtCall stmtCall) {
1071+
String funcName = stmtCall instanceof FunctionCall fc ? fc.getFuncName() : "<unknown>";
1072+
if (stmtCall instanceof ExprMemberMethod) {
1073+
return " passed as code to ." + funcName + "() ->";
1074+
}
1075+
return " passed as code to " + funcName + "() ->";
1076+
}
1077+
}
1078+
return "";
1079+
}
1080+
10651081
private void checkConstructorsUnique(ClassOrModule c) {
10661082
List<ConstructorDef> constrs = c.getConstructors();
10671083

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,51 @@ public void captureParam() {
141141
);
142142
}
143143

144+
@Test
145+
public void overloadCodeVsClosureInterface_prefersClosureInterface() {
146+
testAssertOkLines(false,
147+
"package test",
148+
"interface IntCallback",
149+
" function run(int p)",
150+
"class Wrapper",
151+
" function forEach(code callback)",
152+
" skip",
153+
" function forEach(IntCallback callback)",
154+
" callback.run(1)",
155+
"init",
156+
" let w = new Wrapper()",
157+
" var x = 0",
158+
" w.forEach() p ->",
159+
" x += p"
160+
);
161+
}
162+
163+
@Test
164+
public void nestedCodeClosureCaptureIsValidationError() {
165+
testAssertErrorsLines(false, "passed as code to .addAction() ->",
166+
"package test",
167+
"interface PerkApplyFunc",
168+
" function run(int p)",
169+
"interface VoidCallback",
170+
" function run()",
171+
"class TriggerWrap",
172+
" function addAction(code c)",
173+
" skip",
174+
"native takesInt(int i)",
175+
"function doAfter(real delay, VoidCallback cb)",
176+
" skip",
177+
"function addPerk(PerkApplyFunc f)",
178+
" skip",
179+
"init",
180+
" let roundStartTrigger = new TriggerWrap()",
181+
" addPerk((int plr) -> begin",
182+
" roundStartTrigger.addAction() ->",
183+
" doAfter(0.01) ->",
184+
" takesInt(plr)",
185+
" end)"
186+
);
187+
}
188+
144189
@Test
145190
public void captureThis() {
146191
testAssertOkLines(true,

0 commit comments

Comments
 (0)