Stage 2 Draft / July 19, 2019

Optional Chaining

1Scope

This is the spec text of the Optional Chaining proposal in ECMAScript.

For the syntax, we use the ?. token, with a lookahead at the level of the lexical grammar that allows to discriminate between a?.b (optional chaining) and a?.3:0 (conditional operator, whose meaning cannot be changed due to backward compatibility constraints).

The definitive syntax is still an open issue at this point.

An early version of this proposal used a Nil reference to express short-circuiting. This one is based on syntax only.

Normative additions are marked like this. In order to avoid distraction, we don’t mark mere editorial amendments.

List of significant editorial modifications:

The following features may not be evident at a cursory read:

2Punctuators (11.7)

Syntax

OptionalChainingPunctuator::?.[lookahead ∉ DecimalDigit] OtherPunctuator::one of{()[]....;,<><=>===!====!==+-*%**++--<<>>>>>&|^!~&&||?:=+=-=*=%=**=<<=>>=>>>=&=|=^==> Punctuator::OptionalChainingPunctuator OtherPunctuator DivPunctuator::/ /= RightBracePunctuator::}

3Left-Hand-Side Expressions (12.3)

Syntax

MemberExpression[Yield, Await]:PrimaryExpression[?Yield, ?Await] MemberExpression[?Yield, ?Await][Expression[+In, ?Yield, ?Await]] MemberExpression[?Yield, ?Await].IdentifierName MemberExpression[?Yield, ?Await]TemplateLiteral[?Yield, ?Await, +Tagged] SuperProperty[?Yield, ?Await] MetaProperty newMemberExpression[?Yield, ?Await]Arguments[?Yield, ?Await] SuperProperty[Yield, Await]:super[Expression[+In, ?Yield, ?Await]] super.IdentifierName MetaProperty:NewTarget NewTarget:new.target NewExpression[Yield, Await]:MemberExpression[?Yield, ?Await] newNewExpression[?Yield, ?Await] CallExpression[Yield, Await]:CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] SuperCall[?Yield, ?Await] CallExpression[?Yield, ?Await]Arguments[?Yield, ?Await] CallExpression[?Yield, ?Await][Expression[+In, ?Yield, ?Await]] CallExpression[?Yield, ?Await].IdentifierName CallExpression[?Yield, ?Await]TemplateLiteral[?Yield, ?Await, +Tagged] SuperCall[Yield, Await]:superArguments[?Yield, ?Await] Arguments[Yield, Await]:() (ArgumentList[?Yield, ?Await]) (ArgumentList[?Yield, ?Await],) ArgumentList[Yield, Await]:AssignmentExpression[+In, ?Yield, ?Await] ...AssignmentExpression[+In, ?Yield, ?Await] ArgumentList[?Yield, ?Await],AssignmentExpression[+In, ?Yield, ?Await] ArgumentList[?Yield, ?Await],...AssignmentExpression[+In, ?Yield, ?Await] OptionalExpression[Yield, Await]:MemberExpression[?Yield, ?Await]OptionalChain[?Yield, ?Await] CallExpression[?Yield, ?Await]OptionalChain[?Yield, ?Await] OptionalExpression[?Yield, ?Await]OptionalChain[?Yield, ?Await] OptionalChain[Yield, Await]:?.[Expression[+In, ?Yield, ?Await]] ?.IdentifierName ?.Arguments[?Yield, ?Await] ?.TemplateLiteral[?Yield, ?Await, +Tagged] OptionalChain[?Yield, ?Await][Expression[+In, ?Yield, ?Await]] OptionalChain[?Yield, ?Await].IdentifierName OptionalChain[?Yield, ?Await]Arguments[?Yield, ?Await] OptionalChain[?Yield, ?Await]TemplateLiteral[?Yield, ?Await, +Tagged] LeftHandSideExpression[Yield, Await]:NewExpression[?Yield, ?Await] CallExpression[?Yield, ?Await] OptionalExpression[?Yield, ?Await]

Supplemental Syntax

When processing an instance of the production CallExpression:CoverCallExpressionAndAsyncArrowHead the interpretation of CoverCallExpressionAndAsyncArrowHead is refined using the following grammar:

CallMemberExpression[Yield, Await]:MemberExpression[?Yield, ?Await]Arguments[?Yield, ?Await]

3.1Static Semantics (12.3.1)

3.1.1Static Semantics: Early Errors

OptionalChain[Yield, Await]:?.TemplateLiteral[?Yield, ?Await, +Tagged] OptionalChain[?Yield, ?Await]TemplateLiteral[?Yield, ?Await, +Tagged]
  • It is a Syntax Error if any code matches this production.
Note

This production exists in order to prevent automatic semicolon insertion rules (11.9) to be applied to the following code:

a?.b
`c`

so that it would be interpreted as two valid statements. The purpose is to maintain consistency with similar code without optional chaining operator:

a.b
`c`

which is a valid statement and where automatic semicolon insertion does not apply.

3.1.2Static Semantics: CoveredCallExpression

CallExpression:CoverCallExpressionAndAsyncArrowHead
  1. Return the CallMemberExpression that is covered by CoverCallExpressionAndAsyncArrowHead.

3.1.3Static Semantics: Contains

With parameter symbol.

MemberExpression:MemberExpression.IdentifierName
  1. If MemberExpression Contains symbol is true, return true.
  2. If symbol is a ReservedWord, return false.
  3. If symbol is an Identifier and StringValue of symbol is the same value as the StringValue of IdentifierName, return true.
  4. Return false.
SuperProperty:super.IdentifierName
  1. If symbol is the ReservedWord super, return true.
  2. If symbol is a ReservedWord, return false.
  3. If symbol is an Identifier and StringValue of symbol is the same value as the StringValue of IdentifierName, return true.
  4. Return false.
CallExpression:CallExpression.IdentifierName
  1. If CallExpression Contains symbol is true, return true.
  2. If symbol is a ReservedWord, return false.
  3. If symbol is an Identifier and StringValue of symbol is the same value as the StringValue of IdentifierName, return true.
  4. Return false.
OptionalChain:?.IdentifierName
  1. If symbol is a ReservedWord, return false.
  2. If symbol is an Identifier and StringValue of symbol is the same value as the StringValue of IdentifierName, return true.
  3. Return false.
OptionalChain:OptionalChain.IdentifierName
  1. If OptionalChain Contains symbol is true, return true.
  2. If symbol is a ReservedWord, return false.
  3. If symbol is an Identifier and StringValue of symbol is the same value as the StringValue of IdentifierName, return true.
  4. Return false.

3.1.4Static Semantics: IsFunctionDefinition

MemberExpression:MemberExpression[Expression] MemberExpression.IdentifierName MemberExpressionTemplateLiteral SuperProperty MetaProperty newMemberExpressionArguments NewExpression:newNewExpression LeftHandSideExpression:CallExpression OptionalExpression
  1. Return false.

3.1.5Static Semantics: IsDestructuring

MemberExpression:PrimaryExpression
  1. If PrimaryExpression is either an ObjectLiteral or an ArrayLiteral, return true.
  2. Return false.
MemberExpression:MemberExpression[Expression] MemberExpression.IdentifierName MemberExpressionTemplateLiteral SuperProperty MetaProperty newMemberExpressionArguments NewExpression:newNewExpression LeftHandSideExpression:CallExpression OptionalExpression
  1. Return false.

3.1.6Static Semantics: IsIdentifierRef

MemberExpression:MemberExpression[Expression] MemberExpression.IdentifierName MemberExpressionTemplateLiteral SuperProperty MetaProperty newMemberExpressionArguments NewExpression:newNewExpression LeftHandSideExpression:CallExpression OptionalExpression
  1. Return false.

3.1.7Static Semantics: IsValidSimpleAssignmentTarget

CallExpression:CallExpression[Expression] CallExpression.IdentifierName MemberExpression:MemberExpression[Expression] MemberExpression.IdentifierName SuperProperty
  1. Return true.
CallExpression:CoverCallExpressionAndAsyncArrowHead SuperCall CallExpressionArguments CallExpressionTemplateLiteral NewExpression:newNewExpression MemberExpression:MemberExpressionTemplateLiteral newMemberExpressionArguments NewTarget:new.target LeftHandSideExpression:OptionalExpression
  1. Return false.

3.2Property Accessors (12.3.2)

Note

Properties are accessed by name, using either the dot notation:

or the bracket notation:

The dot notation is explained by the following syntactic conversion:

