diff --git a/docs/advanced/compiler-annotations.md b/docs/advanced/compiler-annotations.md index 67fe10e5..220085a7 100644 --- a/docs/advanced/compiler-annotations.md +++ b/docs/advanced/compiler-annotations.md @@ -416,7 +416,7 @@ NoSelfNS.noSelfFunc("bar") -For more information about how the `self` parameter is handled, see [Functions and the `self` Parameter](functions-and-the-self-parameter.md) +For more information about how the `self` parameter is handled, see [Functions and the `self` Parameter](../the-self-parameter.md) ## @noSelfInFile diff --git a/docs/advanced/functions-and-the-self-parameter.md b/docs/advanced/functions-and-the-self-parameter.md deleted file mode 100644 index de3d646f..00000000 --- a/docs/advanced/functions-and-the-self-parameter.md +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: Functions and the `self` Parameter ---- - -import { SideBySide } from "@site/src/components/SideBySide"; - -## Every Function Has a Context Parameter - -In JavaScript and TypeScript, almost all functions have access to an implicit `this` parameter. In order to maintain compatibility with this, all Lua functions are generated with an extra initial context parameter. - -**Example** - - - -```typescript -function myFunction(arg: string) {} -myFunction("foo"); -``` - -```lua -function myFunction(self, arg) -end -myFunction(nil, "foo") -``` - - - -The reason for this is that a method can be assigned to a stand-alone function and vice-versa. - -**Example** - -```typescript -class MyClass { - myMethod(arg: string) { - console.log("myMethod", arg); - } -} - -let myFunction = function (arg: string) { - console.log("myFunction", arg); -}; - -const c = new MyClass(); - -c.myMethod = myFunction; -c.myMethod("foo"); // should output: myFunction foo -// or -myFunction = c.myMethod; -myFunction("foo"); // should output: myMethod foo; -``` - -If `myFunction` did not have the initial parameter, calling either after being re-assigned would cause potential runtime errors, since `myMethod` would expect an initial parameter and `myFunction` would not. - -Note that even declared functions are assumed to have this extra parameter as well. - -**Example** - - - -```typescript -declare function myLibFunction(arg: string): void; -myLibFunction("foo"); -``` - -```lua -myLibFunction(nil, "foo") -``` - - - -## Removing the Context Parameter - -When dealing with external library functions that don't expect this initial parameter, you will need to inform TypeScriptToLua. This can be done a few different ways. - -### `this: void` - -You can declare any function with `this: void` to prevent generation of this initial argument. - -**Example** - - - -```typescript -declare function myLibFunction(this: void, arg: string): void; -myLibFunction("foo"); -``` - -```lua -myLibFunction("foo") -``` - - - -This works on methods as well, which can be useful if you have class methods which should be called with a dot `.` instead of a colon `:`. - -**Example** - - - -```typescript -declare class MyClass { - withContext(arg: string): void; - withoutContext(this: void, arg: string): void; -} -const c = new MyClass(); -c.withContext("foo"); -c.withoutContext("foo"); -``` - -```lua -local c = __TS__New(MyClass) -c:withContext("foo") -- uses colon : -c.withoutContext("foo") -- uses dot . -``` - - - -Another common scenario is a library function which takes a lua callback function, which should not have a context parameter. - -**Example** - - - - -```typescript -declare function takesCallback( - this: void, - callback: (this: void, arg: string) => void, -): void; - -takesCallback(arg => { - console.log(arg); -}); -``` - -```lua -takesCallback(function(arg) print(arg) end) -``` - - - -### `@noSelf` - -If you wish to specify that all functions in a class, interface or namespace should not have a context parameter, you can use the [`@noSelf`](compiler-annotations.md#noself) annotation. - -**Example** - - - -```typescript -/** @noSelf **/ -declare namespace MyNamespace { - function myFunction(arg: string): void; -} -MyNamespace.myFunction("foo"); -``` - -```lua -MyNamespace.myFunction("foo") -``` - - - -You can override `@noSelf` on a per-function basis by specifying a `this` parameter. - -**Example** - - - -```typescript -/** @noSelf **/ -declare namespace MyNamespace { - function myFunction(this: any, arg: string): void; -} -MyNamespace.myFunction("foo"); -``` - -```lua -MyNamespace:myFunction("foo") -``` - - - -### `@noSelfInFile` - -If you want to specify that all functions in a file should have no context, you can use [`@noSelfInFile`](compiler-annotations.md#noselfinfile) at the top of the file. - -For more information on [`@noSelf`](compiler-annotations.md#noself) and [`@noSelfInFile`](compiler-annotations.md#noselfinfile), please refer to [Compiler Annotations](compiler-annotations). - -## Assignment Errors - -Functions that have a context parameter cannot be assigned to functions that do not, and vice-versa. A common case where this may occur is passing a callback to an api that expects a function that does not take an initial argument. - -**Example** - -```ts -declare function takesCallback(callback: (this: void, arg: string) => void); - -function myCallback(arg: string) {} -takesCallback(myCallback); // Error: 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'. -``` - -This throws an error because if `takesCallback` called `myCallback`, it would do so without passing an initial context parameter. This can be easily fixed simply by wrapping the call in an arrow function. - -**Example** - - - -```typescript -takesCallback((arg) => myCallback(arg)); -``` - -```lua -takesCallback(function(arg) return myCallback(nil, arg) end) -``` - - - -The reason this works is because TypeScriptToLua infers whether the arrow function should take a context parameter or not based on the type it's being assigned to. - -### Overloads - -If a function is overloaded and the signatures differ in context type, you can not assign them: - -```ts -declare function takesFunction(f: Function): void; - -declare function myFunction(this: void, s: string, n: number): void; -declare function myFunction(s: string); - -takesFunction(myFunction); // Error: Unsupported assignment of function with different overloaded types for 'this'. Overloads should all have the same type for 'this'. -``` - -It's best practice to avoid overloads with different context types. diff --git a/docs/the-self-parameter.md b/docs/the-self-parameter.md new file mode 100644 index 00000000..508fdf3d --- /dev/null +++ b/docs/the-self-parameter.md @@ -0,0 +1,255 @@ +--- +title: The Self Parameter +--- + +import { SideBySide } from "@site/src/components/SideBySide"; + +## Why is it there by default + +All functions, by default, have a `self` parameter to preserve JavaScript behaviour. + +Telling TypeScript not to allow `this` to be used will eliminate that parameter from its containing context. + +Other workarounds are available for removing this too. + +> Disabling this self parameter should mainly be done when writing declarations for Lua interop purposes. + +## Removing it + +### `this: void` + +This is a type-safe solution usable whenever describing something callable. + +This tells TypeScript that `this` cannot be used in the context of this function. + +`this: void` results in no `self` parameter to be generated. + +**Example** + + + +```typescript +declare function f(this: void, arg: string): void; +f("foo"); +``` + +```lua +f("foo") +``` + + + +Also useful if you have class methods which should be called with a dot `.` instead of a colon `:`. + +**Example** + + + +```typescript +declare class Class { + colon(arg: string): void; + dot(this: void, arg: string): void; +} + +const c = new Class(); +c.colon("foo"); +c.dot("foo"); +``` + +```lua +local c = __TS__New(Class) +c:colon("foo") +c.dot("foo") +``` + + + +Common Lua libraries use callback functions that don't have a `self` parameter so make sure this is reflected in their declaration. + +**Example** + + + + +```typescript +type Callback = ( + this: void, + arg: string +) => void; + +declare function useCallback( + this: void, + callback: Callback +): void; + +useCallback(arg => { + console.log(arg); +}); +``` + +```lua +useCallback(function(arg) + print(arg) +end) +``` + + + +### `@noSelf` + +If you wish to specify that all functions in a class, interface or namespace should not have a context parameter, you can use the [`@noSelf`](./advanced/compiler-annotations.md#noself) annotation. + +**Example** + + + +```typescript +/** @noSelf **/ +declare namespace Namespace { + function foo(arg: string): void; +} + +Namespace.foo("foo"); +``` + +```lua +Namespace.foo("foo") +``` + + + +You can override `@noSelf` on a per-function basis by specifying a `this` parameter. + +**Example** + + + +```typescript +/** @noSelf **/ +declare namespace Namespace { + function foo(this: any, arg: string): void; +} + +Namespace.foo("foo"); +``` + +```lua +Namespace:foo("foo") +``` + + + +### `@noSelfInFile` + +If you want to specify that all functions in a file should have no context, you can use [`@noSelfInFile`](./advanced/compiler-annotations.md#noselfinfile) at the top of the file. + +For more information on [`@noSelf`](./advanced/compiler-annotations.md#noself) and [`@noSelfInFile`](./advanced/compiler-annotations.md#noselfinfile), please refer to [Compiler Annotations](./advanced/compiler-annotations). + +### `noImplicitSelf` + +Use this option if you do not want implemented functions to have a self parameter. + +Ambient functions (functions described, not implemented) as well as classes and interfaces ignore this option. + +:::note +Use this with `strict` or `noImplicitThis` to ensure you don't use an "implicit this" type in your code. +::: + +_When enabled, if `this` has a type other than an implicit `any`, a `self` parameter will be added for its containing function._ + +```json title=tsconfig.json +{ + "tstl": { + "noImplicitSelf": true + } +} +``` + + + +```typescript +function f() {} +function f2(this: any) {} +const a = () => {}; +class C { + method() {} +} +``` + +```lua +function f() end +function f2(self) end +local a = function() end + +local C = __TS__Class() +function C:method(self) end -- still has self +``` + + + +## Assignment Errors + +See the two types below. + +```typescript +type NoContext = (this: void) => void; +type UseContext = () => void; +``` + +TypeScript sees `NoContext` to be assignable to `UseContext`. + +TypeScriptToLua does not. + +**Example** + +```ts +declare function useCallback(cb: (this: void, arg: string) => void); +// cb's type: (this: void, arg: string) => void + +function callback(arg: string) {} +// callback's type: (arg: string) => void (implicit any) + +useCallback(callback); +``` + +> :x: **Error:** 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'. + +This throws an error because `callback's type` is not assignable to `cb's type` since the latter has an implicit any type which changes how the function should be called. + +To fix this, an arrow function can be used. + +**Example** + + + +```typescript +useCallback((arg) => callback(arg)); +// argument type: (this: void, arg: string) => void +``` + +```lua +useCallback(function(arg) + return callback(nil, arg) +end) +``` + + + +TypeScript says the arrow function has no context due to the parameter's signature making TypeScriptToLua accept the parameter. + +### Overloads + +A similar error occurs if a function is overloaded and the call signature differs between how to use context: + +```ts +declare function useCallback(f: () => {}): void; + +declare function callback(this: void, s: string, n: number): void; +declare function callback(s: string); + +useCallback(callback); +``` + +> :x: **Error:** Unsupported assignment of function with different overloaded types for 'this'. Overloads should all have the same type for 'this'. + +It's best practice to avoid overloads with different context types. diff --git a/sidebars.json b/sidebars.json index 4db4962d..6c6869dc 100644 --- a/sidebars.json +++ b/sidebars.json @@ -3,15 +3,12 @@ "getting-started", "configuration", "caveats", + "the-self-parameter", "editor-support", { "type": "category", "label": "Advanced", - "items": [ - "advanced/writing-declarations", - "advanced/compiler-annotations", - "advanced/functions-and-the-self-parameter" - ] + "items": ["advanced/writing-declarations", "advanced/compiler-annotations"] }, { "type": "category",