Skip to content

Instantly share code, notes, and snippets.

@DrewML
Last active October 14, 2022 07:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DrewML/6a0133cceae82fff9f2b0df2dbdee133 to your computer and use it in GitHub Desktop.
Save DrewML/6a0133cceae82fff9f2b0df2dbdee133 to your computer and use it in GitHub Desktop.

Empty Destructuring Patterns in JavaScript

@RyanCavanaugh posted a fun quiz on Twitter:

screenshot of the linked twitter quiz, showing an example of JavaScript destructuring with no variables in the pattern, and a survey with 3 options: Works as a no-op, fails to parse, or throws an error

I logged a guess for Fails to parse, suspecting that JavaScript was going to surprise me as usual. It did 😄

Wut?

The correct answer to the quiz was Throws (can't read properties from undefined), and I was really curious why.

Turns out, empty destructuring assignments are valid. But not just valid: they're very explicitly supported, all the way back to the introduction of destructuring in ES2015.

Source: https://262.ecma-international.org/6.0/

12.14.5.2 Runtime Semantics: DestructuringAssignmentEvaluation

with parameter value

ObjectAssignmentPattern : { }
    1. Let valid be RequireObjectCoercible(value).
    2. ReturnIfAbrupt(valid).
    3. Return NormalCompletion(empty).

ObjectAssignmentPattern :
    { AssignmentPropertyList }
    { AssignmentPropertyList , }
        1. Let valid be RequireObjectCoercible(value).
        2. ReturnIfAbrupt(valid).
        3. Return the result of performing DestructuringAssignmentEvaluation for AssignmentPropertyList using value as the argument.

The first half of DestructuringAssignmentEvaluation is where this is specified. It only validates the right-hand side of the assignment can be coerced to an object, and nothing more.

All this leads us to one unfortunate truth: every line below is valid code that will not throw an error during parsing or execution 😔

var {} = {}
let {} = {};
const {} = {};
({} = {})

But Why?

With the help of Google and WayBack Machine, I've found an answer...sort of?

https://web.archive.org/web/20161222134616/http://wiki.ecmascript.org/doku.php?id=harmony:destructuring

Empty patterns are allowed. Rationale: same basis case as for initialisers helps code generators avoid extra checks.

(We don’t understand the statement about Empty patterns. Besides, they aren’t allowed by the above grammar. – MarkM & Tom)

The grammar does produce empty patterns such as in var {} = obj; and [] = returnsArray(). Note the parentheses and Kleene * usage. The rationale is that empty initialisers are allowed, and indeed top-down parsers must parse an empty pattern on the left of assignment as a primary initialiser expression first, only then discover a = token and revise the AST appropriately to make the initialiser be a destructuring pattern.

The further rationale is that code generators should not need to check and avoid emitting a no-op empty destructuring assignment or binding, if it happens that there are no properties or elements to destructure for the particular case the code generator is handling (say, based on database records or JSON). This is a bit thin by itself, but with the first point (symmetry with initialisers) it adds up to a better case for empty patterns.

My Interpretation

If I understand the texts above correctly, I think there were 2 motivations:

  1. The {} syntax in JavaScript is sort of "overloaded" to be used for assigning properties (var obj = { a: 1 }), short-hand assigning properties (var obj = { a }), and selecting properties ({ a } = obj). When a parser is inspecting the code, it needs to treat the opening { as the start of a new object literal until it finds a = after }. I think the comments around symmetry are for developer intuition, but I'm guessing there was also a desire prevent the cover grammar1 from making things parsing even more complex.
  2. Code generators (modern example: gRPC) will want to have generic code that can collect a list of fields being accessed, and spit the code out. Not allowing empty destructuring patterns would require codegen authors to add special handling to the case of 0 properties.

Bonus

This is valid too!

function emptyPattern({}) {}

Footnotes

  1. Dr. Axel Rauschmayer explains the concept of a "cover" grammar much better than I could in his article ECMAScript 6: arrow functions and method definitions. Look at the Parsing arrow functions section for an overview.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment