Skip to content

Add reference for two optional chain SyntaxErrors #34676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: "SyntaxError: new keyword cannot be used with an optional chain"
slug: Web/JavaScript/Reference/Errors/Bad_new_optional
page-type: javascript-error
---

{{jsSidebar("Errors")}}

The JavaScript exception "new keyword cannot be used with an optional chain" occurs when the constructor of a {{jsxref("Operators/new", "new")}} expression is an [optional chain](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining), or if there's an optional chain between the constructor and the parenthesized list of arguments.

## Message

```plain
SyntaxError: Invalid optional chain from new expression (V8-based)
SyntaxError: new keyword cannot be used with an optional chain (Firefox)
SyntaxError: Cannot call constructor in an optional chain. (Safari)
```

## Error type

{{jsxref("SyntaxError")}}

## What went wrong?

There are two ways to get this error. The first one is if the constructor expression is an optional chain expression, like this:

```js-nolint example-bad
new Intl?.DateTimeFormat();
Number?.[parseMethod]`Hello, world!`;
```

The second one is if `?.` occurs between the constructor and the arguments list, like this:

```js-nolint
new Intl.DateTimeFormat?.();
```

Optional `new` is specifically forbidden because its syntax is complicated (`new` with and without arguments), and the result is unclear (it would be the only case where `new` does not evaluate to an object value). You need to translate the optional chaining to its underlying condition (see [optional chaining](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) for more information).

```js
const result =
Intl.DateTimeFormat === null || Intl.DateTimeFormat === undefined
? undefined
: new Intl.DateTimeFormat();
```

Remember that optional chaining only short-circuits within a parenthesized unit. If you parenthesize your constructor expression, the optional chaining will not cause an error, because now the constructor does not short-circuit and the result is clear (the constructor will produce `undefined` and then cause the `new` expression to throw).

```js-nolint
new (Intl?.DateTimeFormat)(); // Throws if Intl?.DateTimeFormat is undefined
```

However this is a bit nonsensical anyway because optional chaining prevents errors inside the property access chain, but is then guaranteed to generate an error when calling `new`. You would probably still want to use a conditional check.

Note that optional chaining is only forbidden as the constructor expression. You can use optional chaining inside the argument list, or use optional chaining on the `new` expression as a whole.

```js example-good
new Intl.DateTimeFormat(navigator?.languages);
new Intl.DateTimeFormat().resolvedOptions?.();
```

Note that there's no needs to use `?.` on the `new` expression itself: `new a()?.b`, because `new` is guaranteed to produce a non-nullish object value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note that there's no needs to use `?.` on the `new` expression itself: `new a()?.b`, because `new` is guaranteed to produce a non-nullish object value.
Note that there's no need to use `?.` on the `new` expression itself: `new a()?.b`, because `new` is guaranteed to produce a non-nullish object value.


## See also

