Since starting our work on Test262, the official test suite for the ECMAScript programming language, we’ve seen our fair share of strange tests. For nerds like us, every test has the promise to teach us something new, make us laugh, or bury our head in our hands. But unlike choosing between movies, books, or 18th century shoe makers, I’ve never had trouble picking a favorite JavaScript test.

No, it’s not the cheeky EmptyStatement test, nor is it this test for assignment semantics that still has implementers balking. The test I have in mind concerns the interplay one of the oldest and most derided aspects of the language with one of the newest and most cherished. On at least one occasion, it has saved a major JavaScript engine from regression. Did I mention that it’s only 7 characters long?

The Test

Here it is, stripped down to just the essential bytes:

let
let

The first thing to note is that this is a “negative syntax” test–compliant parsers are expected to throw a SyntaxError before evaluating the code. Knowing this, it might seem like an obvious test. This is an oddly-formatted block-scoped variable declaration, and it fails for the same reason let let fails: you can’t name a variable let… Right?

Well, that’s partially true. We can prove this by changing the name of the binding.

let
lot

That code should run without error, producing a block-scoped binding named lot in the process. But this doesn’t explain the test because let isn’t a JavaScript keyword. Try running this program:

var let

No error! That’s because prior to ES2015, let had no special meaning, nor was it “reserved to allow for [future] extensions,” outside of strict mode. So if you were a JavaScript developer working in 2009 and you weren’t using strict mode (don’t judge; those were different times), you wouldn’t think twice about making a variable named let.

So even though today we have these fancy block-scoped bindings, the let token is not a “keyword.” But then, what makes let let illegal? For that, we have an “early error” to thank. Early errors are special rules that are applied at the very end of the parsing process. From the ECMAScript specification:

LexicalDeclaration : LetOrConst BindingList ;

  • It is a Syntax Error if the BoundNames of BindingList contains "let".

The term BindingList refers to the names that follow let. That restriction explains why the following program produces a SyntaxError:

let let

But you’ll notice this isn’t the same as my favorite test. That’s because there is still one last wrinkle to this problem: automatic semicolon insertion (ASI).

As many people are surprised to learn when they begin programming in JavaScript, the semicolon character (;) is often optional. It takes a fair amount of familiarity with the language to recognize the cases where the semicolon is required, but the hand-wavy description is: for lines that end without a semicolon, a semicolon will be inserted if not doing so would result in a SyntaxError.

If we put together what we know so far, then we can see how the following oddball program is actually valid:

let
function let() {}

At first glance, the first statement looks like another variable declaration split across two lines, just like the let lot example above. But since function is a reserved word, this cannot be. Because of this, ASI steps in and inserts a semicolon for us. That makes the program equivalent to:

let;
function let() {}

So line 1 is actually a (useless) variable reference. This makes the final line a hum-drum function declaration, albeit one with a troubling name, let. When the code is run, the function declaration is initially “hoisted,” so the variable reference on the first line “resolves” to that function. It all makes a weird kind of sense, I guess.

Let’s try applying that same logic to my favorite test. As a refresher, that is:

let
let

Okay, so we know from earlier that let let is invalid. That means ASI ought to step in and transform this to:

let;
let

So this should be parsed as two useless references to some variable named let and executed that way. Unlike the previous example with the function named “let,” there’s no definition for a let variable here. It seems like this should throw a ReferenceError.

But the test is for a SyntaxError. What gives? To answer that, we’ll need to look more closely at the specified order of operations:

  1. Parse the input stream to produce a list of tokens
  2. Assign each token to a specific grammar “production” (which is basically a standalone construct like an expression or statement)
  3. Apply ASI to each production
  4. Detect and report any early errors

In step 1, the parser interprets the 7 characters as three distinct tokens: a let, a “newline” token, and another let. In step 2, the parser decides, “let, newline, let” is a variable declaration. By the time ASI kicks in at step 3, insertion of a semicolon is no longer an option–the parser has already made up its mind that this is a declaration, and you can’t shove a semicolon in there.

So it turns out that the test is not a candidate for ASI, after all. Hello, SyntaxError.

Let it Be

We can’t claim credit for writing this test (that distinction goes to SpiderMonkey developer Jeff Walden), but that doesn’t change our admiration for its elegance or the complexity it describes.

Our appreciation isn’t just academic navel gazing, either. Our work with Test262 routinely uncovers bugs in real-world JavaScript engines, and we relish the opportunity to help improve those other projects. We discovered good ol’ let-let-declaration-split-across-two-lines.js only after we failed it in a patch for an unrelated bug in V8 (that’s the JavaScript engine that powers Google’s Chrome web browser). The test saved the world from a software regression in a major project, and we’re indebted for that!

I need to call attention to the elephant in the room. If you want to get technical about it (and who doesn’t?), my favorite JavaScript test isn’t even really JavaScript. It’s a test for what JavaScript is not. But to us, that just makes it all the more endearing, kind of like learning that your pet “dog” is actually an overgrown Mexican rat. You don’t love it any less; you just love it… differently. If you’d like to find a pet of your own, try browsing through Test262. We’re approaching 25,000 tests, so you’re bound to find something that inspires you!

Comments