Skip to content

Commit fdc21ce

Browse files
committed
Handle 'this' capture in closures and methods
Adds logic to properly capture and reference 'this' in closures and methods, ensuring 'this' is stored in the closure environment when needed. Updates compiler and resolver to support lookup and environment slot assignment for captured 'this', improving closure support for methods referencing 'this'.
1 parent c693621 commit fdc21ce

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

src/compiler.ts

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,36 @@ export class Compiler extends DiagnosticEmitter {
18191819
}
18201820
}
18211821
}
1822+
1823+
// Also check if 'this' is captured (for methods)
1824+
if (preCapturedNames.has(CommonNames.this_)) {
1825+
let thisLocal = flow.lookupLocal(CommonNames.this_);
1826+
if (thisLocal && !thisLocal.isCaptured) {
1827+
thisLocal.isCaptured = true;
1828+
if (!instance.capturedLocals) {
1829+
instance.capturedLocals = new Map();
1830+
}
1831+
if (!instance.capturedLocals.has(thisLocal)) {
1832+
let ptrSize = this.options.usizeType.byteSize;
1833+
let currentOffset = ptrSize;
1834+
for (let _keys = Map_keys(instance.capturedLocals), j = 0, m = _keys.length; j < m; ++j) {
1835+
let existingLocal = _keys[j];
1836+
let endOfSlot = existingLocal.envSlotIndex + existingLocal.type.byteSize;
1837+
if (endOfSlot > currentOffset) currentOffset = endOfSlot;
1838+
}
1839+
let typeSize = thisLocal.type.byteSize;
1840+
let align = typeSize;
1841+
currentOffset = (currentOffset + align - 1) & ~(align - 1);
1842+
thisLocal.envSlotIndex = currentOffset;
1843+
thisLocal.envOwner = instance;
1844+
instance.capturedLocals.set(thisLocal, thisLocal.envSlotIndex);
1845+
}
1846+
if (!instance.envLocal) {
1847+
let envLocal = flow.addScopedLocal("$env", this.options.usizeType);
1848+
instance.envLocal = envLocal;
1849+
}
1850+
}
1851+
}
18221852
}
18231853

18241854
// For closures (functions that capture from outer scope), create a local to cache
@@ -7655,6 +7685,36 @@ export class Compiler extends DiagnosticEmitter {
76557685
}
76567686
break;
76577687
}
7688+
case NodeKind.This: {
7689+
// Handle 'this' capture - look it up in outer flow
7690+
let local = outerFlow.lookupLocal(CommonNames.this_);
7691+
if (!local) {
7692+
local = outerFlow.lookupLocalInOuter(CommonNames.this_);
7693+
}
7694+
if (local && !captures.has(local)) {
7695+
local.isCaptured = true;
7696+
if (!local.envOwner) {
7697+
local.envOwner = <Function>local.parent;
7698+
}
7699+
if (local.envSlotIndex >= 0) {
7700+
captures.set(local, local.envSlotIndex);
7701+
} else {
7702+
let ptrSize = this.options.usizeType.byteSize;
7703+
let currentOffset = ptrSize;
7704+
for (let _keys = Map_keys(captures), idx = 0, cnt = _keys.length; idx < cnt; ++idx) {
7705+
let existingLocal = _keys[idx];
7706+
let endOfSlot = existingLocal.envSlotIndex + existingLocal.type.byteSize;
7707+
if (endOfSlot > currentOffset) currentOffset = endOfSlot;
7708+
}
7709+
let typeSize = local.type.byteSize;
7710+
let align = typeSize;
7711+
currentOffset = (currentOffset + align - 1) & ~(align - 1);
7712+
local.envSlotIndex = currentOffset;
7713+
captures.set(local, local.envSlotIndex);
7714+
}
7715+
}
7716+
break;
7717+
}
76587718
case NodeKind.Function: {
76597719
// For nested function expressions, scan their body but add their params to inner names
76607720
let funcExpr = <FunctionExpression>node;
@@ -8166,6 +8226,17 @@ export class Compiler extends DiagnosticEmitter {
81668226
}
81678227
break;
81688228
}
8229+
case NodeKind.This: {
8230+
// Handle 'this' capture - check if outer function has a 'this' local
8231+
let thisLocal = outerFlow.lookupLocal(CommonNames.this_);
8232+
if (!thisLocal) {
8233+
thisLocal = outerFlow.lookupLocalInOuter(CommonNames.this_);
8234+
}
8235+
if (thisLocal) {
8236+
capturedNames.set(CommonNames.this_, null);
8237+
}
8238+
break;
8239+
}
81698240
case NodeKind.Block: {
81708241
let block = <BlockStatement>node;
81718242
for (let i = 0, k = block.statements.length; i < k; i++) {
@@ -8602,9 +8673,11 @@ export class Compiler extends DiagnosticEmitter {
86028673
let slotOffset = local.envSlotIndex;
86038674
let localType = local.type;
86048675

8605-
// Only copy if this is a parameter (index in parameter range)
8606-
// Local variables will be initialized when their declaration is compiled
8607-
if (local.index >= paramStartIndex && local.index < paramEndIndex) {
8676+
// Copy parameters and 'this' to the environment
8677+
// Local variables (var/let) will be initialized later when their declaration is compiled
8678+
let isParameter = local.index >= paramStartIndex && local.index < paramEndIndex;
8679+
let isThis = hasThis && local.index == 0; // 'this' is at index 0 in methods
8680+
if (isParameter || isThis) {
86088681
stmts.push(
86098682
module.store(
86108683
localType.byteSize,
@@ -8679,6 +8752,22 @@ export class Compiler extends DiagnosticEmitter {
86798752
}
86808753
case NodeKind.This: {
86818754
let thisType = sourceFunction.signature.thisType;
8755+
8756+
// Check if 'this' is captured from an outer scope (closure case)
8757+
if (!thisType && this.options.hasFeature(Feature.Closures)) {
8758+
// Look for 'this' in outer flow - it might be captured
8759+
let thisLocal = flow.lookupLocal(CommonNames.this_);
8760+
if (!thisLocal) {
8761+
thisLocal = flow.lookupLocalInOuter(CommonNames.this_);
8762+
}
8763+
if (thisLocal && thisLocal.isCaptured && thisLocal.envSlotIndex >= 0) {
8764+
// 'this' is captured - load from closure environment
8765+
flow.set(FlowFlags.AccessesThis);
8766+
this.currentType = thisLocal.type;
8767+
return this.compileClosureLoad(thisLocal, expression);
8768+
}
8769+
}
8770+
86828771
if (!thisType) {
86838772
this.error(
86848773
DiagnosticCode._this_cannot_be_referenced_in_current_location,

src/resolver.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,16 @@ export class Resolver extends DiagnosticEmitter {
21752175
return thisLocal;
21762176
}
21772177
}
2178+
// Check for captured 'this' in closures - look up in outer flow chain
2179+
let thisLocal = ctxFlow.lookupLocal(CommonNames.this_);
2180+
if (!thisLocal) {
2181+
thisLocal = ctxFlow.lookupLocalInOuter(CommonNames.this_);
2182+
}
2183+
if (thisLocal) {
2184+
this.currentThisExpression = null;
2185+
this.currentElementExpression = null;
2186+
return thisLocal;
2187+
}
21782188
let parent = ctxFlow.sourceFunction.parent;
21792189
if (parent) {
21802190
this.currentThisExpression = null;

0 commit comments

Comments
 (0)