From b0f9b99b7f5413dc507307caa8d4655577396f60 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 11 Nov 2020 13:32:01 +1000 Subject: [PATCH 1/5] Move and adjust self parameter document --- .../functions-and-the-self-parameter.md | 234 ---------------- docs/faq/the-self-parameter.md | 255 ++++++++++++++++++ sidebars.json | 11 +- 3 files changed, 261 insertions(+), 239 deletions(-) delete mode 100644 docs/advanced/functions-and-the-self-parameter.md create mode 100644 docs/faq/the-self-parameter.md 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/faq/the-self-parameter.md b/docs/faq/the-self-parameter.md new file mode 100644 index 00000000..44695126 --- /dev/null +++ b/docs/faq/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. + +## Removing it + +### `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 +``` + + + +### `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) +``` + + + +## Removing it via annotations + +### `@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). + +## 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..f87c69ff 100644 --- a/sidebars.json +++ b/sidebars.json @@ -7,16 +7,17 @@ { "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", "label": "API", "items": ["api/overview", "api/plugins", "api/printer"] + }, + { + "type": "category", + "label": "FAQ", + "items": ["faq/the-self-parameter"] } ] } From c64df1b2f864900457e29bbef4f3d776b63d8fc2 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 11 Nov 2020 14:22:57 +1000 Subject: [PATCH 2/5] Fix the-self-parameter link --- docs/advanced/compiler-annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/compiler-annotations.md b/docs/advanced/compiler-annotations.md index 67fe10e5..2dacd404 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](../faq/the-self-parameter.md) ## @noSelfInFile From 6708b44cb1b3146437af23d7ea6f37d23b47adbb Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 11 Nov 2020 14:35:06 +1000 Subject: [PATCH 3/5] Move noImplicitSelf to bottom as last option for the-self-parameter --- docs/faq/the-self-parameter.md | 86 +++++++++++++++++----------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/docs/faq/the-self-parameter.md b/docs/faq/the-self-parameter.md index 44695126..a7d16611 100644 --- a/docs/faq/the-self-parameter.md +++ b/docs/faq/the-self-parameter.md @@ -14,48 +14,6 @@ Other workarounds are available for removing this too. ## Removing it -### `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 -``` - - - ### `this: void` This is a type-safe solution usable whenever describing something callable. @@ -135,8 +93,6 @@ end) -## Removing it via annotations - ### `@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. @@ -187,6 +143,48 @@ If you want to specify that all functions in a file should have no context, you 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. From a134dc4297940b6e113226c0abd10e8fbfab8979 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 11 Nov 2020 14:45:56 +1000 Subject: [PATCH 4/5] Add interop note to the-self-parameter --- docs/faq/the-self-parameter.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/faq/the-self-parameter.md b/docs/faq/the-self-parameter.md index a7d16611..e1f0ee1b 100644 --- a/docs/faq/the-self-parameter.md +++ b/docs/faq/the-self-parameter.md @@ -12,6 +12,8 @@ Telling TypeScript not to allow `this` to be used will eliminate that parameter 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` From c05a194aa318cbe135f3fffea66e50b3e4c5f827 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 11 Nov 2020 14:52:12 +1000 Subject: [PATCH 5/5] Move the-self-parameter to after caveats --- docs/advanced/compiler-annotations.md | 2 +- docs/{faq => }/the-self-parameter.md | 8 ++++---- sidebars.json | 6 +----- 3 files changed, 6 insertions(+), 10 deletions(-) rename docs/{faq => }/the-self-parameter.md (91%) diff --git a/docs/advanced/compiler-annotations.md b/docs/advanced/compiler-annotations.md index 2dacd404..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](../faq/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/faq/the-self-parameter.md b/docs/the-self-parameter.md similarity index 91% rename from docs/faq/the-self-parameter.md rename to docs/the-self-parameter.md index e1f0ee1b..508fdf3d 100644 --- a/docs/faq/the-self-parameter.md +++ b/docs/the-self-parameter.md @@ -1,5 +1,5 @@ --- -title: The self parameter +title: The Self Parameter --- import { SideBySide } from "@site/src/components/SideBySide"; @@ -97,7 +97,7 @@ 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. +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** @@ -141,9 +141,9 @@ 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. +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). +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` diff --git a/sidebars.json b/sidebars.json index f87c69ff..6c6869dc 100644 --- a/sidebars.json +++ b/sidebars.json @@ -3,6 +3,7 @@ "getting-started", "configuration", "caveats", + "the-self-parameter", "editor-support", { "type": "category", @@ -13,11 +14,6 @@ "type": "category", "label": "API", "items": ["api/overview", "api/plugins", "api/printer"] - }, - { - "type": "category", - "label": "FAQ", - "items": ["faq/the-self-parameter"] } ] }