From 165567968a15116731011c41664d0053d3bb54a9 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 11 Sep 2024 17:47:35 +0200 Subject: [PATCH 1/4] Resolve bug inferring context type from generic functions in type parameters --- src/transformation/utils/function-context.ts | 9 ++++++++- .../validFunctionAssignments.spec.ts | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/transformation/utils/function-context.ts b/src/transformation/utils/function-context.ts index 3055c4f53..0dccc72af 100644 --- a/src/transformation/utils/function-context.ts +++ b/src/transformation/utils/function-context.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import { CompilerOptions } from "../../CompilerOptions"; import { TransformationContext } from "../context"; import { AnnotationKind, getFileAnnotations, getNodeAnnotations } from "./annotations"; -import { findFirstNodeAbove, getAllCallSignatures, inferAssignedType } from "./typescript"; +import { findFirstNodeAbove, findFirstNonOuterParent, getAllCallSignatures, inferAssignedType } from "./typescript"; export enum ContextType { None = 0, @@ -118,6 +118,13 @@ function computeDeclarationContextType(context: TransformationContext, signature return ContextType.Void; } + if ( + ts.isArrowFunction(signatureDeclaration) && + ts.isCallExpression(findFirstNonOuterParent(signatureDeclaration)) + ) { + return ContextType.Void; + } + if ( ts.isMethodSignature(signatureDeclaration) || ts.isMethodDeclaration(signatureDeclaration) || diff --git a/test/unit/functions/validation/validFunctionAssignments.spec.ts b/test/unit/functions/validation/validFunctionAssignments.spec.ts index 7a68d0ea6..eea8cf53e 100644 --- a/test/unit/functions/validation/validFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/validFunctionAssignments.spec.ts @@ -248,3 +248,21 @@ test("Does not fail on union type signatures (#896)", () => { ) .expectToHaveNoDiagnostics(); }); + +// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1568 +test("No false positives when using generic functions (#1568)", () => { + util.testModule` + /** @noSelf */ + declare namespace Test { + export function testCallback void>( + callback: T, + ): void; + export function testCallback2( + callback: (...args: any[]) => void, + ): void; + } + + Test.testCallback(() => {}); + Test.testCallback2(() => {}); + `.expectToHaveNoDiagnostics(); +}); From 83637a3311b10eba0ce9a016611a8e10f59756f5 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 15 Sep 2024 21:34:23 +0200 Subject: [PATCH 2/4] fix tests --- .../validation/functionPermutations.ts | 11 ++++++--- .../validFunctionAssignments.spec.ts | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/test/unit/functions/validation/functionPermutations.ts b/test/unit/functions/validation/functionPermutations.ts index 14e478498..5743ebcd6 100644 --- a/test/unit/functions/validation/functionPermutations.ts +++ b/test/unit/functions/validation/functionPermutations.ts @@ -351,13 +351,18 @@ const noSelfInFileTestFunctions: TestFunction[] = [ }, ]; -export const anonTestFunctionExpressions: TestFunction[] = [ - { value: "s => s" }, - { value: "(s => s)" }, +export const anonArrowFunctionExpressions: TestFunction[] = [{ value: "s => s" }, { value: "(s => s)" }]; + +export const anonNonArrowFunctionExpressions: TestFunction[] = [ { value: "function(s) { return s; }" }, { value: "(function(s) { return s; })" }, ]; +export const anonTestFunctionExpressions: TestFunction[] = [ + ...anonArrowFunctionExpressions, + ...anonNonArrowFunctionExpressions, +]; + export const selfTestFunctionExpressions: TestFunction[] = [ { value: "function(this: any, s) { return s; }" }, { value: "(function(this: any, s) { return s; })" }, diff --git a/test/unit/functions/validation/validFunctionAssignments.spec.ts b/test/unit/functions/validation/validFunctionAssignments.spec.ts index eea8cf53e..2c9ffc5c6 100644 --- a/test/unit/functions/validation/validFunctionAssignments.spec.ts +++ b/test/unit/functions/validation/validFunctionAssignments.spec.ts @@ -1,6 +1,7 @@ import * as util from "../../../util"; import { - anonTestFunctionExpressions, + anonArrowFunctionExpressions, + anonNonArrowFunctionExpressions, anonTestFunctionType, noSelfTestFunctionExpressions, noSelfTestFunctions, @@ -133,10 +134,10 @@ test.each([ }); test.each([ - ...anonTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["0", "'foobar'"]]), - ...selfTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["0", "'foobar'"]]), + ...anonNonArrowFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["'context'", "'foobar'"]]), + ...selfTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["'context'", "'foobar'"]]), ...noSelfTestFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["'foobar'"]]), -])("Valid function expression argument with no signature (%p, %p)", (testFunction, args) => { +])("Valid function expression argument with no signature have context (%p, %p)", (testFunction, args) => { util.testFunction` const takesFunction: any = (fn: (this: void, ...args: any[]) => any, ...args: any[]) => { return fn(...args); @@ -147,6 +148,20 @@ test.each([ .expectToEqual("foobar"); }); +test.each([...anonArrowFunctionExpressions.map((f): [TestFunction, string[]] => [f, ["'foobar'"]])])( + "Valid arrow function expression argument with no signature do not have context (%p, %p)", + (testFunction, args) => { + util.testFunction` + const takesFunction: any = (fn: (this: void, ...args: any[]) => any, ...args: any[]) => { + return fn(...args); + } + return takesFunction(${testFunction.value}, ${args.join(", ")}); + ` + .setTsHeader(testFunction.definition ?? "") + .expectToEqual("foobar"); + } +); + test.each(validTestFunctionAssignments)("Valid function return (%p)", (testFunction, functionType) => { util.testFunction` function returnsFunction(): ${functionType} { From e6375bf5c2fad4fbe4d4eb64c94f8abebd9018bc Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sat, 21 Sep 2024 17:15:12 +0200 Subject: [PATCH 3/4] fix failing tests --- src/lualib/Await.ts | 2 +- src/lualib/Promise.ts | 4 ++-- src/lualib/Using.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lualib/Await.ts b/src/lualib/Await.ts index 7f6a345ee..2223d29cb 100644 --- a/src/lualib/Await.ts +++ b/src/lualib/Await.ts @@ -52,7 +52,7 @@ export function __TS__AsyncAwaiter(this: void, generator: (this: void) => void) return __TS__Promise.resolve(result).addCallbacks(fulfilled, reject); } - const [success, resultOrError] = coresume(asyncCoroutine, (v: unknown) => { + const [success, resultOrError] = coresume(asyncCoroutine, function(v: unknown) { resolved = true; return __TS__Promise.resolve(v).addCallbacks(resolve, reject); }); diff --git a/src/lualib/Promise.ts b/src/lualib/Promise.ts index c5b6aa2a5..609e0349f 100644 --- a/src/lualib/Promise.ts +++ b/src/lualib/Promise.ts @@ -76,8 +76,8 @@ export class __TS__Promise implements Promise { const [success, error] = pcall( executor, undefined, - v => this.resolve(v), - err => this.reject(err) + this.resolve.bind(this), + this.reject.bind(this) ); if (!success) { // When a promise executor throws, the promise should be rejected with the thrown object as reason diff --git a/src/lualib/Using.ts b/src/lualib/Using.ts index c61d544eb..c13e18aa3 100644 --- a/src/lualib/Using.ts +++ b/src/lualib/Using.ts @@ -5,7 +5,7 @@ export function __TS__Using( ): TReturn { let thrownError; const [ok, result] = xpcall( - () => cb(...args), + () => cb.call(this, ...args), err => (thrownError = err) ); From 1f76bb8583fea16fa8fcf8fe165b741a2c3a5376 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 23 Sep 2024 22:09:29 +0200 Subject: [PATCH 4/4] Fix lualib --- src/lualib/Await.ts | 2 +- src/lualib/Promise.ts | 7 +------ src/lualib/Using.ts | 2 +- src/transformation/builtins/function.ts | 7 +------ 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/lualib/Await.ts b/src/lualib/Await.ts index 2223d29cb..acc2a4fd1 100644 --- a/src/lualib/Await.ts +++ b/src/lualib/Await.ts @@ -52,7 +52,7 @@ export function __TS__AsyncAwaiter(this: void, generator: (this: void) => void) return __TS__Promise.resolve(result).addCallbacks(fulfilled, reject); } - const [success, resultOrError] = coresume(asyncCoroutine, function(v: unknown) { + const [success, resultOrError] = coresume(asyncCoroutine, function (v: unknown) { resolved = true; return __TS__Promise.resolve(v).addCallbacks(resolve, reject); }); diff --git a/src/lualib/Promise.ts b/src/lualib/Promise.ts index 609e0349f..0dfbde234 100644 --- a/src/lualib/Promise.ts +++ b/src/lualib/Promise.ts @@ -73,12 +73,7 @@ export class __TS__Promise implements Promise { constructor(executor: PromiseExecutor) { // Avoid unnecessary local functions allocations by using `pcall` explicitly - const [success, error] = pcall( - executor, - undefined, - this.resolve.bind(this), - this.reject.bind(this) - ); + const [success, error] = pcall(executor, undefined, this.resolve.bind(this), this.reject.bind(this)); if (!success) { // When a promise executor throws, the promise should be rejected with the thrown object as reason this.reject(error); diff --git a/src/lualib/Using.ts b/src/lualib/Using.ts index d02436b80..a720f6e60 100644 --- a/src/lualib/Using.ts +++ b/src/lualib/Using.ts @@ -5,7 +5,7 @@ export function __TS__Using( ): TReturn { let thrownError; const [ok, result] = xpcall( - () => cb.call(this, ...args), + () => cb(...args), err => (thrownError = err) ); diff --git a/src/transformation/builtins/function.ts b/src/transformation/builtins/function.ts index 8b219b6ba..ebf595eed 100644 --- a/src/transformation/builtins/function.ts +++ b/src/transformation/builtins/function.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import { LuaTarget } from "../../CompilerOptions"; import * as lua from "../../LuaAST"; import { TransformationContext } from "../context"; -import { unsupportedForTarget, unsupportedProperty, unsupportedSelfFunctionConversion } from "../utils/diagnostics"; +import { unsupportedForTarget, unsupportedProperty } from "../utils/diagnostics"; import { ContextType, getFunctionContextType } from "../utils/function-context"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformCallAndArguments } from "../visitors/call"; @@ -13,11 +13,6 @@ export function transformFunctionPrototypeCall( node: ts.CallExpression, calledMethod: ts.PropertyAccessExpression ): lua.CallExpression | undefined { - const callerType = context.checker.getTypeAtLocation(calledMethod.expression); - if (getFunctionContextType(context, callerType) === ContextType.Void) { - context.diagnostics.push(unsupportedSelfFunctionConversion(node)); - } - const signature = context.checker.getResolvedSignature(node); const [caller, params] = transformCallAndArguments(context, calledMethod.expression, node.arguments, signature); const expressionName = calledMethod.name.text;