- {{jsxref("Operators/new", "new")}}
- [Optional chaining (`?.`)](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
- [Original discussion on whether "optional new" should be allowed](https://github.com/tc39/proposal-optional-chaining/issues/22)
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "SyntaxError: tagged template cannot be used with optional chain"
slug: Web/JavaScript/Reference/Errors/Bad_optional_template
page-type: javascript-error
---

{{jsSidebar("Errors")}}

The JavaScript exception "tagged template cannot be used with optional chain" occurs when the tag expression of a [tagged template literal](/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) is an [optional chain](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining), or if there's an optional chain between the tag and the template.

## Message

```plain
SyntaxError: Invalid tagged template on optional chain (V8-based)
SyntaxError: tagged template cannot be used with optional chain (Firefox)
SyntaxError: Cannot use tagged templates in an optional chain. (Safari)
```

## Error type

{{jsxref("SyntaxError")}}

## What went wrong?

There are two ways to get this error. The first one is if the tag expression is an optional chain expression, like this:

```js-nolint example-bad
String?.raw`Hello, world!`;
console.log?.()`Hello, world!`;
Number?.[parseMethod]`Hello, world!`;
```

The second one is if `?.` occurs between the tag and the template, like this:

```js-nolint example-bad
String.raw?.`Hello, world!`;
```

Optional chaining in the tag is specifically forbidden because there's no great use case for it, and what the result is expected to be is unclear (should it be `undefined` or the template's value as if it's untagged?). You need to translate the optional chaining to its underlying condition (see [optional chaining](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) for more information).

```js example-good
const result =
String.raw === null || String.raw === undefined
? undefined
: String.raw`Hello, world!`;
```

Remember that optional chaining only short-circuits within a parenthesized unit. If you parenthesize your tag expression, the optional chaining will not cause an error, because now the tag does not short-circuit and the result is clear (the tag will produce `undefined` and then cause the tagged template to throw).

```js-nolint
(console?.log)`Hello, world!`; // Throws if console?.log is undefined
```

However this is a bit nonsensical anyway because optional chaining prevents errors inside the property access chain, but is then guaranteed to generate an error when calling the template tag. You would probably still want to use a conditional check.

Note that optional chaining is only forbidden as the tag expression. You can use optional chaining inside the embedded expressions, or use optional chaining on the tagged template expression as a whole.

```js example-good
console.log`Hello, ${true.constructor?.name}!`; // ['Hello, ', '!', raw: Array(2)] 'Boolean'
console.log`Hello`?.toString(); // undefined
```

## See also

- [Template literals](/en-US/docs/Web/JavaScript/Reference/Template_literals)
- [Optional chaining (`?.`)](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
- [Original discussion on whether optional chaining should be allowed in tagged template literals](https://github.com/tc39/proposal-optional-chaining/issues/54)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ new constructor(arg1, arg2, /* …, */ argN)
### Parameters

- `constructor`
- : A class or function that specifies the type of the object instance.
- : A class or function that specifies the type of the object instance. The expression can be anything with sufficient [precedence](/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table), including an identifier, a [property access](/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors), or another `new` expression, but [optional chaining](/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) is not allowed.
- `arg1`, `arg2`, …, `argN`
- : A list of values that the `constructor` will be called with. `new Foo` is equivalent to `new Foo()`, i.e. if no argument list is specified, `Foo` is called without arguments.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ Notes:
1. The operand can be any expression.
2. The "right-hand side" must be an identifier.
3. The "right-hand side" can be any expression.
4. The "right-hand side" is a comma-separated list of any expression with precedence > 1 (i.e. not comma expressions).
4. The "right-hand side" is a comma-separated list of any expression with precedence > 1 (i.e. not comma expressions). The constructor of a `new` expression cannot be an optional chain.
5. The operand must be a valid assignment target (identifier or property access). Its precedence means `new Foo++` is `(new Foo)++` (a syntax error) and not `new (Foo++)` (a TypeError: (Foo++) is not a constructor).
6. The operand must be a valid assignment target (identifier or property access).
7. The operand cannot be an identifier or a [private property](/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties) access.
Expand All @@ -618,3 +618,14 @@ Notes:
11. The associativity means the two expressions after `?` are implicitly grouped.
12. The "left-hand side" is a single identifier or a parenthesized parameter list.
13. Only valid inside object literals, array literals, or argument lists.

The precedence of groups 17 and 16 may be a bit ambiguous. Here are a few examples to clarify.

- Optional chaining is always substitutable for its respective syntax without optionality (barring a few special cases where optional chaining is forbidden). For example, any place that accepts `a?.b` also accepts `a.b` and vice versa, and similarly for `a?.()`, `a()`, etc.
- Member expressions and computed member expressions are always substitutable for each other.
- Call expressions and `import()` expressions are always substitutable for each other.
- This leaves four classes of expressions: member access, `new` with arguments, function call, and `new` without arguments.
- The "left-hand side" of a member access can be: a member access (`a.b.c`), `new` with arguments (`new a().b`), and function call (`a().b`).
- The "left-hand side" of `new` with arguments can be: a member access (`new a.b()`) and `new` with arguments (`new new a()()`).
- The "left-hand side" of a function call can be: a member access (`a.b()`), `new` with arguments (`new a()()`), and function call (`a()()`).
- The operand of `new` without arguments can be: a member access (`new a.b`), `new` with arguments (`new new a()`), and `new` without arguments (`new new a`).
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ printMagicIndex([0, 1, 2, 3, 4, 5]); // undefined
printMagicIndex(); // undefined; if not using ?., this would throw an error: "Cannot read properties of undefined (reading '42')"
```

### Optional chaining not valid on the left-hand side of an assignment
### Invalid optional chaining

It is invalid to try to assign to the result of an optional chaining expression:

Expand All @@ -123,6 +123,20 @@ const object = {};
object?.property = 1; // SyntaxError: Invalid left-hand side in assignment
```

[Template literal tags](/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) cannot be an optional chain (see [SyntaxError: tagged template cannot be used with optional chain](/en-US/docs/Web/JavaScript/Reference/Errors/Bad_optional_template)):

```js-nolint example-bad
String?.raw`Hello, world!`;
String.raw?.`Hello, world!`; // SyntaxError: Invalid tagged template on optional chain
```

The constructor of {{jsxref("Operators/new", "new")}} expressions cannot be an optional chain (see [SyntaxError: new keyword cannot be used with an optional chain](/en-US/docs/Web/JavaScript/Reference/Errors/Bad_new_optional)):

```js-nolint example-bad
new Intl?.DateTimeFormat(); // SyntaxError: Invalid optional chain from new expression
new Map?.();
```

### Short-circuiting

When using optional chaining with expressions, if the left operand is `null` or `undefined`, the expression will not be evaluated. For instance:
Expand Down