Skip to content

Commit b071a92

Browse files
tomblindPerryvw
authored andcommitted
Tracking object literal shorthand identifier references (#666)
Fixes bug where hoisting wouldn't occur for variables referenced only in object literal shorthand assignments. Also fixes #663
1 parent 9376ea5 commit b071a92

File tree

3 files changed

+61
-32
lines changed

3 files changed

+61
-32
lines changed

src/LuaTransformer.ts

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3597,6 +3597,9 @@ export class LuaTransformer {
35973597
properties.push(tstl.createTableFieldExpression(expression, name, element));
35983598
} else if (ts.isShorthandPropertyAssignment(element)) {
35993599
const valueSymbol = this.checker.getShorthandAssignmentValueSymbol(element);
3600+
if (valueSymbol) {
3601+
this.trackSymbolReference(valueSymbol, element.name);
3602+
}
36003603
const identifier = this.createShorthandIdentifier(valueSymbol, element.name);
36013604
properties.push(tstl.createTableFieldExpression(identifier, name, element));
36023605
} else if (ts.isMethodDeclaration(element)) {
@@ -5525,47 +5528,50 @@ export class LuaTransformer {
55255528
return "____" + tsHelper.fixInvalidLuaIdentifier(name);
55265529
}
55275530

5528-
protected getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId | undefined {
5529-
const symbol = this.checker.getSymbolAtLocation(identifier);
5530-
let symbolId: tstl.SymbolId | undefined;
5531-
if (symbol) {
5532-
// Track first time symbols are seen
5533-
if (!this.symbolIds.has(symbol)) {
5534-
symbolId = this.genSymbolIdCounter++;
5531+
protected trackSymbolReference(symbol: ts.Symbol, identifier: ts.Identifier): tstl.SymbolId | undefined {
5532+
// Track first time symbols are seen
5533+
let symbolId = this.symbolIds.get(symbol);
5534+
if (!symbolId) {
5535+
symbolId = this.genSymbolIdCounter++;
55355536

5536-
const symbolInfo: SymbolInfo = { symbol, firstSeenAtPos: identifier.pos };
5537-
this.symbolIds.set(symbol, symbolId);
5538-
this.symbolInfo.set(symbolId, symbolInfo);
5539-
} else {
5540-
symbolId = this.symbolIds.get(symbol);
5541-
}
5537+
const symbolInfo: SymbolInfo = { symbol, firstSeenAtPos: identifier.pos };
5538+
this.symbolIds.set(symbol, symbolId);
5539+
this.symbolInfo.set(symbolId, symbolInfo);
5540+
}
55425541

5543-
if (this.options.noHoisting) {
5544-
// Check for reference-before-declaration
5545-
const declaration = tsHelper.getFirstDeclaration(symbol, this.currentSourceFile);
5546-
if (declaration && identifier.pos < declaration.pos) {
5547-
throw TSTLErrors.ReferencedBeforeDeclaration(identifier);
5548-
}
5542+
if (this.options.noHoisting) {
5543+
// Check for reference-before-declaration
5544+
const declaration = tsHelper.getFirstDeclaration(symbol, this.currentSourceFile);
5545+
if (declaration && identifier.pos < declaration.pos) {
5546+
throw TSTLErrors.ReferencedBeforeDeclaration(identifier);
55495547
}
5548+
}
55505549

5551-
if (symbolId !== undefined) {
5552-
//Mark symbol as seen in all current scopes
5553-
for (const scope of this.scopeStack) {
5554-
if (!scope.referencedSymbols) {
5555-
scope.referencedSymbols = new Map();
5556-
}
5557-
let references = scope.referencedSymbols.get(symbolId);
5558-
if (!references) {
5559-
references = [];
5560-
scope.referencedSymbols.set(symbolId, references);
5561-
}
5562-
references.push(identifier);
5563-
}
5550+
//Mark symbol as seen in all current scopes
5551+
for (const scope of this.scopeStack) {
5552+
if (!scope.referencedSymbols) {
5553+
scope.referencedSymbols = new Map();
5554+
}
5555+
let references = scope.referencedSymbols.get(symbolId);
5556+
if (!references) {
5557+
references = [];
5558+
scope.referencedSymbols.set(symbolId, references);
55645559
}
5560+
references.push(identifier);
55655561
}
5562+
55665563
return symbolId;
55675564
}
55685565

5566+
protected getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId | undefined {
5567+
const symbol = this.checker.getSymbolAtLocation(identifier);
5568+
if (symbol) {
5569+
return this.trackSymbolReference(symbol, identifier);
5570+
} else {
5571+
return undefined;
5572+
}
5573+
}
5574+
55695575
protected findScope(scopeTypes: ScopeType): Scope | undefined {
55705576
return this.scopeStack
55715577
.slice()

test/unit/functions.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,18 @@ test.each([{}, { noHoisting: true }])("Function rest parameter (unreferenced)",
557557
expect(util.transpileAndExecute(code, compilerOptions)).toBe("foobar");
558558
});
559559

560+
test.each([{}, { noHoisting: true }])("Function rest parameter (referenced in property shorthand)", compilerOptions => {
561+
const code = `
562+
function foo(a: unknown, ...b: string[]) {
563+
const c = { b };
564+
return c.b.join("");
565+
}
566+
return foo("A", "B", "C", "D");
567+
`;
568+
569+
expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD");
570+
});
571+
560572
test.each([{}, { noHoisting: true }])("@vararg", compilerOptions => {
561573
const code = `
562574
/** @vararg */ type LuaVarArg<A extends unknown[]> = A & { __luaVarArg?: never };

test/unit/hoisting.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ test.each([
235235
},
236236
{ code: `function makeFoo() { return new Foo(); } class Foo {}`, identifier: "Foo" },
237237
{ code: `function bar() { return E.A; } enum E { A = "foo" }`, identifier: "E" },
238+
{ code: `function setBar() { const bar = { foo }; } let foo = "foo";`, identifier: "foo" },
238239
])("No Hoisting (%p)", ({ code, identifier }) => {
239240
expect(() => util.transpileString(code, { noHoisting: true })).toThrowExactError(
240241
TSTLErrors.ReferencedBeforeDeclaration(ts.createIdentifier(identifier))
@@ -292,3 +293,13 @@ test("Import hoisted before function", () => {
292293
const code = "return bar;";
293294
expect(util.transpileAndExecute(code, undefined, luaHeader, tsHeader)).toBe("foobar");
294295
});
296+
297+
test("Hoisting Shorthand Property", () => {
298+
const code = `
299+
function foo() {
300+
return { bar }.bar;
301+
}
302+
let bar = "foobar";
303+
return foo();`;
304+
expect(util.transpileAndExecute(code)).toBe("foobar");
305+
});

0 commit comments

Comments
 (0)