Skip to content

Commit 2b8303e

Browse files
committed
Make class transform safer, fixes #771
1 parent afb40a5 commit 2b8303e

3 files changed

Lines changed: 47 additions & 18 deletions

File tree

src/transformation/visitors/class/index.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
3-
import { assert, getOrUpdate, isNonNull } from "../../../utils";
3+
import { getOrUpdate, isNonNull } from "../../../utils";
44
import { FunctionVisitor, TransformationContext } from "../../context";
55
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
66
import {
@@ -59,16 +59,17 @@ export function transformClassAsExpression(
5959
return createImmediatelyInvokedFunctionExpression(classDeclaration, className, expression);
6060
}
6161

62-
const classStacks = new WeakMap<TransformationContext, ts.ClassLikeDeclaration[]>();
62+
const classSuperInfos = new WeakMap<TransformationContext, ClassSuperInfo[]>();
63+
interface ClassSuperInfo {
64+
className: lua.Identifier;
65+
extendsTypeNode?: ts.ExpressionWithTypeArguments;
66+
}
6367

6468
export function transformClassDeclaration(
6569
classDeclaration: ts.ClassLikeDeclaration,
6670
context: TransformationContext,
6771
nameOverride?: lua.Identifier
6872
): OneToManyVisitorResult<lua.Statement> {
69-
const classStack = getOrUpdate(classStacks, context, () => []);
70-
classStack.push(classDeclaration);
71-
7273
let className: lua.Identifier;
7374
let classNameText: string;
7475
if (nameOverride !== undefined) {
@@ -108,6 +109,9 @@ export function transformClassDeclaration(
108109
const extendsTypeNode = getExtendedTypeNode(context, classDeclaration);
109110
const extendsType = getExtendedType(context, classDeclaration);
110111

112+
const superInfo = getOrUpdate(classSuperInfos, context, () => []);
113+
superInfo.push({ className, extendsTypeNode });
114+
111115
if (extendsType) {
112116
checkForLuaLibType(context, extendsType);
113117
}
@@ -308,17 +312,19 @@ export function transformClassDeclaration(
308312
result.push(decorationStatement);
309313
}
310314

311-
classStack.pop();
315+
superInfo.pop();
312316

313317
return result;
314318
}
315319

316320
export const transformSuperExpression: FunctionVisitor<ts.SuperExpression> = (expression, context) => {
317-
const classStack = getOrUpdate(classStacks, context, () => []);
318-
const classDeclaration = classStack[classStack.length - 1];
319-
const typeNode = getExtendedTypeNode(context, classDeclaration);
320-
// `undefined` is a TypeScript error
321-
const extendsExpression = typeNode?.expression;
321+
const superInfos = getOrUpdate(classSuperInfos, context, () => []);
322+
const superInfo = superInfos[superInfos.length - 1];
323+
if (!superInfo) return lua.createAnonymousIdentifier(expression);
324+
const { className, extendsTypeNode } = superInfo;
325+
326+
// Using `super` without extended type node is a TypeScript error
327+
const extendsExpression = extendsTypeNode?.expression;
322328
let baseClassName: lua.AssignmentLeftHandSideExpression | undefined;
323329

324330
if (extendsExpression && ts.isIdentifier(extendsExpression)) {
@@ -330,14 +336,8 @@ export const transformSuperExpression: FunctionVisitor<ts.SuperExpression> = (ex
330336
}
331337

332338
if (!baseClassName) {
333-
assert(classDeclaration.name);
334-
335339
// Use "className.____super" if the base is not a simple identifier
336-
baseClassName = lua.createTableIndexExpression(
337-
transformIdentifier(context, classDeclaration.name),
338-
lua.createStringLiteral("____super"),
339-
expression
340-
);
340+
baseClassName = lua.createTableIndexExpression(className, lua.createStringLiteral("____super"), expression);
341341
}
342342

343343
return lua.createTableIndexExpression(baseClassName, lua.createStringLiteral("prototype"));
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`super without class: code 1`] = `
4+
"local ____exports = {}
5+
____exports.__result = ____.____constructor(self)
6+
return ____exports"
7+
`;
8+
9+
exports[`super without class: diagnostics 1`] = `"main.ts(1,25): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors."`;

test/unit/classes/classes.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,26 @@ test("Subclass constructor across merged namespace", () => {
236236
expect(util.transpileAndExecute("return (new NS.Sub()).prop", undefined, undefined, tsHeader)).toBe("foo");
237237
});
238238

239+
test("super without class", () => {
240+
util.testExpression`super()`.expectDiagnosticsToMatchSnapshot();
241+
});
242+
243+
test("super in unnamed class", () => {
244+
util.testFunction`
245+
class Foo {
246+
public x = true;
247+
}
248+
249+
const Bar = (class extends (Foo) {
250+
constructor() {
251+
super();
252+
}
253+
});
254+
255+
return new Bar().x;
256+
`.expectToMatchJsResult();
257+
});
258+
239259
test("classSuper", () => {
240260
const result = util.transpileAndExecute(
241261
`class a {

0 commit comments

Comments
 (0)