Skip to content

Commit 7e7a724

Browse files
authored
Implement capturing outer this in closures (#1115)
* Implement capturing outer this in closures
1 parent 89f5a71 commit 7e7a724

File tree

4 files changed

+178
-3
lines changed

4 files changed

+178
-3
lines changed

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,20 +401,33 @@ public ILconst get() {
401401

402402
public static ILaddress evaluateLvalue(ImMemberAccess va, ProgramState globalState, LocalState localState) {
403403
ImVar v = va.getVar();
404-
ILconstObject receiver = globalState.toObject(va.getReceiver().evaluate(globalState, localState));
404+
ILconst receiverVal = va.getReceiver().evaluate(globalState, localState);
405+
ILconstObject receiver = globalState.toObject(receiverVal);
406+
if (receiver == null && receiverVal instanceof ILconstInt && va.getReceiver().attrTyp() instanceof ImClassType) {
407+
int objectId = ((ILconstInt) receiverVal).getVal();
408+
if (objectId != 0) {
409+
receiver = globalState.ensureObject((ImClassType) va.getReceiver().attrTyp(),
410+
objectId, va.attrTrace());
411+
}
412+
}
413+
if (receiver == null) {
414+
throw new InterpreterException(va.attrTrace(), "Null pointer dereference");
415+
}
416+
ILconstObject receiverFinal = receiver;
405417
List<Integer> indexes =
406418
va.getIndexes().stream()
407419
.map(ie -> ((ILconstInt) ie.evaluate(globalState, localState)).getVal())
408420
.collect(Collectors.toList());
421+
List<Integer> indexesFinal = indexes;
409422
return new ILaddress() {
410423
@Override
411424
public void set(ILconst value) {
412-
receiver.set(v, indexes, value);
425+
receiverFinal.set(v, indexesFinal, value);
413426
}
414427

415428
@Override
416429
public ILconst get() {
417-
return receiver.get(v, indexes)
430+
return receiverFinal.get(v, indexesFinal)
418431
.orElseGet(() -> va.attrTyp().defaultValue());
419432
}
420433
};

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ public ILconst getHandleByIndex(int val) {
342342
return handleMap.get(val);
343343
}
344344

345+
public ILconstObject ensureObject(ImClassType clazz, int objectId, Element trace) {
346+
ILconstObject existing = indexToObject.get(objectId);
347+
if (existing != null) {
348+
return existing;
349+
}
350+
ILconstObject res = new ILconstObject(clazz, objectId, trace);
351+
indexToObject.put(objectId, res);
352+
return res;
353+
}
354+
345355
public ILconstObject toObject(ILconst val) {
346356
if (val instanceof ILconstObject) {
347357
return (ILconstObject) val;

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class ClosureTranslator {
3030
private Map<ImTypeVar, ImTypeVar> typeVars;
3131
private ImFunction impl;
3232
private ImClass c;
33+
private ImVar capturedThisField;
3334

3435
public ClosureTranslator(ExprClosure e, ImTranslator tr, ImFunction f) {
3536
super();
@@ -167,6 +168,7 @@ private ImClass createClass() {
167168
} else {
168169
impl.getBody().add(JassIm.ImReturn(e, translated));
169170
}
171+
captureEnclosingThis();
170172
transformTranslated(translated);
171173

172174
typeVars = rewriteTypeVars(c);
@@ -245,6 +247,9 @@ public ImType case_ImTypeVarRef(ImTypeVarRef t) {
245247
*/
246248
private void transformTranslated(ImExpr t) {
247249
final List<ImVarAccess> vas = Lists.newArrayList();
250+
final List<ImMemberAccess> receiversToRewrite = Lists.newArrayList();
251+
ImVar closureThisVar = tr.getThisVar(e);
252+
248253
t.accept(new ImExpr.DefaultVisitor() {
249254
@Override
250255
public void visit(ImVarAccess va) {
@@ -254,13 +259,77 @@ public void visit(ImVarAccess va) {
254259
}
255260
}
256261

262+
@Override
263+
public void visit(ImMemberAccess ma) {
264+
super.visit(ma);
265+
if (capturedThisField != null && ma.getReceiver() instanceof ImVarAccess) {
266+
ImVar recvVar = ((ImVarAccess) ma.getReceiver()).getVar();
267+
if (recvVar == closureThisVar && capturesFromEnclosingThis(owningClass(ma.getVar()))) {
268+
receiversToRewrite.add(ma);
269+
}
270+
}
271+
}
257272

258273
});
259274

260275
for (ImVarAccess va : vas) {
261276
ImVar v = getClosureVarFor(va.getVar());
262277
va.replaceBy(JassIm.ImMemberAccess(e, closureThis(), JassIm.ImTypeArguments(), v, JassIm.ImExprs()));
263278
}
279+
280+
for (ImMemberAccess ma : receiversToRewrite) {
281+
ma.setReceiver(JassIm.ImMemberAccess(e, closureThis(), JassIm.ImTypeArguments(), capturedThisField, JassIm.ImExprs()));
282+
}
283+
}
284+
285+
private void captureEnclosingThis() {
286+
ImVar outerThis = getEnclosingThisVar();
287+
if (outerThis != null) {
288+
capturedThisField = getClosureVarFor(outerThis);
289+
}
290+
}
291+
292+
private ImVar getEnclosingThisVar() {
293+
if (f != null && !f.getParameters().isEmpty()) {
294+
ImVar param = f.getParameters().get(0);
295+
if ("this".equals(param.getName()) && param.getType() instanceof ImClassType) {
296+
return param;
297+
}
298+
}
299+
return null;
300+
}
301+
302+
private ImClass owningClass(ImVar var) {
303+
if (var.getParent() != null && var.getParent().getParent() instanceof ImClass) {
304+
return (ImClass) var.getParent().getParent();
305+
}
306+
return null;
307+
}
308+
309+
private boolean capturesFromEnclosingThis(ImClass owner) {
310+
if (owner == null || capturedThisField == null) {
311+
return false;
312+
}
313+
if (!(capturedThisField.getType() instanceof ImClassType)) {
314+
return false;
315+
}
316+
317+
ImClass capturedClass = ((ImClassType) capturedThisField.getType()).getClassDef();
318+
return capturedClass == owner || capturedClass.isSubclassOf(owner);
319+
}
320+
321+
private ImVar findOuterThisVar(ImClass owner) {
322+
// in instance methods, the first parameter is typically "this"
323+
for (ImVar p : f.getParameters()) {
324+
ImType t = p.getType();
325+
if (t instanceof ImClassType) {
326+
ImClassType ct = (ImClassType) t;
327+
if (ct.getClassDef() == owner) {
328+
return p;
329+
}
330+
}
331+
}
332+
return null;
264333
}
265334

266335
private ImVarAccess closureThis() {

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,5 +1531,88 @@ public void mixingLegacyOwner_newType_insideGenericClassMethod() {
15311531
);
15321532
}
15331533

1534+
@Test
1535+
public void arrayListCapacity_lazyClosure_repro() {
1536+
testAssertOkLines(true,
1537+
"package test",
1538+
"",
1539+
"native testSuccess()",
1540+
"",
1541+
"constant int JASS_MAX_ARRAY_SIZE = 8190",
1542+
"",
1543+
"public function lazy<T:>(Lazy<T> l) returns Lazy<T>",
1544+
" return l",
1545+
"",
1546+
"public abstract class Lazy<T:>",
1547+
" T val = null",
1548+
" boolean wasRetrieved = false",
1549+
"",
1550+
" abstract function retrieve() returns T",
1551+
"",
1552+
" function get() returns T",
1553+
" if not wasRetrieved",
1554+
" val = retrieve()",
1555+
" wasRetrieved = true",
1556+
" return val",
1557+
"",
1558+
"public class ArrayList<T:>",
1559+
"",
1560+
"public class CFBuilding",
1561+
" ArrayList<CFBuilding> upgrades = null",
1562+
" Lazy<boolean> hasAAUpgrade = lazy<boolean>(() -> begin",
1563+
" var result = false",
1564+
" if upgrades != null",
1565+
" result = true",
1566+
" return result",
1567+
" end)",
1568+
"",
1569+
"",
1570+
"init",
1571+
" let a = new CFBuilding()",
1572+
" let b = new CFBuilding()",
1573+
" // ensure upgrades is non-null so closure does some work",
1574+
" b.upgrades = new ArrayList<CFBuilding>()",
1575+
" // invoke lazy attribute so its closure is actually referenced",
1576+
" if b.hasAAUpgrade.get() and not a.hasAAUpgrade.get()",
1577+
" testSuccess()"
1578+
);
1579+
}
1580+
1581+
@Test
1582+
public void inheritedField_lazyClosure_uses_enclosing_receiver() {
1583+
testAssertOkLines(true,
1584+
"package test",
1585+
"",
1586+
"native testSuccess()",
1587+
"",
1588+
"public abstract class Lazy<T:>",
1589+
" T val = null",
1590+
" boolean wasRetrieved = false",
1591+
"",
1592+
" abstract function retrieve() returns T",
1593+
"",
1594+
" function get() returns T",
1595+
" if not wasRetrieved",
1596+
" val = retrieve()",
1597+
" wasRetrieved = true",
1598+
" return val",
1599+
"",
1600+
"public function lazy<T:>(Lazy<T> l) returns Lazy<T>",
1601+
" return l",
1602+
"",
1603+
"public class BaseBuilding",
1604+
" boolean hasDetector = true",
1605+
"",
1606+
"public class AdvancedBuilding extends BaseBuilding",
1607+
" Lazy<boolean> detectorAvailable = lazy<boolean>(() -> hasDetector)",
1608+
"",
1609+
"init",
1610+
" let b = new AdvancedBuilding()",
1611+
" b.hasDetector = true",
1612+
" if b.detectorAvailable.get()",
1613+
" testSuccess()"
1614+
);
1615+
}
1616+
15341617

15351618
}

0 commit comments

Comments
 (0)