is identical in its behaviour to

MemberExpression [ <identifier-name-string> ]

and similarly

is identical in its behaviour to

CallExpression [ <identifier-name-string> ]

where <identifier-name-string> is the result of evaluating StringValue of IdentifierName.

3.2.1Runtime Semantics: Evaluation

MemberExpression:MemberExpression[Expression]
  1. Let baseReference be the result of evaluating MemberExpression.
  2. Let baseValue be ? GetValue(baseReference).
  3. Let propertyNameReference be the result of evaluating Expression.
  4. Let propertyNameValue be ? GetValue(propertyNameReference).
  5. Let bv be ? RequireObjectCoercible(baseValue).
  6. Let propertyKey be ? ToPropertyKey(propertyNameValue).
  7. If the code matched by this MemberExpression is strict mode code, let strict be true, else let strict be false.
  8. Return a value of type Reference whose base value component is bv, whose referenced name component is propertyKey, and whose strict reference flag is strict.
  9. Return ? EvaluateDynamicPropertyAccess(baseValue, Expression, strict).
MemberExpression:MemberExpression.IdentifierName
  1. Let baseReference be the result of evaluating MemberExpression.
  2. Let baseValue be ? GetValue(baseReference).
  3. Let bv be ? RequireObjectCoercible(baseValue).
  4. Let propertyNameString be StringValue of IdentifierName.
  5. If the code matched by this MemberExpression is strict mode code, let strict be true, else let strict be false.
  6. 1. Return a value of type Reference whose base value component is bv, whose referenced name component is propertyNameString, and whose strict reference flag is strict.
  7. Return ? EvaluateStaticPropertyAccess(baseValue, IdentifierName, strict);
CallExpression:CallExpression[Expression]

Is evaluated in exactly the same manner as MemberExpression:MemberExpression[Expression] except that the contained CallExpression is evaluated in step 1.

  1. Let baseReference be the result of evaluating CallExpression.
  2. Let baseValue be ? GetValue(baseReference).
  3. If the code matched by this CallExpression is strict mode code, let strict be true, else let strict be false.
  4. Return ? EvaluateDynamicPropertyAccess(baseValue, Expression, strict).
CallExpression:CallExpression.IdentifierName
  1. Let baseReference be the result of evaluating CallExpression.
  2. Let baseValue be ? GetValue(baseReference).
  3. If the code matched by this CallExpression is strict mode code, let strict be true, else let strict be false.
  4. Return ? EvaluateStaticPropertyAccess(baseValue, IdentifierName, strict);

3.2.2Runtime Semantics: EvaluateDynamicPropertyAccess(baseValue, expression, strict )

The abstract operation EvaluateDynamicPropertyAccess takes as arguments a value baseValue, a Parse Node expression, and a Boolean argument strict. It performs the following steps:

  1. Let propertyNameReference be the result of evaluating expression.
  2. Let propertyNameValue be ? GetValue(propertyNameReference).
  3. Let bv be ? RequireObjectCoercible(baseValue).
  4. Let propertyKey be ? ToPropertyKey(propertyNameValue).
  5. Return a value of type Reference whose base value component is bv, whose referenced name component is propertyKey, and whose strict reference flag is strict.

3.2.3Runtime Semantics: EvaluateStaticPropertyAccess(baseValue, identifierName, strict )

The abstract operation EvaluateStaticPropertyAccess takes as arguments a value baseValue, a Parse Node identifierName, and a Boolean argument strict. It performs the following steps:

  1. Let bv be ? RequireObjectCoercible(baseValue).
  2. Let propertyNameString be StringValue of identifierName.
  3. Return a value of type Reference whose base value component is bv, whose referenced name component is propertyNameString, and whose strict reference flag is strict.

3.3Function Calls (12.3.4)

3.3.1Runtime Semantics: Evaluation

