From ed6ab548cf879996b078de86e957428382d596a8 Mon Sep 17 00:00:00 2001 From: Dustin Savery Date: Thu, 29 Nov 2018 12:24:48 -0800 Subject: [PATCH 1/2] Revert "[fix] fixing spec inconsistencies" This reverts commit 68053004fe8d564947ccf43b1076dfb53bc273c1. --- README.md | 44 +++++++++++++++++++++++++-------- spec.html | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ed1f2dd..c953d5e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Current Stage: ## Authors * Claude Pache (@claudepache) * Gabriel Isenberg (@the_gisenberg) -* Dustin Savery (@dustinsavery) ## Overview and motivation When looking for a property value that's deep in a tree-like structure, one often has to check whether intermediate nodes exist: @@ -30,6 +29,19 @@ var street = user.address?.street var fooValue = myForm.querySelector('input[name=foo]')?.value ``` +The call variant of Optional Chaining is useful for dealing with interfaces that have optional methods: + +```js +iterator.return?.() // manually close an iterator +``` +or with methods not universally implemented: +```js +if (myForm.checkValidity?.() === false) { // skip the test in older web browsers + // form validation fails + return; +} +``` + ## Prior Art The following languages implement the operator with the same general semantics as this proposal (i.e., 1) guarding against a null base value, and 2) short-circuiting application to the whole chain): * C#: [Null-conditional operator](https://msdn.microsoft.com/en-us/library/dn986595.aspx) — null-conditional member access or index, in read access. @@ -46,6 +58,7 @@ The Optional Chaining operator is spelled `?.`. It may appear in three positions ```javascript obj?.prop // optional static property access obj?.[expr] // optional dynamic property access +func?.(...args) // optional function or method call ``` ### Notes @@ -66,7 +79,11 @@ a == null ? undefined : a[x] a?.b()       // undefined if `a` is null/undefined a == null ? undefined : a.b() // throws a TypeError if `a.b` is not a function - // otherwise, evaluates to `a.b()` +                             // otherwise, evaluates to `a.b()` + +a?.()       // undefined if `a` is null/undefined +a == null ? undefined : a() // throws a TypeError if `a` is neither null/undefined, nor a function +             // invokes the function `a` otherwise ``` ### Short-circuiting @@ -99,9 +116,9 @@ Let’s call *Optional Chain* an Optional Chaining operator followed by a chain An Optional Chain may be followed by another Optional Chain. ```js -a?.b?.['foo'].c(x).d -a == null ? undefined : a.b['foo'] == null ? undefined : a.b['foo'].c(x).d - // (as always, except that `a` and `a.b['foo']` are evaluated only once) +a?.b[3].c?.(x).d +a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d + // (as always, except that `a` and `a.b[3].c` are evaluated only once) ``` ### Edge case: grouping @@ -117,12 +134,19 @@ That follows from the design choice of specifying the scope of short-circuiting Note that, whatever the semantics are, there is no practical reason to use parentheses in that position anyway. +### Optional deletion + +Because the `delete` operator is very liberal in what it accepts, we have that feature for free: +```js +delete a?.b +// delete (a == null ? undefined : a.b) // that *would* work if `? :` could return a Reference... +a == null ? undefined : delete a.b // this is what we get, really +``` + ## Not supported Although they could be included for completeness, the following are not supported due to lack of real-world use cases or other compelling reasons; see [Issue # 22](https://github.com/tc39/proposal-optional-chaining/issues/22) and [Issue #54](https://github.com/tc39/proposal-optional-chaining/issues/54) for discussion: -* optional function execution: `a?.()` -* optional deletion: `delete a?.b` * optional construction: `new a?.()` * optional template literal: ``a?.`{b}` `` * constructor or template literals in/after an Optional Chain: `new a?.b()`, ``a?.b`{c}` `` @@ -141,16 +165,16 @@ All the above cases will be forbidden by the grammar or by static semantics so t
-
obj?.[expr] looks ugly. Why not use obj?[expr] as does <language X>? +
obj?.[expr] and func?.(arg) look ugly. Why not use obj?[expr] and func?(arg) as does <language X>?
-We don’t use the `obj?[expr]` syntax, because of the difficulty for the parser to efficiently distinguish those forms from the conditional operator, e.g., `obj?[expr].filter(fun):0`. +We don’t use the `obj?[expr]` and `func?(arg)` syntax, because of the difficulty for the parser to efficiently distinguish those forms from the conditional operator, e.g., `obj?[expr].filter(fun):0` and `func?(x - 2) + 3 :1`. Alternative syntaxes for those two cases each have their own flaws; and deciding which one looks the least bad is mostly a question of personal taste. Here is how we made our choice: * pick the best syntax for the `obj?.prop` case, which is expected to occur most often; -* extend the use of the recognisable `?.` sequence of characters to other cases: `obj?.[expr]`. +* extend the use of the recognisable `?.` sequence of characters to other cases: `obj?.[expr]`, `func?.(arg)`. As for <language X>, it has different syntactical constraints than JavaScript because of <some construct not supported by X or working differently in X>. diff --git a/spec.html b/spec.html index 2b9de59..32356e6 100644 --- a/spec.html +++ b/spec.html @@ -157,6 +157,7 @@

Syntax

OptionalChain[Yield, Await] : OptionalChainingPunctuator `[` Expression[+In, ?Yield, ?Await] `]` OptionalChainingPunctuator IdentifierName + OptionalChainingPunctuator Arguments[?Yield, ?Await] OptionalChainingPunctuator TemplateLiteral[?Yield, ?Await, +Tagged] OptionalChain[?Yield, ?Await] `[` Expression[+In, ?Yield, ?Await] `]` OptionalChain[?Yield, ?Await] `.` IdentifierName @@ -477,9 +478,64 @@

