mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
RFC: Update 'nil-forgiving operator' to specify behavior wrt type packs
This commit is contained in:
parent
dbdf91f3ca
commit
e738e1ad15
1 changed files with 40 additions and 15 deletions
|
@ -1,4 +1,4 @@
|
||||||
# nil-forgiving postfix operator !
|
# nil-forgiving postfix operator '!'
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
@ -8,18 +8,22 @@ Introduce syntax to suppress typechecking errors for nilable types by ascribing
|
||||||
|
|
||||||
Typechecking might not be able to figure out that a certain expression is a non-nil type, but the user might know additional context of the expression.
|
Typechecking might not be able to figure out that a certain expression is a non-nil type, but the user might know additional context of the expression.
|
||||||
|
|
||||||
Using `::` ascriptions is the current work-around for this issue, but it's much more verbose and requires providing the full type name when you only want to ascribe T? to T.
|
Using '`::`' ascriptions is the current work-around for this issue, but it's much more verbose and requires providing the full type name when you only want to ascribe `T?` to `T`.
|
||||||
|
|
||||||
The nil-forgiving operator will also allow chaining to be written in a very terse manner:
|
The nil-forgiving operator will also allow chaining to be written in a very terse manner:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local p = a!.b!.c
|
local p = a!.b!.c
|
||||||
```
|
```
|
||||||
|
|
||||||
instead of
|
instead of
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local ((p :: Part).b :: Folder).c
|
local ((p :: Part).b :: Folder).c
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that nil-forgiving operator is **not** a part of member access operator, it can be used in standalone expressions, indexing and other places:
|
Note that nil-forgiving operator is **not** a part of member access operator, it can be used in standalone expressions, indexing and other places:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local p = f(a!)!
|
local p = f(a!)!
|
||||||
local q = b!['X']
|
local q = b!['X']
|
||||||
|
@ -32,18 +36,22 @@ Nil-forgiving operator can be found in some programming languages such as C# (nu
|
||||||
To implement this, we will change the syntax of the *primaryexp*.
|
To implement this, we will change the syntax of the *primaryexp*.
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
|
||||||
|
```ebnf
|
||||||
|
primaryexp = prefixexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs }
|
||||||
```
|
```
|
||||||
primaryexp ::= prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs }
|
|
||||||
```
|
|
||||||
After:
|
After:
|
||||||
```
|
|
||||||
postfixeexp ::= (`.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs) [`!']
|
```ebnf
|
||||||
primaryexp ::= prefixexp [`!'] { postfixeexp }
|
postfixeexp = ('.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs) ['!']
|
||||||
|
primaryexp = prefixexp ['!'] { postfixeexp }
|
||||||
```
|
```
|
||||||
|
|
||||||
When we get the `!` token, we will wrap the expression that we have into a new AstExprNonNilAssertion node.
|
When we get the '`!`' token, we will wrap the expression that we have into a new AstExprNonNilAssertion node.
|
||||||
|
|
||||||
|
An error is generated when the type of expression node is one of the following:
|
||||||
|
|
||||||
An error is generated when the type of expression node this is one of the following:
|
|
||||||
* AstExprConstantBool
|
* AstExprConstantBool
|
||||||
* AstExprConstantNumber
|
* AstExprConstantNumber
|
||||||
* AstExprConstantString
|
* AstExprConstantString
|
||||||
|
@ -52,15 +60,18 @@ An error is generated when the type of expression node this is one of the follow
|
||||||
|
|
||||||
This operator doesn't have any impact on the run-time behavior of the program, it will only affect the type of the expression in the typechecker.
|
This operator doesn't have any impact on the run-time behavior of the program, it will only affect the type of the expression in the typechecker.
|
||||||
|
|
||||||
---
|
### Parsing of assignments
|
||||||
While parsing an assignment expression starts with a *primaryexp*, it performs a check that it has an l-value based on a fixed set of AstNode types.
|
|
||||||
|
An assignment expression starts with a *primaryexp* and we perform a check that it is a valid l-value based on a fixed set of AstNode types.
|
||||||
|
|
||||||
|
Since using '`!`' on an l-value has no effect, we don't extend this list with the new node and will generate a specialized parse error for code like:
|
||||||
|
|
||||||
Since using `!` on an l-value has no effect, we don't extend this list with the new node and will generate a specialized parse error for code like:
|
|
||||||
```lua
|
```lua
|
||||||
p.a! = b
|
p.a! = b
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Handling of union types
|
||||||
|
|
||||||
When operator is used on expression of a union type with a `nil` option, it removes that option from the set.
|
When operator is used on expression of a union type with a `nil` option, it removes that option from the set.
|
||||||
If only one option remains, the union type is replaced with the type of a single option.
|
If only one option remains, the union type is replaced with the type of a single option.
|
||||||
|
|
||||||
|
@ -70,7 +81,13 @@ For any other type, it has no effect and doesn't generate additional warnings.
|
||||||
|
|
||||||
The reason for the last rule is to simplify movement of existing code where context in each location is slightly different.
|
The reason for the last rule is to simplify movement of existing code where context in each location is slightly different.
|
||||||
|
|
||||||
As an example from Roblox, instance path could dynamically change from being know to exist to be missing when script is changed in edit mode.
|
As an example from Roblox, instance path could dynamically change from being known to exist to be missing when script is changed in edit mode.
|
||||||
|
|
||||||
|
### Handling of type packs
|
||||||
|
|
||||||
|
When operator is used on expression that is a type pack ('`...`' or a function call in tail position), we will perform `nil` option filtering for each member of the type pack based on the rules for types.
|
||||||
|
|
||||||
|
If the type pack is variadic or has a variadic tail, we will perform `nil` option filtering for the type inside VariadicTypePack.
|
||||||
|
|
||||||
## Drawbacks
|
## Drawbacks
|
||||||
|
|
||||||
|
@ -84,7 +101,15 @@ The operator might be placed by users to ignore/silence correct warnings and low
|
||||||
|
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
|
||||||
Aside from type assertion operator :: it should be possible to place `assert` function calls before the operation.
|
### Test with assert call
|
||||||
|
|
||||||
|
Aside from type assertion operator '`::`', it should be possible to place `assert` function calls before the operation.
|
||||||
Type refinement/constraints should handle that statement and avoid warning in the following expressions.
|
Type refinement/constraints should handle that statement and avoid warning in the following expressions.
|
||||||
|
|
||||||
But `assert` call will introduce runtime overhead without adding extra safety to the case when the type is nil at run time, in both cases an error will be thrown.
|
But `assert` call will introduce runtime overhead without adding extra safety to the case when the type is nil at run time, in both cases an error will be thrown.
|
||||||
|
|
||||||
|
### Extending function definition syntax
|
||||||
|
|
||||||
|
In a function definition like `function x.f()` we could've extended the syntax to allow `function x!.f()` to silence the analysis warning in case `x` is optional.
|
||||||
|
|
||||||
|
But this would complicate the parsing and analysis of function definition statements and the feature complexity doesn't seem to be worth it in this particular case.
|
||||||
|
|
Loading…
Add table
Reference in a new issue