Stage 4 Draft / April 15, 2020

Optional Chaining

1 Scope

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).

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 may omit mere editorial amendments.

List of significant editorial modifications:

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

2 Punctuators (11.7)

Syntax

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

3 Left-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.1 Static Semantics (12.3.1)

3.1.1 Static 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.2 Static Semantics: CoveredCallExpression

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

3.1.3 Static 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.4 Static Semantics: IsFunctionDefinition

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

3.1.5 Static 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.6 Static Semantics: IsIdentifierRef

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

3.1.7 Static Semantics: IsSimpleAssignmentTarget

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.2 Property 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.1 Runtime 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.2 Runtime 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.3 Runtime 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.3 Function Calls (12.3.4)

3.3.1 Runtime 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.4 Optional 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.1 Runtime Semantics: Evaluation

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

3.4.2 Runtime Semantics: ChainEvaluation

With parameters baseValue and baseReference.

OptionalChain:?.[Expression]
  1. If the code matched by this OptionalChain 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 OptionalChain 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 OptionalChain.
  2. Let tailCall be IsInTailPosition(thisChain).
  3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall).
OptionalChain:OptionalChain[Expression]
  1. Let optionalChain be OptionalChain.
  2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference.
  3. Let newValue be ? GetValue(newReference).
  4. If the code matched by this OptionalChain 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 OptionalChain.
  2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference.
  3. Let newValue be ? GetValue(newReference).
  4. If the code matched by this OptionalChain 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 OptionalChain.
  2. Let newReference be ? ChainEvaluation of optionalChain with arguments baseValue and baseReference.
  3. Let newValue be ? GetValue(newReference).
  4. Let thisChain be this OptionalChain.
  5. Let tailCall be IsInTailPosition(thisChain).
  6. Return ? EvaluateCall(newValue, newReference, Arguments, tailCall).

3.5 Tail Position Calls (14.8)

3.5.1 Static Semantics: HasCallInTailPosition (14.8.2)

With parameter call.

3.5.1.1 Expression 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.