CallExpression:CoverCallExpressionAndAsyncArrowHead
  1. Let expr be CoveredCallExpression of CoverCallExpressionAndAsyncArrowHead.
  2. Let memberExpr be the MemberExpression of expr.
  3. Let arguments be the Arguments of expr.
  4. Let ref be the result of evaluating memberExpr.
  5. Let func be ? GetValue(ref).
  6. If Type(ref) is Reference and IsPropertyReference(ref) is false and GetReferencedName(ref) is "eval", then
  7. 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.
      2. If argList has no elements, return undefined.
      3. Let evalText be the first element of argList.
      4. If the source code matching this CallExpression is strict mode code, let strictCaller be true. Otherwise let strictCaller be false.
      5. Let evalRealm be the current Realm Record.
      6. Perform ? HostEnsureCanCompileStrings(evalRealm, evalRealm).
      7. Return ? PerformEval(evalText, evalRealm, strictCaller, true).
  8. Let thisCall be this CallExpression.
  9. Let tailCall be IsInTailPosition(thisCall).
  10. Return ? EvaluateCall(func, ref, arguments, tailCall).

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

CallExpression:CallExpressionArguments
  1. Let ref be the result of evaluating CallExpression.
  2. Let func be ? GetValue(ref).
  3. Let thisCall be this CallExpression.
  4. Let tailCall be IsInTailPosition(thisCall).
  5. Return ? EvaluateCall(func, ref, Arguments, tailCall).

3.4Optional Chains

Note
An optional chain is a chain of one or more property accesses and function calls, the first of which begins with the token ?..

3.4.1Runtime Semantics: Evaluation

OptionalExpression:MemberExpressionOptionalChain CallExpressionOptionalChain OptionalExpressionOptionalChain
  1. Let baseExpression be the first child of this production (i.e., this MemberExpression, CallExpression, or OptionalExpression).
  2. Let baseReference be ? baseExpression.Evaluation().
  3. Let baseValue be ? GetValue(baseReference).
  4. If baseValue is undefined or null, then
    1. Return undefined.
  5. Let optionalChain be this OptionalChain.
  6. Return ? optionalChain.ChainEvaluation(baseValue, baseReference).

3.4.2Runtime Semantics: ChainEvaluation

With parameters baseValue and baseReference.

OptionalChain:?.[Expression]
  1. If the code matched by this production is strict mode code, let strict be true, else let strict be false.
  2. Return ? EvaluateDynamicPropertyAccess(baseValue, Expression, strict).
OptionalChain:?.IdentifierName
  1. If the code matched by this production is strict mode code, let strict be true, else let strict be false.
  2. Return ? EvaluateStaticPropertyAccess(baseValue, IdentifierName, strict).
OptionalChain:?.Arguments
  1. Let thisChain be this production.
  2. Let tailCall be IsInTailPosition(thisChain).
  3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall).
OptionalChain:OptionalChain[Expression]
  1. Let optionalChain be this OptionalChain.
  2. Let newReference be ? optionalChain.ChainEvaluation(baseValue, baseReference).
  3. Let newValue be ? GetValue(newReference).
  4. If the code matched by this production is strict mode code, let strict be true, else let strict be false.
  5. Return ? EvaluateDynamicPropertyAccess(newValue, Expression, strict).
OptionalChain:OptionalChain.IdentifierName
  1. Let optionalChain be this OptionalChain.
  2. Let newReference be ? optionalChain.ChainEvaluation(baseValue, baseReference).
  3. Let newValue be ? GetValue(newReference).
  4. If the code matched by this production is strict mode code, let strict be true, else let strict be false.
  5. Return ? EvaluateStaticPropertyAccess(newValue, IdentifierName, strict).
OptionalChain:OptionalChainArguments
  1. Let optionalChain be this OptionalChain.
  2. Let newReference be ? optionalChain.ChainEvaluation(baseValue, baseReference).
  3. Let newValue be ? GetValue(newReference).
  4. Let thisChain be this production.
  5. Let tailCall be IsInTailPosition(thisChain).
  6. Return ? EvaluateCall(newValue, newReference, Arguments, tailCall).

3.5Tail Position Calls (14.8)

3.5.1Static Semantics: HasCallInTailPosition (14.8.2)

With parameter call.

3.5.1.1Expression Rules

OptionalExpression:MemberExpressionOptionalChain CallExpressionOptionalChain OptionalExpressionOptionalChain
  1. Return HasCallInTailPosition of OptionalChain with argument call.
OptionalChain:?.[Expression] ?.IdentifierName OptionalChain[Expression] OptionalChain.IdentifierName
  1. Return false.
OptionalChain:?.Arguments OptionalChainArguments
  1. If this OptionalChain is call, return true.
  2. Return false.