From bf676eeaa3cca256a6663f14e7cf477aede25435 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Dec 2019 01:58:42 +0000 Subject: [PATCH 1/9] Generalize class accessors transform --- src/LuaLib.ts | 6 +- src/lualib/Class.ts | 4 +- src/lualib/ClassExtends.ts | 20 ++ src/lualib/ClassIndex.ts | 21 -- src/lualib/ClassNewIndex.ts | 17 -- src/lualib/Descriptors.ts | 72 ++++++ src/lualib/Index.ts | 26 --- src/lualib/NewIndex.ts | 24 -- src/lualib/declarations/tstl.d.ts | 2 + src/transformation/context/context.ts | 8 + src/transformation/visitors/class/index.ts | 25 ++- .../visitors/class/members/accessors.ts | 128 ++++------- .../visitors/class/members/constructor.ts | 2 +- .../visitors/class/members/fields.ts | 20 -- src/transformation/visitors/class/setup.ts | 209 +----------------- 15 files changed, 169 insertions(+), 415 deletions(-) create mode 100644 src/lualib/ClassExtends.ts delete mode 100644 src/lualib/ClassIndex.ts delete mode 100644 src/lualib/ClassNewIndex.ts create mode 100644 src/lualib/Descriptors.ts delete mode 100644 src/lualib/Index.ts delete mode 100644 src/lualib/NewIndex.ts diff --git a/src/LuaLib.ts b/src/LuaLib.ts index efafbab50..a39a1a0d9 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -25,20 +25,18 @@ export enum LuaLibFeature { ArrayFlatMap = "ArrayFlatMap", ArraySetLength = "ArraySetLength", Class = "Class", - ClassIndex = "ClassIndex", - ClassNewIndex = "ClassNewIndex", + ClassExtends = "ClassExtends", Decorate = "Decorate", + Descriptors = "Descriptors", Error = "Error", FunctionApply = "FunctionApply", FunctionBind = "FunctionBind", FunctionCall = "FunctionCall", - Index = "Index", InstanceOf = "InstanceOf", InstanceOfObject = "InstanceOfObject", Iterator = "Iterator", Map = "Map", New = "New", - NewIndex = "NewIndex", Number = "Number", NumberIsFinite = "NumberIsFinite", NumberIsNaN = "NumberIsNaN", diff --git a/src/lualib/Class.ts b/src/lualib/Class.ts index 14aa98587..d5d7af93d 100644 --- a/src/lualib/Class.ts +++ b/src/lualib/Class.ts @@ -1,7 +1,5 @@ function __TS__Class(): LuaClass { - const c = {} as LuaClass; - c.__index = c; - c.prototype = {}; + const c: LuaClass = { prototype: {} }; c.prototype.__index = c.prototype; c.prototype.constructor = c; return c; diff --git a/src/lualib/ClassExtends.ts b/src/lualib/ClassExtends.ts new file mode 100644 index 000000000..125704030 --- /dev/null +++ b/src/lualib/ClassExtends.ts @@ -0,0 +1,20 @@ +function __TS__ClassExtends(this: void, target: LuaClass, base: LuaClass): void { + target.____super = base; + + const staticMetatable: any = setmetatable({ __index: base }, base); + setmetatable(target, staticMetatable); + const baseMetatable = getmetatable(base); + if (baseMetatable) { + if (typeof baseMetatable.__index === "function") { + staticMetatable.__index = baseMetatable.__index; + } + + if (typeof baseMetatable.__newindex === "function") { + staticMetatable.__newindex = baseMetatable.__newindex; + } + } + + setmetatable(target.prototype, base.prototype); + if (typeof base.prototype.__index === "function") target.prototype.__index = base.prototype.__index; + if (typeof base.prototype.__newindex === "function") target.prototype.__newindex = base.prototype.__newindex; +} diff --git a/src/lualib/ClassIndex.ts b/src/lualib/ClassIndex.ts deleted file mode 100644 index 0f753e3bb..000000000 --- a/src/lualib/ClassIndex.ts +++ /dev/null @@ -1,21 +0,0 @@ -function __TS__ClassIndex(this: void, classTable: LuaClass, key: any): any { - while (true) { - const getters = rawget(classTable, "____getters"); - if (getters) { - const getter = getters[key]; - if (getter) { - return getter(classTable); - } - } - - classTable = rawget(classTable, "____super"); - if (!classTable) { - break; - } - - const val = rawget(classTable, key); - if (val !== null) { - return val; - } - } -} diff --git a/src/lualib/ClassNewIndex.ts b/src/lualib/ClassNewIndex.ts deleted file mode 100644 index 99205093c..000000000 --- a/src/lualib/ClassNewIndex.ts +++ /dev/null @@ -1,17 +0,0 @@ -function __TS__ClassNewIndex(this: void, classTable: LuaClass, key: any, val: any): void { - let tbl = classTable; - do { - const setters = rawget(tbl, "____setters"); - if (setters) { - const setter = setters[key]; - if (setter) { - setter(tbl, val); - return; - } - } - - tbl = rawget(tbl, "____super"); - } while (tbl); - - rawset(classTable, key, val); -} diff --git a/src/lualib/Descriptors.ts b/src/lualib/Descriptors.ts new file mode 100644 index 000000000..1a8d91521 --- /dev/null +++ b/src/lualib/Descriptors.ts @@ -0,0 +1,72 @@ +function ____descriptorIndex(this: any, key: string): void { + const value = rawget(this, key); + if (value !== null) { + return value; + } + + let metatable = getmetatable(this); + while (metatable) { + const rawResult = rawget(metatable, key); + if (rawResult !== undefined) { + return rawResult; + } + + const descriptors = rawget(metatable, "_descriptors"); + if (descriptors) { + const descriptor: PropertyDescriptor = descriptors[key]; + if (descriptor) { + if (descriptor.get) { + return descriptor.get.call(this); + } + + return; + } + } + + metatable = getmetatable(metatable); + } +} + +function ____descriptorNewindex(this: any, key: string, value: any): void { + let metatable = getmetatable(this); + while (metatable) { + const descriptors = rawget(metatable, "_descriptors"); + if (descriptors) { + const descriptor: PropertyDescriptor = descriptors[key]; + if (descriptor) { + if (descriptor.set) { + descriptor.set.call(this, value); + } + + return; + } + } + + metatable = getmetatable(metatable); + } + + rawset(this, key, value); +} + +function __TS__SetDescriptor(this: void, metatable: any, prop: string, descriptor: PropertyDescriptor): void { + if (descriptor.get) metatable.__index = ____descriptorIndex; + if (descriptor.set) metatable.__newindex = ____descriptorNewindex; + if (!metatable._descriptors) metatable._descriptors = {}; + metatable._descriptors[prop] = descriptor; +} + +function __TS__ObjectDefineProperty( + this: void, + object: T, + prop: string, + descriptor: PropertyDescriptor +): T { + let metatable = getmetatable(object); + if (!metatable) { + metatable = {}; + setmetatable(object, metatable); + } + + __TS__SetDescriptor(metatable, prop, descriptor); + return object; +} diff --git a/src/lualib/Index.ts b/src/lualib/Index.ts deleted file mode 100644 index ee7c2b9c7..000000000 --- a/src/lualib/Index.ts +++ /dev/null @@ -1,26 +0,0 @@ -function __TS__Index(this: void, classProto: LuaObject): (this: void, tbl: LuaObject, key: any) => any { - return (tbl, key) => { - let proto = classProto; - while (true) { - const val = rawget(proto, key); - if (val !== null) { - return val; - } - - const getters = rawget(proto, "____getters"); - if (getters) { - const getter = getters[key]; - if (getter) { - return getter(tbl); - } - } - - const base = rawget(rawget(proto, "constructor"), "____super"); - if (!base) { - break; - } - - proto = rawget(base, "prototype"); - } - }; -} diff --git a/src/lualib/NewIndex.ts b/src/lualib/NewIndex.ts deleted file mode 100644 index da0d4d12a..000000000 --- a/src/lualib/NewIndex.ts +++ /dev/null @@ -1,24 +0,0 @@ -function __TS__NewIndex(this: void, classProto: LuaObject): (this: void, tbl: LuaObject, key: any, val: any) => void { - return (tbl, key, val) => { - let proto = classProto; - while (true) { - const setters = rawget(proto, "____setters"); - if (setters) { - const setter = setters[key]; - if (setter) { - setter(tbl, val); - return; - } - } - - const base = rawget(rawget(proto, "constructor"), "____super"); - if (!base) { - break; - } - - proto = rawget(base, "prototype"); - } - - rawset(tbl, key, val); - }; -} diff --git a/src/lualib/declarations/tstl.d.ts b/src/lualib/declarations/tstl.d.ts index 9216870f2..5b27c7b0a 100644 --- a/src/lualib/declarations/tstl.d.ts +++ b/src/lualib/declarations/tstl.d.ts @@ -12,6 +12,7 @@ interface LuaClass { ____getters?: { [key: string]: (self: LuaClass) => any }; ____setters?: { [key: string]: (self: LuaClass, val: any) => void }; __index?: any; + __newindex?: any; } interface LuaObject { @@ -19,4 +20,5 @@ interface LuaObject { ____getters?: { [key: string]: (self: LuaObject) => any }; ____setters?: { [key: string]: (self: LuaObject, val: any) => void }; __index?: any; + __newindex?: any; } diff --git a/src/transformation/context/context.ts b/src/transformation/context/context.ts index 07d16f143..42543eafb 100644 --- a/src/transformation/context/context.ts +++ b/src/transformation/context/context.ts @@ -5,11 +5,19 @@ import { unwrapVisitorResult } from "../utils/lua-ast"; import { isFileModule } from "../utils/typescript"; import { ExpressionLikeNode, ObjectVisitor, StatementLikeNode, VisitorMap } from "./visitors"; +export interface AllAccessorDeclarations { + firstAccessor: ts.AccessorDeclaration; + secondAccessor: ts.AccessorDeclaration | undefined; + getAccessor: ts.GetAccessorDeclaration | undefined; + setAccessor: ts.SetAccessorDeclaration | undefined; +} + export interface EmitResolver { isValueAliasDeclaration(node: ts.Node): boolean; isReferencedAliasDeclaration(node: ts.Node, checkChildren?: boolean): boolean; isTopLevelValueImportEqualsWithEntityName(node: ts.ImportEqualsDeclaration): boolean; moduleExportsSomeValue(moduleReferenceExpression: ts.Expression): boolean; + getAllAccessorDeclarations(declaration: ts.AccessorDeclaration): AllAccessorDeclarations; } export interface DiagnosticsProducingTypeChecker extends ts.TypeChecker { diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index e9c8c108c..c807812d8 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -32,7 +32,7 @@ import { isAmbientNode } from "../../utils/typescript"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; import { createConstructorDecorationStatement } from "./decorators"; -import { isGetAccessorOverride, transformAccessorDeclaration } from "./members/accessors"; +import { transformAccessorDeclarations } from "./members/accessors"; import { createConstructorName, transformConstructorDeclaration } from "./members/constructor"; import { transformClassInstanceFields } from "./members/fields"; import { transformMethodDeclaration } from "./members/method"; @@ -239,15 +239,12 @@ export function transformClassDeclaration( ); if (constructorResult) result.push(constructorResult); - } else if ( - instanceFields.length > 0 || - classDeclaration.members.some(m => isGetAccessorOverride(context, m, classDeclaration)) - ) { + } else if (instanceFields.length > 0) { // Generate a constructor if none was defined in a class with instance fields that need initialization // localClassName.prototype.____constructor = function(self, ...) // baseClassName.prototype.____constructor(self, ...) // ... - const constructorBody = transformClassInstanceFields(context, classDeclaration, instanceFields); + const constructorBody = transformClassInstanceFields(context, instanceFields); const superCall = lua.createExpressionStatement( lua.createCallExpression( lua.createTableIndexExpression( @@ -276,12 +273,16 @@ export function transformClassDeclaration( } // Transform accessors - result.push( - ...classDeclaration.members - .filter(ts.isAccessor) - .map(accessor => transformAccessorDeclaration(context, accessor, localClassName)) - .filter(isNonNull) - ); + for (const member of classDeclaration.members) { + if (!ts.isAccessor(member)) continue; + const accessors = context.resolver.getAllAccessorDeclarations(member); + if (accessors.firstAccessor !== member) continue; + + const accessorsResult = transformAccessorDeclarations(context, accessors, localClassName); + if (accessorsResult) { + result.push(accessorsResult); + } + } // Transform methods result.push( diff --git a/src/transformation/visitors/class/members/accessors.ts b/src/transformation/visitors/class/members/accessors.ts index 2da5f0f70..64241bc24 100644 --- a/src/transformation/visitors/class/members/accessors.ts +++ b/src/transformation/visitors/class/members/accessors.ts @@ -1,104 +1,54 @@ import * as ts from "typescript"; import * as lua from "../../../../LuaAST"; -import { TransformationContext } from "../../../context"; +import { AllAccessorDeclarations, TransformationContext } from "../../../context"; import { createSelfIdentifier } from "../../../utils/lua-ast"; +import { importLuaLibFeature, LuaLibFeature } from "../../../utils/lualib"; import { transformFunctionBody, transformParameters } from "../../function"; -import { transformIdentifier } from "../../identifier"; -import { getExtendedType, isStaticNode } from "../utils"; - -// TODO: Inline to `hasMemberInClassOrAncestor`? -function* classWithAncestors( - context: TransformationContext, - classDeclaration: ts.ClassLikeDeclarationBase -): Generator { - yield classDeclaration; - - const extendsType = getExtendedType(context, classDeclaration); - if (!extendsType) { - return false; - } - - const symbol = extendsType.getSymbol(); - if (symbol === undefined) { - return false; - } - - const symbolDeclarations = symbol.getDeclarations(); - if (symbolDeclarations === undefined) { - return false; - } - - const declaration = symbolDeclarations.find(ts.isClassLike); - if (!declaration) { - return false; - } - - yield* classWithAncestors(context, declaration); -} - -export const hasMemberInClassOrAncestor = ( - context: TransformationContext, - classDeclaration: ts.ClassLikeDeclarationBase, - callback: (m: ts.ClassElement) => boolean -) => [...classWithAncestors(context, classDeclaration)].some(c => c.members.some(callback)); - -function getPropertyName(propertyName: ts.PropertyName): string | number | undefined { - if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName) || ts.isNumericLiteral(propertyName)) { - return propertyName.text; - } else { - return undefined; // TODO: how to handle computed property names? - } -} - -function isSamePropertyName(a: ts.PropertyName, b: ts.PropertyName): boolean { - const aName = getPropertyName(a); - const bName = getPropertyName(b); - return aName !== undefined && aName === bName; -} - -export function isGetAccessorOverride( - context: TransformationContext, - element: ts.ClassElement, - classDeclaration: ts.ClassLikeDeclarationBase -): element is ts.GetAccessorDeclaration { - if (!ts.isGetAccessor(element) || isStaticNode(element)) { - return false; - } - - return hasMemberInClassOrAncestor( - context, - classDeclaration, - m => ts.isPropertyDeclaration(m) && m.initializer !== undefined && isSamePropertyName(m.name, element.name) - ); -} - -export function transformAccessorDeclaration( - context: TransformationContext, - node: ts.AccessorDeclaration, - className: lua.Identifier -): lua.Statement | undefined { - if (node.body === undefined) { - return undefined; - } - - const name = transformIdentifier(context, node.name as ts.Identifier); +import { transformPropertyName } from "../../literal"; +import { isStaticNode } from "../utils"; +function transformAccessor(context: TransformationContext, node: ts.AccessorDeclaration): lua.FunctionExpression { const [params, dot, restParam] = transformParameters(context, node.parameters, createSelfIdentifier()); - const [body] = transformFunctionBody(context, node.parameters, node.body, restParam); - const accessorFunction = lua.createFunctionExpression( + const body = node.body ? transformFunctionBody(context, node.parameters, node.body, restParam)[0] : []; + return lua.createFunctionExpression( lua.createBlock(body), params, dot, restParam, lua.FunctionExpressionFlags.Declaration ); +} - const methodTable = isStaticNode(node) - ? lua.cloneIdentifier(className) - : lua.createTableIndexExpression(lua.cloneIdentifier(className), lua.createStringLiteral("prototype")); +export function transformAccessorDeclarations( + context: TransformationContext, + { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations, + className: lua.Identifier +): lua.Statement | undefined { + const propertyName = transformPropertyName(context, firstAccessor.name); + const descriptor = lua.createTableExpression([]); + + if (getAccessor) { + const getterFunction = transformAccessor(context, getAccessor); + descriptor.fields.push(lua.createTableFieldExpression(getterFunction, lua.createStringLiteral("get"))); + } + + if (setAccessor) { + const setterFunction = transformAccessor(context, setAccessor); + descriptor.fields.push(lua.createTableFieldExpression(setterFunction, lua.createStringLiteral("set"))); + } - const classAccessorsName = ts.isGetAccessorDeclaration(node) ? "____getters" : "____setters"; - const classAccessors = lua.createTableIndexExpression(methodTable, lua.createStringLiteral(classAccessorsName)); - const accessorPath = lua.createTableIndexExpression(classAccessors, lua.createStringLiteral(name.text)); - return lua.createAssignmentStatement(accessorPath, accessorFunction, node); + importLuaLibFeature(context, LuaLibFeature.Descriptors); + const call = isStaticNode(firstAccessor) + ? lua.createCallExpression(lua.createIdentifier(`__TS__ObjectDefineProperty`), [ + lua.cloneIdentifier(className), + propertyName, + descriptor, + ]) + : lua.createCallExpression(lua.createIdentifier(`__TS__SetDescriptor`), [ + lua.createTableIndexExpression(lua.cloneIdentifier(className), lua.createStringLiteral("prototype")), + propertyName, + descriptor, + ]); + + return lua.createExpressionStatement(call); } diff --git a/src/transformation/visitors/class/members/constructor.ts b/src/transformation/visitors/class/members/constructor.ts index adefb1143..271e02a5f 100644 --- a/src/transformation/visitors/class/members/constructor.ts +++ b/src/transformation/visitors/class/members/constructor.ts @@ -40,7 +40,7 @@ export function transformConstructorDeclaration( // Check for field declarations in constructor const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined); - const classInstanceFields = transformClassInstanceFields(context, classDeclaration, instanceFields); + const classInstanceFields = transformClassInstanceFields(context, instanceFields); // If there are field initializers and the first statement is a super call, // move super call between default assignments and initializers diff --git a/src/transformation/visitors/class/members/fields.ts b/src/transformation/visitors/class/members/fields.ts index 87b91e235..b825030a7 100644 --- a/src/transformation/visitors/class/members/fields.ts +++ b/src/transformation/visitors/class/members/fields.ts @@ -3,11 +3,9 @@ import * as lua from "../../../../LuaAST"; import { TransformationContext } from "../../../context"; import { createSelfIdentifier } from "../../../utils/lua-ast"; import { transformPropertyName } from "../../literal"; -import { isGetAccessorOverride } from "./accessors"; export function transformClassInstanceFields( context: TransformationContext, - classDeclaration: ts.ClassLikeDeclaration, instanceFields: ts.PropertyDeclaration[] ): lua.Statement[] { const statements: lua.Statement[] = []; @@ -27,23 +25,5 @@ export function transformClassInstanceFields( statements.push(assignClassField); } - const getOverrides = classDeclaration.members.filter((m): m is ts.GetAccessorDeclaration => - isGetAccessorOverride(context, m, classDeclaration) - ); - - for (const getter of getOverrides) { - const getterName = transformPropertyName(context, getter.name); - - const resetGetter = lua.createExpressionStatement( - lua.createCallExpression(lua.createIdentifier("rawset"), [ - createSelfIdentifier(), - getterName, - lua.createNilLiteral(), - ]), - classDeclaration.members.find(ts.isConstructorDeclaration) ?? classDeclaration - ); - statements.push(resetGetter); - } - return statements; } diff --git a/src/transformation/visitors/class/setup.ts b/src/transformation/visitors/class/setup.ts index 2a4a1c8d0..858a9aa5c 100644 --- a/src/transformation/visitors/class/setup.ts +++ b/src/transformation/visitors/class/setup.ts @@ -9,9 +9,8 @@ import { hasDefaultExportModifier, } from "../../utils/export"; import { createExportsIdentifier, createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; -import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; -import { hasMemberInClassOrAncestor } from "./members/accessors"; -import { getExtendedTypeNode, isStaticNode } from "./utils"; +import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; +import { getExtendedTypeNode } from "./utils"; export function createClassSetup( context: TransformationContext, @@ -62,209 +61,23 @@ export function createClassSetup( ) ); - // localClassName.____getters = {} - if (statement.members.some(m => ts.isGetAccessor(m) && isStaticNode(m))) { - const classGetters = lua.createTableIndexExpression( - lua.cloneIdentifier(localClassName), - lua.createStringLiteral("____getters") - ); - const assignClassGetters = lua.createAssignmentStatement(classGetters, lua.createTableExpression(), statement); - result.push(assignClassGetters); - - importLuaLibFeature(context, LuaLibFeature.ClassIndex); - } - - // localClassName.____setters = {} - if (statement.members.some(m => ts.isSetAccessor(m) && isStaticNode(m))) { - const classSetters = lua.createTableIndexExpression( - lua.cloneIdentifier(localClassName), - lua.createStringLiteral("____setters") - ); - const assignClassSetters = lua.createAssignmentStatement(classSetters, lua.createTableExpression(), statement); - result.push(assignClassSetters); - - importLuaLibFeature(context, LuaLibFeature.ClassNewIndex); - } - - // localClassName.prototype - const createClassPrototype = () => - lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("prototype")); - - // localClassName.prototype.____getters = {} - if (statement.members.some(m => ts.isGetAccessor(m) && !isStaticNode(m))) { - const classPrototypeGetters = lua.createTableIndexExpression( - createClassPrototype(), - lua.createStringLiteral("____getters") - ); - const assignClassPrototypeGetters = lua.createAssignmentStatement( - classPrototypeGetters, - lua.createTableExpression(), - statement - ); - result.push(assignClassPrototypeGetters); - } - - if (hasMemberInClassOrAncestor(context, statement, m => ts.isGetAccessor(m) && !isStaticNode(m))) { - // localClassName.prototype.__index = __TS__Index(localClassName.prototype) - const classPrototypeIndex = lua.createTableIndexExpression( - createClassPrototype(), - lua.createStringLiteral("__index") - ); - const assignClassPrototypeIndex = lua.createAssignmentStatement( - classPrototypeIndex, - transformLuaLibFunction(context, LuaLibFeature.Index, undefined, createClassPrototype()), - statement - ); - result.push(assignClassPrototypeIndex); - } - - if (statement.members.some(m => ts.isSetAccessor(m) && !isStaticNode(m))) { - // localClassName.prototype.____setters = {} - const classPrototypeSetters = lua.createTableIndexExpression( - createClassPrototype(), - lua.createStringLiteral("____setters") - ); - const assignClassPrototypeSetters = lua.createAssignmentStatement( - classPrototypeSetters, - lua.createTableExpression(), - statement - ); - result.push(assignClassPrototypeSetters); - } - - if (hasMemberInClassOrAncestor(context, statement, m => ts.isSetAccessor(m) && !isStaticNode(m))) { - // localClassName.prototype.__newindex = __TS__NewIndex(localClassName.prototype) - const classPrototypeNewIndex = lua.createTableIndexExpression( - createClassPrototype(), - lua.createStringLiteral("__newindex") - ); - const assignClassPrototypeIndex = lua.createAssignmentStatement( - classPrototypeNewIndex, - transformLuaLibFunction(context, LuaLibFeature.NewIndex, undefined, createClassPrototype()), - statement - ); - result.push(assignClassPrototypeIndex); - } - - const hasStaticGetters = hasMemberInClassOrAncestor( - context, - statement, - m => ts.isGetAccessor(m) && isStaticNode(m) - ); - const hasStaticSetters = hasMemberInClassOrAncestor( - context, - statement, - m => ts.isSetAccessor(m) && isStaticNode(m) - ); - if (extendsType) { const extendedTypeNode = getExtendedTypeNode(context, statement); if (extendedTypeNode === undefined) { throw UndefinedTypeNode(statement); } - // localClassName.____super = extendsExpression - const createClassBase = () => - lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("____super")); - const assignClassBase = lua.createAssignmentStatement( - createClassBase(), - context.transformExpression(extendedTypeNode.expression), - extendedTypeNode.expression - ); - result.push(assignClassBase); - - if (hasStaticGetters || hasStaticSetters) { - const metatableFields: lua.TableFieldExpression[] = []; - if (hasStaticGetters) { - // __index = __TS__ClassIndex - metatableFields.push( - lua.createTableFieldExpression( - lua.createIdentifier("__TS__ClassIndex"), - lua.createStringLiteral("__index"), - extendedTypeNode.expression - ) - ); - } else { - // __index = localClassName.____super - metatableFields.push( - lua.createTableFieldExpression( - createClassBase(), - lua.createStringLiteral("__index"), - extendedTypeNode.expression - ) - ); - } - - if (hasStaticSetters) { - // __newindex = __TS__ClassNewIndex - metatableFields.push( - lua.createTableFieldExpression( - lua.createIdentifier("__TS__ClassNewIndex"), - lua.createStringLiteral("__newindex"), - extendedTypeNode.expression - ) - ); - } - - const setClassMetatable = lua.createExpressionStatement( - lua.createCallExpression( - lua.createIdentifier("setmetatable"), - [lua.cloneIdentifier(localClassName), lua.createTableExpression(metatableFields)], - extendedTypeNode.expression + result.push( + lua.createExpressionStatement( + transformLuaLibFunction( + context, + LuaLibFeature.ClassExtends, + statement, + lua.cloneIdentifier(localClassName), + context.transformExpression(extendedTypeNode.expression) ) - ); - result.push(setClassMetatable); - } else { - // setmetatable(localClassName, localClassName.____super) - const setClassMetatable = lua.createExpressionStatement( - lua.createCallExpression( - lua.createIdentifier("setmetatable"), - [lua.cloneIdentifier(localClassName), createClassBase()], - extendedTypeNode.expression - ) - ); - result.push(setClassMetatable); - } - - // setmetatable(localClassName.prototype, localClassName.____super.prototype) - const basePrototype = lua.createTableIndexExpression(createClassBase(), lua.createStringLiteral("prototype")); - const setClassPrototypeMetatable = lua.createExpressionStatement( - lua.createCallExpression(lua.createIdentifier("setmetatable"), [createClassPrototype(), basePrototype]), - extendedTypeNode.expression - ); - result.push(setClassPrototypeMetatable); - } else if (hasStaticGetters || hasStaticSetters) { - const metatableFields: lua.TableFieldExpression[] = []; - if (hasStaticGetters) { - // __index = __TS__ClassIndex - metatableFields.push( - lua.createTableFieldExpression( - lua.createIdentifier("__TS__ClassIndex"), - lua.createStringLiteral("__index"), - statement - ) - ); - } - - if (hasStaticSetters) { - // __newindex = __TS__ClassNewIndex - metatableFields.push( - lua.createTableFieldExpression( - lua.createIdentifier("__TS__ClassNewIndex"), - lua.createStringLiteral("__newindex"), - statement - ) - ); - } - - const setClassMetatable = lua.createExpressionStatement( - lua.createCallExpression(lua.createIdentifier("setmetatable"), [ - lua.cloneIdentifier(localClassName), - lua.createTableExpression(metatableFields), - ]), - statement + ) ); - result.push(setClassMetatable); } return result; From 27b931ab43a912fe425b380da8541f9e53f5afe1 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Dec 2019 04:39:02 +0000 Subject: [PATCH 2/9] Remove `rawset` from the list of used builtins --- src/transformation/utils/safe-names.ts | 1 - test/unit/identifiers.spec.ts | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/src/transformation/utils/safe-names.ts b/src/transformation/utils/safe-names.ts index 9969bd561..4353dfda8 100644 --- a/src/transformation/utils/safe-names.ts +++ b/src/transformation/utils/safe-names.ts @@ -40,7 +40,6 @@ export const luaBuiltins: ReadonlySet = new Set([ "pcall", "print", "rawget", - "rawset", "repeat", "require", "self", diff --git a/test/unit/identifiers.spec.ts b/test/unit/identifiers.spec.ts index 88c5cc23b..8b802abb6 100644 --- a/test/unit/identifiers.spec.ts +++ b/test/unit/identifiers.spec.ts @@ -453,21 +453,6 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => { expect(util.transpileAndExecute(code)).toBe(true); }); - test("variable (rawset)", () => { - const code = ` - const rawset = "foobar"; - class A { - prop = "prop"; - } - class B extends A { - get prop() { return rawset; } - } - const b = new B(); - return b.prop;`; - - expect(util.transpileAndExecute(code)).toBe("foobar"); - }); - test("variable (require)", () => { const code = ` const require = "foobar"; From 5921a8d0ab24223934391099ad498aa191e2194d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 19 Dec 2019 04:55:25 +0000 Subject: [PATCH 3/9] Fix sourcemaps --- src/transformation/visitors/class/index.ts | 34 +++++++++++----------- src/transformation/visitors/class/setup.ts | 10 +++---- src/transformation/visitors/class/utils.ts | 27 ++++++++--------- test/unit/printer/sourcemaps.spec.ts | 5 ++-- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index c807812d8..c609d5c4e 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -38,7 +38,7 @@ import { transformClassInstanceFields } from "./members/fields"; import { transformMethodDeclaration } from "./members/method"; import { checkForLuaLibType } from "./new"; import { createClassSetup } from "./setup"; -import { getExtendedType, getExtendedTypeNode, isStaticNode } from "./utils"; +import { getExtendedType, getExtendedNode, isStaticNode } from "./utils"; export function transformClassAsExpression( expression: ts.ClassLikeDeclaration, @@ -105,23 +105,23 @@ export function transformClassDeclaration( } // Get type that is extended - const extendsType = getExtendedType(context, classDeclaration); + const extendedType = getExtendedType(context, classDeclaration); - if (extendsType) { - checkForLuaLibType(context, extendsType); + if (extendedType) { + checkForLuaLibType(context, extendedType); } - if (!(isExtension || isMetaExtension) && extendsType) { + if (!(isExtension || isMetaExtension) && extendedType) { // Non-extensions cannot extend extension classes - const extendsAnnotations = getTypeAnnotations(context, extendsType); + const extendsAnnotations = getTypeAnnotations(context, extendedType); if (extendsAnnotations.has(AnnotationKind.Extension) || extendsAnnotations.has(AnnotationKind.MetaExtension)) { throw InvalidExtendsExtension(classDeclaration); } } // You cannot extend LuaTable classes - if (extendsType) { - const annotations = getTypeAnnotations(context, extendsType); + if (extendedType) { + const annotations = getTypeAnnotations(context, extendedType); if (annotations.has(AnnotationKind.LuaTable)) { throw InvalidExtendsLuaTable(classDeclaration); } @@ -143,11 +143,11 @@ export function transformClassDeclaration( // Overwrite the original className with the class we are overriding for extensions if (isMetaExtension) { - if (!extendsType) { + if (!extendedType) { throw MissingMetaExtension(classDeclaration); } - const extendsName = lua.createStringLiteral(extendsType.symbol.name as string); + const extendsName = lua.createStringLiteral(extendedType.symbol.name as string); className = lua.createIdentifier("__meta__" + extendsName.value); // local className = debug.getregistry()["extendsName"] @@ -173,8 +173,8 @@ export function transformClassDeclaration( const [extensionName] = extensionDirective.args; if (extensionName) { className = lua.createIdentifier(extensionName); - } else if (extendsType) { - className = lua.createIdentifier(extendsType.symbol.name); + } else if (extendedType) { + className = lua.createIdentifier(extendedType.symbol.name); } } @@ -193,7 +193,7 @@ export function transformClassDeclaration( if (!isExtension && !isMetaExtension) { result.push( - ...createClassSetup(context, classDeclaration, className, localClassName, classNameText, extendsType) + ...createClassSetup(context, classDeclaration, className, localClassName, classNameText, extendedType) ); } else { for (const f of instanceFields) { @@ -228,7 +228,7 @@ export function transformClassDeclaration( ); if (constructorResult) result.push(constructorResult); - } else if (!extendsType) { + } else if (!extendedType) { // Generate a constructor if none was defined in a base class const constructorResult = transformConstructorDeclaration( context, @@ -317,12 +317,12 @@ export function transformClassDeclaration( export const transformSuperExpression: FunctionVisitor = (expression, context) => { const classStack = getOrUpdate(classStacks, context, () => []); const classDeclaration = classStack[classStack.length - 1]; - const typeNode = getExtendedTypeNode(context, classDeclaration); - if (typeNode === undefined) { + const extendedNode = getExtendedNode(context, classDeclaration); + if (extendedNode === undefined) { throw UnknownSuperType(expression); } - const extendsExpression = typeNode.expression; + const extendsExpression = extendedNode.expression; let baseClassName: lua.AssignmentLeftHandSideExpression | undefined; if (ts.isIdentifier(extendsExpression)) { diff --git a/src/transformation/visitors/class/setup.ts b/src/transformation/visitors/class/setup.ts index 858a9aa5c..3aec046a3 100644 --- a/src/transformation/visitors/class/setup.ts +++ b/src/transformation/visitors/class/setup.ts @@ -10,7 +10,7 @@ import { } from "../../utils/export"; import { createExportsIdentifier, createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; -import { getExtendedTypeNode } from "./utils"; +import { getExtendedNode, getExtendsClause } from "./utils"; export function createClassSetup( context: TransformationContext, @@ -62,8 +62,8 @@ export function createClassSetup( ); if (extendsType) { - const extendedTypeNode = getExtendedTypeNode(context, statement); - if (extendedTypeNode === undefined) { + const extendedNode = getExtendedNode(context, statement); + if (extendedNode === undefined) { throw UndefinedTypeNode(statement); } @@ -72,9 +72,9 @@ export function createClassSetup( transformLuaLibFunction( context, LuaLibFeature.ClassExtends, - statement, + getExtendsClause(statement), lua.cloneIdentifier(localClassName), - context.transformExpression(extendedTypeNode.expression) + context.transformExpression(extendedNode.expression) ) ) ); diff --git a/src/transformation/visitors/class/utils.ts b/src/transformation/visitors/class/utils.ts index b75d1855b..18da8fcf8 100644 --- a/src/transformation/visitors/class/utils.ts +++ b/src/transformation/visitors/class/utils.ts @@ -6,20 +6,21 @@ export function isStaticNode(node: ts.Node): boolean { return (node.modifiers ?? []).some(m => m.kind === ts.SyntaxKind.StaticKeyword); } -export function getExtendedTypeNode( +export function getExtendsClause(node: ts.ClassLikeDeclarationBase): ts.HeritageClause | undefined { + return (node.heritageClauses ?? []).find(clause => clause.token === ts.SyntaxKind.ExtendsKeyword); +} + +export function getExtendedNode( context: TransformationContext, node: ts.ClassLikeDeclarationBase ): ts.ExpressionWithTypeArguments | undefined { - if (node && node.heritageClauses) { - for (const clause of node.heritageClauses) { - if (clause.token === ts.SyntaxKind.ExtendsKeyword) { - const superType = context.checker.getTypeAtLocation(clause.types[0]); - const annotations = getTypeAnnotations(context, superType); - if (!annotations.has(AnnotationKind.PureAbstract)) { - return clause.types[0]; - } - } - } + const extendsClause = getExtendsClause(node); + if (!extendsClause) return; + + const superType = context.checker.getTypeAtLocation(extendsClause.types[0]); + const annotations = getTypeAnnotations(context, superType); + if (!annotations.has(AnnotationKind.PureAbstract)) { + return extendsClause.types[0]; } } @@ -27,6 +28,6 @@ export function getExtendedType( context: TransformationContext, node: ts.ClassLikeDeclarationBase ): ts.Type | undefined { - const extendedTypeNode = getExtendedTypeNode(context, node); - return extendedTypeNode && context.checker.getTypeAtLocation(extendedTypeNode); + const extendedNode = getExtendedNode(context, node); + return extendedNode && context.checker.getTypeAtLocation(extendedNode); } diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index 918d430d1..94669aab1 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -86,9 +86,8 @@ test.each([ assertPatterns: [ { luaPattern: "Bar = __TS__Class()", typeScriptPattern: "class Bar" }, { luaPattern: "Bar.name =", typeScriptPattern: "class Bar" }, - { luaPattern: "Bar.____super = Foo", typeScriptPattern: "Foo {" }, - { luaPattern: "setmetatable(Bar,", typeScriptPattern: "Foo {" }, - { luaPattern: "setmetatable(Bar.prototype,", typeScriptPattern: "Foo {" }, + { luaPattern: "__TS__ClassExtends(", typeScriptPattern: "extends" }, + { luaPattern: "Foo)", typeScriptPattern: "Foo {" }, { luaPattern: "function Bar.prototype.____constructor", typeScriptPattern: "constructor" }, ], }, From f67725808a6fbad045eb94e011ab66e71e57665d Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 20 Jan 2020 10:43:42 +0000 Subject: [PATCH 4/9] Improve class extends test case --- test/unit/printer/sourcemaps.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index 94669aab1..143936a91 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -76,7 +76,7 @@ test.each([ { code: ` // @ts-ignore - class Bar extends Foo { + class Bar extends Foo() { constructor() { super(); } @@ -86,8 +86,8 @@ test.each([ assertPatterns: [ { luaPattern: "Bar = __TS__Class()", typeScriptPattern: "class Bar" }, { luaPattern: "Bar.name =", typeScriptPattern: "class Bar" }, - { luaPattern: "__TS__ClassExtends(", typeScriptPattern: "extends" }, - { luaPattern: "Foo)", typeScriptPattern: "Foo {" }, + { luaPattern: "__TS__ClassExtends", typeScriptPattern: "extends" }, + { luaPattern: "Foo(", typeScriptPattern: "Foo(" }, { luaPattern: "function Bar.prototype.____constructor", typeScriptPattern: "constructor" }, ], }, From 77bf0315acf340a6f6e5936de0aed05b53250698 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 20 Jan 2020 10:52:43 +0000 Subject: [PATCH 5/9] Improve lualib types --- src/lualib/Descriptors.ts | 7 ++++--- src/lualib/InstanceOf.ts | 2 +- src/lualib/declarations/tstl.d.ts | 19 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lualib/Descriptors.ts b/src/lualib/Descriptors.ts index 1a8d91521..8df17fc00 100644 --- a/src/lualib/Descriptors.ts +++ b/src/lualib/Descriptors.ts @@ -48,11 +48,12 @@ function ____descriptorNewindex(this: any, key: string, value: any): void { rawset(this, key, value); } -function __TS__SetDescriptor(this: void, metatable: any, prop: string, descriptor: PropertyDescriptor): void { - if (descriptor.get) metatable.__index = ____descriptorIndex; - if (descriptor.set) metatable.__newindex = ____descriptorNewindex; +function __TS__SetDescriptor(this: void, metatable: Metatable, prop: string, descriptor: PropertyDescriptor): void { if (!metatable._descriptors) metatable._descriptors = {}; metatable._descriptors[prop] = descriptor; + + if (descriptor.get) metatable.__index = ____descriptorIndex; + if (descriptor.set) metatable.__newindex = ____descriptorNewindex; } function __TS__ObjectDefineProperty( diff --git a/src/lualib/InstanceOf.ts b/src/lualib/InstanceOf.ts index b9b47f707..b856ddaa9 100644 --- a/src/lualib/InstanceOf.ts +++ b/src/lualib/InstanceOf.ts @@ -1,4 +1,4 @@ -function __TS__InstanceOf(this: void, obj: LuaObject, classTbl: LuaClass): boolean { +function __TS__InstanceOf(this: void, obj: LuaClassInstance, classTbl: LuaClass): boolean { if (typeof classTbl !== "object") { // tslint:disable-next-line: no-string-throw throw "Right-hand side of 'instanceof' is not an object"; diff --git a/src/lualib/declarations/tstl.d.ts b/src/lualib/declarations/tstl.d.ts index 5b27c7b0a..11c34c809 100644 --- a/src/lualib/declarations/tstl.d.ts +++ b/src/lualib/declarations/tstl.d.ts @@ -6,19 +6,18 @@ interface Vararg extends Array {} /** @forRange */ declare function forRange(start: number, limit: number, step?: number): number[]; -interface LuaClass { - prototype: LuaObject; - ____super?: LuaClass; - ____getters?: { [key: string]: (self: LuaClass) => any }; - ____setters?: { [key: string]: (self: LuaClass, val: any) => void }; +interface Metatable { + _descriptors?: Record; __index?: any; __newindex?: any; } -interface LuaObject { +interface LuaClass extends Metatable { + prototype: LuaClassInstance; + [Symbol.hasInstance]?(instance: LuaClassInstance): any; + ____super?: LuaClass; +} + +interface LuaClassInstance extends Metatable { constructor: LuaClass; - ____getters?: { [key: string]: (self: LuaObject) => any }; - ____setters?: { [key: string]: (self: LuaObject, val: any) => void }; - __index?: any; - __newindex?: any; } From 1851195adf479f80f05ed119ece1c67bcb7766c0 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 20 Jan 2020 11:04:39 +0000 Subject: [PATCH 6/9] Add few comments to lualib --- src/lualib/ClassExtends.ts | 13 ++++++------- src/lualib/Descriptors.ts | 2 ++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lualib/ClassExtends.ts b/src/lualib/ClassExtends.ts index 125704030..e297cba93 100644 --- a/src/lualib/ClassExtends.ts +++ b/src/lualib/ClassExtends.ts @@ -1,20 +1,19 @@ function __TS__ClassExtends(this: void, target: LuaClass, base: LuaClass): void { target.____super = base; + // Set base class as a metatable, because descriptors use `getmetatable` to get extended prototype const staticMetatable: any = setmetatable({ __index: base }, base); setmetatable(target, staticMetatable); + const baseMetatable = getmetatable(base); if (baseMetatable) { - if (typeof baseMetatable.__index === "function") { - staticMetatable.__index = baseMetatable.__index; - } - - if (typeof baseMetatable.__newindex === "function") { - staticMetatable.__newindex = baseMetatable.__newindex; - } + // Re-add metatable events defined by descriptors + if (typeof baseMetatable.__index === "function") staticMetatable.__index = baseMetatable.__index; + if (typeof baseMetatable.__newindex === "function") staticMetatable.__newindex = baseMetatable.__newindex; } setmetatable(target.prototype, base.prototype); + // Re-add metatable events defined by accessors with `__TS__SetDescriptor` if (typeof base.prototype.__index === "function") target.prototype.__index = base.prototype.__index; if (typeof base.prototype.__newindex === "function") target.prototype.__newindex = base.prototype.__newindex; } diff --git a/src/lualib/Descriptors.ts b/src/lualib/Descriptors.ts index 8df17fc00..eb5198408 100644 --- a/src/lualib/Descriptors.ts +++ b/src/lualib/Descriptors.ts @@ -48,6 +48,7 @@ function ____descriptorNewindex(this: any, key: string, value: any): void { rawset(this, key, value); } +// It's also used directly in class transform to add descriptors to the prototype function __TS__SetDescriptor(this: void, metatable: Metatable, prop: string, descriptor: PropertyDescriptor): void { if (!metatable._descriptors) metatable._descriptors = {}; metatable._descriptors[prop] = descriptor; @@ -56,6 +57,7 @@ function __TS__SetDescriptor(this: void, metatable: Metatable, prop: string, des if (descriptor.set) metatable.__newindex = ____descriptorNewindex; } +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty function __TS__ObjectDefineProperty( this: void, object: T, From 3b81007525945c1bd2abb0966230954142462e55 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Mon, 20 Jan 2020 11:50:36 +0000 Subject: [PATCH 7/9] Temporary revert getter property override removal --- src/transformation/visitors/class/index.ts | 11 +-- .../visitors/class/members/accessors.ts | 67 ++++++++++++++++++- .../visitors/class/members/constructor.ts | 2 +- .../visitors/class/members/fields.ts | 22 ++++++ 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/transformation/visitors/class/index.ts b/src/transformation/visitors/class/index.ts index c609d5c4e..37c87c425 100644 --- a/src/transformation/visitors/class/index.ts +++ b/src/transformation/visitors/class/index.ts @@ -32,13 +32,13 @@ import { isAmbientNode } from "../../utils/typescript"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; import { createConstructorDecorationStatement } from "./decorators"; -import { transformAccessorDeclarations } from "./members/accessors"; +import { isGetAccessorOverride, transformAccessorDeclarations } from "./members/accessors"; import { createConstructorName, transformConstructorDeclaration } from "./members/constructor"; import { transformClassInstanceFields } from "./members/fields"; import { transformMethodDeclaration } from "./members/method"; import { checkForLuaLibType } from "./new"; import { createClassSetup } from "./setup"; -import { getExtendedType, getExtendedNode, isStaticNode } from "./utils"; +import { getExtendedNode, getExtendedType, isStaticNode } from "./utils"; export function transformClassAsExpression( expression: ts.ClassLikeDeclaration, @@ -239,12 +239,15 @@ export function transformClassDeclaration( ); if (constructorResult) result.push(constructorResult); - } else if (instanceFields.length > 0) { + } else if ( + instanceFields.length > 0 || + classDeclaration.members.some(m => isGetAccessorOverride(context, m, classDeclaration)) + ) { // Generate a constructor if none was defined in a class with instance fields that need initialization // localClassName.prototype.____constructor = function(self, ...) // baseClassName.prototype.____constructor(self, ...) // ... - const constructorBody = transformClassInstanceFields(context, instanceFields); + const constructorBody = transformClassInstanceFields(context, classDeclaration, instanceFields); const superCall = lua.createExpressionStatement( lua.createCallExpression( lua.createTableIndexExpression( diff --git a/src/transformation/visitors/class/members/accessors.ts b/src/transformation/visitors/class/members/accessors.ts index 64241bc24..50cc8a10f 100644 --- a/src/transformation/visitors/class/members/accessors.ts +++ b/src/transformation/visitors/class/members/accessors.ts @@ -5,7 +5,7 @@ import { createSelfIdentifier } from "../../../utils/lua-ast"; import { importLuaLibFeature, LuaLibFeature } from "../../../utils/lualib"; import { transformFunctionBody, transformParameters } from "../../function"; import { transformPropertyName } from "../../literal"; -import { isStaticNode } from "../utils"; +import { getExtendedType, isStaticNode } from "../utils"; function transformAccessor(context: TransformationContext, node: ts.AccessorDeclaration): lua.FunctionExpression { const [params, dot, restParam] = transformParameters(context, node.parameters, createSelfIdentifier()); @@ -52,3 +52,68 @@ export function transformAccessorDeclarations( return lua.createExpressionStatement(call); } + +function* classWithAncestors( + context: TransformationContext, + classDeclaration: ts.ClassLikeDeclarationBase +): Generator { + yield classDeclaration; + + const extendsType = getExtendedType(context, classDeclaration); + if (!extendsType) { + return false; + } + + const symbol = extendsType.getSymbol(); + if (symbol === undefined) { + return false; + } + + const symbolDeclarations = symbol.getDeclarations(); + if (symbolDeclarations === undefined) { + return false; + } + + const declaration = symbolDeclarations.find(ts.isClassLike); + if (!declaration) { + return false; + } + + yield* classWithAncestors(context, declaration); +} + +export const hasMemberInClassOrAncestor = ( + context: TransformationContext, + classDeclaration: ts.ClassLikeDeclarationBase, + callback: (m: ts.ClassElement) => boolean +) => [...classWithAncestors(context, classDeclaration)].some(c => c.members.some(callback)); + +function getPropertyName(propertyName: ts.PropertyName): string | number | undefined { + if (ts.isIdentifier(propertyName) || ts.isStringLiteral(propertyName) || ts.isNumericLiteral(propertyName)) { + return propertyName.text; + } else { + return undefined; // TODO: how to handle computed property names? + } +} + +function isSamePropertyName(a: ts.PropertyName, b: ts.PropertyName): boolean { + const aName = getPropertyName(a); + const bName = getPropertyName(b); + return aName !== undefined && aName === bName; +} + +export function isGetAccessorOverride( + context: TransformationContext, + element: ts.ClassElement, + classDeclaration: ts.ClassLikeDeclarationBase +): element is ts.GetAccessorDeclaration { + if (!ts.isGetAccessor(element) || isStaticNode(element)) { + return false; + } + + return hasMemberInClassOrAncestor( + context, + classDeclaration, + m => ts.isPropertyDeclaration(m) && m.initializer !== undefined && isSamePropertyName(m.name, element.name) + ); +} diff --git a/src/transformation/visitors/class/members/constructor.ts b/src/transformation/visitors/class/members/constructor.ts index 271e02a5f..adefb1143 100644 --- a/src/transformation/visitors/class/members/constructor.ts +++ b/src/transformation/visitors/class/members/constructor.ts @@ -40,7 +40,7 @@ export function transformConstructorDeclaration( // Check for field declarations in constructor const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined); - const classInstanceFields = transformClassInstanceFields(context, instanceFields); + const classInstanceFields = transformClassInstanceFields(context, classDeclaration, instanceFields); // If there are field initializers and the first statement is a super call, // move super call between default assignments and initializers diff --git a/src/transformation/visitors/class/members/fields.ts b/src/transformation/visitors/class/members/fields.ts index b825030a7..9117c1519 100644 --- a/src/transformation/visitors/class/members/fields.ts +++ b/src/transformation/visitors/class/members/fields.ts @@ -3,9 +3,11 @@ import * as lua from "../../../../LuaAST"; import { TransformationContext } from "../../../context"; import { createSelfIdentifier } from "../../../utils/lua-ast"; import { transformPropertyName } from "../../literal"; +import { isGetAccessorOverride } from "./accessors"; export function transformClassInstanceFields( context: TransformationContext, + classDeclaration: ts.ClassLikeDeclaration, instanceFields: ts.PropertyDeclaration[] ): lua.Statement[] { const statements: lua.Statement[] = []; @@ -25,5 +27,25 @@ export function transformClassInstanceFields( statements.push(assignClassField); } + // TODO: Remove when `useDefineForClassFields` would be `true` by default + + const getOverrides = classDeclaration.members.filter((m): m is ts.GetAccessorDeclaration => + isGetAccessorOverride(context, m, classDeclaration) + ); + + for (const getter of getOverrides) { + const getterName = transformPropertyName(context, getter.name); + + const resetGetter = lua.createExpressionStatement( + lua.createCallExpression(lua.createIdentifier("rawset"), [ + createSelfIdentifier(), + getterName, + lua.createNilLiteral(), + ]), + classDeclaration.members.find(ts.isConstructorDeclaration) ?? classDeclaration + ); + statements.push(resetGetter); + } + return statements; } From f10e2fe5797e0fe27222b090f616cbf73a14631f Mon Sep 17 00:00:00 2001 From: ark120202 Date: Sat, 29 Feb 2020 11:35:35 +0000 Subject: [PATCH 8/9] Add changelog --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2e2d9f8..87b03b901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Changelog +## Unreleased + +- Fixed class accessors not working when base class is lacking type information (#725) + +- Class extension code has been extracted to lualib + + ```ts + class A {} + class B extends A {} + ``` + + ```diff + A = __TS__Class() + B = __TS__Class() + -B.____super = A + -setmetatable(B, B.____super) + -setmetatable(B.prototype, B.____super.prototype) + +__TS__ClassExtends(A, B) + ``` + +- Generated code for class accessors is more dynamic now + + ```ts + class A { + get a() { + return true; + } + } + ``` + + ```diff + A = __TS__Class() + -A.prototype.____getters = {} + -A.prototype.__index = __TS__Index(A.prototype) + -function A.prototype.____getters.a(self) + - return true + -end + +__TS__SetDescriptor( + + A.prototype, + + "a", + + { + + get = function(self) + + return true + + end + + } + +) + ``` + + This change simplifies our codebase and opens a path to object accessors implementation + ## 0.31.0 - **Breaking:** The old annotation syntax (`/* !varArg */`) **no longer works**, the only currently supported syntax is: From 03489ad97f3dbdd701d265a78af6cd9fe465f4a3 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Wed, 11 Mar 2020 12:05:33 +0000 Subject: [PATCH 9/9] Change sourcemap class extends mapping pattern --- test/unit/printer/sourcemaps.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/printer/sourcemaps.spec.ts b/test/unit/printer/sourcemaps.spec.ts index 143936a91..c12a89b69 100644 --- a/test/unit/printer/sourcemaps.spec.ts +++ b/test/unit/printer/sourcemaps.spec.ts @@ -76,7 +76,7 @@ test.each([ { code: ` // @ts-ignore - class Bar extends Foo() { + class Bar extends Foo { constructor() { super(); } @@ -87,7 +87,7 @@ test.each([ { luaPattern: "Bar = __TS__Class()", typeScriptPattern: "class Bar" }, { luaPattern: "Bar.name =", typeScriptPattern: "class Bar" }, { luaPattern: "__TS__ClassExtends", typeScriptPattern: "extends" }, - { luaPattern: "Foo(", typeScriptPattern: "Foo(" }, + { luaPattern: "Foo", typeScriptPattern: "Foo" }, { luaPattern: "function Bar.prototype.____constructor", typeScriptPattern: "constructor" }, ], },