diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5842b0a..5ebfb096c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- `Function.length` is supported now + ## 0.34.0 - Added new `"luaTarget"` option value - `"universal"`. Choosing this target makes TypeScriptToLua generate code compatible with all supported Lua targets. diff --git a/src/transformation/builtins/function.ts b/src/transformation/builtins/function.ts index 0d84e48a8..f6cb8a6a0 100644 --- a/src/transformation/builtins/function.ts +++ b/src/transformation/builtins/function.ts @@ -1,6 +1,8 @@ +import * as ts from "typescript"; +import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; -import { unsupportedProperty, unsupportedSelfFunctionConversion } from "../utils/diagnostics"; +import { unsupportedForTarget, unsupportedProperty, unsupportedSelfFunctionConversion } from "../utils/diagnostics"; import { ContextType, getFunctionContextType } from "../utils/function-context"; import { createUnpackCall } from "../utils/lua-ast"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; @@ -35,3 +37,31 @@ export function transformFunctionPrototypeCall( context.diagnostics.push(unsupportedProperty(expression.name, "function", expressionName)); } } + +export function transformFunctionProperty( + context: TransformationContext, + node: ts.PropertyAccessExpression +): lua.Expression | undefined { + switch (node.name.text) { + case "length": + if (context.luaTarget === LuaTarget.Lua51 || context.luaTarget === LuaTarget.Universal) { + context.diagnostics.push(unsupportedForTarget(node, "function.length", LuaTarget.Lua51)); + } + + // debug.getinfo(fn) + const getInfoCall = lua.createCallExpression( + lua.createTableIndexExpression(lua.createIdentifier("debug"), lua.createStringLiteral("getinfo")), + [context.transformExpression(node.expression)] + ); + + const nparams = lua.createTableIndexExpression(getInfoCall, lua.createStringLiteral("nparams")); + + const contextType = getFunctionContextType(context, context.checker.getTypeAtLocation(node.expression)); + return contextType === ContextType.NonVoid + ? lua.createBinaryExpression(nparams, lua.createNumericLiteral(1), lua.SyntaxKind.SubtractionOperator) + : nparams; + + default: + context.diagnostics.push(unsupportedProperty(node.name, "function", node.name.text)); + } +} diff --git a/src/transformation/builtins/index.ts b/src/transformation/builtins/index.ts index f6be3c0f2..352ac9c2f 100644 --- a/src/transformation/builtins/index.ts +++ b/src/transformation/builtins/index.ts @@ -16,7 +16,7 @@ import { PropertyCallExpression } from "../visitors/call"; import { checkForLuaLibType } from "../visitors/class/new"; import { transformArrayProperty, transformArrayPrototypeCall } from "./array"; import { transformConsoleCall } from "./console"; -import { transformFunctionPrototypeCall } from "./function"; +import { transformFunctionPrototypeCall, transformFunctionProperty } from "./function"; import { transformGlobalCall } from "./global"; import { transformMathCall, transformMathProperty } from "./math"; import { transformNumberConstructorCall, transformNumberPrototypeCall } from "./number"; @@ -38,6 +38,10 @@ export function transformBuiltinPropertyAccessExpression( return transformArrayProperty(context, node); } + if (isFunctionType(context, ownerType)) { + return transformFunctionProperty(context, node); + } + if (ts.isIdentifier(node.expression) && isStandardLibraryType(context, ownerType, undefined)) { switch (node.expression.text) { case "Math": diff --git a/test/unit/functions/__snapshots__/functions.spec.ts.snap b/test/unit/functions/__snapshots__/functions.spec.ts.snap index e1e5b49d2..345e31731 100644 --- a/test/unit/functions/__snapshots__/functions.spec.ts.snap +++ b/test/unit/functions/__snapshots__/functions.spec.ts.snap @@ -1,5 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`function.length unsupported ("5.1"): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local function fn(self) + end + return debug.getinfo(fn).nparams - 1 +end +return ____exports" +`; + +exports[`function.length unsupported ("5.1"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; + +exports[`function.length unsupported ("universal"): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local function fn(self) + end + return debug.getinfo(fn).nparams - 1 +end +return ____exports" +`; + +exports[`function.length unsupported ("universal"): diagnostics 1`] = `"main.ts(3,16): error TSTL: function.length is/are not supported for target Lua 5.1."`; + exports[`missing declaration name: code 1`] = ` "function ____(self) end" diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 7e4aa6a08..20f76e273 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -1,4 +1,6 @@ +import * as tstl from "../../../src"; import * as util from "../../util"; +import { unsupportedForTarget } from "../../../src/transformation/utils/diagnostics"; test("Arrow Function Expression", () => { util.testFunction` @@ -185,6 +187,31 @@ test("Function call", () => { `.expectToMatchJsResult(); }); +test.each([ + "function fn() {}", + "function fn(x, y, z) {}", + "function fn(x, y, z, ...args) {}", + "function fn(...args) {}", + "function fn(this: void) {}", + "function fn(this: void, x, y, z) {}", + "function fnReference(x, y, z) {} const fn = fnReference;", + "const wrap = (fn: (...args: any[]) => any) => (...args: any[]) => fn(...args); const fn = wrap((x, y, z) => {});", +])("function.length (%p)", declaration => { + util.testFunction` + ${declaration} + return fn.length; + `.expectToMatchJsResult(); +}); + +test.each([tstl.LuaTarget.Lua51, tstl.LuaTarget.Universal])("function.length unsupported (%p)", luaTarget => { + util.testFunction` + function fn() {} + return fn.length; + ` + .setOptions({ luaTarget }) + .expectDiagnosticsToMatchSnapshot([unsupportedForTarget.code]); +}); + test("Recursive function definition", () => { util.testFunction` function f() { return typeof f; };