diff --git a/language-extensions/index.d.ts b/language-extensions/index.d.ts index 033012960..79067b1cd 100644 --- a/language-extensions/index.d.ts +++ b/language-extensions/index.d.ts @@ -1,3 +1,11 @@ +/** + * Indicates a type is a language extension provided by TypescriptToLua. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TBrand A string used to uniquely identify the language extension type + */ +declare type LuaExtension = { [T in TBrand]: { readonly __luaExtensionSymbol: unique symbol } }; + /** * Returns multiple values from a function, by wrapping them in a LuaMultiReturn tuple. * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions @@ -5,7 +13,7 @@ * @param T A tuple type with each element type representing a return value's type. * @param values Return values. */ -declare function $multi(...values: T): LuaMultiReturn; +declare const $multi: ((...values: T) => LuaMultiReturn) & LuaExtension<"__luaMultiFunctionBrand">; /** * Represents multiple return values as a tuple. @@ -13,7 +21,7 @@ declare function $multi(...values: T): LuaMultiReturn; * * @param T A tuple type with each element type representing a return value's type. */ -declare type LuaMultiReturn = T & { readonly __luaMultiReturnBrand: unique symbol }; +declare type LuaMultiReturn = T & LuaExtension<"__luaMultiReturnBrand">; /** * Creates a Lua-style numeric for loop (for i=start,limit,step) when used in for...of. Not valid in any other context. @@ -23,7 +31,42 @@ declare type LuaMultiReturn = T & { readonly __luaMultiReturnBr * @param limit The last number in the sequence to iterate over. * @param step The amount to increment each iteration. */ -declare function $range(start: number, limit: number, step?: number): Iterable; +declare const $range: ((start: number, limit: number, step?: number) => Iterable) & + LuaExtension<"__luaRangeFunctionBrand">; + +/** + * Represents a Lua-style iterator which is returned from a LuaIterable. + * For simple iterators (with no state), this is just a function. + * For complex iterators that use a state, this is a LuaMultiReturn tuple containing a function, the state, and the initial value to pass to the function. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param state The state object returned from the LuaIterable. + * @param lastValue The last value returned from this function. If iterating LuaMultiReturn values, this is the first value of the tuple. + */ +declare type LuaIterator = TState extends undefined + ? (this: void) => TValue + : LuaMultiReturn< + [ + ( + this: void, + state: TState, + lastValue: TValue extends LuaMultiReturn ? TTuple[0] : TValue + ) => TValue, + TState, + TValue extends LuaMultiReturn ? TTuple[0] : TValue + ] + >; + +/** + * Represents a Lua-style iteratable which iterates single values in a `for...in` loop (ex. `for x in iter() do`). + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TValue The type of value returned each iteration. If this is LuaMultiReturn, multiple values will be returned each iteration. + * @param TState The type of the state value passed back to the iterator function each iteration. + */ +declare type LuaIterable = Iterable & + LuaIterator & + LuaExtension<"__luaIterableBrand">; /** * Calls to functions with this type are translated to `left + right`. @@ -33,9 +76,8 @@ declare function $range(start: number, limit: number, step?: number): Iterable = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaAdditionBrand: unique symbol; -}; +declare type LuaAddition = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaAdditionBrand">; /** * Calls to methods with this type are translated to `left + right`, where `left` is the object with the method. @@ -44,9 +86,8 @@ declare type LuaAddition = ((left: TLeft, right: TRight) * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaAdditionMethod = ((right: TRight) => TReturn) & { - readonly __luaAdditionMethodBrand: unique symbol; -}; +declare type LuaAdditionMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaAdditionMethodBrand">; /** * Calls to functions with this type are translated to `left - right`. @@ -56,9 +97,8 @@ declare type LuaAdditionMethod = ((right: TRight) => TReturn) & * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaSubtraction = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaSubtractionBrand: unique symbol; -}; +declare type LuaSubtraction = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaSubtractionBrand">; /** * Calls to methods with this type are translated to `left - right`, where `left` is the object with the method. @@ -67,9 +107,8 @@ declare type LuaSubtraction = ((left: TLeft, right: TRig * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaSubtractionMethod = ((right: TRight) => TReturn) & { - readonly __luaSubtractionMethodBrand: unique symbol; -}; +declare type LuaSubtractionMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaSubtractionMethodBrand">; /** * Calls to functions with this type are translated to `left * right`. @@ -79,9 +118,8 @@ declare type LuaSubtractionMethod = ((right: TRight) => TReturn * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaMultiplication = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaMultiplicationBrand: unique symbol; -}; +declare type LuaMultiplication = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaMultiplicationBrand">; /** * Calls to methods with this type are translated to `left * right`, where `left` is the object with the method. @@ -90,9 +128,8 @@ declare type LuaMultiplication = ((left: TLeft, right: T * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaMultiplicationMethod = ((right: TRight) => TReturn) & { - readonly __luaMultiplicationMethodBrand: unique symbol; -}; +declare type LuaMultiplicationMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaMultiplicationMethodBrand">; /** * Calls to functions with this type are translated to `left / right`. @@ -102,9 +139,8 @@ declare type LuaMultiplicationMethod = ((right: TRight) => TRet * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaDivision = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaDivisionBrand: unique symbol; -}; +declare type LuaDivision = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaDivisionBrand">; /** * Calls to methods with this type are translated to `left / right`, where `left` is the object with the method. @@ -113,9 +149,8 @@ declare type LuaDivision = ((left: TLeft, right: TRight) * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaDivisionMethod = ((right: TRight) => TReturn) & { - readonly __luaDivisionMethodBrand: unique symbol; -}; +declare type LuaDivisionMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaDivisionMethodBrand">; /** * Calls to functions with this type are translated to `left % right`. @@ -125,9 +160,8 @@ declare type LuaDivisionMethod = ((right: TRight) => TReturn) & * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaModulo = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaModuloBrand: unique symbol; -}; +declare type LuaModulo = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaModuloBrand">; /** * Calls to methods with this type are translated to `left % right`, where `left` is the object with the method. @@ -136,9 +170,7 @@ declare type LuaModulo = ((left: TLeft, right: TRight) = * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaModuloMethod = ((right: TRight) => TReturn) & { - readonly __luaModuloMethodBrand: unique symbol; -}; +declare type LuaModuloMethod = ((right: TRight) => TReturn) & LuaExtension<"__luaModuloMethodBrand">; /** * Calls to functions with this type are translated to `left ^ right`. @@ -148,9 +180,8 @@ declare type LuaModuloMethod = ((right: TRight) => TReturn) & { * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaPower = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaPowerBrand: unique symbol; -}; +declare type LuaPower = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaPowerBrand">; /** * Calls to methods with this type are translated to `left ^ right`, where `left` is the object with the method. @@ -159,9 +190,7 @@ declare type LuaPower = ((left: TLeft, right: TRight) => * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaPowerMethod = ((right: TRight) => TReturn) & { - readonly __luaPowerMethodBrand: unique symbol; -}; +declare type LuaPowerMethod = ((right: TRight) => TReturn) & LuaExtension<"__luaPowerMethodBrand">; /** * Calls to functions with this type are translated to `left // right`. @@ -171,9 +200,8 @@ declare type LuaPowerMethod = ((right: TRight) => TReturn) & { * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaFloorDivision = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaFloorDivisionBrand: unique symbol; -}; +declare type LuaFloorDivision = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaFloorDivisionBrand">; /** * Calls to methods with this type are translated to `left // right`, where `left` is the object with the method. @@ -182,9 +210,8 @@ declare type LuaFloorDivision = ((left: TLeft, right: TR * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaFloorDivisionMethod = ((right: TRight) => TReturn) & { - readonly __luaFloorDivisionMethodBrand: unique symbol; -}; +declare type LuaFloorDivisionMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaFloorDivisionMethodBrand">; /** * Calls to functions with this type are translated to `left & right`. @@ -194,9 +221,8 @@ declare type LuaFloorDivisionMethod = ((right: TRight) => TRetu * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseAnd = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaBitwiseAndBrand: unique symbol; -}; +declare type LuaBitwiseAnd = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseAndBrand">; /** * Calls to methods with this type are translated to `left & right`, where `left` is the object with the method. @@ -205,9 +231,8 @@ declare type LuaBitwiseAnd = ((left: TLeft, right: TRigh * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseAndMethod = ((right: TRight) => TReturn) & { - readonly __luaBitwiseAndMethodBrand: unique symbol; -}; +declare type LuaBitwiseAndMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseAndMethodBrand">; /** * Calls to functions with this type are translated to `left | right`. @@ -217,9 +242,8 @@ declare type LuaBitwiseAndMethod = ((right: TRight) => TReturn) * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseOr = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaBitwiseOrBrand: unique symbol; -}; +declare type LuaBitwiseOr = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseOrBrand">; /** * Calls to methods with this type are translated to `left | right`, where `left` is the object with the method. @@ -228,9 +252,8 @@ declare type LuaBitwiseOr = ((left: TLeft, right: TRight * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseOrMethod = ((right: TRight) => TReturn) & { - readonly __luaBitwiseOrMethodBrand: unique symbol; -}; +declare type LuaBitwiseOrMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseOrMethodBrand">; /** * Calls to functions with this type are translated to `left ~ right`. @@ -240,9 +263,8 @@ declare type LuaBitwiseOrMethod = ((right: TRight) => TReturn) * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseExclusiveOr = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaBitwiseExclusiveOrBrand: unique symbol; -}; +declare type LuaBitwiseExclusiveOr = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseExclusiveOrBrand">; /** * Calls to methods with this type are translated to `left ~ right`, where `left` is the object with the method. @@ -251,9 +273,8 @@ declare type LuaBitwiseExclusiveOr = ((left: TLeft, righ * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseExclusiveOrMethod = ((right: TRight) => TReturn) & { - readonly __luaBitwiseExclusiveOrMethodBrand: unique symbol; -}; +declare type LuaBitwiseExclusiveOrMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseExclusiveOrMethodBrand">; /** * Calls to functions with this type are translated to `left << right`. @@ -263,9 +284,8 @@ declare type LuaBitwiseExclusiveOrMethod = ((right: TRight) => * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseLeftShift = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaBitwiseLeftShiftBrand: unique symbol; -}; +declare type LuaBitwiseLeftShift = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseLeftShiftBrand">; /** * Calls to methods with this type are translated to `left << right`, where `left` is the object with the method. @@ -274,9 +294,8 @@ declare type LuaBitwiseLeftShift = ((left: TLeft, right: * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseLeftShiftMethod = ((right: TRight) => TReturn) & { - readonly __luaBitwiseLeftShiftMethodBrand: unique symbol; -}; +declare type LuaBitwiseLeftShiftMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseLeftShiftMethodBrand">; /** * Calls to functions with this type are translated to `left >> right`. @@ -286,9 +305,8 @@ declare type LuaBitwiseLeftShiftMethod = ((right: TRight) => TR * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseRightShift = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaBitwiseRightShiftBrand: unique symbol; -}; +declare type LuaBitwiseRightShift = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseRightShiftBrand">; /** * Calls to methods with this type are translated to `left >> right`, where `left` is the object with the method. @@ -297,9 +315,8 @@ declare type LuaBitwiseRightShift = ((left: TLeft, right * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseRightShiftMethod = ((right: TRight) => TReturn) & { - readonly __luaBitwiseRightShiftMethodBrand: unique symbol; -}; +declare type LuaBitwiseRightShiftMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaBitwiseRightShiftMethodBrand">; /** * Calls to functions with this type are translated to `left .. right`. @@ -309,9 +326,8 @@ declare type LuaBitwiseRightShiftMethod = ((right: TRight) => T * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaConcat = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaConcatBrand: unique symbol; -}; +declare type LuaConcat = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaConcatBrand">; /** * Calls to methods with this type are translated to `left .. right`, where `left` is the object with the method. @@ -320,9 +336,7 @@ declare type LuaConcat = ((left: TLeft, right: TRight) = * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaConcatMethod = ((right: TRight) => TReturn) & { - readonly __luaConcatMethodBrand: unique symbol; -}; +declare type LuaConcatMethod = ((right: TRight) => TReturn) & LuaExtension<"__luaConcatMethodBrand">; /** * Calls to functions with this type are translated to `left < right`. @@ -332,9 +346,8 @@ declare type LuaConcatMethod = ((right: TRight) => TReturn) & { * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaLessThan = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaLessThanBrand: unique symbol; -}; +declare type LuaLessThan = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaLessThanBrand">; /** * Calls to methods with this type are translated to `left < right`, where `left` is the object with the method. @@ -343,9 +356,8 @@ declare type LuaLessThan = ((left: TLeft, right: TRight) * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaLessThanMethod = ((right: TRight) => TReturn) & { - readonly __luaLessThanMethodBrand: unique symbol; -}; +declare type LuaLessThanMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaLessThanMethodBrand">; /** * Calls to functions with this type are translated to `left > right`. @@ -355,9 +367,8 @@ declare type LuaLessThanMethod = ((right: TRight) => TReturn) & * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaGreaterThan = ((left: TLeft, right: TRight) => TReturn) & { - readonly __luaGreaterThanBrand: unique symbol; -}; +declare type LuaGreaterThan = ((left: TLeft, right: TRight) => TReturn) & + LuaExtension<"__luaGreaterThanBrand">; /** * Calls to methods with this type are translated to `left > right`, where `left` is the object with the method. @@ -366,9 +377,8 @@ declare type LuaGreaterThan = ((left: TLeft, right: TRig * @param TRight The type of the right-hand-side of the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaGreaterThanMethod = ((right: TRight) => TReturn) & { - readonly __luaGreaterThanMethodBrand: unique symbol; -}; +declare type LuaGreaterThanMethod = ((right: TRight) => TReturn) & + LuaExtension<"__luaGreaterThanMethodBrand">; /** * Calls to functions with this type are translated to `-operand`. @@ -377,9 +387,7 @@ declare type LuaGreaterThanMethod = ((right: TRight) => TReturn * @param TOperand The type of the value in the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaNegation = ((operand: TOperand) => TReturn) & { - readonly __luaNegationBrand: unique symbol; -}; +declare type LuaNegation = ((operand: TOperand) => TReturn) & LuaExtension<"__luaNegationBrand">; /** * Calls to method with this type are translated to `-operand`, where `operand` is the object with the method. @@ -387,7 +395,7 @@ declare type LuaNegation = ((operand: TOperand) => TReturn) & * * @param TReturn The resulting (return) type of the operation. */ -declare type LuaNegationMethod = (() => TReturn) & { readonly __luaNegationMethodBrand: unique symbol }; +declare type LuaNegationMethod = (() => TReturn) & LuaExtension<"__luaNegationMethodBrand">; /** * Calls to functions with this type are translated to `~operand`. @@ -396,9 +404,7 @@ declare type LuaNegationMethod = (() => TReturn) & { readonly __luaNega * @param TOperand The type of the value in the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseNot = ((operand: TOperand) => TReturn) & { - readonly __luaBitwiseNotBrand: unique symbol; -}; +declare type LuaBitwiseNot = ((operand: TOperand) => TReturn) & LuaExtension<"__luaBitwiseNotBrand">; /** * Calls to method with this type are translated to `~operand`, where `operand` is the object with the method. @@ -406,7 +412,7 @@ declare type LuaBitwiseNot = ((operand: TOperand) => TReturn) * * @param TReturn The resulting (return) type of the operation. */ -declare type LuaBitwiseNotMethod = (() => TReturn) & { readonly __luaBitwiseNotMethodBrand: unique symbol }; +declare type LuaBitwiseNotMethod = (() => TReturn) & LuaExtension<"__luaBitwiseNotMethodBrand">; /** * Calls to functions with this type are translated to `#operand`. @@ -415,9 +421,7 @@ declare type LuaBitwiseNotMethod = (() => TReturn) & { readonly __luaBi * @param TOperand The type of the value in the operation. * @param TReturn The resulting (return) type of the operation. */ -declare type LuaLength = ((operand: TOperand) => TReturn) & { - readonly __luaLengthBrand: unique symbol; -}; +declare type LuaLength = ((operand: TOperand) => TReturn) & LuaExtension<"__luaLengthBrand">; /** * Calls to method with this type are translated to `#operand`, where `operand` is the object with the method. @@ -425,4 +429,4 @@ declare type LuaLength = ((operand: TOperand) => TReturn) & { * * @param TReturn The resulting (return) type of the operation. */ -declare type LuaLengthMethod = (() => TReturn) & { readonly __luaLengthMethodBrand: unique symbol }; +declare type LuaLengthMethod = (() => TReturn) & LuaExtension<"__luaLengthMethodBrand">; diff --git a/src/lualib/Iterator.ts b/src/lualib/Iterator.ts index 809ca69ee..d6112cd88 100644 --- a/src/lualib/Iterator.ts +++ b/src/lualib/Iterator.ts @@ -27,7 +27,7 @@ function __TS__IteratorStringStep(this: string, index: number): [number, string] function __TS__Iterator( this: void, iterable: string | GeneratorIterator | Iterable | readonly T[] -): [(...args: any[]) => [any, any] | [], ...any[]] { +): [(...args: any[]) => [any, any] | [], ...any[]] | LuaIterable> { if (typeof iterable === "string") { return [__TS__IteratorStringStep, iterable, 0]; } else if ("____coroutine" in iterable) { @@ -36,6 +36,6 @@ function __TS__Iterator( const iterator = iterable[Symbol.iterator](); return [__TS__IteratorIteratorStep, iterator]; } else { - return ipairs(iterable as readonly T[]) as any; + return ipairs(iterable as readonly T[]); } } diff --git a/src/lualib/declarations/global.d.ts b/src/lualib/declarations/global.d.ts index 450fba8dc..117438817 100644 --- a/src/lualib/declarations/global.d.ts +++ b/src/lualib/declarations/global.d.ts @@ -25,10 +25,4 @@ declare function unpack(list: T[], i?: number, j?: number): T[]; declare function select(index: number, ...args: T[]): T; declare function select(index: "#", ...args: T[]): number; -/** - * @luaIterator - * @tupleReturn - */ -type LuaTupleIterator = Iterable & { " LuaTupleIterator": never }; - -declare function ipairs(t: Record): LuaTupleIterator<[number, T]>; +declare function ipairs(t: Record): LuaIterable, Record>; diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 76e8b4632..e8873746a 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -118,6 +118,10 @@ export const luaIteratorForbiddenUsage = createErrorDiagnosticFactory( "the '@tupleReturn' annotation." ); +export const invalidMultiIterableWithoutDestructuring = createErrorDiagnosticFactory( + "LuaIterable with a LuaMultiReturn return value type must be destructured." +); + export const unsupportedAccessorInObjectLiteral = createErrorDiagnosticFactory( "Accessors in object literal are not supported." ); diff --git a/src/transformation/utils/language-extensions.ts b/src/transformation/utils/language-extensions.ts index 391993f8a..efcd7d810 100644 --- a/src/transformation/utils/language-extensions.ts +++ b/src/transformation/utils/language-extensions.ts @@ -1,10 +1,11 @@ import * as ts from "typescript"; -import * as path from "path"; +import { TransformationContext } from "../context"; export enum ExtensionKind { MultiFunction = "MultiFunction", MultiType = "MultiType", RangeFunction = "RangeFunction", + IterableType = "IterableType", AdditionOperatorType = "AdditionOperatorType", AdditionOperatorMethodType = "AdditionOperatorMethodType", SubtractionOperatorType = "SubtractionOperatorType", @@ -43,74 +44,66 @@ export enum ExtensionKind { LengthOperatorMethodType = "LengthOperatorMethodType", } -const functionNameToExtensionKind: { [name: string]: ExtensionKind } = { - $multi: ExtensionKind.MultiFunction, - $range: ExtensionKind.RangeFunction, +const extensionKindToFunctionName: { [T in ExtensionKind]?: string } = { + [ExtensionKind.MultiFunction]: "$multi", + [ExtensionKind.RangeFunction]: "$range", }; -const typeNameToExtensionKind: { [name: string]: ExtensionKind } = { - LuaMultiReturn: ExtensionKind.MultiType, - LuaAddition: ExtensionKind.AdditionOperatorType, - LuaAdditionMethod: ExtensionKind.AdditionOperatorMethodType, - LuaSubtraction: ExtensionKind.SubtractionOperatorType, - LuaSubtractionMethod: ExtensionKind.SubtractionOperatorMethodType, - LuaMultiplication: ExtensionKind.MultiplicationOperatorType, - LuaMultiplicationMethod: ExtensionKind.MultiplicationOperatorMethodType, - LuaDivision: ExtensionKind.DivisionOperatorType, - LuaDivisionMethod: ExtensionKind.DivisionOperatorMethodType, - LuaModulo: ExtensionKind.ModuloOperatorType, - LuaModuloMethod: ExtensionKind.ModuloOperatorMethodType, - LuaPower: ExtensionKind.PowerOperatorType, - LuaPowerMethod: ExtensionKind.PowerOperatorMethodType, - LuaFloorDivision: ExtensionKind.FloorDivisionOperatorType, - LuaFloorDivisionMethod: ExtensionKind.FloorDivisionOperatorMethodType, - LuaBitwiseAnd: ExtensionKind.BitwiseAndOperatorType, - LuaBitwiseAndMethod: ExtensionKind.BitwiseAndOperatorMethodType, - LuaBitwiseOr: ExtensionKind.BitwiseOrOperatorType, - LuaBitwiseOrMethod: ExtensionKind.BitwiseOrOperatorMethodType, - LuaBitwiseExclusiveOr: ExtensionKind.BitwiseExclusiveOrOperatorType, - LuaBitwiseExclusiveOrMethod: ExtensionKind.BitwiseExclusiveOrOperatorMethodType, - LuaBitwiseLeftShift: ExtensionKind.BitwiseLeftShiftOperatorType, - LuaBitwiseLeftShiftMethod: ExtensionKind.BitwiseLeftShiftOperatorMethodType, - LuaBitwiseRightShift: ExtensionKind.BitwiseRightShiftOperatorType, - LuaBitwiseRightShiftMethod: ExtensionKind.BitwiseRightShiftOperatorMethodType, - LuaConcat: ExtensionKind.ConcatOperatorType, - LuaConcatMethod: ExtensionKind.ConcatOperatorMethodType, - LuaLessThan: ExtensionKind.LessThanOperatorType, - LuaLessThanMethod: ExtensionKind.LessThanOperatorMethodType, - LuaGreaterThan: ExtensionKind.GreaterThanOperatorType, - LuaGreaterThanMethod: ExtensionKind.GreaterThanOperatorMethodType, - LuaNegation: ExtensionKind.NegationOperatorType, - LuaNegationMethod: ExtensionKind.NegationOperatorMethodType, - LuaBitwiseNot: ExtensionKind.BitwiseNotOperatorType, - LuaBitwiseNotMethod: ExtensionKind.BitwiseNotOperatorMethodType, - LuaLength: ExtensionKind.LengthOperatorType, - LuaLengthMethod: ExtensionKind.LengthOperatorMethodType, +const extensionKindToTypeBrand: { [T in ExtensionKind]: string } = { + [ExtensionKind.MultiFunction]: "__luaMultiFunctionBrand", + [ExtensionKind.MultiType]: "__luaMultiReturnBrand", + [ExtensionKind.RangeFunction]: "__luaRangeFunctionBrand", + [ExtensionKind.IterableType]: "__luaIterableBrand", + [ExtensionKind.AdditionOperatorType]: "__luaAdditionBrand", + [ExtensionKind.AdditionOperatorMethodType]: "__luaAdditionMethodBrand", + [ExtensionKind.SubtractionOperatorType]: "__luaSubtractionBrand", + [ExtensionKind.SubtractionOperatorMethodType]: "__luaSubtractionMethodBrand", + [ExtensionKind.MultiplicationOperatorType]: "__luaMultiplicationBrand", + [ExtensionKind.MultiplicationOperatorMethodType]: "__luaMultiplicationMethodBrand", + [ExtensionKind.DivisionOperatorType]: "__luaDivisionBrand", + [ExtensionKind.DivisionOperatorMethodType]: "__luaDivisionMethodBrand", + [ExtensionKind.ModuloOperatorType]: "__luaModuloBrand", + [ExtensionKind.ModuloOperatorMethodType]: "__luaModuloMethodBrand", + [ExtensionKind.PowerOperatorType]: "__luaPowerBrand", + [ExtensionKind.PowerOperatorMethodType]: "__luaPowerMethodBrand", + [ExtensionKind.FloorDivisionOperatorType]: "__luaFloorDivisionBrand", + [ExtensionKind.FloorDivisionOperatorMethodType]: "__luaFloorDivisionMethodBrand", + [ExtensionKind.BitwiseAndOperatorType]: "__luaBitwiseAndBrand", + [ExtensionKind.BitwiseAndOperatorMethodType]: "__luaBitwiseAndMethodBrand", + [ExtensionKind.BitwiseOrOperatorType]: "__luaBitwiseOrBrand", + [ExtensionKind.BitwiseOrOperatorMethodType]: "__luaBitwiseOrMethodBrand", + [ExtensionKind.BitwiseExclusiveOrOperatorType]: "__luaBitwiseExclusiveOrBrand", + [ExtensionKind.BitwiseExclusiveOrOperatorMethodType]: "__luaBitwiseExclusiveOrMethodBrand", + [ExtensionKind.BitwiseLeftShiftOperatorType]: "__luaBitwiseLeftShiftBrand", + [ExtensionKind.BitwiseLeftShiftOperatorMethodType]: "__luaBitwiseLeftShiftMethodBrand", + [ExtensionKind.BitwiseRightShiftOperatorType]: "__luaBitwiseRightShiftBrand", + [ExtensionKind.BitwiseRightShiftOperatorMethodType]: "__luaBitwiseRightShiftMethodBrand", + [ExtensionKind.ConcatOperatorType]: "__luaConcatBrand", + [ExtensionKind.ConcatOperatorMethodType]: "__luaConcatMethodBrand", + [ExtensionKind.LessThanOperatorType]: "__luaLessThanBrand", + [ExtensionKind.LessThanOperatorMethodType]: "__luaLessThanMethodBrand", + [ExtensionKind.GreaterThanOperatorType]: "__luaGreaterThanBrand", + [ExtensionKind.GreaterThanOperatorMethodType]: "__luaGreaterThanMethodBrand", + [ExtensionKind.NegationOperatorType]: "__luaNegationBrand", + [ExtensionKind.NegationOperatorMethodType]: "__luaNegationMethodBrand", + [ExtensionKind.BitwiseNotOperatorType]: "__luaBitwiseNotBrand", + [ExtensionKind.BitwiseNotOperatorMethodType]: "__luaBitwiseNotMethodBrand", + [ExtensionKind.LengthOperatorType]: "__luaLengthBrand", + [ExtensionKind.LengthOperatorMethodType]: "__luaLengthMethodBrand", }; -function isSourceFileFromLanguageExtensions(sourceFile: ts.SourceFile): boolean { - const extensionDirectory = path.resolve(__dirname, "../../../language-extensions"); - const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); - return extensionDirectory === sourceFileDirectory; +export function isExtensionType(type: ts.Type, extensionKind: ExtensionKind): boolean { + const typeBrand = extensionKindToTypeBrand[extensionKind]; + return typeBrand !== undefined && type.getProperty(typeBrand) !== undefined; } -export function getExtensionKind(declaration: ts.Declaration): ExtensionKind | undefined { - const sourceFile = declaration.getSourceFile(); - if (isSourceFileFromLanguageExtensions(sourceFile)) { - if (ts.isFunctionDeclaration(declaration) && declaration.name?.text) { - const extensionKind = functionNameToExtensionKind[declaration.name.text]; - if (extensionKind) { - return extensionKind; - } - } - - if (ts.isTypeAliasDeclaration(declaration)) { - const extensionKind = typeNameToExtensionKind[declaration.name.text]; - if (extensionKind) { - return extensionKind; - } - } - - throw new Error("Unknown extension kind"); - } +export function isExtensionFunction( + context: TransformationContext, + symbol: ts.Symbol, + extensionKind: ExtensionKind +): boolean { + return ( + symbol.getName() === extensionKindToFunctionName[extensionKind] && + symbol.declarations.some(d => isExtensionType(context.checker.getTypeAtLocation(d), extensionKind)) + ); } diff --git a/src/transformation/visitors/language-extensions/iterable.ts b/src/transformation/visitors/language-extensions/iterable.ts new file mode 100644 index 000000000..769e15093 --- /dev/null +++ b/src/transformation/visitors/language-extensions/iterable.ts @@ -0,0 +1,82 @@ +import * as ts from "typescript"; +import * as lua from "../../../LuaAST"; +import * as extensions from "../../utils/language-extensions"; +import { TransformationContext } from "../../context"; +import { getVariableDeclarationBinding, transformForInitializer } from "../loops/utils"; +import { transformArrayBindingElement } from "../variable-declaration"; +import { invalidMultiIterableWithoutDestructuring } from "../../utils/diagnostics"; +import { cast } from "../../../utils"; +import { isMultiReturnType } from "./multi"; + +export function isIterableType(type: ts.Type): boolean { + return extensions.isExtensionType(type, extensions.ExtensionKind.IterableType); +} + +export function returnsIterableType(context: TransformationContext, node: ts.CallExpression): boolean { + const signature = context.checker.getResolvedSignature(node); + const type = signature?.getReturnType(); + return type ? isIterableType(type) : false; +} + +export function isIterableExpression(context: TransformationContext, expression: ts.Expression): boolean { + const type = context.checker.getTypeAtLocation(expression); + return isIterableType(type); +} + +function transformForOfMultiIterableStatement( + context: TransformationContext, + statement: ts.ForOfStatement, + block: lua.Block +): lua.Statement { + const luaIterator = context.transformExpression(statement.expression); + let identifiers: lua.Identifier[] = []; + + if (ts.isVariableDeclarationList(statement.initializer)) { + // Variables declared in for loop + // for ${initializer} in ${iterable} do + const binding = getVariableDeclarationBinding(context, statement.initializer); + if (ts.isArrayBindingPattern(binding)) { + identifiers = binding.elements.map(e => transformArrayBindingElement(context, e)); + } else { + context.diagnostics.push(invalidMultiIterableWithoutDestructuring(binding)); + } + } else if (ts.isArrayLiteralExpression(statement.initializer)) { + // Variables NOT declared in for loop - catch iterator values in temps and assign + // for ____value0 in ${iterable} do + // ${initializer} = ____value0 + identifiers = statement.initializer.elements.map((_, i) => lua.createIdentifier(`____value${i}`)); + if (identifiers.length > 0) { + block.statements.unshift( + lua.createAssignmentStatement( + statement.initializer.elements.map(e => + cast(context.transformExpression(e), lua.isAssignmentLeftHandSideExpression) + ), + identifiers + ) + ); + } + } else { + context.diagnostics.push(invalidMultiIterableWithoutDestructuring(statement.initializer)); + } + + if (identifiers.length === 0) { + identifiers.push(lua.createAnonymousIdentifier()); + } + + return lua.createForInStatement(block, identifiers, [luaIterator], statement); +} + +export function transformForOfIterableStatement( + context: TransformationContext, + statement: ts.ForOfStatement, + block: lua.Block +): lua.Statement { + const type = context.checker.getTypeAtLocation(statement.expression); + if (type.aliasTypeArguments?.length === 2 && isMultiReturnType(type.aliasTypeArguments[0])) { + return transformForOfMultiIterableStatement(context, statement, block); + } + + const luaIterator = context.transformExpression(statement.expression); + const identifier = transformForInitializer(context, statement.initializer, block); + return lua.createForInStatement(block, [identifier], [luaIterator], statement); +} diff --git a/src/transformation/visitors/language-extensions/multi.ts b/src/transformation/visitors/language-extensions/multi.ts index 8ddb54c83..b292d9aa1 100644 --- a/src/transformation/visitors/language-extensions/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -1,22 +1,16 @@ import * as ts from "typescript"; import * as extensions from "../../utils/language-extensions"; import { TransformationContext } from "../../context"; -import { invalidMultiFunctionUse } from "../../utils/diagnostics"; import { findFirstNodeAbove } from "../../utils/typescript"; - -const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean => - extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiFunction; - -const isMultiTypeDeclaration = (declaration: ts.Declaration): boolean => - extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiType; +import { isIterableExpression } from "./iterable"; +import { invalidMultiFunctionUse } from "../../utils/diagnostics"; export function isMultiReturnType(type: ts.Type): boolean { - return type.aliasSymbol?.declarations?.some(isMultiTypeDeclaration) ?? false; + return extensions.isExtensionType(type, extensions.ExtensionKind.MultiType); } export function isMultiFunctionCall(context: TransformationContext, expression: ts.CallExpression): boolean { - const type = context.checker.getTypeAtLocation(expression.expression); - return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; + return isMultiFunctionNode(context, expression.expression); } export function returnsMultiType(context: TransformationContext, node: ts.CallExpression): boolean { @@ -30,8 +24,8 @@ export function isMultiReturnCall(context: TransformationContext, expression: ts } export function isMultiFunctionNode(context: TransformationContext, node: ts.Node): boolean { - const type = context.checker.getTypeAtLocation(node); - return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; + const symbol = context.checker.getSymbolAtLocation(node); + return symbol ? extensions.isExtensionFunction(context, symbol, extensions.ExtensionKind.MultiFunction) : false; } export function isInMultiReturnFunction(context: TransformationContext, node: ts.Node) { @@ -86,6 +80,11 @@ export function shouldMultiReturnCallBeWrapped(context: TransformationContext, n return false; } + // LuaIterable in for...of + if (ts.isForOfStatement(node.parent) && isIterableExpression(context, node)) { + return false; + } + return true; } @@ -99,8 +98,7 @@ export function findMultiAssignmentViolations( if (!ts.isShorthandPropertyAssignment(element)) continue; const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); if (valueSymbol) { - const declaration = valueSymbol.valueDeclaration; - if (declaration && isMultiFunctionDeclaration(declaration)) { + if (extensions.isExtensionFunction(context, valueSymbol, extensions.ExtensionKind.MultiFunction)) { context.diagnostics.push(invalidMultiFunctionUse(element)); result.push(element); } diff --git a/src/transformation/visitors/language-extensions/operators.ts b/src/transformation/visitors/language-extensions/operators.ts index b217a26e8..dddecf338 100644 --- a/src/transformation/visitors/language-extensions/operators.ts +++ b/src/transformation/visitors/language-extensions/operators.ts @@ -49,10 +49,7 @@ const unaryOperatorMappings = new Map([ - ...binaryOperatorMappings.keys(), - ...unaryOperatorMappings.keys(), -]); +const operatorMapExtensions = [...binaryOperatorMappings.keys(), ...unaryOperatorMappings.keys()]; const bitwiseOperatorMapExtensions = new Set([ extensions.ExtensionKind.BitwiseAndOperatorType, @@ -84,25 +81,15 @@ function getOperatorMapExtensionKindForCall(context: TransformationContext, node if (!typeDeclaration) { return; } - const mapping = extensions.getExtensionKind(typeDeclaration); - if (mapping !== undefined && operatorMapExtensions.has(mapping)) { - return mapping; - } -} - -function isOperatorMapDeclaration(declaration: ts.Declaration) { - const typeDeclaration = getTypeDeclaration(declaration); - if (typeDeclaration) { - const extensionKind = extensions.getExtensionKind(typeDeclaration); - return extensionKind !== undefined ? operatorMapExtensions.has(extensionKind) : false; - } + const type = context.checker.getTypeFromTypeNode(typeDeclaration.type); + return operatorMapExtensions.find(extensionKind => extensions.isExtensionType(type, extensionKind)); } function isOperatorMapType(context: TransformationContext, type: ts.Type): boolean { if (type.isUnionOrIntersection()) { return type.types.some(t => isOperatorMapType(context, t)); } else { - return type.symbol?.declarations?.some(isOperatorMapDeclaration); + return operatorMapExtensions.some(extensionKind => extensions.isExtensionType(type, extensionKind)); } } diff --git a/src/transformation/visitors/language-extensions/range.ts b/src/transformation/visitors/language-extensions/range.ts index d68729c3c..00a3c249e 100644 --- a/src/transformation/visitors/language-extensions/range.ts +++ b/src/transformation/visitors/language-extensions/range.ts @@ -8,17 +8,13 @@ import { transformArguments } from "../call"; import { assert } from "../../../utils"; import { invalidRangeControlVariable } from "../../utils/diagnostics"; -const isRangeFunctionDeclaration = (declaration: ts.Declaration): boolean => - extensions.getExtensionKind(declaration) === extensions.ExtensionKind.RangeFunction; - export function isRangeFunction(context: TransformationContext, expression: ts.CallExpression): boolean { - const type = context.checker.getTypeAtLocation(expression.expression); - return type.symbol?.declarations?.some(isRangeFunctionDeclaration) ?? false; + return isRangeFunctionNode(context, expression.expression); } export function isRangeFunctionNode(context: TransformationContext, node: ts.Node): boolean { const symbol = context.checker.getSymbolAtLocation(node); - return symbol?.declarations?.some(isRangeFunctionDeclaration) ?? false; + return symbol ? extensions.isExtensionFunction(context, symbol, extensions.ExtensionKind.RangeFunction) : false; } function getControlVariable(context: TransformationContext, statement: ts.ForOfStatement) { diff --git a/src/transformation/visitors/loops/for-of.ts b/src/transformation/visitors/loops/for-of.ts index 047ed3535..e8241f40f 100644 --- a/src/transformation/visitors/loops/for-of.ts +++ b/src/transformation/visitors/loops/for-of.ts @@ -8,6 +8,7 @@ import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; import { isArrayType, isNumberType } from "../../utils/typescript"; import { transformArguments } from "../call"; import { transformIdentifier } from "../identifier"; +import { isIterableExpression, transformForOfIterableStatement } from "../language-extensions/iterable"; import { isRangeFunction, transformRangeStatement } from "../language-extensions/range"; import { transformArrayBindingElement } from "../variable-declaration"; import { getVariableDeclarationBinding, transformForInitializer, transformLoopBody } from "./utils"; @@ -156,6 +157,8 @@ export const transformForOfStatement: FunctionVisitor = (node return transformRangeStatement(context, node, body); } else if (ts.isCallExpression(node.expression) && isForRangeType(context, node.expression.expression)) { return transformForRangeStatement(context, node, body); + } else if (isIterableExpression(context, node.expression)) { + return transformForOfIterableStatement(context, node, body); } else if (isLuaIteratorType(context, node.expression)) { return transformForOfLuaIteratorStatement(context, node, body); } else if (isArrayType(context, context.checker.getTypeAtLocation(node.expression))) { diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 7e7a7ddf8..f309874f7 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -12,6 +12,7 @@ import { shouldMultiReturnCallBeWrapped, isMultiFunctionCall, isMultiReturnType, + isInMultiReturnFunction, } from "./language-extensions/multi"; import { invalidMultiFunctionReturnType } from "../utils/diagnostics"; @@ -20,6 +21,8 @@ function transformExpressionsInReturn( node: ts.Expression, insideTryCatch: boolean ): lua.Expression[] { + const expressionType = context.checker.getTypeAtLocation(node); + if (ts.isCallExpression(node)) { // $multi(...) if (isMultiFunctionCall(context, node)) { @@ -40,6 +43,9 @@ function transformExpressionsInReturn( if (insideTryCatch && returnsMultiType(context, node) && !shouldMultiReturnCallBeWrapped(context, node)) { return [wrapInTable(context.transformExpression(node))]; } + } else if (isInMultiReturnFunction(context, node) && isMultiReturnType(expressionType)) { + // Unpack objects typed as LuaMultiReturn + return [createUnpackCall(context, context.transformExpression(node), node)]; } if (!isInTupleReturnFunction(context, node)) { @@ -47,7 +53,6 @@ function transformExpressionsInReturn( } let results: lua.Expression[]; - const expressionType = context.checker.getTypeAtLocation(node); // Parent function is a TupleReturn function if (ts.isArrayLiteralExpression(node)) { diff --git a/test/unit/language-extensions/__snapshots__/iterable.spec.ts.snap b/test/unit/language-extensions/__snapshots__/iterable.spec.ts.snap new file mode 100644 index 000000000..1f76110a4 --- /dev/null +++ b/test/unit/language-extensions/__snapshots__/iterable.spec.ts.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LuaIterable with LuaMultiReturn value type invalid LuaIterable without destructuring ("for (const s of testIterable()) {}"): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local function testIterable() + local strsArray = {{\\"a1\\", \\"a2\\"}, {\\"b1\\", \\"b2\\"}, {\\"c1\\", \\"c2\\"}} + local i = 0 + return function() + local strs = strsArray[(function() + local ____tmp = i + i = ____tmp + 1 + return ____tmp + end)() + 1] + if strs then + return table.unpack(strs) + end + end + end + for ____ in testIterable() do + end +end +return ____exports" +`; + +exports[`LuaIterable with LuaMultiReturn value type invalid LuaIterable without destructuring ("for (const s of testIterable()) {}"): diagnostics 1`] = `"main.ts(14,24): error TSTL: LuaIterable with a LuaMultiReturn return value type must be destructured."`; + +exports[`LuaIterable with LuaMultiReturn value type invalid LuaIterable without destructuring ("let s; for (s of testIterable()) {}"): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local function testIterable() + local strsArray = {{\\"a1\\", \\"a2\\"}, {\\"b1\\", \\"b2\\"}, {\\"c1\\", \\"c2\\"}} + local i = 0 + return function() + local strs = strsArray[(function() + local ____tmp = i + i = ____tmp + 1 + return ____tmp + end)() + 1] + if strs then + return table.unpack(strs) + end + end + end + local s + for ____ in testIterable() do + end +end +return ____exports" +`; + +exports[`LuaIterable with LuaMultiReturn value type invalid LuaIterable without destructuring ("let s; for (s of testIterable()) {}"): diagnostics 1`] = `"main.ts(14,25): error TSTL: LuaIterable with a LuaMultiReturn return value type must be destructured."`; diff --git a/test/unit/language-extensions/iterable.spec.ts b/test/unit/language-extensions/iterable.spec.ts new file mode 100644 index 000000000..b6f7ab141 --- /dev/null +++ b/test/unit/language-extensions/iterable.spec.ts @@ -0,0 +1,552 @@ +import * as path from "path"; +import * as util from "../../util"; +import * as tstl from "../../../src"; +import { invalidMultiIterableWithoutDestructuring } from "../../../src/transformation/utils/diagnostics"; + +const iterableProjectOptions: tstl.CompilerOptions = { + types: [path.resolve(__dirname, "../../../language-extensions")], +}; + +describe("simple LuaIterable", () => { + const testIterable = ` + function testIterable(this: void): LuaIterable { + const strs = ["a", "b", "c"]; + let i = 0; + return (() => strs[i++]) as any; + } + `; + const testResults = ["a", "b", "c"]; + + test("const control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + for (const s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("let control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + for (let s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("external control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + let s: string; + for (s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function forward", () => { + util.testFunction` + ${testIterable} + function forward() { return testIterable(); } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function indirect forward", () => { + util.testFunction` + ${testIterable} + function forward() { const iter = testIterable(); return iter; } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("arrow function forward", () => { + util.testFunction` + ${testIterable} + const forward = () => testIterable(); + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("manual use", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + const iter = testIterable(); + while (true) { + const val = iter(); + if (!val) { + break; + } + results.push(val); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); +}); + +describe("LuaIterable using state", () => { + const testIterable = ` + function iterator(this: void, strs: string[], lastStr: string) { + return strs[strs.indexOf(lastStr) + 1]; + } + const testIterable = (() => $multi(iterator, ["a", "b", "c"], "")) as (() => LuaIterable); + `; + const testResults = ["a", "b", "c"]; + + test("const control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + for (const s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("let control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + for (let s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("external control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + let s: string; + for (s of testIterable()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function forward", () => { + util.testFunction` + ${testIterable} + function forward() { return testIterable(); } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function indirect forward", () => { + util.testFunction` + ${testIterable} + function forward() { const iter = testIterable(); return iter; } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("arrow function forward", () => { + util.testFunction` + ${testIterable} + const forward = () => testIterable(); + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("manual use", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + let [iter, state, val] = testIterable(); + while (true) { + val = iter(state, val); + if (!val) { + break; + } + results.push(val); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); +}); + +describe("LuaIterable with array value type", () => { + const testIterable = ` + function testIterable(this: void): LuaIterable { + const strsArray = [["a1", "a2"], ["b1", "b2"], ["c1", "c2"]]; + let i = 0; + return (() => strsArray[i++]) as any; + } + `; + const testResults = [ + ["a1", "a2"], + ["b1", "b2"], + ["c1", "c2"], + ]; + + test("basic destructuring", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + for (const [x, y] of testIterable()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with external control variable", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + let x: string, y: string; + for ([x, y] of testIterable()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with function forward", () => { + util.testFunction` + ${testIterable} + function forward() { return testIterable(); } + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with function indirect forward", () => { + util.testFunction` + ${testIterable} + function forward() { const iter = testIterable(); return iter; } + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure arrow function forward", () => { + util.testFunction` + ${testIterable} + const forward = () => testIterable(); + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("manual use", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + const iter = testIterable(); + while (true) { + const val = iter(); + if (!val) { + break; + } + results.push(val); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); +}); + +describe("LuaIterable with LuaMultiReturn value type", () => { + const testIterable = ` + function testIterable(this: void): LuaIterable> { + const strsArray = [["a1", "a2"], ["b1", "b2"], ["c1", "c2"]]; + let i = 0; + return (() => { + const strs = strsArray[i++]; + if (strs) { + return $multi(...strs); + } + }) as any; + } + `; + const testResults = [ + ["a1", "a2"], + ["b1", "b2"], + ["c1", "c2"], + ]; + + test("basic destructuring", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + for (const [x, y] of testIterable()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with external control variable", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + let x: string, y: string; + for ([x, y] of testIterable()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with function forward", () => { + util.testFunction` + ${testIterable} + function forward() { return testIterable(); } + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure with function indirect forward", () => { + util.testFunction` + ${testIterable} + function forward() { const iter = testIterable(); return iter; } + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure arrow function forward", () => { + util.testFunction` + ${testIterable} + const forward = () => testIterable(); + const results: Array = []; + for (const [x, y] of forward()) { + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("destructure manual use", () => { + util.testFunction` + ${testIterable} + const results: Array = []; + const iter = testIterable(); + while (true) { + const [x, y] = iter(); + if (!x) { + break; + } + results.push([x, y]); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test.each(["for (const s of testIterable()) {}", "let s; for (s of testIterable()) {}"])( + "invalid LuaIterable without destructuring (%p)", + statement => { + util.testFunction` + ${testIterable} + ${statement} + ` + .setOptions(iterableProjectOptions) + .expectDiagnosticsToMatchSnapshot([invalidMultiIterableWithoutDestructuring.code]); + } + ); +}); + +describe("LuaIterable property", () => { + const testIterable = ` + class IterableTester { + public strs = ["a", "b", "c"]; + + public get values(): LuaIterable { + let i = 0; + return (() => this.strs[i++]) as any; + } + } + const tester = new IterableTester(); + `; + const testResults = ["a", "b", "c"]; + + test("basic usage", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + for (const s of tester.values) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("external control variable", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + let s: string; + for (s of tester.values) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function forward", () => { + util.testFunction` + ${testIterable} + function forward() { return tester.values; } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("function indirect forward", () => { + util.testFunction` + ${testIterable} + function forward() { const iter = tester.values; return iter; } + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("arrow function forward", () => { + util.testFunction` + ${testIterable} + const forward = () => tester.values; + const results: string[] = []; + for (const s of forward()) { + results.push(s); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); + + test("manual use", () => { + util.testFunction` + ${testIterable} + const results: string[] = []; + const iter = tester.values; + while (true) { + const val = iter(); + if (!val) { + break; + } + results.push(val); + } + return results; + ` + .setOptions(iterableProjectOptions) + .expectToEqual(testResults); + }); +}); diff --git a/test/unit/language-extensions/multi.spec.ts b/test/unit/language-extensions/multi.spec.ts index ff1fd403a..0b99cf4dc 100644 --- a/test/unit/language-extensions/multi.spec.ts +++ b/test/unit/language-extensions/multi.spec.ts @@ -130,15 +130,37 @@ test("allow $multi call in ArrowFunction body", () => { .expectToEqual(1); }); +test("forward $multi call", () => { + util.testFunction` + function foo() { return $multi(1, 2); } + function call() { return foo(); } + const [resultA, resultB] = call(); + return [resultA, resultB]; + ` + .setOptions(multiProjectOptions) + .expectToEqual([1, 2]); +}); + +test("forward $multi call indirect", () => { + util.testFunction` + function foo() { return $multi(1, 2); } + function call() { const m = foo(); return m; } + const [resultA, resultB] = call(); + return [resultA, resultB]; + ` + .setOptions(multiProjectOptions) + .expectToEqual([1, 2]); +}); + test("forward $multi call in ArrowFunction body", () => { util.testFunction` - const foo = () => $multi(1); + const foo = () => $multi(1, 2); const call = () => foo(); - const [result] = call(); - return result; + const [resultA, resultB] = call(); + return [resultA, resultB]; ` .setOptions(multiProjectOptions) - .expectToEqual(1); + .expectToEqual([1, 2]); }); test.each(["0", "i"])("allow LuaMultiReturn numeric access (%s)", expression => {