The `new` Operator (

Function Calls (12.3.4)

- -

(not modified)

- + + +

Runtime Semantics: Evaluation

+ CallExpression : CoverCallExpressionAndAsyncArrowHead + + 1. Let _expr_ be CoveredCallExpression of |CoverCallExpressionAndAsyncArrowHead|. + 1. Let _memberExpr_ be the |MemberExpression| of _expr_. + 1. Let _arguments_ be the |Arguments| of _expr_. + 1. Let _ref_ be the result of evaluating _memberExpr_. + 1. Let _func_ be ? GetValue(_ref_). + 1. If Type(_ref_) is Reference, IsPropertyReference(_ref_) is *false*, and GetReferencedName(_ref_) is `"eval"`, then + 1. If SameValue(_func_, %eval%) is *true*, then + 1. Let _argList_ be ? ArgumentListEvaluation of _arguments_. + 1. If _argList_ has no elements, return *undefined*. + 1. Let _evalText_ be the first element of _argList_. + 1. If the source code matching this |CallExpression| is strict mode code, let _strictCaller_ be *true*. Otherwise let _strictCaller_ be *false*. + 1. Let _evalRealm_ be the current Realm Record. + 1. Perform ? HostEnsureCanCompileStrings(_evalRealm_, _evalRealm_). + 1. Return ? PerformEval(_evalText_, _evalRealm_, _strictCaller_, *true*). + 1. Let _thisCall_ be this |CallExpression|. + 1. Let _tailCall_ be IsInTailPosition(_thisCall_). + 1. Return ? EvaluateCall(_func_, _ref_, _arguments_, _tailCall_). + +

A |CallExpression| evaluation that executes step 6.a.vii is a direct eval.

+ CallExpression : CallExpression Arguments + + 1. Let _ref_ be the result of evaluating |CallExpression|. + 1. Let _func_ be ? GetValue(_ref_). + 1. Let _thisCall_ be this |CallExpression|. + 1. Let _tailCall_ be IsInTailPosition(_thisCall_). + 1. Return ? EvaluateCall(_func_, _ref_, |Arguments|, _tailCall_). + +
+ + +

Runtime Semantics: EvaluateCall ( _func_, _ref_, _arguments_, _tailPosition_ )

+

The abstract operation EvaluateCall takes as arguments a value _func_, a value _ref_, a Parse Node _arguments_, and a Boolean argument _tailPosition_. It performs the following steps:

+ + 1. If Type(_ref_) is Reference, then + 1. If IsPropertyReference(_ref_) is *true*, then + 1. Let _thisValue_ be GetThisValue(_ref_). + 1. Else the base of _ref_ is an Environment Record, + 1. Let _refEnv_ be GetBase(_ref_). + 1. Let _thisValue_ be _refEnv_.WithBaseObject(). + 1. Else Type(_ref_) is not Reference, + 1. Let _thisValue_ be *undefined*. + 1. Let _argList_ be ArgumentListEvaluation of _arguments_. + 1. ReturnIfAbrupt(_argList_). + 1. If Type(_func_) is not Object, throw a *TypeError* exception. + 1. If IsCallable(_func_) is *false*, throw a *TypeError* exception. + 1. If _tailPosition_ is *true*, perform PrepareForTailCall(). + 1. Let _result_ be Call(_func_, _thisValue_, _argList_). + 1. Assert: If _tailPosition_ is *true*, the above call will not return here, but instead evaluation will continue as if the following return has already occurred. + 1. Assert: If _result_ is not an abrupt completion, then Type(_result_) is an ECMAScript language type. + 1. Return _result_. + +
+

The `super` Keyword (12.3.5)

@@ -501,7 +557,8 @@

Tagged Templates (

Optional Chains

- An optional chain is a chain of property accesses introduced by an |OptionalChainingPunctuator|. + An optional chain is a chain of property accesses and function calls introduced by an |OptionalChainingPunctuator|. +

Runtime Semantics: Evaluation

@@ -533,6 +590,12 @@

Runtime Semantics: ChainEvaluation

1. If the code matched by this production is strict mode code, let _strict_ be *true*, else let _strict_ be *false*. 1. Return ? EvaluateStaticPropertyAccess(_baseValue_, |IdentifierName|, _strict_). + OptionalChain : OptionalChainingPunctuator Arguments + + 1. Let _thisChain_ be this production. + 1. Let _tailCall_ be IsInTailPosition(_thisChain_). + 1. Return ? EvaluateCall(_baseValue_, _baseReference_, |Arguments|, _tailCall_). + OptionalChain : OptionalChain `[` Expression `]` 1. Let _optionalChain_ be this |OptionalChain|. @@ -606,6 +669,7 @@

Expression Rules

OptionalChain : + OptionalChainingPunctuator Arguments OptionalChain Arguments @@ -618,4 +682,4 @@

Expression Rules

\ No newline at end of file +--> From 22ba2ca8bc03edbfb97472d7b5ec837d7d408dec Mon Sep 17 00:00:00 2001 From: Dustin Savery Date: Thu, 29 Nov 2018 12:51:18 -0800 Subject: [PATCH 2/2] [tiny] adding name as champion --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c953d5e..23bb811 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Current Stage: ## Authors * Claude Pache (@claudepache) * Gabriel Isenberg (@the_gisenberg) +* Dustin Savery (@dustinsavery) ## Overview and motivation When looking for a property value that's deep in a tree-like structure, one often has to check whether intermediate nodes exist: