@@ -82,6 +82,8 @@ export abstract class LuaTranspiler {
8282
8383 public luaLibFeatureSet : Set < LuaLibFeature > ;
8484
85+ private readonly typeValidationCache : Map < ts . Type , Set < ts . Type > > = new Map < ts . Type , Set < ts . Type > > ( ) ;
86+
8587 constructor ( checker : ts . TypeChecker , options : CompilerOptions , sourceFile : ts . SourceFile ) {
8688 this . indent = "" ;
8789 this . checker = checker ;
@@ -948,6 +950,7 @@ export abstract class LuaTranspiler {
948950 this . validateAssignment ( node . right , rightType , leftType ) ;
949951
950952 if ( ts . isArrayLiteralExpression ( node . left ) ) {
953+ // Destructuring assignment
951954 const vars = node . left . elements . map ( e => this . transpileExpression ( e ) ) . join ( "," ) ;
952955 const vals = tsHelper . isTupleReturnCall ( node . right , this . checker )
953956 ? rhs : this . transpileDestructingAssignmentValue ( node . right ) ;
@@ -1409,26 +1412,59 @@ export abstract class LuaTranspiler {
14091412 }
14101413 }
14111414
1412- public validateAssignment ( node : ts . Node , fromType : ts . Type , toType : ts . Type ) : void {
1415+ public validateAssignment ( node : ts . Node , fromType : ts . Type , toType : ts . Type , toName ?: string ) : void {
1416+ if ( toType === fromType ) {
1417+ return ;
1418+ }
1419+
14131420 if ( ( toType . flags & ts . TypeFlags . Any ) !== 0 ) {
14141421 // Assigning to un-typed variable
14151422 return ;
1416- } else if ( ( fromType as ts . TypeReference ) . typeArguments && ( toType as ts . TypeReference ) . typeArguments ) {
1423+ }
1424+
1425+ // Use cache to avoid repeating check for same types (protects against infinite loop in recursive types)
1426+ let fromTypeCache = this . typeValidationCache . get ( fromType ) ;
1427+ if ( fromTypeCache ) {
1428+ if ( fromTypeCache . has ( toType ) ) {
1429+ return ;
1430+ }
1431+ } else {
1432+ fromTypeCache = new Set ( ) ;
1433+ this . typeValidationCache . set ( fromType , fromTypeCache ) ;
1434+ }
1435+ fromTypeCache . add ( toType ) ;
1436+
1437+ // Check function assignments
1438+ const fromHasContext = tsHelper . isFunctionWithContext ( fromType , this . checker ) ;
1439+ const toHasContext = tsHelper . isFunctionWithContext ( toType , this . checker ) ;
1440+ if ( fromHasContext !== toHasContext ) {
1441+ if ( fromHasContext ) {
1442+ throw TSTLErrors . UnsupportedFunctionConversion ( node , toName ) ;
1443+ } else {
1444+ throw TSTLErrors . UnsupportedMethodConversion ( node , toName ) ;
1445+ }
1446+ }
1447+
1448+ if ( ( fromType as ts . TypeReference ) . typeArguments && ( toType as ts . TypeReference ) . typeArguments ) {
14171449 // Recurse into tuples/arrays
14181450 ( toType as ts . TypeReference ) . typeArguments . forEach ( ( t , i ) => {
1419- this . validateAssignment ( node , ( fromType as ts . TypeReference ) . typeArguments [ i ] , t ) ;
1451+ this . validateAssignment ( node , ( fromType as ts . TypeReference ) . typeArguments [ i ] , t , toName ) ;
14201452 } ) ;
1421- } else {
1422- // Check function assignments
1423- const fromHasContext = tsHelper . isFunctionWithContext ( fromType , this . checker ) ;
1424- const toHasContext = tsHelper . isFunctionWithContext ( toType , this . checker ) ;
1425- if ( fromHasContext !== toHasContext ) {
1426- if ( fromHasContext ) {
1427- throw TSTLErrors . UnsupportedFunctionConversion ( node ) ;
1428- } else {
1429- throw TSTLErrors . UnsupportedMethodConversion ( node ) ;
1453+ }
1454+
1455+ if ( ( toType . flags & ts . TypeFlags . Object ) !== 0
1456+ && ( ( toType as ts . ObjectType ) . objectFlags & ts . ObjectFlags . ClassOrInterface ) !== 0
1457+ && toType . symbol && toType . symbol . members && fromType . symbol && fromType . symbol . members ) {
1458+ // Recurse into interfaces
1459+ toType . symbol . members . forEach (
1460+ ( toMember , memberName ) => {
1461+ const fromMember = fromType . symbol . members . get ( memberName ) ;
1462+ const toMemberType = this . checker . getTypeOfSymbolAtLocation ( toMember , node ) ;
1463+ const fromMemberType = this . checker . getTypeOfSymbolAtLocation ( fromMember , node ) ;
1464+ this . validateAssignment ( node , fromMemberType , toMemberType ,
1465+ toName ? `${ toName } .${ memberName } ` : memberName . toString ( ) ) ;
14301466 }
1431- }
1467+ ) ;
14321468 }
14331469 }
14341470
@@ -1446,7 +1482,7 @@ export abstract class LuaTranspiler {
14461482 const param = params [ i ] ;
14471483 const paramType = this . checker . getTypeAtLocation ( param ) ;
14481484 const sigType = this . checker . getTypeAtLocation ( sig . parameters [ i ] . valueDeclaration ) ;
1449- this . validateAssignment ( param , paramType , sigType ) ;
1485+ this . validateAssignment ( param , paramType , sigType , sig . parameters [ i ] . name ) ;
14501486 parameters . push ( this . transpileExpression ( param ) ) ;
14511487 }
14521488 } else {
0 commit comments