From 17448c788d824f15b5000f22a2b83d55499ba666 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 2 Mar 2019 10:33:39 -0700 Subject: [PATCH 01/11] all functions take a self parameter - also added noSelf directive to add `this: void` namespace/class-wide - rewrote function assignment tests to be more thorough and maintainable - fixed bug in generators where parameters were being hidden --- src/Decorator.ts | 3 + src/LuaTransformer.ts | 30 +- src/TSHelper.ts | 34 +- src/lualib/ArrayConcat.ts | 6 +- src/lualib/ArrayEvery.ts | 4 +- src/lualib/ArrayFilter.ts | 4 +- src/lualib/ArrayForEach.ts | 4 +- src/lualib/ArrayIndexOf.ts | 2 +- src/lualib/ArrayMap.ts | 2 +- src/lualib/ArrayPush.ts | 2 +- src/lualib/ArrayReverse.ts | 2 +- src/lualib/ArrayShift.ts | 4 +- src/lualib/ArraySlice.ts | 2 +- src/lualib/ArraySome.ts | 4 +- src/lualib/ArraySort.ts | 4 +- src/lualib/ArraySplice.ts | 2 +- src/lualib/ArrayUnshift.ts | 4 +- src/lualib/ClassIndex.ts | 6 +- src/lualib/ClassNewIndex.ts | 8 +- src/lualib/FunctionApply.ts | 8 +- src/lualib/FunctionBind.ts | 10 +- src/lualib/FunctionCall.ts | 8 +- src/lualib/Index.ts | 4 +- src/lualib/InstanceOf.ts | 2 +- src/lualib/Iterator.ts | 2 +- src/lualib/Map.ts | 2 +- src/lualib/NewIndex.ts | 10 +- src/lualib/ObjectAssign.ts | 2 +- src/lualib/ObjectEntries.ts | 2 +- src/lualib/ObjectKeys.ts | 2 +- src/lualib/ObjectValues.ts | 2 +- src/lualib/Set.ts | 2 +- src/lualib/StringConcat.ts | 2 +- src/lualib/StringReplace.ts | 4 +- src/lualib/StringSplit.ts | 2 +- src/lualib/Symbol.ts | 2 +- src/lualib/SymbolRegistry.ts | 4 +- src/lualib/WeakMap.ts | 2 +- src/lualib/WeakSet.ts | 2 +- test/src/util.ts | 4 +- test/translation/lua/callNamespace.lua | 2 +- .../translation/lua/functionRestArguments.lua | 2 +- .../translation/lua/modulesFunctionExport.lua | 2 +- .../lua/modulesFunctionNoExport.lua | 2 +- ...modulesNamespaceNestedWithMemberExport.lua | 2 +- .../lua/modulesNamespaceWithMemberExport.lua | 2 +- .../modulesNamespaceWithMemberNoExport.lua | 2 +- test/translation/lua/namespace.lua | 2 +- test/translation/lua/namespaceMerge.lua | 4 +- test/translation/lua/namespaceNested.lua | 2 +- test/translation/lua/namespacePhantom.lua | 2 +- test/translation/lua/returnDefault.lua | 2 +- .../lua/shorthandPropertyAssignment.lua | 2 +- test/translation/lua/tupleReturn.lua | 40 +- test/unit/assignmentDestructuring.spec.ts | 2 +- test/unit/assignments.spec.ts | 1018 ++++++++--------- test/unit/declarations.spec.ts | 11 +- test/unit/modules.spec.ts | 11 + test/unit/objectLiteral.spec.ts | 2 +- 59 files changed, 643 insertions(+), 674 deletions(-) diff --git a/src/Decorator.ts b/src/Decorator.ts index bf113ee71..84066caa6 100644 --- a/src/Decorator.ts +++ b/src/Decorator.ts @@ -23,6 +23,8 @@ export class Decorator { return DecoratorKind.NoClassOr; case "luaiterator": return DecoratorKind.LuaIterator; + case "noself": + return DecoratorKind.NoSelf; } return undefined; @@ -47,4 +49,5 @@ export enum DecoratorKind { TupleReturn = "TupleReturn", NoClassOr = "NoClassOr", LuaIterator = "LuaIterator", + NoSelf = "NoSelf", } diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 7d7420ca6..84ad6c4ea 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1444,16 +1444,13 @@ export class LuaTransformer { private transformGeneratorFunction( parameters: ts.NodeArray, body: ts.Block, - transformedParameters: tstl.Identifier[], - dotsLiteral: tstl.DotsLiteral, spreadIdentifier?: tstl.Identifier ): [tstl.Statement[], Scope] { this.importLuaLibFeature(LuaLibFeature.Symbol); const [functionBody, functionScope] = this.transformFunctionBody( parameters, - body, - spreadIdentifier + body ); const coroutineIdentifier = tstl.createIdentifier("____co"); @@ -1468,12 +1465,7 @@ export class LuaTransformer { tstl.createTableIndexExpression(tstl.createIdentifier("coroutine"), tstl.createStringLiteral("create") ), - [tstl.createFunctionExpression( - tstl.createBlock(functionBody), - transformedParameters, - dotsLiteral, - spreadIdentifier), - ] + [tstl.createFunctionExpression(tstl.createBlock(functionBody))] ) ); @@ -1576,6 +1568,12 @@ export class LuaTransformer { //return ____it tstl.createReturnStatement([itIdentifier]), ]; + + if (spreadIdentifier) { + const spreadTable = this.wrapInTable(tstl.createDotsLiteral()); + block.unshift(tstl.createVariableDeclarationStatement(spreadIdentifier, spreadTable)); + } + return [block, functionScope]; } @@ -1596,8 +1594,6 @@ export class LuaTransformer { ? this.transformGeneratorFunction( functionDeclaration.parameters, functionDeclaration.body, - params, - dotsLiteral, restParamName ) : this.transformFunctionBody( @@ -3100,16 +3096,14 @@ export class LuaTransformer { } const callPath = this.transformExpression(node.expression); - const signatureDeclaration = signature.getDeclaration(); + const signatureDeclaration = signature && signature.getDeclaration(); if (signatureDeclaration - && !ts.isPropertyAccessExpression(node.expression) - && tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.NonVoid - && !ts.isElementAccessExpression(node.expression)) + && tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.Void) { + parameters = this.transformArguments(node.arguments, signature); + } else { const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G"); parameters = this.transformArguments(node.arguments, signature, context); - } else { - parameters = this.transformArguments(node.arguments, signature); } const expressionType = this.checker.getTypeAtLocation(node.expression); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index a6ec285ef..f9cbe951b 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -554,22 +554,24 @@ export class TSHelper { ? ContextType.Void : ContextType.NonVoid; } - if (ts.isMethodDeclaration(signatureDeclaration) || ts.isMethodSignature(signatureDeclaration)) { - // Method - return ContextType.NonVoid; - } - if (ts.isPropertySignature(signatureDeclaration.parent) - || ts.isPropertyDeclaration(signatureDeclaration.parent) - || ts.isPropertyAssignment(signatureDeclaration.parent)) { - // Lambda property - return ContextType.NonVoid; - } - if (ts.isBinaryExpression(signatureDeclaration.parent)) { - // Function expression: check type being assigned to - return TSHelper.getFunctionContextType( - checker.getTypeAtLocation(signatureDeclaration.parent.left), checker); - } - return ContextType.Void; + + let scopeDeclaration: ts.Declaration = signatureDeclaration; + while (true) { + scopeDeclaration = TSHelper.findFirstNodeAbove( + scopeDeclaration, + (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration => + ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) + ); + if (!scopeDeclaration) { + break; + } + + const scopeType = checker.getTypeAtLocation(scopeDeclaration); + if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { + return ContextType.Void; + } + } + return ContextType.NonVoid; } public static reduceContextTypes(contexts: ContextType[]): ContextType { diff --git a/src/lualib/ArrayConcat.ts b/src/lualib/ArrayConcat.ts index 06d5206b3..5eaf56cb7 100644 --- a/src/lualib/ArrayConcat.ts +++ b/src/lualib/ArrayConcat.ts @@ -1,7 +1,7 @@ -declare function pcall(func: () => any): any; -declare function type(val: any): string; +declare function pcall(this: void, func: () => any): any; +declare function type(this: void, val: any): string; -function __TS__ArrayConcat(arr1: any[], ...args: any[]): any[] { +function __TS__ArrayConcat(this: void, arr1: any[], ...args: any[]): any[] { const out: any[] = []; for (const val of arr1) { out[out.length] = val; diff --git a/src/lualib/ArrayEvery.ts b/src/lualib/ArrayEvery.ts index e8434175a..cc08c2fab 100644 --- a/src/lualib/ArrayEvery.ts +++ b/src/lualib/ArrayEvery.ts @@ -1,4 +1,6 @@ -function __TS__ArrayEvery(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean { +function __TS__ArrayEvery(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean) + : boolean +{ for (let i = 0; i < arr.length; i++) { if (!callbackfn(arr[i], i, arr)) { return false; diff --git a/src/lualib/ArrayFilter.ts b/src/lualib/ArrayFilter.ts index 8f0ac2fde..2a64a7389 100644 --- a/src/lualib/ArrayFilter.ts +++ b/src/lualib/ArrayFilter.ts @@ -1,4 +1,6 @@ -function __TS__ArrayFilter(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): T[] { +function __TS__ArrayFilter(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean) + : T[] +{ const result: T[] = []; for (let i = 0; i < arr.length; i++) { if (callbackfn(arr[i], i, arr)) { diff --git a/src/lualib/ArrayForEach.ts b/src/lualib/ArrayForEach.ts index 3001915f0..6f6c931f7 100644 --- a/src/lualib/ArrayForEach.ts +++ b/src/lualib/ArrayForEach.ts @@ -1,4 +1,6 @@ -function __TS__ArrayForEach(arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any): void { +function __TS__ArrayForEach(this: void, arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any) + : void +{ for (let i = 0; i < arr.length; i++) { callbackFn(arr[i], i, arr); } diff --git a/src/lualib/ArrayIndexOf.ts b/src/lualib/ArrayIndexOf.ts index c50ce5ded..a2eb5ebf0 100644 --- a/src/lualib/ArrayIndexOf.ts +++ b/src/lualib/ArrayIndexOf.ts @@ -1,4 +1,4 @@ -function __TS__ArrayIndexOf(arr: T[], searchElement: T, fromIndex?: number): number { +function __TS__ArrayIndexOf(this: void, arr: T[], searchElement: T, fromIndex?: number): number { const len = arr.length; if (len === 0) { return -1; diff --git a/src/lualib/ArrayMap.ts b/src/lualib/ArrayMap.ts index f7ea7aa50..04ee22a48 100644 --- a/src/lualib/ArrayMap.ts +++ b/src/lualib/ArrayMap.ts @@ -1,4 +1,4 @@ -function __TS__ArrayMap(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => U): U[] { +function __TS__ArrayMap(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => U): U[] { const newArray: U[] = []; for (let i = 0; i < arr.length; i++) { newArray[i] = callbackfn(arr[i], i, arr); diff --git a/src/lualib/ArrayPush.ts b/src/lualib/ArrayPush.ts index 8e1d8e324..f09005e44 100644 --- a/src/lualib/ArrayPush.ts +++ b/src/lualib/ArrayPush.ts @@ -1,4 +1,4 @@ -function __TS__ArrayPush(arr: T[], ...items: T[]): number { +function __TS__ArrayPush(this: void, arr: T[], ...items: T[]): number { for (const item of items) { arr[arr.length] = item; } diff --git a/src/lualib/ArrayReverse.ts b/src/lualib/ArrayReverse.ts index 3c4839417..ae1fb90dd 100644 --- a/src/lualib/ArrayReverse.ts +++ b/src/lualib/ArrayReverse.ts @@ -1,4 +1,4 @@ -function __TS__ArrayReverse(arr: any[]): any[] { +function __TS__ArrayReverse(this: void, arr: any[]): any[] { let i = 0; let j = arr.length - 1; while (i < j) { diff --git a/src/lualib/ArrayShift.ts b/src/lualib/ArrayShift.ts index a95df1a49..475886eb2 100644 --- a/src/lualib/ArrayShift.ts +++ b/src/lualib/ArrayShift.ts @@ -1,6 +1,6 @@ declare namespace table { - function remove(arr: T[], idx: number): T; + function remove(this: void, arr: T[], idx: number): T; } -function __TS__ArrayShift(arr: T[]): T { +function __TS__ArrayShift(this: void, arr: T[]): T { return table.remove(arr, 1); } diff --git a/src/lualib/ArraySlice.ts b/src/lualib/ArraySlice.ts index f3eb5b3d6..8005e9afc 100644 --- a/src/lualib/ArraySlice.ts +++ b/src/lualib/ArraySlice.ts @@ -1,5 +1,5 @@ // https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 22.1.3.23 -function __TS__ArraySlice(list: T[], first: number, last: number): T[] { +function __TS__ArraySlice(this: void, list: T[], first: number, last: number): T[] { const len = list.length; let k: number; diff --git a/src/lualib/ArraySome.ts b/src/lualib/ArraySome.ts index d03e7a9fe..293e60c60 100644 --- a/src/lualib/ArraySome.ts +++ b/src/lualib/ArraySome.ts @@ -1,4 +1,6 @@ -function __TS__ArraySome(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean { +function __TS__ArraySome(this: void, arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean) + : boolean +{ for (let i = 0; i < arr.length; i++) { if (callbackfn(arr[i], i, arr)) { return true; diff --git a/src/lualib/ArraySort.ts b/src/lualib/ArraySort.ts index 18745695c..df96395a1 100644 --- a/src/lualib/ArraySort.ts +++ b/src/lualib/ArraySort.ts @@ -1,7 +1,7 @@ declare namespace table { - function sort(arr: T[], compareFn?: (a: T, b: T) => number): void; + function sort(this: void, arr: T[], compareFn?: (a: T, b: T) => number): void; } -function __TS__ArraySort(arr: T[], compareFn?: (a: T, b: T) => number): T[] { +function __TS__ArraySort(this: void, arr: T[], compareFn?: (a: T, b: T) => number): T[] { table.sort(arr, compareFn); return arr; } diff --git a/src/lualib/ArraySplice.ts b/src/lualib/ArraySplice.ts index b875d706c..a1e4e29c4 100644 --- a/src/lualib/ArraySplice.ts +++ b/src/lualib/ArraySplice.ts @@ -1,4 +1,4 @@ -function __TS__ArraySplice(list: T[], start: number, deleteCount: number, ...items: T[]): T[] { +function __TS__ArraySplice(this: void, list: T[], start: number, deleteCount: number, ...items: T[]): T[] { const len = list.length; diff --git a/src/lualib/ArrayUnshift.ts b/src/lualib/ArrayUnshift.ts index cb0031100..1239033ea 100644 --- a/src/lualib/ArrayUnshift.ts +++ b/src/lualib/ArrayUnshift.ts @@ -1,7 +1,7 @@ declare namespace table { - function insert(arr: T[], idx: number, val: T): void; + function insert(this: void, arr: T[], idx: number, val: T): void; } -function __TS__ArrayUnshift(arr: T[], ...items: T[]): number { +function __TS__ArrayUnshift(this: void, arr: T[], ...items: T[]): number { for (let i = items.length - 1; i >= 0; --i) { table.insert(arr, 1, items[i]); } diff --git a/src/lualib/ClassIndex.ts b/src/lualib/ClassIndex.ts index f879fd9aa..a231205bb 100644 --- a/src/lualib/ClassIndex.ts +++ b/src/lualib/ClassIndex.ts @@ -1,11 +1,11 @@ interface LuaClass { ____super?: LuaClass; - ____getters?: { [key: string]: (self: LuaClass) => any }; + ____getters?: { [key: string]: (this: void, self: LuaClass) => any }; } -declare function rawget(obj: T, key: K): T[K]; +declare function rawget(this: void, obj: T, key: K): T[K]; -function __TS__ClassIndex(classTable: LuaClass, key: keyof LuaClass): any { +function __TS__ClassIndex(this: void, classTable: LuaClass, key: keyof LuaClass): any { while (true) { const getters = rawget(classTable, "____getters"); if (getters) { diff --git a/src/lualib/ClassNewIndex.ts b/src/lualib/ClassNewIndex.ts index 65108c6a2..68b5db7f7 100644 --- a/src/lualib/ClassNewIndex.ts +++ b/src/lualib/ClassNewIndex.ts @@ -1,12 +1,12 @@ interface LuaClass { ____super?: LuaClass; - ____setters?: { [key: string]: (self: LuaClass, val: any) => void }; + ____setters?: { [key: string]: (this: void, self: LuaClass, val: any) => void }; } -declare function rawget(obj: T, key: K): T[K]; -declare function rawset(obj: T, key: K, val: T[K]): void; +declare function rawget(this: void, obj: T, key: K): T[K]; +declare function rawset(this: void, obj: T, key: K, val: T[K]): void; -function __TS__ClassNewIndex(classTable: LuaClass, key: keyof LuaClass, val: any): void { +function __TS__ClassNewIndex(this: void, classTable: LuaClass, key: keyof LuaClass, val: any): void { let tbl = classTable; do { const setters = rawget(tbl, "____setters"); diff --git a/src/lualib/FunctionApply.ts b/src/lualib/FunctionApply.ts index 6776f0e02..80e7b69b6 100644 --- a/src/lualib/FunctionApply.ts +++ b/src/lualib/FunctionApply.ts @@ -1,12 +1,12 @@ -declare function unpack(list: T[], i?: number, j?: number): T[]; +declare function unpack(this: void, list: T[], i?: number, j?: number): T[]; declare namespace table { - export function unpack(list: T[], i?: number, j?: number): T[]; + export function unpack(this: void, list: T[], i?: number, j?: number): T[]; } -type ApplyFn = (...argArray: any[]) => any; +type ApplyFn = (this: void, ...argArray: any[]) => any; -function __TS__FunctionApply(fn: ApplyFn, thisArg: any, argsArray?: any[]): any { +function __TS__FunctionApply(this: void, fn: ApplyFn, thisArg: any, argsArray?: any[]): any { if (argsArray) { return fn(thisArg, (unpack || table.unpack)(argsArray)); } else { diff --git a/src/lualib/FunctionBind.ts b/src/lualib/FunctionBind.ts index e1e7675b5..09e663c91 100644 --- a/src/lualib/FunctionBind.ts +++ b/src/lualib/FunctionBind.ts @@ -1,14 +1,14 @@ -declare function unpack(list: T[], i?: number, j?: number): T[]; +declare function unpack(this: void, list: T[], i?: number, j?: number): T[]; declare namespace table { - export function insert(t: T[], pos: number, value: T): void; + export function insert(this: void, t: T[], pos: number, value: T): void; - export function unpack(list: T[], i?: number, j?: number): T[]; + export function unpack(this: void, list: T[], i?: number, j?: number): T[]; } -type BindFn = (...argArray: any[]) => any; +type BindFn = (this: void, ...argArray: any[]) => any; -function __TS__FunctionBind(fn: BindFn, thisArg: any, ...boundArgs: any[]): (...args: any[]) => any { +function __TS__FunctionBind(this: void, fn: BindFn, thisArg: any, ...boundArgs: any[]): (...args: any[]) => any { return (...argArray: any[]) => { for (let i = 0; i < boundArgs.length; ++i) { table.insert(argArray, i + 1, boundArgs[i]); diff --git a/src/lualib/FunctionCall.ts b/src/lualib/FunctionCall.ts index fe126ccd5..66a2be373 100644 --- a/src/lualib/FunctionCall.ts +++ b/src/lualib/FunctionCall.ts @@ -1,11 +1,11 @@ -declare function unpack(list: T[], i?: number, j?: number): T[]; +declare function unpack(this: void, list: T[], i?: number, j?: number): T[]; declare namespace table { - export function unpack(list: T[], i?: number, j?: number): T[]; + export function unpack(this: void, list: T[], i?: number, j?: number): T[]; } -type CallFn = (...argArray: any[]) => any; +type CallFn = (this: void, ...argArray: any[]) => any; -function __TS__FunctionCall(fn: CallFn, thisArg: any, ...args: any[]): any { +function __TS__FunctionCall(this: void, fn: CallFn, thisArg: any, ...args: any[]): any { return fn(thisArg, (unpack || table.unpack)(args)); } diff --git a/src/lualib/Index.ts b/src/lualib/Index.ts index a5f44081f..88c8e0035 100644 --- a/src/lualib/Index.ts +++ b/src/lualib/Index.ts @@ -5,12 +5,12 @@ interface LuaClass { declare interface LuaObject { constructor: LuaClass; - ____getters?: { [key: string]: (self: LuaObject) => any }; + ____getters?: { [key: string]: (this: void, self: LuaObject) => any }; } declare function rawget(obj: T, key: K): T[K]; -function __TS__Index(classProto: LuaObject): (tbl: LuaObject, key: keyof LuaObject) => any { +function __TS__Index(this: void, classProto: LuaObject): (this: void, tbl: LuaObject, key: keyof LuaObject) => any { return (tbl, key) => { let proto = classProto; while (true) { diff --git a/src/lualib/InstanceOf.ts b/src/lualib/InstanceOf.ts index c23c3604a..b59674943 100644 --- a/src/lualib/InstanceOf.ts +++ b/src/lualib/InstanceOf.ts @@ -6,7 +6,7 @@ interface LuaObject { constructor: LuaClass; } -function __TS__InstanceOf(obj: LuaObject, classTbl: LuaClass): boolean { +function __TS__InstanceOf(this: void, obj: LuaObject, classTbl: LuaClass): boolean { if (obj !== undefined) { let luaClass = obj.constructor; while (luaClass !== undefined) { diff --git a/src/lualib/Iterator.ts b/src/lualib/Iterator.ts index 805792052..b45fafe46 100644 --- a/src/lualib/Iterator.ts +++ b/src/lualib/Iterator.ts @@ -1,4 +1,4 @@ -function __TS__Iterator(iterable: Iterable): () => T { +function __TS__Iterator(this: void, iterable: Iterable): (this: void) => T { const iterator = iterable[Symbol.iterator](); return () => { const result = iterator.next(); diff --git a/src/lualib/Map.ts b/src/lualib/Map.ts index fd55ef62f..f47d81abb 100644 --- a/src/lualib/Map.ts +++ b/src/lualib/Map.ts @@ -1,5 +1,5 @@ /** @tupleReturn */ -declare function next(t: { [k: string]: TValue }, index?: TKey): [TKey, TValue]; +declare function next(this: void, t: { [k: string]: TValue }, index?: TKey): [TKey, TValue]; class Map { public size: number; diff --git a/src/lualib/NewIndex.ts b/src/lualib/NewIndex.ts index d50d76ab7..efe07f075 100644 --- a/src/lualib/NewIndex.ts +++ b/src/lualib/NewIndex.ts @@ -5,13 +5,15 @@ interface LuaClass { declare interface LuaObject { constructor: LuaClass; - ____setters?: { [key: string]: (self: LuaObject, val: any) => void }; + ____setters?: { [key: string]: (this: void, self: LuaObject, val: any) => void }; } -declare function rawget(obj: T, key: K): T[K]; -declare function rawset(obj: T, key: K, val: T[K]): void; +declare function rawget(this: void, obj: T, key: K): T[K]; +declare function rawset(this: void, obj: T, key: K, val: T[K]): void; -function __TS__NewIndex(classProto: LuaObject): (tbl: LuaObject, key: keyof LuaObject, val: any) => void { +function __TS__NewIndex(this: void, classProto: LuaObject) + : (this: void, tbl: LuaObject, key: keyof LuaObject, val: any) => void +{ return (tbl, key, val) => { let proto = classProto; while (true) { diff --git a/src/lualib/ObjectAssign.ts b/src/lualib/ObjectAssign.ts index ff0b3fb41..da49d9eb0 100644 --- a/src/lualib/ObjectAssign.ts +++ b/src/lualib/ObjectAssign.ts @@ -1,5 +1,5 @@ // https://tc39.github.io/ecma262/#sec-object.assign -function __TS__ObjectAssign(to: T, ...sources: object[]): T { +function __TS__ObjectAssign(this: void, to: T, ...sources: object[]): T { if (to === undefined) { return to; } diff --git a/src/lualib/ObjectEntries.ts b/src/lualib/ObjectEntries.ts index 51f32cf10..f491bd039 100644 --- a/src/lualib/ObjectEntries.ts +++ b/src/lualib/ObjectEntries.ts @@ -1,4 +1,4 @@ -function __TS__ObjectEntries(obj: any): Array { +function __TS__ObjectEntries(this: void, obj: any): Array { const result = []; for (const key in obj) { result[result.length] = [key, obj[key]]; diff --git a/src/lualib/ObjectKeys.ts b/src/lualib/ObjectKeys.ts index 5358acad8..8163a620a 100644 --- a/src/lualib/ObjectKeys.ts +++ b/src/lualib/ObjectKeys.ts @@ -1,4 +1,4 @@ -function __TS__ObjectKeys(obj: any): Array { +function __TS__ObjectKeys(this: void, obj: any): Array { const result = []; for (const key in obj) { result[result.length] = key; diff --git a/src/lualib/ObjectValues.ts b/src/lualib/ObjectValues.ts index c7a17377a..e850f6a31 100644 --- a/src/lualib/ObjectValues.ts +++ b/src/lualib/ObjectValues.ts @@ -1,4 +1,4 @@ -function __TS__ObjectValues(obj: any): Array { +function __TS__ObjectValues(this: void, obj: any): Array { const result = []; for (const key in obj) { result[result.length] = obj[key]; diff --git a/src/lualib/Set.ts b/src/lualib/Set.ts index 2b06ee1d4..e1bf8fc39 100644 --- a/src/lualib/Set.ts +++ b/src/lualib/Set.ts @@ -1,5 +1,5 @@ /** @tupleReturn */ -declare function next(t: { [k: string]: TValue }, index?: TKey): [TKey, TValue]; +declare function next(this: void, t: { [k: string]: TValue }, index?: TKey): [TKey, TValue]; class Set { public size: number; diff --git a/src/lualib/StringConcat.ts b/src/lualib/StringConcat.ts index ae90c6b61..ce3f6dd6e 100644 --- a/src/lualib/StringConcat.ts +++ b/src/lualib/StringConcat.ts @@ -1,4 +1,4 @@ -function __TS__StringConcat(str1: string, ...args: string[]): string { +function __TS__StringConcat(this: void, str1: string, ...args: string[]): string { let out = str1; for (const arg of args) { out = out + arg; diff --git a/src/lualib/StringReplace.ts b/src/lualib/StringReplace.ts index 7324db680..3f51b8b43 100644 --- a/src/lualib/StringReplace.ts +++ b/src/lualib/StringReplace.ts @@ -1,8 +1,8 @@ declare namespace string { /** @tupleReturn */ - function gsub(source: string, searchValue: string, replaceValue: string): [string, number]; + function gsub(this: void, source: string, searchValue: string, replaceValue: string): [string, number]; } -function __TS__StringReplace(source: string, searchValue: string, replaceValue: string): string { +function __TS__StringReplace(this: void, source: string, searchValue: string, replaceValue: string): string { return string.gsub(source, searchValue, replaceValue)[0]; } diff --git a/src/lualib/StringSplit.ts b/src/lualib/StringSplit.ts index 005f57436..734dac38c 100644 --- a/src/lualib/StringSplit.ts +++ b/src/lualib/StringSplit.ts @@ -1,4 +1,4 @@ -function __TS__StringSplit(source: string, separator?: string, limit?: number): string[] { +function __TS__StringSplit(this: void, source: string, separator?: string, limit?: number): string[] { if (limit === undefined) { limit = 4294967295; } diff --git a/src/lualib/Symbol.ts b/src/lualib/Symbol.ts index 2464b5f20..5c7bf0a7c 100644 --- a/src/lualib/Symbol.ts +++ b/src/lualib/Symbol.ts @@ -1,4 +1,4 @@ -declare function setmetatable(obj: T, metatable: any): T; +declare function setmetatable(this: void, obj: T, metatable: any): T; // tslint:disable-next-line: variable-name const ____symbolMetatable = { diff --git a/src/lualib/SymbolRegistry.ts b/src/lualib/SymbolRegistry.ts index c9c42e4e3..c1fa21df5 100644 --- a/src/lualib/SymbolRegistry.ts +++ b/src/lualib/SymbolRegistry.ts @@ -1,7 +1,7 @@ // tslint:disable-next-line: variable-name const ____symbolRegistry: Record = {}; -function __TS__SymbolRegistryFor(key: string): symbol { +function __TS__SymbolRegistryFor(this: void, key: string): symbol { if (!____symbolRegistry[key]) { ____symbolRegistry[key] = __TS__Symbol(key); } @@ -9,7 +9,7 @@ function __TS__SymbolRegistryFor(key: string): symbol { return ____symbolRegistry[key]; } -function __TS__SymbolRegistryKeyFor(sym: symbol): string { +function __TS__SymbolRegistryKeyFor(this: void, sym: symbol): string { for (const key in ____symbolRegistry) { if (____symbolRegistry[key] === sym) return key; } diff --git a/src/lualib/WeakMap.ts b/src/lualib/WeakMap.ts index 3f92cf3df..407b268a3 100644 --- a/src/lualib/WeakMap.ts +++ b/src/lualib/WeakMap.ts @@ -1,4 +1,4 @@ -declare function setmetatable(obj: T, metatable: any): T; +declare function setmetatable(this: void, obj: T, metatable: any): T; class WeakMap { private items: {[key: string]: TValue}; // Type of key is actually TKey diff --git a/src/lualib/WeakSet.ts b/src/lualib/WeakSet.ts index 7cfae57f3..cbe7b5ede 100644 --- a/src/lualib/WeakSet.ts +++ b/src/lualib/WeakSet.ts @@ -1,4 +1,4 @@ -declare function setmetatable(obj: T, metatable: any): T; +declare function setmetatable(this: void, obj: T, metatable: any): T; class WeakSet { private items: {[key: string]: boolean}; // Key type is actually TValue diff --git a/test/src/util.ts b/test/src/util.ts index a27bcc851..17ecb62cb 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -93,7 +93,7 @@ export function transpileAndExecute( ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics" ): any { - const wrappedTsString = `declare function JSONStringify(p: any): string; + const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; ${tsHeader ? tsHeader : ""} function __runTest(): any {${tsStr}}`; @@ -111,7 +111,7 @@ export function transpileExecuteAndReturnExport( luaHeader?: string ): any { - const wrappedTsString = `declare function JSONStringify(p: any): string; + const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; ${tsStr}`; const lua = `return (function() diff --git a/test/translation/lua/callNamespace.lua b/test/translation/lua/callNamespace.lua index b0ec0d650..40b907163 100644 --- a/test/translation/lua/callNamespace.lua +++ b/test/translation/lua/callNamespace.lua @@ -1 +1 @@ -Namespace.myFunction(); +Namespace:myFunction(); diff --git a/test/translation/lua/functionRestArguments.lua b/test/translation/lua/functionRestArguments.lua index 25fe06c01..b440062ed 100644 --- a/test/translation/lua/functionRestArguments.lua +++ b/test/translation/lua/functionRestArguments.lua @@ -1,3 +1,3 @@ -varargsFunction = function(a, ...) +varargsFunction = function(self, a, ...) local b = ({...}); end; diff --git a/test/translation/lua/modulesFunctionExport.lua b/test/translation/lua/modulesFunctionExport.lua index 12e69553c..7b8d3dd58 100644 --- a/test/translation/lua/modulesFunctionExport.lua +++ b/test/translation/lua/modulesFunctionExport.lua @@ -1,4 +1,4 @@ local exports = exports or {}; -exports.publicFunc = function() +exports.publicFunc = function(self) end; return exports; diff --git a/test/translation/lua/modulesFunctionNoExport.lua b/test/translation/lua/modulesFunctionNoExport.lua index 37ea15ea9..59eb73913 100644 --- a/test/translation/lua/modulesFunctionNoExport.lua +++ b/test/translation/lua/modulesFunctionNoExport.lua @@ -1,2 +1,2 @@ -publicFunc = function() +publicFunc = function(self) end; diff --git a/test/translation/lua/modulesNamespaceNestedWithMemberExport.lua b/test/translation/lua/modulesNamespaceNestedWithMemberExport.lua index 44509f50d..bde538a11 100644 --- a/test/translation/lua/modulesNamespaceNestedWithMemberExport.lua +++ b/test/translation/lua/modulesNamespaceNestedWithMemberExport.lua @@ -5,7 +5,7 @@ do TestSpace.TestNestedSpace = TestSpace.TestNestedSpace or {}; local TestNestedSpace = TestSpace.TestNestedSpace; do - TestNestedSpace.innerFunc = function() + TestNestedSpace.innerFunc = function(self) end; end end diff --git a/test/translation/lua/modulesNamespaceWithMemberExport.lua b/test/translation/lua/modulesNamespaceWithMemberExport.lua index 020bcf4d5..1e267f322 100644 --- a/test/translation/lua/modulesNamespaceWithMemberExport.lua +++ b/test/translation/lua/modulesNamespaceWithMemberExport.lua @@ -2,7 +2,7 @@ local exports = exports or {}; exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do - TestSpace.innerFunc = function() + TestSpace.innerFunc = function(self) end; end return exports; diff --git a/test/translation/lua/modulesNamespaceWithMemberNoExport.lua b/test/translation/lua/modulesNamespaceWithMemberNoExport.lua index b0abeb433..bdbade0fe 100644 --- a/test/translation/lua/modulesNamespaceWithMemberNoExport.lua +++ b/test/translation/lua/modulesNamespaceWithMemberNoExport.lua @@ -3,7 +3,7 @@ exports.TestSpace = exports.TestSpace or {}; local TestSpace = exports.TestSpace; do local innerFunc; - innerFunc = function() + innerFunc = function(self) end; end return exports; diff --git a/test/translation/lua/namespace.lua b/test/translation/lua/namespace.lua index bdf6664fd..f59170157 100644 --- a/test/translation/lua/namespace.lua +++ b/test/translation/lua/namespace.lua @@ -1,6 +1,6 @@ myNamespace = myNamespace or {}; do local nsMember; - nsMember = function() + nsMember = function(self) end; end diff --git a/test/translation/lua/namespaceMerge.lua b/test/translation/lua/namespaceMerge.lua index 3c515447c..7bac3efb6 100644 --- a/test/translation/lua/namespaceMerge.lua +++ b/test/translation/lua/namespaceMerge.lua @@ -25,11 +25,11 @@ MergedClass.prototype.methodB = function(self) end; MergedClass = MergedClass or {}; do - MergedClass.namespaceFunc = function() + MergedClass.namespaceFunc = function(self) end; end local mergedClass = MergedClass.new(); mergedClass:methodB(); mergedClass:propertyFunc(); MergedClass:staticMethodB(); -MergedClass.namespaceFunc(); +MergedClass:namespaceFunc(); diff --git a/test/translation/lua/namespaceNested.lua b/test/translation/lua/namespaceNested.lua index dd1006ee9..e409f968e 100644 --- a/test/translation/lua/namespaceNested.lua +++ b/test/translation/lua/namespaceNested.lua @@ -4,7 +4,7 @@ do local myNestedNamespace = myNamespace.myNestedNamespace; do local nsMember; - nsMember = function() + nsMember = function(self) end; end end diff --git a/test/translation/lua/namespacePhantom.lua b/test/translation/lua/namespacePhantom.lua index 3844b51be..63de0e172 100644 --- a/test/translation/lua/namespacePhantom.lua +++ b/test/translation/lua/namespacePhantom.lua @@ -1,2 +1,2 @@ -nsMember = function() +nsMember = function(self) end; diff --git a/test/translation/lua/returnDefault.lua b/test/translation/lua/returnDefault.lua index a0e35c570..7b013615d 100644 --- a/test/translation/lua/returnDefault.lua +++ b/test/translation/lua/returnDefault.lua @@ -1,3 +1,3 @@ -myFunc = function() +myFunc = function(self) return; end; diff --git a/test/translation/lua/shorthandPropertyAssignment.lua b/test/translation/lua/shorthandPropertyAssignment.lua index 0c5898188..96a5da700 100644 --- a/test/translation/lua/shorthandPropertyAssignment.lua +++ b/test/translation/lua/shorthandPropertyAssignment.lua @@ -1,4 +1,4 @@ local f; -f = function(x) +f = function(____, x) return ({x = x}); end; diff --git a/test/translation/lua/tupleReturn.lua b/test/translation/lua/tupleReturn.lua index c8d492bec..bc81abc3f 100644 --- a/test/translation/lua/tupleReturn.lua +++ b/test/translation/lua/tupleReturn.lua @@ -1,28 +1,28 @@ -tupleReturn = function() +tupleReturn = function(self) return 0, "foobar"; end; -tupleReturn(); -noTupleReturn(); -local a, b = tupleReturn(); -local c, d = table.unpack(noTupleReturn()); -a, b = tupleReturn(); -c, d = table.unpack(noTupleReturn()); -local e = ({tupleReturn()}); -local f = noTupleReturn(); -e = ({tupleReturn()}); -f = noTupleReturn(); -foo(({tupleReturn()})); -foo(noTupleReturn()); -tupleReturnFromVar = function() +tupleReturn(_G); +noTupleReturn(_G); +local a, b = tupleReturn(_G); +local c, d = table.unpack(noTupleReturn(_G)); +a, b = tupleReturn(_G); +c, d = table.unpack(noTupleReturn(_G)); +local e = ({tupleReturn(_G)}); +local f = noTupleReturn(_G); +e = ({tupleReturn(_G)}); +f = noTupleReturn(_G); +foo(_G, ({tupleReturn(_G)})); +foo(_G, noTupleReturn(_G)); +tupleReturnFromVar = function(self) local r = {1, "baz"}; return table.unpack(r); end; -tupleReturnForward = function() - return tupleReturn(); +tupleReturnForward = function(self) + return tupleReturn(_G); end; -tupleNoForward = function() - return ({tupleReturn()}); +tupleNoForward = function(self) + return ({tupleReturn(_G)}); end; -tupleReturnUnpack = function() - return table.unpack(tupleNoForward()); +tupleReturnUnpack = function(self) + return table.unpack(tupleNoForward(_G)); end; diff --git a/test/unit/assignmentDestructuring.spec.ts b/test/unit/assignmentDestructuring.spec.ts index e146dac39..985ee4472 100644 --- a/test/unit/assignmentDestructuring.spec.ts +++ b/test/unit/assignmentDestructuring.spec.ts @@ -5,7 +5,7 @@ import * as util from "../src/util"; export class AssignmentDestructuringTests { private readonly assignmentDestruturingTs = ` - declare function myFunc(): [number, string]; + declare function myFunc(this: void): [number, string]; let [a, b] = myFunc();`; @Test("Assignment destructuring [5.1]") diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index dda85cc20..6f50acc75 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1,27 +1,260 @@ -import { Expect, Test, TestCase } from "alsatian"; +import { Expect, Test, TestCase, TestCases } from "alsatian"; import { TranspileError } from "../../src/TranspileError"; import * as util from "../src/util"; +import { TSTLErrors } from "../../src/TSTLErrors"; const fs = require("fs"); -export class AssignmentTests { +interface TestFunction { + value: string; + definition?: string; +} - public static readonly funcAssignTestCode = - `let func: {(s: string): string} = function(s) { return s + "+func"; }; - let lambda: (s: string) => string = s => s + "+lambda"; - let thisFunc: {(this: Foo, s: string): string} = function(s) { return s + "+thisFunc"; }; - let thisLambda: (this: Foo, s: string) => string = s => s + "+thisLambda"; - class Foo { - method(s: string): string { return s + "+method"; } - lambdaProp: (s: string) => string = s => s + "+lambdaProp"; - voidMethod(this: void, s: string): string { return s + "+voidMethod"; } - voidLambdaProp: (this: void, s: string) => string = s => s + "+voidLambdaProp"; - static voidStaticMethod(this: void, s: string): string { return s + "+voidStaticMethod"; } - static voidStaticLambdaProp: (this: void, s: string) => string = s => s + "+voidStaticLambdaProp"; - static staticMethod(s: string): string { return s + "+staticMethod"; } - static staticLambdaProp: (s: string) => string = s => s + "+staticLambdaProp"; - } - const foo = new Foo();`; +const selfTestFunctions: TestFunction[] = [ + { + value: "selfFunc", + definition: `let selfFunc: {(this: any, s: string): string} = function(s) { return s; };`, + }, + { + value: "selfLambda", + definition: `let selfLambda: (this: any, s: string) => string = s => s;`, + }, + { + value: "anonFunc", + definition: `let anonFunc: {(s: string): string} = function(s) { return s; };`, + }, + { + value: "anonLambda", + definition: `let anonLambda: (s: string) => string = s => s;`, + }, + { + value: "methodClass.method", + definition: `class MethodClass { method(this: any, s: string): string { return s; } } + const methodClass = new MethodClass();`, + }, + { + value: "anonMethodClass.anonMethod", + definition: `class AnonMethodClass { anonMethod(s: string): string { return s; } } + const anonMethodClass = new AnonMethodClass();`, + }, + { + value: "funcPropClass.funcProp", + definition: `class FuncPropClass { funcProp: (this: any, s: string) => string = s => s; } + const funcPropClass = new FuncPropClass();`, + }, + { + value: "anonFuncPropClass.anonFuncProp", + definition: `class AnonFuncPropClass { anonFuncProp: (this: any, s: string) => string = s => s; } + const anonFuncPropClass = new AnonFuncPropClass();`, + }, + { + value: "StaticMethodClass.staticMethod", + definition: `class StaticMethodClass { + static staticMethod(this: any, s: string): string { return s; } + }`, + }, + { + value: "AnonStaticMethodClass.anonStaticMethod", + definition: `class AnonStaticMethodClass { static anonStaticMethod(s: string): string { return s; } }`, + }, + { + value: "StaticFuncPropClass.staticFuncProp", + definition: `class StaticFuncPropClass { + static staticFuncProp: (this: any, s: string) => string = s => s; + }`, + }, + { + value: "AnonStaticFuncPropClass.anonStaticFuncProp", + definition: `class AnonStaticFuncPropClass { + static anonStaticFuncProp: (s: string) => string = s => s; + }`, + }, + { + value: "FuncNs.nsFunc", + definition: `namespace FuncNs { export function nsFunc(s: string) { return s; } }`, + }, + { + value: "FuncNestedNs.NestedNs.nestedNsFunc", + definition: `namespace FuncNestedNs { + export namespace NestedNs { export function nestedNsFunc(s: string) { return s; } } + }`, + }, + { + value: "LambdaNs.nsLambda", + definition: `namespace LambdaNs { + export let nsLambda: (s: string) => string = s => s; + }`, + }, + { + value: "LambdaNestedNs.NestedNs.nestedNsLambda", + definition: `namespace LambdaNestedNs { + export namespace NestedNs { export let nestedNsLambda: (s: string) => string = s => s } + }`, + }, +]; + +const noSelfTestFunctions: TestFunction[] = [ + { + value: "voidFunc", + definition: `let voidFunc: {(this: void, s: string): string} = function(s) { return s; };`, + }, + { + value: "voidLambda", + definition: `let voidLambda: (this: void, s: string) => string = s => s;`, + }, + { + value: "voidMethodClass.voidMethod", + definition: `class VoidMethodClass { + voidMethod(this: void, s: string): string { return s; } + } + const voidMethodClass = new VoidMethodClass();`, + }, + { + value: "voidFuncPropClass.voidFuncProp", + definition: `class VoidFuncPropClass { + voidFuncProp: (this: void, s: string) => string = s => s; + } + const voidFuncPropClass = new VoidFuncPropClass();`, + }, + { + value: "StaticVoidMethodClass.staticVoidMethod", + definition: `class StaticVoidMethodClass { + static staticVoidMethod(this: void, s: string): string { return s; } + }`, + }, + { + value: "StaticVoidFuncPropClass.staticVoidFuncProp", + definition: `class StaticVoidFuncPropClass { + static staticVoidFuncProp: (this: void, s: string) => string = s => s; + }`, + }, + { + value: "NoSelfFuncNs.noSelfNsFunc", + definition: `/** @noSelf */ namespace NoSelfFuncNs { export function noSelfNsFunc(s: string) { return s; } }`, + }, + { + value: "NoSelfFuncNestedNs.NestedNs.noSelfNestedNsFunc", + definition: `/** @noSelf */ namespace NoSelfFuncNestedNs { + export namespace NestedNs { export function noSelfNestedNsFunc(s: string) { return s; } } + }`, + }, + { + value: "NoSelfLambdaNs.noSelfNsLambda", + definition: `/** @noSelf */ namespace NoSelfLambdaNs { + export let noSelfNsLambda: (s: string) => string = s => s; + }`, + }, + { + value: "NoSelfLambdaNestedNs.NestedNs.noSelfNestedNsLambda", + definition: `/** @noSelf */ namespace NoSelfLambdaNestedNs { + export namespace NestedNs { export let noSelfNestedNsLambda: (s: string) => string = s => s } + }`, + }, + { + value: "noSelfMethodClass.noSelfMethod", + definition: `/** @noSelf */ class NoSelfMethodClass { noSelfMethod(s: string): string { return s; } } + const noSelfMethodClass = new NoSelfMethodClass();`, + }, + { + value: "NoSelfStaticMethodClass.noSelfStaticMethod", + definition: `/** @noSelf */ class NoSelfStaticMethodClass { + static noSelfStaticMethod(s: string): string { return s; } + }`, + }, + { + value: "noSelfFuncPropClass.noSelfFuncProp", + definition: `/** @noSelf */ class NoSelfFuncPropClass { noSelfFuncProp(s: string): string { return s; } } + const noSelfFuncPropClass = new NoSelfFuncPropClass();`, + }, + { + value: "NoSelfStaticFuncPropClass.noSelfStaticFuncProp", + definition: `/** @noSelf */ class NoSelfStaticFuncPropClass { + static noSelfStaticFuncProp(s: string): string { return s; } + }`, + }, +]; + +const anonTestFunctionExpressions: TestFunction[] = [ + {value: `s => s`}, + {value: `(s => s)`}, + {value: `function(s) { return s; }`}, + {value: `(function(s) { return s; })`}, +]; + +const selfTestFunctionExpressions: TestFunction[] = [ + {value: `function(this: any, s) { return s; }`}, + {value: `(function(this: any, s) { return s; })`}, +]; + +const noSelfTestFunctionExpressions: TestFunction[] = [ + {value: `function(this: void, s) { return s; }`}, + {value: `(function(this: void, s) { return s; })`}, +]; + +const anonTestFunctionType = "(s: string) => string"; +const selfTestFunctionType = "(this: any, s: string) => string"; +const noSelfTestFunctionType = "(this: void, s: string) => string"; + +type TestFunctionCast = [ + /*testFunction: */TestFunction, + /*castedFunction: */string, + /*isMethodConversion?: */boolean? +]; +const validTestFunctionCasts: TestFunctionCast[] = [ + ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`]), + ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`]), + ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`]), + ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`]), + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`]), + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`]), +]; +const invalidTestFunctionCasts: TestFunctionCast[] = [ + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`, false]), + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`, false]), + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`, false]), + ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`, false]), + ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`, true]), + ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`, true]), +]; + +type TestFunctionAssignment = [ + /*testFunction: */TestFunction, + /*functionType: */string, + /*isMethodConversion?: */boolean? +]; +const validTestFunctionAssignments: TestFunctionAssignment[] = [ + ...selfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), + ...selfTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), + ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), + ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), + ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), + ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), + ...selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), + ...selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), + ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), +]; +const invalidTestFunctionAssignments: TestFunctionAssignment[] = [ + ...selfTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType, false]), + ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType, true]), + ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), + ...selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType, false]), + ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType, true]), + ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), +]; + +function *testFunctionCombinations(functionsA: TestFunction[], functionsB: TestFunction[], ...args: any[]) + : IterableIterator<[TestFunction, TestFunction, ...any[]]> +{ + for (const assigneeFunction of functionsA) { + for (const valueFunction of functionsB) { + if (assigneeFunction !== valueFunction) { + yield [assigneeFunction, valueFunction, ...args]; + } + } + } +} + +export class AssignmentTests { @TestCase(`"abc"`, `"abc"`) @TestCase("3", "3") @@ -101,7 +334,7 @@ export class AssignmentTests { @Test("TupleReturn assignment") public tupleReturnFunction(): void { const code = `/** @tupleReturn */\n` - + `declare function abc(): number[]\n` + + `declare function abc(this: void): number[]\n` + `let [a,b] = abc();`; const lua = util.transpileString(code); @@ -111,7 +344,7 @@ export class AssignmentTests { @Test("TupleReturn Single assignment") public tupleReturnSingleAssignment(): void { const code = `/** @tupleReturn */\n` - + `declare function abc(): [number, string];\n` + + `declare function abc(this: void): [number, string];\n` + `let a = abc();` + `a = abc();`; @@ -135,7 +368,7 @@ export class AssignmentTests { public tupleReturnNameSpace(): void { const code = `declare namespace def {\n` + `/** @tupleReturn */\n` - + `function abc() {}\n` + + `function abc(this: void) {}\n` + `}\n` + `let [a,b] = def.abc();`; @@ -204,518 +437,231 @@ export class AssignmentTests { .toThrowError(TranspileError, `Cannot use Lua keyword ${identifier} as identifier.`); } - @TestCase("func", "lambda", "foo+lambda") - @TestCase("func", "s => s", "foo") - @TestCase("func", "(s => s)", "foo") - @TestCase("func", "function(s) { return s; }", "foo") - @TestCase("func", "(function(s) { return s; })", "foo") - @TestCase("func", "function(this: void, s: string) { return s; }", "foo") - @TestCase("func", "s => foo.method(s)", "foo+method") - @TestCase("func", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("func", "Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("func", "Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("func", "foo.voidMethod", "foo+voidMethod") - @TestCase("func", "foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("lambda", "func", "foo+func") - @TestCase("lambda", "s => s", "foo") - @TestCase("lambda", "(s => s)", "foo") - @TestCase("lambda", "function(s) { return s; }", "foo") - @TestCase("lambda", "(function(s) { return s; })", "foo") - @TestCase("lambda", "function(this: void, s: string) { return s; }", "foo") - @TestCase("lambda", "s => foo.method(s)", "foo+method") - @TestCase("lambda", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("lambda", "Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("lambda", "Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("lambda", "foo.voidMethod", "foo+voidMethod") - @TestCase("lambda", "foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("Foo.voidStaticMethod", "func", "foo+func") - @TestCase("Foo.voidStaticMethod", "lambda", "foo+lambda") - @TestCase("Foo.voidStaticMethod", "s => s", "foo") - @TestCase("Foo.voidStaticMethod", "(s => s)", "foo") - @TestCase("Foo.voidStaticMethod", "function(s) { return s; }", "foo") - @TestCase("Foo.voidStaticMethod", "(function(s) { return s; })", "foo") - @TestCase("Foo.voidStaticMethod", "function(this: void, s: string) { return s; }", "foo") - @TestCase("Foo.voidStaticMethod", "s => foo.method(s)", "foo+method") - @TestCase("Foo.voidStaticMethod", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("Foo.voidStaticMethod", "Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("Foo.voidStaticMethod", "foo.voidMethod", "foo+voidMethod") - @TestCase("Foo.voidStaticMethod", "foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("Foo.voidStaticLambdaProp", "func", "foo+func") - @TestCase("Foo.voidStaticLambdaProp", "lambda", "foo+lambda") - @TestCase("Foo.voidStaticLambdaProp", "s => s", "foo") - @TestCase("Foo.voidStaticLambdaProp", "(s => s)", "foo") - @TestCase("Foo.voidStaticLambdaProp", "function(s) { return s; }", "foo") - @TestCase("Foo.voidStaticLambdaProp", "(function(s) { return s; })", "foo") - @TestCase("Foo.voidStaticLambdaProp", "function(this: void, s: string) { return s; }", "foo") - @TestCase("Foo.voidStaticLambdaProp", "s => foo.method(s)", "foo+method") - @TestCase("Foo.voidStaticLambdaProp", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("Foo.voidStaticLambdaProp", "Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("Foo.voidStaticLambdaProp", "foo.voidMethod", "foo+voidMethod") - @TestCase("Foo.voidStaticLambdaProp", "foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("foo.voidMethod", "func", "foo+func") - @TestCase("foo.voidMethod", "lambda", "foo+lambda") - @TestCase("foo.voidMethod", "s => s", "foo") - @TestCase("foo.voidMethod", "(s => s)", "foo") - @TestCase("foo.voidMethod", "function(s) { return s; }", "foo") - @TestCase("foo.voidMethod", "(function(s) { return s; })", "foo") - @TestCase("foo.voidMethod", "function(this: void, s: string) { return s; }", "foo") - @TestCase("foo.voidMethod", "s => foo.method(s)", "foo+method") - @TestCase("foo.voidMethod", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("foo.voidMethod", "Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("foo.voidMethod", "Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("foo.voidMethod", "foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("foo.voidLambdaProp", "func", "foo+func") - @TestCase("foo.voidLambdaProp", "lambda", "foo+lambda") - @TestCase("foo.voidLambdaProp", "s => s", "foo") - @TestCase("foo.voidLambdaProp", "(s => s)", "foo") - @TestCase("foo.voidLambdaProp", "function(s) { return s; }", "foo") - @TestCase("foo.voidLambdaProp", "(function(s) { return s; })", "foo") - @TestCase("foo.voidLambdaProp", "function(this: void, s: string) { return s; }", "foo") - @TestCase("foo.voidLambdaProp", "s => foo.method(s)", "foo+method") - @TestCase("foo.voidLambdaProp", "s => foo.lambdaProp(s)", "foo+lambdaProp") - @TestCase("foo.voidLambdaProp", "Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("foo.voidLambdaProp", "Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("foo.voidLambdaProp", "foo.voidMethod", "foo+voidMethod") - @TestCase("func", "<(s: string) => string>lambda", "foo+lambda") - @TestCase("func", "lambda as ((s: string) => string)", "foo+lambda") + @TestCases(testFunctionCombinations(selfTestFunctions, selfTestFunctions)) + @TestCases(testFunctionCombinations(selfTestFunctions, selfTestFunctionExpressions)) + @TestCases(testFunctionCombinations(selfTestFunctions, anonTestFunctionExpressions)) + @TestCases(testFunctionCombinations(noSelfTestFunctions, noSelfTestFunctions)) + @TestCases(testFunctionCombinations(noSelfTestFunctions, noSelfTestFunctionExpressions)) + @TestCases(testFunctionCombinations(noSelfTestFunctions, anonTestFunctionExpressions)) @Test("Valid function assignment") - public validFunctionAssignment(func: string, assignTo: string, expectResult: string): void { - const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo}; return ${func}("foo");`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + public validFunctionAssignment(assigneeFunction: TestFunction, valueFunction: TestFunction) + : void + { + const header = + `${assigneeFunction.definition || ""} + ${valueFunction.definition || ""}`; + const code = + `${assigneeFunction.value} = ${valueFunction.value}; + return ${assigneeFunction.value}("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foobar"); } - @TestCase("func", "foo+func") - @TestCase("lambda", "foo+lambda") - @TestCase("Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("foo.voidMethod", "foo+voidMethod") - @TestCase("foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("s => s", "foo") - @TestCase("(s => s)", "foo") - @TestCase("function(s) { return s; }", "foo") - @TestCase("(function(s) { return s; })", "foo") - @TestCase("function(this: void, s: string) { return s; }", "foo") - @TestCase("func", "foo+func", "string | ((s: string) => string)") - @TestCase("func", "foo+func", "T") - @TestCase("<(s: string) => string>func", "foo+func") - @TestCase("func as ((s: string) => string)", "foo+func") - @Test("Valid function argument") - public validFunctionArgument(func: string, expectResult: string, funcType?: string): void { - if (!funcType) { - funcType = "(s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function takesFunc string)>(fn: ${funcType}) { - return (fn as any)("foo"); - } - return takesFunc(${func});`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(testFunctionCombinations(selfTestFunctions, noSelfTestFunctions, true)) + @TestCases(testFunctionCombinations(selfTestFunctions, noSelfTestFunctionExpressions, true)) + @TestCases(testFunctionCombinations(noSelfTestFunctions, selfTestFunctions, false)) + @TestCases(testFunctionCombinations(noSelfTestFunctions, selfTestFunctionExpressions, false)) + @Test("Invalid function assignment") + public invalidFunctionAssignment( + assigneeFunction: TestFunction, + valueFunction: TestFunction, + isMethodConversion: boolean + ): void + { + const code = + `${assigneeFunction.definition || ""} + ${valueFunction.definition || ""} + ${assigneeFunction.value} = ${valueFunction.value};`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined) + : TSTLErrors.UnsupportedFunctionConversion(undefined); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + } + + @TestCases(validTestFunctionCasts) + @Test("Valid function assignment with cast") + public validFunctionAssignmentWithCast(testFunction: TestFunction, castedFunction: string): void { + const code = + `let fn: typeof ${testFunction.value}; + fn = ${castedFunction}; + return fn("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + } + + @TestCases(invalidTestFunctionCasts) + @Test("Invalid function assignment with cast") + public invalidFunctionAssignmentWithCast( + testFunction: TestFunction, + castedFunction: string, + isMethodConversion: boolean + ): void { + const code = + `${testFunction.definition || ""} + let fn: typeof ${testFunction.value}; + fn = ${castedFunction};`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined) + : TSTLErrors.UnsupportedFunctionConversion(undefined); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); } - @TestCase("s => s", "foo") - @TestCase("(s => s)", "foo") - @TestCase("function(s) { return s; }", "foo") - @TestCase("(function(s) { return s; })", "foo") - @TestCase("function(this: void, s: string) { return s; }", "foo") - @Test("Valid function expression argument with no signature") - public validFunctionExpressionArgumentNoSignature(func: string, expectResult: string): void { - const code = `${AssignmentTests.funcAssignTestCode} - const takesFunc: any = (fn: (s: string) => string) => { - return (fn as any)("foo"); - } - return takesFunc(${func});`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(validTestFunctionAssignments) + @Test("Valid function argument") + public validFunctionArgument(testFunction: TestFunction, functionType: string): void { + const code = + `function takesFunction(fn: ${functionType}) { + return fn("foobar"); + } + return takesFunction(${testFunction.value});`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("func", "foo+func") - @TestCase("lambda", "foo+lambda") - @TestCase("Foo.voidStaticMethod", "foo+voidStaticMethod") - @TestCase("Foo.voidStaticLambdaProp", "foo+voidStaticLambdaProp") - @TestCase("foo.voidMethod", "foo+voidMethod") - @TestCase("foo.voidLambdaProp", "foo+voidLambdaProp") - @TestCase("s => s", "foo") - @TestCase("(s => s)", "foo") - @TestCase("function(s) { return s; }", "foo") - @TestCase("(function(s) { return s; })", "foo") - @TestCase("function(this: void, s: string) { return s; }", "foo") - @TestCase("func", "foo+func", "string | ((s: string) => string)") - @TestCase("<(s: string) => string>func", "foo+func") - @TestCase("func as ((s: string) => string)", "foo+func") - @Test("Valid function return") - public validFunctionReturn(func: string, expectResult: string, funcType?: string): void { - if (!funcType) { - funcType = "(s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function returnsFunc(): ${funcType} { - return ${func}; - } - const fn = returnsFunc(); - return (fn as any)("foo");`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(invalidTestFunctionAssignments) + @Test("Invalid function argument") + public invalidFunctionArgument(testFunction: TestFunction, functionType: string, isMethodConversion: boolean) + : void + { + const code = + `declare function takesFunction(fn: ${functionType}); + ${testFunction.definition || ""} + takesFunction(${testFunction.value});`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") + : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + } + + @TestCases(validTestFunctionCasts) + @Test("Valid function argument with cast") + public validFunctionArgumentWithCast(testFunction: TestFunction, castedFunction: string): void { + const code = + `function takesFunction(fn: typeof ${testFunction.value}) { + return fn("foobar"); + } + return takesFunction(${castedFunction});`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("foo.method", "foo.lambdaProp", "foo+lambdaProp") - @TestCase("foo.method", "s => s", "foo") - @TestCase("foo.method", "function(s) { return s; }", "foo") - @TestCase("foo.method", "(function(s) { return s; })", "foo") - @TestCase("foo.method", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("foo.method", "s => func(s)", "foo+func") - @TestCase("foo.method", "s => lambda(s)", "foo+lambda") - @TestCase("foo.method", "Foo.staticMethod", "foo+staticMethod") - @TestCase("foo.method", "Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("foo.method", "thisFunc", "foo+thisFunc") - @TestCase("foo.method", "thisLambda", "foo+thisLambda") - @TestCase("foo.lambdaProp", "foo.method", "foo+method") - @TestCase("foo.lambdaProp", "s => s", "foo") - @TestCase("foo.lambdaProp", "(s => s)", "foo") - @TestCase("foo.lambdaProp", "function(s) { return s; }", "foo") - @TestCase("foo.lambdaProp", "(function(s) { return s; })", "foo") - @TestCase("foo.lambdaProp", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("foo.lambdaProp", "s => func(s)", "foo+func") - @TestCase("foo.lambdaProp", "s => lambda(s)", "foo+lambda") - @TestCase("foo.lambdaProp", "Foo.staticMethod", "foo+staticMethod") - @TestCase("foo.lambdaProp", "Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("foo.lambdaProp", "thisFunc", "foo+thisFunc") - @TestCase("foo.lambdaProp", "thisLambda", "foo+thisLambda") - @TestCase("Foo.staticMethod", "foo.method", "foo+method") - @TestCase("Foo.staticMethod", "foo.lambdaProp", "foo+lambdaProp") - @TestCase("Foo.staticMethod", "s => s", "foo") - @TestCase("Foo.staticMethod", "(s => s)", "foo") - @TestCase("Foo.staticMethod", "function(s) { return s; }", "foo") - @TestCase("Foo.staticMethod", "(function(s) { return s; })", "foo") - @TestCase("Foo.staticMethod", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("Foo.staticMethod", "s => func(s)", "foo+func") - @TestCase("Foo.staticMethod", "s => lambda(s)", "foo+lambda") - @TestCase("Foo.staticMethod", "Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("Foo.staticMethod", "thisFunc", "foo+thisFunc") - @TestCase("Foo.staticMethod", "thisLambda", "foo+thisLambda") - @TestCase("Foo.staticLambdaProp", "foo.method", "foo+method") - @TestCase("Foo.staticLambdaProp", "foo.lambdaProp", "foo+lambdaProp") - @TestCase("Foo.staticLambdaProp", "s => s", "foo") - @TestCase("Foo.staticLambdaProp", "(s => s)", "foo") - @TestCase("Foo.staticLambdaProp", "function(s) { return s; }", "foo") - @TestCase("Foo.staticLambdaProp", "(function(s) { return s; })", "foo") - @TestCase("Foo.staticLambdaProp", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("Foo.staticLambdaProp", "s => func(s)", "foo+func") - @TestCase("Foo.staticLambdaProp", "s => lambda(s)", "foo+lambda") - @TestCase("Foo.staticLambdaProp", "Foo.staticMethod", "foo+staticMethod") - @TestCase("Foo.staticLambdaProp", "thisFunc", "foo+thisFunc") - @TestCase("Foo.staticLambdaProp", "thisLambda", "foo+thisLambda") - @TestCase("thisFunc", "foo.method", "foo+method") - @TestCase("thisFunc", "foo.lambdaProp", "foo+lambdaProp") - @TestCase("thisFunc", "s => s", "foo") - @TestCase("thisFunc", "(s => s)", "foo") - @TestCase("thisFunc", "function(s) { return s; }", "foo") - @TestCase("thisFunc", "(function(s) { return s; })", "foo") - @TestCase("thisFunc", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("thisFunc", "s => func(s)", "foo+func") - @TestCase("thisFunc", "s => lambda(s)", "foo+lambda") - @TestCase("thisFunc", "Foo.staticMethod", "foo+staticMethod") - @TestCase("thisFunc", "Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("thisFunc", "thisLambda", "foo+thisLambda") - @TestCase("thisLambda", "foo.method", "foo+method") - @TestCase("thisLambda", "foo.lambdaProp", "foo+lambdaProp") - @TestCase("thisLambda", "s => s", "foo") - @TestCase("thisLambda", "(s => s)", "foo") - @TestCase("thisLambda", "function(s) { return s; }", "foo") - @TestCase("thisLambda", "(function(s) { return s; })", "foo") - @TestCase("thisLambda", "function(this: Foo, s: string) { return s; }", "foo") - @TestCase("thisLambda", "s => func(s)", "foo+func") - @TestCase("thisLambda", "s => lambda(s)", "foo+lambda") - @TestCase("thisLambda", "Foo.staticMethod", "foo+staticMethod") - @TestCase("thisLambda", "Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("thisLambda", "thisFunc", "foo+thisFunc") - @TestCase("foo.method", "<(this: Foo, s: string) => string>foo.lambdaProp", "foo+lambdaProp") - @TestCase("foo.method", "foo.lambdaProp as ((this: Foo, s: string) => string)", "foo+lambdaProp") - @Test("Valid method assignment") - public validMethodAssignment(func: string, assignTo: string, expectResult: string): void { - const code = `${AssignmentTests.funcAssignTestCode} - ${func} = ${assignTo}; - foo.method = ${func}; - return foo.method("foo");`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(invalidTestFunctionCasts) + @Test("Invalid function argument with cast") + public invalidFunctionArgumentWithCast( + testFunction: TestFunction, + castedFunction: string, + isMethodConversion: boolean + ): void + { + const code = + `${testFunction.definition || ""} + declare function takesFunction(fn: typeof ${testFunction.value}); + takesFunction(${castedFunction});`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") + : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + } + + // TODO: Fix function expression inference with generic types. The following should work, but doesn't: + // function takesFunction string>(fn: T) { ... } + // takesFunction(s => s); // Error: cannot convert method to function + // @TestCases(validTestFunctionAssignments) // Use this instead of other TestCases when fixed + @TestCases(selfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType])) + @TestCases(selfTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType])) + @TestCases(noSelfTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType])) + @TestCases(selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType])) + @TestCases(selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType])) + @TestCases(noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType])) + @Test("Valid function generic argument") + public validFunctionGenericArgument(testFunction: TestFunction, functionType: string): void { + const code = + `function takesFunction(fn: T) { + return fn("foobar"); + } + return takesFunction(${testFunction.value});`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("foo.method", "foo+method") - @TestCase("foo.lambdaProp", "foo+lambdaProp") - @TestCase("Foo.staticMethod", "foo+staticMethod") - @TestCase("Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("thisFunc", "foo+thisFunc") - @TestCase("thisLambda", "foo+thisLambda") - @TestCase("s => s", "foo") - @TestCase("(s => s)", "foo") - @TestCase("function(s) { return s; }", "foo") - @TestCase("(function(s) { return s; })", "foo") - @TestCase("function(this: Foo, s: string) { return s; }", "foo") - @TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)") - @TestCase("foo.method", "foo+method", "T") - @TestCase("<(this: Foo, s: string) => string>foo.method", "foo+method") - @TestCase("foo.method as ((this: Foo, s: string) => string)", "foo+method") - @Test("Valid method argument") - public validMethodArgument(func: string, expectResult: string, funcType?: string): void { - if (!funcType) { - funcType = "(this: Foo, s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function takesMethod string)>(meth: ${funcType}) { - foo.method = meth as any; - } - takesMethod(${func}); - return foo.method("foo");`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(invalidTestFunctionAssignments) + @Test("Invalid function generic argument") + public invalidFunctionGenericArgument(testFunction: TestFunction, functionType: string, isMethodConversion: boolean) + : void + { + const code = + `declare function takesFunction(fn: T); + ${testFunction.definition || ""} + takesFunction(${testFunction.value});`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") + : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + } + + @TestCases(anonTestFunctionExpressions.map(f => [f, "0", "'foobar'"])) + @TestCases(selfTestFunctionExpressions.map(f => [f, "0", "'foobar'"])) + @TestCases(noSelfTestFunctionExpressions.map(f => [f, "'foobar'"])) + @Test("Valid function expression argument with no signature") + public validFunctionExpressionArgumentNoSignature(testFunction: TestFunction, ...args: string[]): void { + const code = + `const takesFunction: any = (fn: (this: void, ...args: any[]) => any, ...args: any[]) => { + return fn(...args); + } + return takesFunction(${testFunction.value}, ${args.join(", ")});`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("foo.method", "foo+method") - @TestCase("foo.lambdaProp", "foo+lambdaProp") - @TestCase("Foo.staticMethod", "foo+staticMethod") - @TestCase("Foo.staticLambdaProp", "foo+staticLambdaProp") - @TestCase("thisFunc", "foo+thisFunc") - @TestCase("thisLambda", "foo+thisLambda") - @TestCase("s => s", "foo") - @TestCase("(s => s)", "foo") - @TestCase("function(s) { return s; }", "foo") - @TestCase("(function(s) { return s; })", "foo") - @TestCase("function(this: Foo, s: string) { return s; }", "foo") - @TestCase("foo.method", "foo+method", "string | ((this: Foo, s: string) => string)") - @TestCase("<(this: Foo, s: string) => string>foo.method", "foo+method") - @TestCase("foo.method as ((this: Foo, s: string) => string)", "foo+method") - @Test("Valid method return") - public validMethodReturn(func: string, expectResult: string, funcType?: string): void { - if (!funcType) { - funcType = "(this: Foo, s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function returnMethod(): ${funcType} { - return ${func}; - } - foo.method = returnMethod() as any; - return foo.method("foo");`; - const result = util.transpileAndExecute(code); - Expect(result).toBe(expectResult); + @TestCases(validTestFunctionAssignments) + @Test("Valid function return") + public validFunctionReturn(testFunction: TestFunction, functionType: string): void { + const code = + `function returnsFunction(): ${functionType} { + return ${testFunction.value}; + } + const fn = returnsFunction(); + return fn("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("func", "foo.method") - @TestCase("func", "foo.lambdaProp") - @TestCase("func", "Foo.staticMethod") - @TestCase("func", "Foo.staticLambdaProp") - @TestCase("func", "function(this: Foo, s: string) { return s; }") - @TestCase("lambda", "foo.method") - @TestCase("lambda", "foo.lambdaProp") - @TestCase("lambda", "Foo.staticMethod") - @TestCase("lambda", "Foo.staticLambdaProp") - @TestCase("lambda", "function(this: Foo, s: string) { return s; }") - @TestCase("foo.voidMethod", "foo.method") - @TestCase("foo.voidMethod", "foo.lambdaProp") - @TestCase("foo.voidMethod", "Foo.staticMethod") - @TestCase("foo.voidMethod", "Foo.staticLambdaProp") - @TestCase("foo.voidMethod", "function(this: Foo, s: string) { return s; }") - @TestCase("foo.voidLambdaProp", "foo.method") - @TestCase("foo.voidLambdaProp", "foo.lambdaProp") - @TestCase("foo.voidLambdaProp", "Foo.staticMethod") - @TestCase("foo.voidLambdaProp", "Foo.staticLambdaProp") - @TestCase("foo.voidLambdaProp", "function(this: Foo, s: string) { return s; }") - @TestCase("Foo.voidStaticMethod", "foo.method") - @TestCase("Foo.voidStaticMethod", "foo.lambdaProp") - @TestCase("Foo.voidStaticMethod", "Foo.staticMethod") - @TestCase("Foo.voidStaticMethod", "Foo.staticLambdaProp") - @TestCase("Foo.voidStaticMethod", "function(this: Foo, s: string) { return s; }") - @TestCase("Foo.voidStaticLambdaProp", "foo.method") - @TestCase("Foo.voidStaticLambdaProp", "foo.lambdaProp") - @TestCase("Foo.voidStaticLambdaProp", "Foo.staticMethod") - @TestCase("Foo.voidStaticLambdaProp", "Foo.staticLambdaProp") - @TestCase("Foo.voidStaticLambdaProp", "function(this: Foo, s: string) { return s; }") - @TestCase("func", "(foo.method as (string | ((this: Foo, s: string) => string)))") - @TestCase("func", "<(s: string) => string>foo.method") - @TestCase("func", "foo.method as ((s: string) => string)") - @Test("Invalid function assignment") - public invalidFunctionAssignment(func: string, assignTo: string): void { - const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from method to function. To fix, wrap the method in an arrow function."); - } - - @TestCase("foo.method") - @TestCase("foo.lambdaProp") - @TestCase("Foo.staticMethod") - @TestCase("Foo.staticLambdaProp") - @TestCase("thisFunc") - @TestCase("thisLambda") - @TestCase("function(this: Foo, s: string) { return s; }") - @TestCase("foo.method", "string | ((s: string) => string)") - @TestCase("foo.method", "T") - @Test("Invalid function argument") - public invalidFunctionArgument(func: string, funcType?: string): void { - if (!funcType) { - funcType = "(s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - declare function takesFunc string)>(fn: ${funcType}); - takesFunc(${func});`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from method to function \"fn\". To fix, wrap the method in an arrow function."); + @TestCases(invalidTestFunctionAssignments) + @Test("Invalid function return") + public invalidFunctionReturn(testFunction: TestFunction, functionType: string, isMethodConversion: boolean): void { + const code = + `${testFunction.definition || ""} + function returnsFunction(): ${functionType} { + return ${testFunction.value}; + }`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined) + : TSTLErrors.UnsupportedFunctionConversion(undefined); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + } + + @TestCases(validTestFunctionCasts) + @Test("Valid function return with cast") + public validFunctionReturnWithCast(testFunction: TestFunction, castedFunction: string): void { + const code = + `function returnsFunction(): typeof ${testFunction.value} { + return ${castedFunction}; + } + const fn = returnsFunction(); + return fn("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); } - @TestCase("<(s: string) => string>foo.method") - @TestCase("foo.method as ((s: string) => string)") - @Test("Invalid function argument cast") - public invalidFunctionArgumentCast(func: string): void { - const code = `${AssignmentTests.funcAssignTestCode} - declare function takesFunc string)>(fn: (s: string) => string); - takesFunc(${func});`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from method to function. To fix, wrap the method in an arrow function."); - } - - @TestCase("foo.method") - @TestCase("foo.lambdaProp") - @TestCase("Foo.staticMethod") - @TestCase("Foo.staticLambdaProp") - @TestCase("thisFunc") - @TestCase("thisLambda") - @TestCase("function(this: Foo, s: string) { return s; }") - @TestCase("foo.method", "string | ((s: string) => string)") - @TestCase("foo.method", "T") - @TestCase("<(s: string) => string>foo.method") - @TestCase("foo.method as ((s: string) => string)") - @Test("Invalid function return") - public invalidFunctionReturn(func: string, funcType?: string): void { - if (!funcType) { - funcType = "(s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function returnsFunc string)>(): ${funcType} { - return ${func}; - }`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from method to function. To fix, wrap the method in an arrow function."); - } - - @TestCase("foo.method", "func") - @TestCase("foo.method", "lambda") - @TestCase("foo.method", "Foo.voidStaticMethod") - @TestCase("foo.method", "Foo.voidStaticLambdaProp") - @TestCase("foo.method", "foo.voidMethod") - @TestCase("foo.method", "foo.voidLambdaProp") - @TestCase("foo.method", "function(this: void, s: string) { return s; }") - @TestCase("foo.lambdaProp", "func") - @TestCase("foo.lambdaProp", "lambda") - @TestCase("foo.lambdaProp", "Foo.voidStaticMethod") - @TestCase("foo.lambdaProp", "Foo.voidStaticLambdaProp") - @TestCase("foo.lambdaProp", "foo.voidMethod") - @TestCase("foo.lambdaProp", "foo.voidLambdaProp") - @TestCase("foo.lambdaProp", "function(this: void, s: string) { return s; }") - @TestCase("Foo.staticMethod", "func") - @TestCase("Foo.staticMethod", "lambda") - @TestCase("Foo.staticMethod", "Foo.voidStaticMethod") - @TestCase("Foo.staticMethod", "Foo.voidStaticLambdaProp") - @TestCase("Foo.staticMethod", "foo.voidMethod") - @TestCase("Foo.staticMethod", "foo.voidLambdaProp") - @TestCase("Foo.staticMethod", "function(this: void, s: string) { return s; }") - @TestCase("Foo.staticLambdaProp", "func") - @TestCase("Foo.staticLambdaProp", "lambda") - @TestCase("Foo.staticLambdaProp", "Foo.voidStaticMethod") - @TestCase("Foo.staticLambdaProp", "Foo.voidStaticLambdaProp") - @TestCase("Foo.staticLambdaProp", "foo.voidMethod") - @TestCase("Foo.staticLambdaProp", "foo.voidLambdaProp") - @TestCase("Foo.staticLambdaProp", "function(this: void, s: string) { return s; }") - @TestCase("thisFunc", "func") - @TestCase("thisFunc", "lambda") - @TestCase("thisFunc", "Foo.voidStaticMethod") - @TestCase("thisFunc", "Foo.voidStaticLambdaProp") - @TestCase("thisFunc", "foo.voidMethod") - @TestCase("thisFunc", "foo.voidLambdaProp") - @TestCase("thisFunc", "function(this: void, s: string) { return s; }") - @TestCase("thisLambda", "func") - @TestCase("thisLambda", "lambda") - @TestCase("thisLambda", "Foo.voidStaticMethod") - @TestCase("thisLambda", "Foo.voidStaticLambdaProp") - @TestCase("thisLambda", "foo.voidMethod") - @TestCase("thisLambda", "foo.voidLambdaProp") - @TestCase("thisLambda", "function(this: void, s: string) { return s; }") - @TestCase("foo.method", "(func as string | ((s: string) => string))") - @TestCase("foo.method", "<(this: Foo, s: string) => string>func") - @TestCase("foo.method", "func as ((this: Foo, s: string) => string)") - @Test("Invalid method assignment") - public invalidMethodAssignment(func: string, assignTo: string): void { - const code = `${AssignmentTests.funcAssignTestCode} ${func} = ${assignTo};`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from function to method. To fix, wrap the function in an arrow function or declare" - + " the function with an explicit 'this' parameter."); - } - - @TestCase("func") - @TestCase("lambda") - @TestCase("Foo.voidStaticMethod") - @TestCase("Foo.voidStaticLambdaProp") - @TestCase("foo.voidMethod") - @TestCase("foo.voidLambdaProp") - @TestCase("function(this: void, s: string) { return s; }") - @TestCase("func", "string | ((this: Foo, s: string) => string)") - @TestCase("func", "T") - @Test("Invalid method argument") - public invalidMethodArgument(func: string, funcType?: string): void { - if (!funcType) { - funcType = "(this: Foo, s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - declare function takesMethod string)>(meth: ${funcType}); - takesMethod(${func});`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from function to method \"meth\". To fix, wrap the function in an arrow function " - + "or declare the function with an explicit 'this' parameter."); - } - - @TestCase("<(this: Foo, s: string) => string>func") - @TestCase("func as ((this: Foo, s: string) => string)") - @Test("Invalid method argument cast") - public invalidMethodArgumentCast(func: string): void { - const code = `${AssignmentTests.funcAssignTestCode} - declare function takesMethod string)>( - meth: (this: Foo, s: string) => string); - takesMethod(${func});`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from function to method. To fix, wrap the function in an arrow function " - + "or declare the function with an explicit 'this' parameter."); - } - - @TestCase("func") - @TestCase("lambda") - @TestCase("Foo.voidStaticMethod") - @TestCase("Foo.voidStaticLambdaProp") - @TestCase("foo.voidMethod") - @TestCase("foo.voidLambdaProp") - @TestCase("function(this: void, s: string) { return s; }") - @TestCase("func", "string | ((this: Foo, s: string) => string)") - @TestCase("func", "T") - @TestCase("<(this: Foo, s: string) => string>func") - @TestCase("func as ((this: Foo, s: string) => string)") - @Test("Invalid method return") - public invalidMethodReturn(func: string, funcType?: string): void { - if (!funcType) { - funcType = "(this: Foo, s: string) => string"; - } - const code = `${AssignmentTests.funcAssignTestCode} - function returnsMethod string)>(): ${funcType} { - return ${func}; - }`; - Expect(() => util.transpileString(code)).toThrowError( - TranspileError, - "Unsupported conversion from function to method. To fix, wrap the function in an arrow function " - + "or declare the function with an explicit 'this' parameter."); + @TestCases(invalidTestFunctionCasts) + @Test("Invalid function return with cast") + public invalidFunctionReturnWithCast( + testFunction: TestFunction, + castedFunction: string, + isMethodConversion: boolean + ): void + { + const code = + `${testFunction.definition || ""} + function returnsFunction(): typeof ${testFunction.value} { + return ${castedFunction}; + }`; + const err = isMethodConversion + ? TSTLErrors.UnsupportedMethodConversion(undefined) + : TSTLErrors.UnsupportedFunctionConversion(undefined); + Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); } @Test("Interface method assignment") @@ -736,7 +682,7 @@ export class AssignmentTests { @Test("Valid function tuple assignment") public validFunctionTupleAssignment(): void { - const code = `interface Func { (s: string): string; } + const code = `interface Func { (this: void, s: string): string; } function getTuple(): [number, Func] { return [1, s => s]; } let [i, f]: [number, Func] = getTuple(); return f("foo");`; @@ -746,13 +692,14 @@ export class AssignmentTests { @Test("Invalid function tuple assignment") public invalidFunctionTupleAssignment(): void { - const code = `interface Func { (s: string): string; } + const code = `interface Func { (this: void, s: string): string; } interface Meth { (this: {}, s: string): string; } declare function getTuple(): [number, Meth]; let [i, f]: [number, Func] = getTuple();`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - "Unsupported conversion from method to function. To fix, wrap the method in an arrow function."); + TSTLErrors.UnsupportedFunctionConversion(undefined).message + ); } @Test("Valid method tuple assignment") @@ -770,14 +717,14 @@ export class AssignmentTests { @Test("Invalid method tuple assignment") public invalidMethodTupleAssignment(): void { - const code = `interface Func { (s: string): string; } + const code = `interface Func { (this: void, s: string): string; } interface Meth { (this: {}, s: string): string; } declare function getTuple(): [number, Func]; let [i, f]: [number, Meth] = getTuple();`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - "Unsupported conversion from function to method. To fix, wrap the function in an arrow function or declare" - + " the function with an explicit 'this' parameter."); + TSTLErrors.UnsupportedMethodConversion(undefined).message + ); } @Test("Valid interface method assignment") @@ -799,13 +746,14 @@ export class AssignmentTests { const b: B = a;`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - "Unsupported conversion from method to function \"fn\". To fix, wrap the method in an arrow function."); + TSTLErrors.UnsupportedFunctionConversion(undefined, "fn").message + ); } - @TestCase("(s: string) => string", ["foo"], "foobar") - @TestCase("{(s: string): string}", ["foo"], "foobar") - @TestCase("(s1: string, s2: string) => string", ["foo", "baz"], "foobaz") - @TestCase("{(s1: string, s2: string): string}", ["foo", "baz"], "foobaz") + @TestCase("(this: any, s: string) => string", ["foo"], "foobar") + @TestCase("{(this: any, s: string): string}", ["foo"], "foobar") + @TestCase("(this: any, s1: string, s2: string) => string", ["foo", "baz"], "foobaz") + @TestCase("{(this: any, s1: string, s2: string): string}", ["foo", "baz"], "foobaz") @Test("Valid function overload assignment") public validFunctionOverloadAssignment(assignType: string, args: string[], expectResult: string): void { const code = `interface O { @@ -819,22 +767,22 @@ export class AssignmentTests { Expect(result).toBe(expectResult); } - @TestCase("(s: string) => string") - @TestCase("(s1: string, s2: string) => string") - @TestCase("{(s: string): string}") - @TestCase("{(this: {}, s1: string, s2: string): string}") + @TestCase("(this: void, s: string) => string") + @TestCase("(this: void, s1: string, s2: string) => string") + @TestCase("{(this: void, s: string): string}") + @TestCase("{(this: any, s1: string, s2: string): string}") @Test("Invalid function overload assignment") public invalidFunctionOverloadAssignment(assignType: string): void { const code = `interface O { - (this: {}, s1: string, s2: string): string; - (s: string): string; + (this: any, s1: string, s2: string): string; + (this: void, s: string): string; } declare const o: O; let f: ${assignType} = o;`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - "Unsupported assignment of mixed function/method overload. " - + "Overloads should either be all functions or all methods, but not both."); + TSTLErrors.UnsupportedOverloadAssignment(undefined).message + ); } @TestCase("s => s") diff --git a/test/unit/declarations.spec.ts b/test/unit/declarations.spec.ts index ae7531aba..12bb8d8d6 100644 --- a/test/unit/declarations.spec.ts +++ b/test/unit/declarations.spec.ts @@ -8,7 +8,7 @@ export class DeclarationTests // Arrange const libLua = `function declaredFunction(x) return 3*x end`; - const tsHeader = `declare function declaredFunction(x: number): number;`; + const tsHeader = `declare function declaredFunction(this: void, x: number): number;`; const source = `return declaredFunction(2) + 4;`; @@ -26,7 +26,7 @@ export class DeclarationTests const tsHeader = ` /** @tupleReturn */ - declare function declaredFunction(x: number): [number, number];`; + declare function declaredFunction(this: void, x: number): [number, number];`; const source = ` const tuple = declaredFunction(3); @@ -48,7 +48,7 @@ export class DeclarationTests function myNameSpace.declaredFunction(x) return 3*x end `; - const tsHeader = `declare namespace myNameSpace { function declaredFunction(x: number): number; }`; + const tsHeader = `declare namespace myNameSpace { function declaredFunction(this: void, x: number): number; }`; const source = `return myNameSpace.declaredFunction(2) + 4;`; @@ -87,7 +87,8 @@ export class DeclarationTests public declarationFunctionCallback(): void { // Arrange const libLua = `function declaredFunction(callback) return callback(4) end`; - const tsHeader = `declare function declaredFunction(callback: (x: number) => number): number;`; + const tsHeader = + `declare function declaredFunction(this: void, callback: (this: void, x: number) => number): number;`; const source = `return declaredFunction(x => 2 * x);`; @@ -108,7 +109,7 @@ export class DeclarationTests const tsHeader = `declare interface MyInterface { - declaredFunction(callback: (x: number) => number): number; + declaredFunction(callback: (this: void, x: number) => number): number; } declare var myInstance: MyInterface;`; diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index f5a6ad416..417350ecc 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -64,4 +64,15 @@ export class LuaModuleTests { }`; Expect(util.transpileAndExecute("return a.b.foo;", undefined, undefined, code)).toBe("foo"); } + + @Test("Access this in module") + public accessThisInModule(): void { + const header = + `module M { + export const foo = "foo"; + export function bar() { return foo + "bar"; } + }`; + const code = `return M.bar();`; + Expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foobar"); + } } diff --git a/test/unit/objectLiteral.spec.ts b/test/unit/objectLiteral.spec.ts index dd94ff419..65c46410b 100644 --- a/test/unit/objectLiteral.spec.ts +++ b/test/unit/objectLiteral.spec.ts @@ -9,7 +9,7 @@ export class ObjectLiteralTests { @TestCase(`{"a":3,b:"4"}`, `{a = 3, b = "4"};`) @TestCase(`{["a"]:3,b:"4"}`, `{a = 3, b = "4"};`) @TestCase(`{["a"+123]:3,b:"4"}`, `{["a" .. 123] = 3, b = "4"};`) - @TestCase(`{[myFunc()]:3,b:"4"}`, `{[myFunc()] = 3, b = "4"};`) + @TestCase(`{[myFunc()]:3,b:"4"}`, `{[myFunc(_G)] = 3, b = "4"};`) @TestCase(`{x}`, `{x = x};`) @Test("Object Literal") public objectLiteral(inp: string, out: string): void { From f42a7bff5d539d97299f32ccbbc000580ee5ca64 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sat, 2 Mar 2019 11:17:03 -0700 Subject: [PATCH 02/11] fixed array lib issues --- src/lualib/ArrayFindIndex.ts | 3 ++- src/lualib/ArraySort.ts | 2 +- test/unit/lualib/lualib.spec.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lualib/ArrayFindIndex.ts b/src/lualib/ArrayFindIndex.ts index b60190c09..53d83cb39 100644 --- a/src/lualib/ArrayFindIndex.ts +++ b/src/lualib/ArrayFindIndex.ts @@ -1,6 +1,7 @@ function __TS__ArrayFindIndex( + this: void, arr: T[], - callbackFn: (this: void, element: T, index?: number, array?: T[]) => boolean + callbackFn: (element: T, index?: number, array?: T[]) => boolean ): number { for (let i = 0, len = arr.length; i < len; i++) { if (callbackFn(arr[i], i, arr)) { diff --git a/src/lualib/ArraySort.ts b/src/lualib/ArraySort.ts index 3721d819d..49fc196b0 100644 --- a/src/lualib/ArraySort.ts +++ b/src/lualib/ArraySort.ts @@ -1,5 +1,5 @@ declare namespace table { - function sort(this: void, arr: T[], compareFn?: (a: T, b: T) => boolean): void; + function sort(this: void, arr: T[], compareFn?: (this: void, a: T, b: T) => boolean): void; } function __TS__ArraySort(this: void, arr: T[], compareFn?: (a: T, b: T) => number): T[] { if (compareFn !== undefined) { diff --git a/test/unit/lualib/lualib.spec.ts b/test/unit/lualib/lualib.spec.ts index bb5c7f51e..59b4f4418 100644 --- a/test/unit/lualib/lualib.spec.ts +++ b/test/unit/lualib/lualib.spec.ts @@ -401,7 +401,7 @@ export class LuaLibTests return JSONStringify(testArray)`, undefined, undefined, - `declare function tonumber(e: any): number` + `declare function tonumber(this: void, e: any): number` ); // Assert From 12763c1ad8aaddb967b446317a24ddad1b1bb3c7 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 3 Mar 2019 06:44:10 -0700 Subject: [PATCH 03/11] a few small tweaks - fixed bad definition in one of the assign test functions - performing diagnostics on invalif function assignment tests - added check in transpileString to ensure ignoreDiagnostics command line flag is respected --- test/src/util.ts | 9 +++++++-- test/unit/assignments.spec.ts | 16 ++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/test/src/util.ts b/test/src/util.ts index 17ecb62cb..2437ddc94 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -15,7 +15,12 @@ export function transpileString( str: string, options?: CompilerOptions, ignoreDiagnostics = true, - filePath = "file.ts"): string { + filePath = "file.ts" +): string +{ + if (ignoreDiagnostics === false) { + ignoreDiagnostics = process.argv[2] === "--ignoreDiagnostics"; + } if (options) { if (options.noHeader === undefined) { options.noHeader = true; @@ -95,7 +100,7 @@ export function transpileAndExecute( { const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; ${tsHeader ? tsHeader : ""} - function __runTest(): any {${tsStr}}`; + function __runTest(this: void): any {${tsStr}}`; const lua = `${luaHeader ? luaHeader : ""} ${transpileString(wrappedTsString, compilerOptions, ignoreDiagnosticsOverride)} diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 6f50acc75..23075d279 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -44,7 +44,7 @@ const selfTestFunctions: TestFunction[] = [ }, { value: "anonFuncPropClass.anonFuncProp", - definition: `class AnonFuncPropClass { anonFuncProp: (this: any, s: string) => string = s => s; } + definition: `class AnonFuncPropClass { anonFuncProp: (s: string) => string = s => s; } const anonFuncPropClass = new AnonFuncPropClass();`, }, { @@ -474,7 +474,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined) : TSTLErrors.UnsupportedFunctionConversion(undefined); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @TestCases(validTestFunctionCasts) @@ -501,7 +501,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined) : TSTLErrors.UnsupportedFunctionConversion(undefined); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @TestCases(validTestFunctionAssignments) @@ -527,7 +527,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @TestCases(validTestFunctionCasts) @@ -556,7 +556,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } // TODO: Fix function expression inference with generic types. The following should work, but doesn't: @@ -591,7 +591,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @TestCases(anonTestFunctionExpressions.map(f => [f, "0", "'foobar'"])) @@ -630,7 +630,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined) : TSTLErrors.UnsupportedFunctionConversion(undefined); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @TestCases(validTestFunctionCasts) @@ -661,7 +661,7 @@ export class AssignmentTests { const err = isMethodConversion ? TSTLErrors.UnsupportedMethodConversion(undefined) : TSTLErrors.UnsupportedFunctionConversion(undefined); - Expect(() => util.transpileString(code)).toThrowError(TranspileError, err.message); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @Test("Interface method assignment") From 1a2761acc0fae3e055bee816e4fad3b4744e9d53 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 3 Mar 2019 16:07:00 -0700 Subject: [PATCH 04/11] split out searching for `@noSelf` to its own function --- src/TSHelper.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index f9cbe951b..4881d8c05 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -542,6 +542,22 @@ export class TSHelper { return signatureDeclarations; } + public static hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeChecker): boolean { + const scopeDeclaration = TSHelper.findFirstNodeAbove( + declaration, + (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration => + ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) + ); + if (!scopeDeclaration) { + return false; + } + const scopeType = checker.getTypeAtLocation(scopeDeclaration); + if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { + return true; + } + return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker); + } + public static getDeclarationContextType( signatureDeclaration: ts.SignatureDeclaration, checker: ts.TypeChecker @@ -555,22 +571,10 @@ export class TSHelper { : ContextType.NonVoid; } - let scopeDeclaration: ts.Declaration = signatureDeclaration; - while (true) { - scopeDeclaration = TSHelper.findFirstNodeAbove( - scopeDeclaration, - (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration => - ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) - ); - if (!scopeDeclaration) { - break; - } - - const scopeType = checker.getTypeAtLocation(scopeDeclaration); - if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { - return ContextType.Void; - } + if (TSHelper.hasNoSelfAncestor(signatureDeclaration, checker)) { + return ContextType.Void; } + return ContextType.NonVoid; } From 6145fc30a482e6cc4ec832b3b8bd2cd7916465cd Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 3 Mar 2019 16:07:25 -0700 Subject: [PATCH 05/11] Renamed and reworded function assignment errors to make sense in new context --- src/LuaTransformer.ts | 6 +++--- src/TSTLErrors.ts | 30 ++++++++++++++---------------- test/unit/assignments.spec.ts | 34 +++++++++++++++++----------------- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ec833fff0..ae3851507 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3848,7 +3848,7 @@ export class LuaTransformer { const expression = node.expression as ts.PropertyAccessExpression; const callerType = this.checker.getTypeAtLocation(expression.expression); if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) { - throw TSTLErrors.UnsupportedMethodConversion(node); + throw TSTLErrors.UnsupportedSelfFunctionConversion(node); } const params = this.transformArguments(node.arguments); const caller = this.transformExpression(expression.expression); @@ -4284,9 +4284,9 @@ export class LuaTransformer { throw TSTLErrors.UnsupportedOverloadAssignment(node, toName); } else if (fromContext !== toContext && fromContext !== ContextType.None && toContext !== ContextType.None) { if (toContext === ContextType.Void) { - throw TSTLErrors.UnsupportedFunctionConversion(node, toName); + throw TSTLErrors.UnsupportedNoSelfFunctionConversion(node, toName); } else { - throw TSTLErrors.UnsupportedMethodConversion(node, toName); + throw TSTLErrors.UnsupportedSelfFunctionConversion(node, toName); } } diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 53a00cce7..f666c3abe 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -78,32 +78,30 @@ export class TSTLErrors { public static UnsupportedForTarget = (functionality: string, version: string, node: ts.Node) => new TranspileError(`${functionality} is/are not supported for target Lua ${version}.`, node); - public static UnsupportedFunctionConversion = (node: ts.Node, name?: string) => { + public static UnsupportedNoSelfFunctionConversion = (node: ts.Node, name?: string) => { if (name) { return new TranspileError( - `Unsupported conversion from method to function "${name}". ` + - `To fix, wrap the method in an arrow function.`, + `Unable to convert function with a 'this' parameter to function "${name}" with no 'this'. ` + + `To fix, wrap in an arrow function, or declare with 'this: void'.`, node); } else { return new TranspileError( - `Unsupported conversion from method to function. ` + - `To fix, wrap the method in an arrow function.`, + `Unable to convert function with a 'this' parameter to function with no 'this'. ` + + `To fix, wrap in an arrow function, or declare with 'this: void'.`, node); } }; - public static UnsupportedMethodConversion = (node: ts.Node, name?: string) => { + public static UnsupportedSelfFunctionConversion = (node: ts.Node, name?: string) => { if (name) { return new TranspileError( - `Unsupported conversion from function to method "${name}". ` + - `To fix, wrap the function in an arrow function or declare the function with` + - ` an explicit 'this' parameter.`, + `Unable to convert function with no 'this' parameter to function "${name}" with 'this'. ` + + `To fix, wrap in an arrow function or declare with 'this: any'.`, node); } else { return new TranspileError( - `Unsupported conversion from function to method. ` + - `To fix, wrap the function in an arrow function or declare the function with` + - ` an explicit 'this' parameter.`, + `Unable to convert function with no 'this' parameter to function with 'this'. ` + + `To fix, wrap in an arrow function or declare with 'this: any'.`, node); } }; @@ -111,13 +109,13 @@ export class TSTLErrors { public static UnsupportedOverloadAssignment = (node: ts.Node, name?: string) => { if (name) { return new TranspileError( - `Unsupported assignment of mixed function/method overload to "${name}". ` + - `Overloads should either be all functions or all methods, but not both.`, + `Unsupported assignment of function with different overloaded types for 'this' to "${name}". ` + + `Overloads should all have the same type for 'this'.`, node); } else { return new TranspileError( - `Unsupported assignment of mixed function/method overload. ` + - `Overloads should either be all functions or all methods, but not both.`, + `Unsupported assignment of function with different overloaded types for 'this'. ` + + `Overloads should all have the same type for 'this'.`, node); } }; diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 23075d279..155d5e2db 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -472,8 +472,8 @@ export class AssignmentTests { ${valueFunction.definition || ""} ${assigneeFunction.value} = ${valueFunction.value};`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined) - : TSTLErrors.UnsupportedFunctionConversion(undefined); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -499,8 +499,8 @@ export class AssignmentTests { let fn: typeof ${testFunction.value}; fn = ${castedFunction};`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined) - : TSTLErrors.UnsupportedFunctionConversion(undefined); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -525,8 +525,8 @@ export class AssignmentTests { ${testFunction.definition || ""} takesFunction(${testFunction.value});`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") - : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -554,8 +554,8 @@ export class AssignmentTests { declare function takesFunction(fn: typeof ${testFunction.value}); takesFunction(${castedFunction});`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") - : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -589,8 +589,8 @@ export class AssignmentTests { ${testFunction.definition || ""} takesFunction(${testFunction.value});`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined, "fn") - : TSTLErrors.UnsupportedFunctionConversion(undefined, "fn"); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -628,8 +628,8 @@ export class AssignmentTests { return ${testFunction.value}; }`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined) - : TSTLErrors.UnsupportedFunctionConversion(undefined); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -659,8 +659,8 @@ export class AssignmentTests { return ${castedFunction}; }`; const err = isMethodConversion - ? TSTLErrors.UnsupportedMethodConversion(undefined) - : TSTLErrors.UnsupportedFunctionConversion(undefined); + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } @@ -698,7 +698,7 @@ export class AssignmentTests { let [i, f]: [number, Func] = getTuple();`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - TSTLErrors.UnsupportedFunctionConversion(undefined).message + TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined).message ); } @@ -723,7 +723,7 @@ export class AssignmentTests { let [i, f]: [number, Meth] = getTuple();`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - TSTLErrors.UnsupportedMethodConversion(undefined).message + TSTLErrors.UnsupportedSelfFunctionConversion(undefined).message ); } @@ -746,7 +746,7 @@ export class AssignmentTests { const b: B = a;`; Expect(() => util.transpileString(code)).toThrowError( TranspileError, - TSTLErrors.UnsupportedFunctionConversion(undefined, "fn").message + TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn").message ); } From 4fc1982f7451519940891061314caeead836f76b Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 5 Mar 2019 15:26:29 -0700 Subject: [PATCH 06/11] Replace O(N^2) function assignment tests with more focused ones to reduce overall amount of testing required. I beleive this should still catch all the known edge-cases. --- test/unit/assignments.spec.ts | 102 ++++++++++++++++------------------ 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 155d5e2db..3e8088725 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1,4 +1,4 @@ -import { Expect, Test, TestCase, TestCases } from "alsatian"; +import { Expect, Test, TestCase, TestCases, FocusTest } from "alsatian"; import { TranspileError } from "../../src/TranspileError"; import * as util from "../src/util"; @@ -198,7 +198,7 @@ const noSelfTestFunctionType = "(this: void, s: string) => string"; type TestFunctionCast = [ /*testFunction: */TestFunction, /*castedFunction: */string, - /*isMethodConversion?: */boolean? + /*isSelfConversion?: */boolean? ]; const validTestFunctionCasts: TestFunctionCast[] = [ ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`]), @@ -220,7 +220,7 @@ const invalidTestFunctionCasts: TestFunctionCast[] = [ type TestFunctionAssignment = [ /*testFunction: */TestFunction, /*functionType: */string, - /*isMethodConversion?: */boolean? + /*isSelfConversion?: */boolean? ]; const validTestFunctionAssignments: TestFunctionAssignment[] = [ ...selfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), @@ -242,18 +242,6 @@ const invalidTestFunctionAssignments: TestFunctionAssignment[] = [ ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), ]; -function *testFunctionCombinations(functionsA: TestFunction[], functionsB: TestFunction[], ...args: any[]) - : IterableIterator<[TestFunction, TestFunction, ...any[]]> -{ - for (const assigneeFunction of functionsA) { - for (const valueFunction of functionsB) { - if (assigneeFunction !== valueFunction) { - yield [assigneeFunction, valueFunction, ...args]; - } - } - } -} - export class AssignmentTests { @TestCase(`"abc"`, `"abc"`) @@ -437,41 +425,49 @@ export class AssignmentTests { .toThrowError(TranspileError, `Cannot use Lua keyword ${identifier} as identifier.`); } - @TestCases(testFunctionCombinations(selfTestFunctions, selfTestFunctions)) - @TestCases(testFunctionCombinations(selfTestFunctions, selfTestFunctionExpressions)) - @TestCases(testFunctionCombinations(selfTestFunctions, anonTestFunctionExpressions)) - @TestCases(testFunctionCombinations(noSelfTestFunctions, noSelfTestFunctions)) - @TestCases(testFunctionCombinations(noSelfTestFunctions, noSelfTestFunctionExpressions)) - @TestCases(testFunctionCombinations(noSelfTestFunctions, anonTestFunctionExpressions)) + @TestCases(validTestFunctionAssignments) + @Test("Valid function variable declaration") + public validFunctionDeclaration(testFunction: TestFunction, functionType: string): void { + const code = + `const fn: ${functionType} = ${testFunction.value}; + return fn("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + } + + @TestCases(validTestFunctionAssignments) @Test("Valid function assignment") - public validFunctionAssignment(assigneeFunction: TestFunction, valueFunction: TestFunction) + public validFunctionAssignment(testFunction: TestFunction, functionType: string): void { + const code = + `let fn: ${functionType}; + fn = ${testFunction.value}; + return fn("foobar");`; + Expect(util.transpileAndExecute(code, undefined, undefined, testFunction.definition)).toBe("foobar"); + } + + @TestCases(invalidTestFunctionAssignments) + @Test("Invalid function variable declaration") + public invalidFunctionDeclaration(testFunction: TestFunction, functionType: string, isSelfConversion: boolean) : void { - const header = - `${assigneeFunction.definition || ""} - ${valueFunction.definition || ""}`; const code = - `${assigneeFunction.value} = ${valueFunction.value}; - return ${assigneeFunction.value}("foobar");`; - Expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foobar"); + `${testFunction.definition || ""} + const fn: ${functionType} = ${testFunction.value};`; + const err = isSelfConversion + ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) + : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); + Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); } - @TestCases(testFunctionCombinations(selfTestFunctions, noSelfTestFunctions, true)) - @TestCases(testFunctionCombinations(selfTestFunctions, noSelfTestFunctionExpressions, true)) - @TestCases(testFunctionCombinations(noSelfTestFunctions, selfTestFunctions, false)) - @TestCases(testFunctionCombinations(noSelfTestFunctions, selfTestFunctionExpressions, false)) + @TestCases(invalidTestFunctionAssignments) @Test("Invalid function assignment") - public invalidFunctionAssignment( - assigneeFunction: TestFunction, - valueFunction: TestFunction, - isMethodConversion: boolean - ): void + public invalidFunctionAssignment(testFunction: TestFunction, functionType: string, isSelfConversion: boolean) + : void { const code = - `${assigneeFunction.definition || ""} - ${valueFunction.definition || ""} - ${assigneeFunction.value} = ${valueFunction.value};`; - const err = isMethodConversion + `${testFunction.definition || ""} + let fn: ${functionType}; + fn = ${testFunction.value};`; + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -492,13 +488,13 @@ export class AssignmentTests { public invalidFunctionAssignmentWithCast( testFunction: TestFunction, castedFunction: string, - isMethodConversion: boolean + isSelfConversion: boolean ): void { const code = `${testFunction.definition || ""} let fn: typeof ${testFunction.value}; fn = ${castedFunction};`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -517,14 +513,14 @@ export class AssignmentTests { @TestCases(invalidTestFunctionAssignments) @Test("Invalid function argument") - public invalidFunctionArgument(testFunction: TestFunction, functionType: string, isMethodConversion: boolean) + public invalidFunctionArgument(testFunction: TestFunction, functionType: string, isSelfConversion: boolean) : void { const code = `declare function takesFunction(fn: ${functionType}); ${testFunction.definition || ""} takesFunction(${testFunction.value});`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -546,14 +542,14 @@ export class AssignmentTests { public invalidFunctionArgumentWithCast( testFunction: TestFunction, castedFunction: string, - isMethodConversion: boolean + isSelfConversion: boolean ): void { const code = `${testFunction.definition || ""} declare function takesFunction(fn: typeof ${testFunction.value}); takesFunction(${castedFunction});`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -581,14 +577,14 @@ export class AssignmentTests { @TestCases(invalidTestFunctionAssignments) @Test("Invalid function generic argument") - public invalidFunctionGenericArgument(testFunction: TestFunction, functionType: string, isMethodConversion: boolean) + public invalidFunctionGenericArgument(testFunction: TestFunction, functionType: string, isSelfConversion: boolean) : void { const code = `declare function takesFunction(fn: T); ${testFunction.definition || ""} takesFunction(${testFunction.value});`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined, "fn"); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -621,13 +617,13 @@ export class AssignmentTests { @TestCases(invalidTestFunctionAssignments) @Test("Invalid function return") - public invalidFunctionReturn(testFunction: TestFunction, functionType: string, isMethodConversion: boolean): void { + public invalidFunctionReturn(testFunction: TestFunction, functionType: string, isSelfConversion: boolean): void { const code = `${testFunction.definition || ""} function returnsFunction(): ${functionType} { return ${testFunction.value}; }`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); @@ -650,7 +646,7 @@ export class AssignmentTests { public invalidFunctionReturnWithCast( testFunction: TestFunction, castedFunction: string, - isMethodConversion: boolean + isSelfConversion: boolean ): void { const code = @@ -658,7 +654,7 @@ export class AssignmentTests { function returnsFunction(): typeof ${testFunction.value} { return ${castedFunction}; }`; - const err = isMethodConversion + const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined) : TSTLErrors.UnsupportedNoSelfFunctionConversion(undefined); Expect(() => util.transpileString(code, undefined, false)).toThrowError(TranspileError, err.message); From b642310bdea38539045939a898221a9ecffcd7be Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Tue, 5 Mar 2019 15:36:03 -0700 Subject: [PATCH 07/11] fixed `this` in module test to actually access `this` and removed a leftover FocusTest reference --- test/unit/assignments.spec.ts | 2 +- test/unit/modules.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 3e8088725..0879d97c0 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -1,4 +1,4 @@ -import { Expect, Test, TestCase, TestCases, FocusTest } from "alsatian"; +import { Expect, Test, TestCase, TestCases } from "alsatian"; import { TranspileError } from "../../src/TranspileError"; import * as util from "../src/util"; diff --git a/test/unit/modules.spec.ts b/test/unit/modules.spec.ts index 417350ecc..606e39985 100644 --- a/test/unit/modules.spec.ts +++ b/test/unit/modules.spec.ts @@ -70,7 +70,7 @@ export class LuaModuleTests { const header = `module M { export const foo = "foo"; - export function bar() { return foo + "bar"; } + export function bar() { return this.foo + "bar"; } }`; const code = `return M.bar();`; Expect(util.transpileAndExecute(code, undefined, undefined, header)).toBe("foobar"); From aa19e6bb8e9ab89e490e3a53fd2e7f8c586fd733 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 6 Mar 2019 10:56:07 -0700 Subject: [PATCH 08/11] limited @noSelf recursion to namespaces and fixed @noSelf on class expressions --- src/TSHelper.ts | 7 +++++-- test/unit/assignments.spec.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index c93db3ab1..f3aadb3a3 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -549,7 +549,7 @@ export class TSHelper { const scopeDeclaration = TSHelper.findFirstNodeAbove( declaration, (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration => - ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) + ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n) ); if (!scopeDeclaration) { return false; @@ -558,7 +558,10 @@ export class TSHelper { if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { return true; } - return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker); + if (ts.isModuleDeclaration(scopeDeclaration)) { + return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker); // Recurse namespaces + } + return false; } public static getDeclarationContextType( diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index cb0f6d69a..398386f33 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -91,6 +91,15 @@ const selfTestFunctions: TestFunction[] = [ export namespace NestedNs { export let nestedNsLambda: (s: string) => string = s => s } }`, }, + { + value: "anonMethodClassInNoSelfNs.method", + definition: `/** @noSelf */ namespace AnonMethodClassInNoSelfNs { + export class MethodClass { + method(s: string): string { return s; } + } + } + const anonMethodClassInNoSelfNs = new AnonMethodClassInNoSelfNs.MethodClass();`, + }, ]; const noSelfTestFunctions: TestFunction[] = [ @@ -172,6 +181,13 @@ const noSelfTestFunctions: TestFunction[] = [ static noSelfStaticFuncProp(s: string): string { return s; } }`, }, + { + value: "noSelfMethodClassExpression.noSelfMethod", + definition: `/** @noSelf */ const NoSelfMethodClassExpression = class { + noSelfMethod(s: string): string { return s; } + } + const noSelfMethodClassExpression = new NoSelfMethodClassExpression();`, + }, ]; const anonTestFunctionExpressions: TestFunction[] = [ From 684789550ad0e6d5e018bb164516e64053cbcd2e Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Wed, 6 Mar 2019 12:20:22 -0700 Subject: [PATCH 09/11] @noSelf support on interfaces and added/fixed more tests --- src/TSHelper.ts | 7 +- test/unit/assignments.spec.ts | 139 +++++++++++++++++++++++++--------- 2 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index f3aadb3a3..9b73bffdf 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -548,8 +548,11 @@ export class TSHelper { public static hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeChecker): boolean { const scopeDeclaration = TSHelper.findFirstNodeAbove( declaration, - (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration => - ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n) + (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration | ts.InterfaceDeclaration => + ts.isModuleDeclaration(n) + || ts.isClassDeclaration(n) + || ts.isClassExpression(n) + || ts.isInterfaceDeclaration(n) ); if (!scopeDeclaration) { return false; diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 398386f33..6fdca6834 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -50,8 +50,8 @@ const selfTestFunctions: TestFunction[] = [ { value: "StaticMethodClass.staticMethod", definition: `class StaticMethodClass { - static staticMethod(this: any, s: string): string { return s; } - }`, + static staticMethod(this: any, s: string): string { return s; } + }`, }, { value: "AnonStaticMethodClass.anonStaticMethod", @@ -60,14 +60,14 @@ const selfTestFunctions: TestFunction[] = [ { value: "StaticFuncPropClass.staticFuncProp", definition: `class StaticFuncPropClass { - static staticFuncProp: (this: any, s: string) => string = s => s; - }`, + static staticFuncProp: (this: any, s: string) => string = s => s; + }`, }, { value: "AnonStaticFuncPropClass.anonStaticFuncProp", definition: `class AnonStaticFuncPropClass { - static anonStaticFuncProp: (s: string) => string = s => s; - }`, + static anonStaticFuncProp: (s: string) => string = s => s; + }`, }, { value: "FuncNs.nsFunc", @@ -76,29 +76,62 @@ const selfTestFunctions: TestFunction[] = [ { value: "FuncNestedNs.NestedNs.nestedNsFunc", definition: `namespace FuncNestedNs { - export namespace NestedNs { export function nestedNsFunc(s: string) { return s; } } - }`, + export namespace NestedNs { export function nestedNsFunc(s: string) { return s; } } + }`, }, { value: "LambdaNs.nsLambda", definition: `namespace LambdaNs { - export let nsLambda: (s: string) => string = s => s; - }`, + export let nsLambda: (s: string) => string = s => s; + }`, }, { value: "LambdaNestedNs.NestedNs.nestedNsLambda", definition: `namespace LambdaNestedNs { - export namespace NestedNs { export let nestedNsLambda: (s: string) => string = s => s } - }`, + export namespace NestedNs { export let nestedNsLambda: (s: string) => string = s => s } + }`, + }, + { + value: "methodInterface.method", + definition: `interface MethodInterface { method(this: any, s: string): string; } + const methodInterface: MethodInterface = { method: function(this: any, s: string): string { return s; } }`, + }, + { + value: "anonMethodInterface.anonMethod", + definition: `interface AnonMethodInterface { anonMethod(s: string): string; } + const anonMethodInterface: AnonMethodInterface = { + anonMethod: function(this: any, s: string): string { return s; } + };`, + }, + { + value: "funcPropInterface.funcProp", + definition: `interface FuncPropInterface { funcProp: (this: any, s: string) => string; } + const funcPropInterface: FuncPropInterface = { funcProp: function(this: any, s: string) { return s; } };`, + }, + { + value: "anonFuncPropInterface.anonFuncProp", + definition: `interface AnonFuncPropInterface { anonFuncProp: (s: string) => string; } + const anonFuncPropInterface: AnonFuncPropInterface = { anonFuncProp: (s: string): string => s };`, }, { value: "anonMethodClassInNoSelfNs.method", definition: `/** @noSelf */ namespace AnonMethodClassInNoSelfNs { - export class MethodClass { - method(s: string): string { return s; } + export class MethodClass { + method(s: string): string { return s; } + } + } + const anonMethodClassInNoSelfNs = new AnonMethodClassInNoSelfNs.MethodClass();`, + }, + { + value: "anonMethodInterfaceInNoSelfNs.method", + definition: `/** @noSelf */ namespace AnonMethodInterfaceInNoSelfNs { + export interface MethodInterface { + method(s: string): string; + } } - } - const anonMethodClassInNoSelfNs = new AnonMethodClassInNoSelfNs.MethodClass();`, + const anonMethodInterfaceInNoSelfNs: AnonMethodInterfaceInNoSelfNs.MethodInterface = { + method: function(s: string): string { return s; } + };`, }, ]; @@ -114,28 +147,28 @@ const noSelfTestFunctions: TestFunction[] = [ { value: "voidMethodClass.voidMethod", definition: `class VoidMethodClass { - voidMethod(this: void, s: string): string { return s; } - } - const voidMethodClass = new VoidMethodClass();`, + voidMethod(this: void, s: string): string { return s; } + } + const voidMethodClass = new VoidMethodClass();`, }, { value: "voidFuncPropClass.voidFuncProp", definition: `class VoidFuncPropClass { - voidFuncProp: (this: void, s: string) => string = s => s; - } - const voidFuncPropClass = new VoidFuncPropClass();`, + voidFuncProp: (this: void, s: string) => string = s => s; + } + const voidFuncPropClass = new VoidFuncPropClass();`, }, { value: "StaticVoidMethodClass.staticVoidMethod", definition: `class StaticVoidMethodClass { - static staticVoidMethod(this: void, s: string): string { return s; } - }`, + static staticVoidMethod(this: void, s: string): string { return s; } + }`, }, { value: "StaticVoidFuncPropClass.staticVoidFuncProp", definition: `class StaticVoidFuncPropClass { - static staticVoidFuncProp: (this: void, s: string) => string = s => s; - }`, + static staticVoidFuncProp: (this: void, s: string) => string = s => s; + }`, }, { value: "NoSelfFuncNs.noSelfNsFunc", @@ -144,20 +177,20 @@ const noSelfTestFunctions: TestFunction[] = [ { value: "NoSelfFuncNestedNs.NestedNs.noSelfNestedNsFunc", definition: `/** @noSelf */ namespace NoSelfFuncNestedNs { - export namespace NestedNs { export function noSelfNestedNsFunc(s: string) { return s; } } - }`, + export namespace NestedNs { export function noSelfNestedNsFunc(s: string) { return s; } } + }`, }, { value: "NoSelfLambdaNs.noSelfNsLambda", definition: `/** @noSelf */ namespace NoSelfLambdaNs { - export let noSelfNsLambda: (s: string) => string = s => s; - }`, + export let noSelfNsLambda: (s: string) => string = s => s; + }`, }, { value: "NoSelfLambdaNestedNs.NestedNs.noSelfNestedNsLambda", definition: `/** @noSelf */ namespace NoSelfLambdaNestedNs { - export namespace NestedNs { export let noSelfNestedNsLambda: (s: string) => string = s => s } - }`, + export namespace NestedNs { export let noSelfNestedNsLambda: (s: string) => string = s => s } + }`, }, { value: "noSelfMethodClass.noSelfMethod", @@ -167,19 +200,51 @@ const noSelfTestFunctions: TestFunction[] = [ { value: "NoSelfStaticMethodClass.noSelfStaticMethod", definition: `/** @noSelf */ class NoSelfStaticMethodClass { - static noSelfStaticMethod(s: string): string { return s; } - }`, + static noSelfStaticMethod(s: string): string { return s; } + }`, }, { value: "noSelfFuncPropClass.noSelfFuncProp", - definition: `/** @noSelf */ class NoSelfFuncPropClass { noSelfFuncProp(s: string): string { return s; } } + definition: `/** @noSelf */ class NoSelfFuncPropClass { noSelfFuncProp: (s: string) => string = s => s; } const noSelfFuncPropClass = new NoSelfFuncPropClass();`, }, { value: "NoSelfStaticFuncPropClass.noSelfStaticFuncProp", definition: `/** @noSelf */ class NoSelfStaticFuncPropClass { - static noSelfStaticFuncProp(s: string): string { return s; } - }`, + static noSelfStaticFuncProp: (s: string) => string = s => s; + }`, + }, + { + value: "voidMethodInterface.voidMethod", + definition: `interface VoidMethodInterface { + voidMethod(this: void, s: string): string; + } + const voidMethodInterface: VoidMethodInterface = { + voidMethod(this: void, s: string): string { return s; } + };`, + }, + { + value: "voidFuncPropInterface.voidFuncProp", + definition: `interface VoidFuncPropInterface { + voidFuncProp: (this: void, s: string) => string; + } + const voidFuncPropInterface: VoidFuncPropInterface = { + voidFuncProp: function(this: void, s: string): string { return s; } + };`, + }, + { + value: "noSelfMethodInterface.noSelfMethod", + definition: `/** @noSelf */ interface NoSelfMethodInterface { noSelfMethod(s: string): string; } + const noSelfMethodInterface: NoSelfMethodInterface = { + noSelfMethod: function(s: string): string { return s; } + };`, + }, + { + value: "noSelfFuncPropInterface.noSelfFuncProp", + definition: `/** @noSelf */ interface NoSelfFuncPropInterface { noSelfFuncProp(s: string): string; } + const noSelfFuncPropInterface: NoSelfFuncPropInterface = { + noSelfFuncProp: (s: string): string => s + };`, }, { value: "noSelfMethodClassExpression.noSelfMethod", From 71b60edd79fbb6255062f0f19b143ffb41e990fc Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Thu, 7 Mar 2019 06:19:48 -0700 Subject: [PATCH 10/11] added @noSelfInFile file-scope directive --- src/Decorator.ts | 3 +++ src/TSHelper.ts | 23 ++++++++++++++++++-- test/src/util.ts | 4 ++-- test/unit/assignments.spec.ts | 40 +++++++++++++++++++++++++++++++---- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/Decorator.ts b/src/Decorator.ts index 84066caa6..ff8ab59b4 100644 --- a/src/Decorator.ts +++ b/src/Decorator.ts @@ -25,6 +25,8 @@ export class Decorator { return DecoratorKind.LuaIterator; case "noself": return DecoratorKind.NoSelf; + case "noselfinfile": + return DecoratorKind.NoSelfInFile; } return undefined; @@ -50,4 +52,5 @@ export enum DecoratorKind { NoClassOr = "NoClassOr", LuaIterator = "LuaIterator", NoSelf = "NoSelf", + NoSelfInFile = "NoSelfInFile", } diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 9b73bffdf..da72ee42d 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -249,6 +249,21 @@ export class TSHelper { return decMap; } + public static getCustomFileDirectives(file: ts.SourceFile): Map { + const decMap = new Map(); + if (file.statements.length > 0) { + const tags = ts.getJSDocTags(file.statements[0]); + for (const tag of tags) { + const tagName = tag.tagName.escapedText as string; + if (Decorator.isValid(tagName)) { + const dec = new Decorator(tagName, tag.comment ? tag.comment.split(" ") : []); + decMap.set(dec.kind, dec); + } + } + } + return decMap; + } + // Search up until finding a node satisfying the callback public static findFirstNodeAbove(node: ts.Node, callback: (n: ts.Node) => n is T): T { let current = node; @@ -548,8 +563,9 @@ export class TSHelper { public static hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeChecker): boolean { const scopeDeclaration = TSHelper.findFirstNodeAbove( declaration, - (n): n is ts.ModuleDeclaration | ts.ClassLikeDeclaration | ts.InterfaceDeclaration => - ts.isModuleDeclaration(n) + (n): n is ts.SourceFile | ts.ModuleDeclaration | ts.ClassLikeDeclaration | ts.InterfaceDeclaration => + ts.isSourceFile(n) + || ts.isModuleDeclaration(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n) || ts.isInterfaceDeclaration(n) @@ -557,6 +573,9 @@ export class TSHelper { if (!scopeDeclaration) { return false; } + if (ts.isSourceFile(scopeDeclaration)) { + return TSHelper.getCustomFileDirectives(scopeDeclaration).has(DecoratorKind.NoSelfInFile); + } const scopeType = checker.getTypeAtLocation(scopeDeclaration); if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { return true; diff --git a/test/src/util.ts b/test/src/util.ts index 2437ddc94..c3706d092 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -98,8 +98,8 @@ export function transpileAndExecute( ignoreDiagnosticsOverride = process.argv[2] === "--ignoreDiagnostics" ): any { - const wrappedTsString = `declare function JSONStringify(this: void, p: any): string; - ${tsHeader ? tsHeader : ""} + const wrappedTsString = `${tsHeader ? tsHeader : ""} + declare function JSONStringify(this: void, p: any): string; function __runTest(this: void): any {${tsStr}}`; const lua = `${luaHeader ? luaHeader : ""} diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 6fdca6834..f7bcdaad2 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -255,6 +255,29 @@ const noSelfTestFunctions: TestFunction[] = [ }, ]; +const noSelfInFileTestFunctions: TestFunction[] = [ + { + value: "noSelfInFileFunc", + definition: `/** @noSelfInFile */ let noSelfInFileFunc: {(s: string): string} = function(s) { return s; };`, + }, + { + value: "noSelfInFileLambda", + definition: `/** @noSelfInFile */ let noSelfInFileLambda: (s: string) => string = s => s;`, + }, + { + value: "NoSelfInFileFuncNs.noSelfInFileNsFunc", + definition: `/** @noSelfInFile */ namespace NoSelfInFileFuncNs { + export function noSelfInFileNsFunc(s: string) { return s; } + }`, + }, + { + value: "NoSelfInFileLambdaNs.noSelfInFileNsLambda", + definition: `/** @noSelfInFile */ namespace NoSelfInFileLambdaNs { + export let noSelfInFileNsLambda: (s: string) => string = s => s; + }`, + }, +]; + const anonTestFunctionExpressions: TestFunction[] = [ {value: `s => s`}, {value: `(s => s)`}, @@ -288,12 +311,18 @@ const validTestFunctionCasts: TestFunctionCast[] = [ ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`]), ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`]), ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`, false]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`, false]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`]), ]; const invalidTestFunctionCasts: TestFunctionCast[] = [ ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`, false]), ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`, false]), ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`, false]), ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`, false]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`, false]), + ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`, false]), ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`, true]), ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`, true]), ]; @@ -307,6 +336,8 @@ const validTestFunctionAssignments: TestFunctionAssignment[] = [ ...selfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), ...selfTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), + ...noSelfInFileTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), + ...noSelfInFileTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType]), ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType]), ...anonTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType]), @@ -318,6 +349,7 @@ const invalidTestFunctionAssignments: TestFunctionAssignment[] = [ ...selfTestFunctions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType, false]), ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, anonTestFunctionType, true]), ...noSelfTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), + ...noSelfInFileTestFunctions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), ...selfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, noSelfTestFunctionType, false]), ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, anonTestFunctionType, true]), ...noSelfTestFunctionExpressions.map((f): TestFunctionAssignment => [f, selfTestFunctionType, true]), @@ -598,8 +630,8 @@ export class AssignmentTests { : void { const code = - `declare function takesFunction(fn: ${functionType}); - ${testFunction.definition || ""} + `${testFunction.definition || ""} + declare function takesFunction(fn: ${functionType}); takesFunction(${testFunction.value});`; const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") @@ -662,8 +694,8 @@ export class AssignmentTests { : void { const code = - `declare function takesFunction(fn: T); - ${testFunction.definition || ""} + `${testFunction.definition || ""} + declare function takesFunction(fn: T); takesFunction(${testFunction.value});`; const err = isSelfConversion ? TSTLErrors.UnsupportedSelfFunctionConversion(undefined, "fn") From c3b3c657253c8e6a6bc971d474e7655939f5e533 Mon Sep 17 00:00:00 2001 From: Tom <26638278+tomblind@users.noreply.github.com> Date: Sun, 10 Mar 2019 07:22:16 -0600 Subject: [PATCH 11/11] only preventing noSelf recursion on methods now and cleaned up some tests --- src/TSHelper.ts | 35 +++++++++++----- test/unit/assignments.spec.ts | 77 +++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 58d773285..835ed0f1b 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -574,12 +574,7 @@ export class TSHelper { public static hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeChecker): boolean { const scopeDeclaration = TSHelper.findFirstNodeAbove( declaration, - (n): n is ts.SourceFile | ts.ModuleDeclaration | ts.ClassLikeDeclaration | ts.InterfaceDeclaration => - ts.isSourceFile(n) - || ts.isModuleDeclaration(n) - || ts.isClassDeclaration(n) - || ts.isClassExpression(n) - || ts.isInterfaceDeclaration(n) + (n): n is ts.SourceFile | ts.ModuleDeclaration => ts.isSourceFile(n) || ts.isModuleDeclaration(n) ); if (!scopeDeclaration) { return false; @@ -591,10 +586,7 @@ export class TSHelper { if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { return true; } - if (ts.isModuleDeclaration(scopeDeclaration)) { - return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker); // Recurse namespaces - } - return false; + return TSHelper.hasNoSelfAncestor(scopeDeclaration, checker); } public static getDeclarationContextType( @@ -610,6 +602,29 @@ export class TSHelper { : ContextType.NonVoid; } + if (ts.isMethodSignature(signatureDeclaration) + || ts.isMethodDeclaration(signatureDeclaration) + || ts.isConstructSignatureDeclaration(signatureDeclaration) + || ts.isConstructorDeclaration(signatureDeclaration) + || (signatureDeclaration.parent && ts.isPropertyDeclaration(signatureDeclaration.parent)) + || (signatureDeclaration.parent && ts.isPropertySignature(signatureDeclaration.parent))) + { + // Class/interface methods only respect @noSelf on their parent + const scopeDeclaration = TSHelper.findFirstNodeAbove( + signatureDeclaration, + (n): n is ts.ClassLikeDeclaration | ts.InterfaceDeclaration => + ts.isClassDeclaration(n) + || ts.isClassExpression(n) + || ts.isInterfaceDeclaration(n) + ); + const scopeType = checker.getTypeAtLocation(scopeDeclaration); + if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { + return ContextType.Void; + } + return ContextType.NonVoid; + } + + // Walk up to find @noSelf or @noSelfOnFile if (TSHelper.hasNoSelfAncestor(signatureDeclaration, checker)) { return ContextType.Void; } diff --git a/test/unit/assignments.spec.ts b/test/unit/assignments.spec.ts index 9238a7276..4e584cf40 100644 --- a/test/unit/assignments.spec.ts +++ b/test/unit/assignments.spec.ts @@ -133,6 +133,13 @@ const selfTestFunctions: TestFunction[] = [ method: function(s: string): string { return s; } };`, }, + { + value: "anonFunctionNestedInNoSelfClass", + definition: `/** @noSelf */ class AnonFunctionNestedInNoSelfClass { + method() { return function(s: string) { return s; } } + } + const anonFunctionNestedInNoSelfClass = (new AnonFunctionNestedInNoSelfClass).method();`, + }, ]; const noSelfTestFunctions: TestFunction[] = [ @@ -253,6 +260,16 @@ const noSelfTestFunctions: TestFunction[] = [ } const noSelfMethodClassExpression = new NoSelfMethodClassExpression();`, }, + { + value: "anonFunctionNestedInClassInNoSelfNs", + definition: `/** @noSelf */ namespace AnonFunctionNestedInClassInNoSelfNs { + export class AnonFunctionNestedInClass { + method() { return function(s: string) { return s; } } + } + } + const anonFunctionNestedInClassInNoSelfNs = + (new AnonFunctionNestedInClassInNoSelfNs.AnonFunctionNestedInClass).method();`, + }, ]; const noSelfInFileTestFunctions: TestFunction[] = [ @@ -276,6 +293,13 @@ const noSelfInFileTestFunctions: TestFunction[] = [ export let noSelfInFileNsLambda: (s: string) => string = s => s; }`, }, + { + value: "noSelfInFileFuncNestedInClass", + definition: `/** @noSelfInFile */ class NoSelfInFileFuncNestedInClass { + method() { return function(s: string) { return s; } } + } + const noSelfInFileFuncNestedInClass = (new NoSelfInFileFuncNestedInClass).method();`, + }, ]; const anonTestFunctionExpressions: TestFunction[] = [ @@ -305,26 +329,26 @@ type TestFunctionCast = [ /*isSelfConversion?: */boolean? ]; const validTestFunctionCasts: TestFunctionCast[] = [ - ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`]), - ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`]), - ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`]), - ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`]), - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`]), - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`, false]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`, false]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`]), + [selfTestFunctions[0], `<${anonTestFunctionType}>(${selfTestFunctions[0].value})`], + [selfTestFunctions[0], `(${selfTestFunctions[0].value}) as (${anonTestFunctionType})`], + [selfTestFunctions[0], `<${selfTestFunctionType}>(${selfTestFunctions[0].value})`], + [selfTestFunctions[0], `(${selfTestFunctions[0].value}) as (${selfTestFunctionType})`], + [noSelfTestFunctions[0], `<${noSelfTestFunctionType}>(${noSelfTestFunctions[0].value})`], + [noSelfTestFunctions[0], `(${noSelfTestFunctions[0].value}) as (${noSelfTestFunctionType})`], + [noSelfInFileTestFunctions[0], `<${anonTestFunctionType}>(${noSelfInFileTestFunctions[0].value})`], + [noSelfInFileTestFunctions[0], `(${noSelfInFileTestFunctions[0].value}) as (${anonTestFunctionType})`], + [noSelfInFileTestFunctions[0], `<${noSelfTestFunctionType}>(${noSelfInFileTestFunctions[0].value})`], + [noSelfInFileTestFunctions[0], `(${noSelfInFileTestFunctions[0].value}) as (${noSelfTestFunctionType})`], ]; const invalidTestFunctionCasts: TestFunctionCast[] = [ - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${anonTestFunctionType}>(${f.value})`, false]), - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${anonTestFunctionType})`, false]), - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`, false]), - ...noSelfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`, false]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `<${selfTestFunctionType}>(${f.value})`, false]), - ...noSelfInFileTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${selfTestFunctionType})`, false]), - ...selfTestFunctions.map((f): TestFunctionCast => [f, `<${noSelfTestFunctionType}>(${f.value})`, true]), - ...selfTestFunctions.map((f): TestFunctionCast => [f, `(${f.value}) as (${noSelfTestFunctionType})`, true]), + [noSelfTestFunctions[0], `<${anonTestFunctionType}>(${noSelfTestFunctions[0].value})`, false], + [noSelfTestFunctions[0], `(${noSelfTestFunctions[0].value}) as (${anonTestFunctionType})`, false], + [noSelfTestFunctions[0], `<${selfTestFunctionType}>(${noSelfTestFunctions[0].value})`, false], + [noSelfTestFunctions[0], `(${noSelfTestFunctions[0].value}) as (${selfTestFunctionType})`, false], + [noSelfInFileTestFunctions[0], `<${selfTestFunctionType}>(${noSelfInFileTestFunctions[0].value})`, false], + [noSelfInFileTestFunctions[0], `(${noSelfInFileTestFunctions[0].value}) as (${selfTestFunctionType})`, false], + [selfTestFunctions[0], `<${noSelfTestFunctionType}>(${selfTestFunctions[0].value})`, true], + [selfTestFunctions[0], `(${selfTestFunctions[0].value}) as (${noSelfTestFunctionType})`, true], ]; type TestFunctionAssignment = [ @@ -894,6 +918,23 @@ export class AssignmentTests { ); } + @TestCase("noSelf") + @TestCase("noSelfInFile") + @Test("noSelf function method argument") + public noSelfFunctionMethodArgument(noSelfTag: string): void { + const header = + `/** @${noSelfTag} */ namespace NS { + export class C { + method(fn: (s: string) => string) { return fn("foo"); } + } + } + function foo(this: void, s: string) { return s; }`; + const code = + `const c = new NS.C(); + return c.method(foo);`; + Expect(util.transpileAndExecute(code, undefined, undefined, header, false)).toBe("foo"); + } + @TestCase("s => s") @TestCase("(s => s)") @TestCase("function(s) { return s; }")