Specifying the Existential Operator using Abrupt Completion

# Claude Pache (4 years ago)

I have thought about the right semantics (and the issues) of the existential operator.

user.getPlan?().value?.score;

The intended semantics of ? is that, whenever its LHS evaluates to null or undefined, the evaluation of the whole expression (or subexpression) is interrupted and return immediately undefined. In other word, it may be seen as an abrupt completion, as demonstrated by the following expansion:

(do {
    let result = user           // user
    result = result.getPlan // _.getPlan
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.call(user)  // _()
    result = result.value       // _.value
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.score       // _.score
})

Now, it should be determined, whenever such an abrupt completion is encountered during evaluation, in which cases it should propagate, and in which case it should be transformed into a normal completion of value undefined. Roughly, property accesses and function calls should propagate it when it is found on their LHS, and in all other cases it should be mutated it to NormalCompletion(undefined). E.g.:

(user.getPlan?().value?.score || 0).toFixed(2) // the `||` operator interrupts the abrupt completion propagation

Here is a simple strawman that illustrates how that idea could be implemented in the spec. It has some known (and unknown) issues, but I think it gives a good idea of the mechanism. And it resolves quite naturally the non-compositional issue of the original strawman (see the remark at the end of the message).

The specification is patched as follows:

6.2.2 The Completion Record Specification Type

An additional value is allowed for the [[type]] field, besides normal, break, continue, return, or throw; namely: failsoft.

6.2.3.1 GetValue(V)

An additional parameter is added to that abstract operation:

GetValue(V, propagateFailsoft = false)

If the second argument is absent, it is presumed to be false. An additional step is prepended to the algorithm:

  1. If V is an abrupt completion of [[type]] failsoft and if propagateFailsoft is false,
    a. Let V be NormalCompletion(undefined).

12.3 Left-Hand-Side Expressions

The production of MemberExpression is expanded as follows:

FailsoftMark: "?"

MemberExpression:
(...)
MemberExpression FailsoftMark

The runtime semantics is the following:

MemberExpression: MemberExpression FailsoftMark

1. Let ref be the result of evaluating MemberExpression.
2. Let val be GetValue(ref, true).
3. ReturnIfAbrupt(val).
3. If val is null or undefined,
    a. Return Completion([[type]]: failsoft, [[value]]: undefined, [[target]]: empty).
4. Return val.

Here, there is an issue in expressions like a.b?(c), because the correct this value of the method call won't be able to be determined. This can be resolved by further suitable refinements.

Finally, in the algorithms involving LeftHandSideExpression's (section 12.3), some calls to GetValue(...) are replaced by GetValue(..., true). They are:

Property Accessors
12.3.2.. Runtime Semantics: Evaluation
MemberExpression : MemberExpression [ Expression ]
Step 2. Let baseValue be GetValue(baseReference, true).

The new operator
12.3.3.1 Runtime Semantics: Evaluation
NewExpression : new NewExpression
Step 2. Let constructor be GetValue(ref, true).

ibid.
MemberExpression : new MemberExpression Arguments
Step 2. Let constructor be GetValue(ref, true).

Function Calls
12.3.4.2 Runtime Semantics: EvaluateCall
Step 1. Let func be GetValue(ref, true).

In all other cases, a call to GetValue(...) will intercept a failsoft abrupt completion and return undefined.

A notable fact of that strawman is that the two following expressions are equivalent:

(a?.b).c
a?.b.c

because evaluating a ParenthesizedExpression does not apply GetValue(...) (Section 12.2.10.4).

—Claude

# Andreas Rossberg (4 years ago)

On 21 May 2014 14:33, Claude Pache <claude.pache at gmail.com> wrote:

I have thought about the right semantics (and the issues) of the existential operator.

user.getPlan?().value?.score;

The intended semantics of ? is that, whenever its LHS evaluates to null or undefined, the evaluation of the whole expression (or subexpression) is interrupted and return immediately undefined. In other word, it may be seen as an abrupt completion, as demonstrated by the following expansion:

