@@ -475,6 +475,19 @@ export class LuaTransformer {
475475 }
476476 }
477477
478+ // You cannot extend LuaTable classes
479+ if ( extendsType ) {
480+ const decorators = tsHelper . getCustomDecorators ( extendsType , this . checker ) ;
481+ if ( decorators . has ( DecoratorKind . LuaTable ) ) {
482+ throw TSTLErrors . InvalidExtendsLuaTable ( statement ) ;
483+ }
484+ }
485+
486+ // LuaTable classes must be ambient
487+ if ( decorators . has ( DecoratorKind . LuaTable ) && ! tsHelper . isAmbient ( statement ) ) {
488+ throw TSTLErrors . ForbiddenLuaTableNonDeclaration ( statement ) ;
489+ }
490+
478491 // Get all properties with value
479492 const properties = statement . members . filter ( ts . isPropertyDeclaration ) . filter ( member => member . initializer ) ;
480493
@@ -1981,6 +1994,22 @@ export class LuaTransformer {
19811994 ) ;
19821995 }
19831996
1997+ if ( ts . isCallExpression ( expression ) && ts . isPropertyAccessExpression ( expression . expression ) ) {
1998+ const ownerType = this . checker . getTypeAtLocation ( expression . expression . expression ) ;
1999+ const classDecorators = tsHelper . getCustomDecorators ( ownerType , this . checker ) ;
2000+ if ( classDecorators . has ( DecoratorKind . LuaTable ) ) {
2001+ this . validateLuaTableCall (
2002+ expression as ts . CallExpression & { expression : ts . PropertyAccessExpression } ,
2003+ true
2004+ ) ;
2005+ return this . transformLuaTableExpressionStatement (
2006+ statement as ts . ExpressionStatement
2007+ & { expression : ts . CallExpression }
2008+ & { expression : { expression : ts . PropertyAccessExpression } }
2009+ ) ;
2010+ }
2011+ }
2012+
19842013 return tstl . createExpressionStatement ( this . expectExpression ( this . transformExpression ( expression ) ) ) ;
19852014 }
19862015
@@ -2666,6 +2695,10 @@ export class LuaTransformer {
26662695 throw TSTLErrors . InvalidInstanceOfExtension ( expression ) ;
26672696 }
26682697
2698+ if ( decorators . has ( DecoratorKind . LuaTable ) ) {
2699+ throw TSTLErrors . InvalidInstanceOfLuaTable ( expression ) ;
2700+ }
2701+
26692702 if ( tsHelper . isStandardLibraryType ( rhsType , "ObjectConstructor" , this . program ) ) {
26702703 return this . transformLuaLibFunction ( LuaLibFeature . InstanceOfObject , expression , lhs ) ;
26712704 }
@@ -2697,6 +2730,7 @@ export class LuaTransformer {
26972730 const rightType = this . checker . getTypeAtLocation ( expression . right ) ;
26982731 const leftType = this . checker . getTypeAtLocation ( expression . left ) ;
26992732 this . validateFunctionAssignment ( expression . right , rightType , leftType ) ;
2733+ this . validatePropertyAssignment ( expression ) ;
27002734
27012735 if ( tsHelper . isArrayLengthAssignment ( expression , this . checker , this . program ) ) {
27022736 // array.length = x
@@ -3393,6 +3427,17 @@ export class LuaTransformer {
33933427 ) ;
33943428 }
33953429
3430+ if ( classDecorators . has ( DecoratorKind . LuaTable ) ) {
3431+ if ( node . arguments && node . arguments . length > 0 ) {
3432+ throw TSTLErrors . ForbiddenLuaTableUseException (
3433+ "No parameters are allowed when constructing a LuaTable object." ,
3434+ node
3435+ ) ;
3436+ } else {
3437+ return tstl . createTableExpression ( ) ;
3438+ }
3439+ }
3440+
33963441 return tstl . createCallExpression (
33973442 tstl . createTableIndexExpression ( name , tstl . createStringLiteral ( "new" ) ) ,
33983443 params ,
@@ -3535,6 +3580,18 @@ export class LuaTransformer {
35353580 return this . transformSymbolCallExpression ( node ) ;
35363581 }
35373582
3583+ const classDecorators = tsHelper . getCustomDecorators ( ownerType , this . checker ) ;
3584+
3585+ if ( classDecorators . has ( DecoratorKind . LuaTable ) ) {
3586+ this . validateLuaTableCall (
3587+ node as ts . CallExpression & { expression : ts . PropertyAccessExpression } ,
3588+ false
3589+ ) ;
3590+ return this . transformLuaTableCallExpression (
3591+ node as ts . CallExpression & { expression : ts . PropertyAccessExpression }
3592+ ) ;
3593+ }
3594+
35383595 switch ( ownerType . flags ) {
35393596 case ts . TypeFlags . String :
35403597 case ts . TypeFlags . StringLiteral :
@@ -3712,6 +3769,10 @@ export class LuaTransformer {
37123769 return tstl . createIdentifier ( property , node ) ;
37133770 }
37143771
3772+ if ( decorators . has ( DecoratorKind . LuaTable ) ) {
3773+ return this . transformLuaTableProperty ( node ) ;
3774+ }
3775+
37153776 // Catch math expressions
37163777 if ( ts . isIdentifier ( node . expression ) ) {
37173778 const ownerType = this . checker . getTypeAtLocation ( node . expression ) ;
@@ -3851,6 +3912,16 @@ export class LuaTransformer {
38513912 }
38523913 }
38533914
3915+ private transformLuaTableProperty ( node : ts . PropertyAccessExpression ) : tstl . UnaryExpression | undefined {
3916+ switch ( node . name . escapedText ) {
3917+ case "length" :
3918+ const propertyAccessExpression = this . expectExpression ( this . transformExpression ( node . expression ) ) ;
3919+ return tstl . createUnaryExpression ( propertyAccessExpression , tstl . SyntaxKind . LengthOperator , node ) ;
3920+ default :
3921+ throw TSTLErrors . UnsupportedProperty ( "LuaTable" , node . name . escapedText as string , node ) ;
3922+ }
3923+ }
3924+
38543925 public transformElementAccessExpression ( expression : ts . ElementAccessExpression ) : ExpressionVisitResult {
38553926 const table = this . expectExpression ( this . transformExpression ( expression . expression ) ) ;
38563927 const index = this . expectExpression ( this . transformExpression ( expression . argumentExpression ) ) ;
@@ -4219,6 +4290,91 @@ export class LuaTransformer {
42194290 }
42204291 }
42214292
4293+ private validateLuaTableCall (
4294+ expression : ts . CallExpression & { expression : ts . PropertyAccessExpression } ,
4295+ isWithinExpressionStatement : boolean
4296+ ) : void {
4297+ const methodName = expression . expression . name . escapedText ;
4298+ if ( expression . arguments . some ( argument => ts . isSpreadElement ( argument ) ) ) {
4299+ throw TSTLErrors . ForbiddenLuaTableUseException ( "Arguments cannot be spread." , expression ) ;
4300+ }
4301+
4302+ switch ( methodName ) {
4303+ case "get" :
4304+ if ( expression . arguments . length !== 1 ) {
4305+ throw TSTLErrors . ForbiddenLuaTableUseException (
4306+ "One parameter is required for get()." ,
4307+ expression
4308+ ) ;
4309+ }
4310+ break ;
4311+ case "set" :
4312+ if ( expression . arguments . length !== 2 ) {
4313+ throw TSTLErrors . ForbiddenLuaTableUseException (
4314+ "Two parameters are required for set()." ,
4315+ expression
4316+ ) ;
4317+ }
4318+ if ( ! isWithinExpressionStatement ) {
4319+ throw TSTLErrors . ForbiddenLuaTableSetExpression ( expression ) ;
4320+ }
4321+ break ;
4322+ }
4323+ }
4324+
4325+ private transformLuaTableExpressionStatement (
4326+ node : ts . ExpressionStatement
4327+ & { expression : ts . CallExpression }
4328+ & { expression : { expression : ts . PropertyAccessExpression } }
4329+ ) : tstl . VariableDeclarationStatement | tstl . AssignmentStatement {
4330+ const methodName = node . expression . expression . name . escapedText ;
4331+ const signature = this . checker . getResolvedSignature ( node . expression ) ;
4332+ const tableName = ( node . expression . expression . expression as ts . Identifier ) . escapedText ;
4333+ const luaTable = tstl . createIdentifier ( tableName ) ;
4334+ const params = this . transformArguments ( ( node . expression as ts . CallExpression ) . arguments , signature ) ;
4335+
4336+ switch ( methodName ) {
4337+ case "get" :
4338+ return tstl . createVariableDeclarationStatement (
4339+ tstl . createAnonymousIdentifier ( node . expression ) ,
4340+ tstl . createTableIndexExpression ( luaTable , params [ 0 ] , node . expression ) ,
4341+ node . expression
4342+ ) ;
4343+ case "set" :
4344+ return tstl . createAssignmentStatement (
4345+ tstl . createTableIndexExpression ( luaTable , params [ 0 ] , node . expression ) ,
4346+ params . splice ( 1 ) ,
4347+ node . expression
4348+ ) ;
4349+ default :
4350+ throw TSTLErrors . ForbiddenLuaTableUseException (
4351+ "Unsupported method." ,
4352+ node . expression
4353+ ) ;
4354+ }
4355+ }
4356+
4357+ private transformLuaTableCallExpression (
4358+ expression : ts . CallExpression & { expression : ts . PropertyAccessExpression }
4359+ ) : tstl . Expression {
4360+ const method = expression . expression ;
4361+ const methodName = method . name . escapedText ;
4362+ const signature = this . checker . getResolvedSignature ( expression ) ;
4363+ const tableName = ( method . expression as ts . Identifier ) . escapedText ;
4364+ const luaTable = tstl . createIdentifier ( tableName ) ;
4365+ const params = this . transformArguments ( expression . arguments , signature ) ;
4366+
4367+ switch ( methodName ) {
4368+ case "get" :
4369+ return tstl . createTableIndexExpression ( luaTable , params [ 0 ] , expression ) ;
4370+ default :
4371+ throw TSTLErrors . ForbiddenLuaTableUseException (
4372+ "Unsupported method." ,
4373+ expression
4374+ ) ;
4375+ }
4376+ }
4377+
42224378 private transformArrayCallExpression ( node : ts . CallExpression ) : tstl . CallExpression {
42234379 const expression = node . expression as ts . PropertyAccessExpression ;
42244380 const signature = this . checker . getResolvedSignature ( node ) ;
@@ -4823,6 +4979,22 @@ export class LuaTransformer {
48234979 }
48244980 }
48254981
4982+ private validatePropertyAssignment ( node : ts . Node ) : void {
4983+ if ( ts . isBinaryExpression ( node ) && ts . isPropertyAccessExpression ( node . left ) ) {
4984+ const leftType = this . checker . getTypeAtLocation ( node . left . expression ) ;
4985+ const decorators = tsHelper . getCustomDecorators ( leftType , this . checker ) ;
4986+ if ( decorators . has ( DecoratorKind . LuaTable ) ) {
4987+ switch ( node . left . name . escapedText as string ) {
4988+ case "length" :
4989+ throw TSTLErrors . ForbiddenLuaTableUseException (
4990+ `A LuaTable object's length cannot be re-assigned.` ,
4991+ node
4992+ ) ;
4993+ }
4994+ }
4995+ }
4996+ }
4997+
48264998 private wrapInFunctionCall ( expression : tstl . Expression ) : tstl . FunctionExpression {
48274999 const returnStatement = tstl . createReturnStatement ( [ expression ] ) ;
48285000 return tstl . createFunctionExpression (
0 commit comments