From e738e1ad151072e6ca5a572d3fd0c954294c7846 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 8 Mar 2022 15:29:52 +0200 Subject: [PATCH] RFC: Update 'nil-forgiving operator' to specify behavior wrt type packs --- rfcs/syntax-nil-forgiving-operator.md | 55 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/rfcs/syntax-nil-forgiving-operator.md b/rfcs/syntax-nil-forgiving-operator.md index 63d5cabb..7552d416 100644 --- a/rfcs/syntax-nil-forgiving-operator.md +++ b/rfcs/syntax-nil-forgiving-operator.md @@ -1,4 +1,4 @@ -# nil-forgiving postfix operator ! +# nil-forgiving postfix operator '!' ## 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. -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: + ```lua local p = a!.b!.c ``` + instead of + ```lua 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: + ```lua local p = f(a!)! 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*. Before: + +```ebnf +primaryexp = prefixexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } ``` -primaryexp ::= prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } -``` + After: -``` -postfixeexp ::= (`.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs) [`!'] -primaryexp ::= prefixexp [`!'] { postfixeexp } + +```ebnf +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 * AstExprConstantNumber * 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. ---- -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. +### Parsing of assignments + +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 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. 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. -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 @@ -84,7 +101,15 @@ The operator might be placed by users to ignore/silence correct warnings and low ## 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. 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.