(do {
    let result = user           // user
    result = result.getPlan // _.getPlan
    if (result == null)         // _?
        break                   // abrupt completion

Drive-by comment: This would break the switch or loop statement surrounding the do-expression (or be an error if there isn't any). You need to wrap the statements into a labelled block and break that.

# Allen Wirfs-Brock (4 years ago)

On May 21, 2014, at 5:56 AM, Andreas Rossberg wrote:

On 21 May 2014 14:33, Claude Pache <claude.pache at gmail.com> wrote:

I have thought about the right semantics (and the issues) of the existential operator.

user.getPlan?().value?.score;

The intended semantics of ? is that, whenever its LHS evaluates to null or undefined, the evaluation of the whole expression (or subexpression) is interrupted and return immediately undefined. In other word, it may be seen as an abrupt completion, as demonstrated by the following expansion:

(do { let result = user // user result = result.getPlan // _.getPlan if (result == null) // _? break // abrupt completion

Drive-by comment: This would break the switch or loop statement surrounding the do-expression (or be an error if there isn't any). You need to wrap the statements into a labelled block and break that.

Well, completion records are just a specification device and if we need a new [[type]] of abrupt completion to simplify specifying some semantics we certainly could add it.

# C. Scott Ananian (2 years ago)

On Wed, May 21, 2014 at 8:33 AM, Claude Pache <claude.pache at gmail.com>

wrote:

I have thought about the right semantics (and the issues) of the existential operator.

user.getPlan?().value?.score;

The intended semantics of ? is that, whenever its LHS evaluates to null or undefined, the evaluation of the whole expression (or subexpression) is interrupted and return immediately undefined. In other word, it may be seen as an abrupt completion, as demonstrated by the following expansion:

(do {
    let result = user           // user
    result = result.getPlan // _.getPlan
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.call(user)  // _()
    result = result.value       // _.value
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.score       // _.score
})

Now, it should be determined, whenever such an abrupt completion is encountered during evaluation, in which cases it should propagate, and in which case it should be transformed into a normal completion of value undefined. Roughly, property accesses and function calls should propagate it when it is found on their LHS, and in all other cases it should be mutated it to NormalCompletion(undefined). E.g.:

(user.getPlan?().value?.score || 0).toFixed(2) // the `||` operator

interrupts the abrupt completion propagation

Here is a simple strawman that illustrates how that idea could be implemented in the spec. It has some known (and unknown) issues, but I think it gives a good idea of the mechanism. And it resolves quite naturally the non-compositional issue of the original strawman (see the remark at the end of the message).

The specification is patched as follows:

6.2.2 The Completion Record Specification Type

An additional value is allowed for the [[type]] field, besides normal,

break, continue, return, or throw; namely: failsoft.

6.2.3.1 GetValue(V)

An additional parameter is added to that abstract operation:

GetValue(V, propagateFailsoft = false)

If the second argument is absent, it is presumed to be false. An additional step is prepended to the algorithm:

  1. If V is an abrupt completion of [[type]] failsoft and if propagateFailsoft is false, a. Let V be NormalCompletion(undefined).

12.3 Left-Hand-Side Expressions

The production of MemberExpression is expanded as follows:

FailsoftMark: "?"

MemberExpression: (...) MemberExpression FailsoftMark

The runtime semantics is the following:

MemberExpression: MemberExpression FailsoftMark

1. Let ref be the result of evaluating MemberExpression.
2. Let val be GetValue(ref, true).
3. ReturnIfAbrupt(val).
3. If val is null or undefined,
    a. Return Completion([[type]]: failsoft, [[value]]: undefined,

[[target]]: empty). 4. Return val.

Here, there is an issue in expressions like a.b?(c), because the correct this value of the method call won't be able to be determined. This can be resolved by further suitable refinements.

Finally, in the algorithms involving LeftHandSideExpression's (section 12.3), some calls to GetValue(...) are replaced by GetValue(..., true). They are:

Property Accessors 12.3.2.. Runtime Semantics: Evaluation MemberExpression : MemberExpression [ Expression ] Step 2. Let baseValue be GetValue(baseReference, true).

The new operator 12.3.3.1 Runtime Semantics: Evaluation NewExpression : new NewExpression Step 2. Let constructor be GetValue(ref, true).

ibid. MemberExpression : new MemberExpression Arguments Step 2. Let constructor be GetValue(ref, true).

Function Calls 12.3.4.2 Runtime Semantics: EvaluateCall Step 1. Let func be GetValue(ref, true).

In all other cases, a call to GetValue(...) will intercept a failsoft abrupt completion and return undefined.

A notable fact of that strawman is that the two following expressions are equivalent:

(a?.b).c
a?.b.c

because evaluating a ParenthesizedExpression does not apply GetValue(...) (Section 12.2.10.4).

—Claude

Whatever happened to this proposal? I'd love to see a proposal for the existential operator continue to move ahead, and Claude's was the best one, IMO.

# Claude Pache (2 years ago)

Le 13 janv. 2016 à 18:06, C. Scott Ananian <ecmascript at cscott.net> a écrit :

On Wed, May 21, 2014 at 8:33 AM, Claude Pache <claude.pache at gmail.com <mailto:claude.pache at gmail.com>> wrote:

I have thought about the right semantics (and the issues) of the existential operator.

user.getPlan?().value?.score;

The intended semantics of ? is that, whenever its LHS evaluates to null or undefined, the evaluation of the whole expression (or subexpression) is interrupted and return immediately undefined. In other word, it may be seen as an abrupt completion, as demonstrated by the following expansion:

(do {
    let result = user           // user
    result = result.getPlan // _.getPlan
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.call(user)  // _()
    result = result.value       // _.value
    if (result == null)         // _?
        break                   // abrupt completion
    result = result.score       // _.score
})

Now, it should be determined, whenever such an abrupt completion is encountered during evaluation, in which cases it should propagate, and in which case it should be transformed into a normal completion of value undefined. Roughly, property accesses and function calls should propagate it when it is found on their LHS, and in all other cases it should be mutated it to NormalCompletion(undefined). E.g.:

(user.getPlan?().value?.score || 0).toFixed(2) // the `||` operator interrupts the abrupt completion propagation

Here is a simple strawman that illustrates how that idea could be implemented in the spec. It has some known (and unknown) issues, but I think it gives a good idea of the mechanism. And it resolves quite naturally the non-compositional issue of the original strawman (see the remark at the end of the message).

The specification is patched as follows:

6.2.2 The Completion Record Specification Type

An additional value is allowed for the [[type]] field, besides normal, break, continue, return, or throw; namely: failsoft.

6.2.3.1 GetValue(V)

An additional parameter is added to that abstract operation:

GetValue(V, propagateFailsoft = false)

If the second argument is absent, it is presumed to be false. An additional step is prepended to the algorithm:

  1. If V is an abrupt completion of [[type]] failsoft and if propagateFailsoft is false, a. Let V be NormalCompletion(undefined).

12.3 Left-Hand-Side Expressions

The production of MemberExpression is expanded as follows:

FailsoftMark: "?"

MemberExpression: (...) MemberExpression FailsoftMark

The runtime semantics is the following:

MemberExpression: MemberExpression FailsoftMark

1. Let ref be the result of evaluating MemberExpression.
2. Let val be GetValue(ref, true).
3. ReturnIfAbrupt(val).
3. If val is null or undefined,
    a. Return Completion([[type]]: failsoft, [[value]]: undefined, [[target]]: empty).
4. Return val.

Here, there is an issue in expressions like a.b?(c), because the correct this value of the method call won't be able to be determined. This can be resolved by further suitable refinements.

Finally, in the algorithms involving LeftHandSideExpression's (section 12.3), some calls to GetValue(...) are replaced by GetValue(..., true). They are:

Property Accessors 12.3.2.. Runtime Semantics: Evaluation MemberExpression : MemberExpression [ Expression ] Step 2. Let baseValue be GetValue(baseReference, true).

The new operator 12.3.3.1 Runtime Semantics: Evaluation NewExpression : new NewExpression Step 2. Let constructor be GetValue(ref, true).

ibid. MemberExpression : new MemberExpression Arguments Step 2. Let constructor be GetValue(ref, true).

Function Calls 12.3.4.2 Runtime Semantics: EvaluateCall Step 1. Let func be GetValue(ref, true).

In all other cases, a call to GetValue(...) will intercept a failsoft abrupt completion and return undefined.

A notable fact of that strawman is that the two following expressions are equivalent:

(a?.b).c
a?.b.c

because evaluating a ParenthesizedExpression does not apply GetValue(...) (Section 12.2.10.4).

—Claude

Whatever happened to this proposal? I'd love to see a proposal for the existential operator continue to move ahead, and Claude's was the best one, IMO. --scott

Since nobody seems to have taken this, I will submit a formal proposal for stage 0 very soon (before two weeks).

With respect to the above old draft, my proposal will use a special Reference instead of a special abrupt completion (see 1); but the basic idea remains the same: the essential difference is that the spec will propagate the special Reference at chosen points instead of catching the special abrupt completion everywhere else.

The exact syntax will remain open, as ?. has some issues, and there are nonproblematic alternatives such as .? and ..; but the semantics should be ok.

# Kevin Smith (2 years ago)

Since nobody seems to have taken this, I will submit a formal proposal for stage 0 very soon (before two weeks).

Thanks Claude - looking forward to it!

# Gary Guo (2 years ago)

Note that this can possibly complicate the parser as additional effort is required to distinguish it from conditional expression.

From: zenparsing at gmail.com Date: Thu, 14 Jan 2016 15:08:21 +0000 Subject: Re: Specifying the Existential Operator using Abrupt Completion To: claude.pache at gmail.com; ecmascript at cscott.net CC: es-discuss at mozilla.org

Since nobody seems to have taken this, I will submit a formal proposal for stage 0 very soon (before two weeks). Thanks Claude - looking forward to it!

# Isiah Meadows (2 years ago)

As if that weren't the case with async arrow functions with a parenthesized parameter list? I know that takes potentially an expression to differentiate the two.

# John Lenz (2 years ago)

Did this happen? I would like to see some progress here.

# Claude Pache (2 years ago)

(Sorry for spam.) For reference in case someone searches for that topic in the archive; see: esdiscuss.org/topic/optional