Search code examples
javascriptnode.jsecmascript-6v8

switch statement and scopes in ES2015


Consider this ES2015 module and the behavior when run in node v4.4.5.

'use strict'

const outer = 1

switch ('foo') {
  case 'bar':
    const heyBar = 'HEY_BAR'
    break
  case 'baz':
    const heyBaz = 'HEY_BAZ'
    break
  default:
    const heyDefault = 'HEY_DEFAULT'
}
console.log(
  outer, // 1, makes sense, same top-level scope
  heyBar, // undefined. huh? I thought switch did NOT create a child scope
  heyBaz, // undefined. huh? I thought switch did NOT create a child scope
  heyDefault) // 'HEY_DEFAULT' makes sense

This seems internally inconsistent to me. If the switch statement did NOT create a lexical scope, I would expect all the hey* variables to be part of the main scope and to all behave consistently. If the switch statement did create a lexical scope, I would still expect them to be consistent, but the variables declared in the case clauses behave like they are in a child scope, whereas the variables in the default clause behaves like it is in the outer scope.

My question is are there any child scopes involved in a switch statement, and if so what are the details of how they behave?

In node v6.4.0, the behavior is different. It looks like the switch block does indeed create a child block scope.

ReferenceError: heyBar is not defined

And that seems a lot more straightforward to understand.


Solution

  • I can't reproduce your behavior at all. I immediately get a ReferenceError (Node 6.4.0 and current Firefox for that matter):

    ReferenceError: heyBar is not defined
    

    Which seems like the correct behavior to me. AFAIK switch statements with brackets DO create a block, and thus a lexical scope for block-scoped entities. The case statements themselves do not create their own blocks.

    If we expand this example with a foo case in the switch statement, it throws a ReferenceError as well:

    'use strict'
    
    const outer = 1
    
    switch ('foo') {
      case 'bar':
        const heyBar = 'HEY_BAR'
        break
      case 'baz':
        const heyBaz = 'HEY_BAZ'
        break
      case 'foo':
        const heyFoo = 'HEY_FOO'
        break
      default:
        const heyDefault = 'HEY_DEFAULT'
    }
    console.log(
      outer,
      heyFoo,
      heyBar,
      heyBaz,
      heyDefault) // ReferenceError: heyFoo is not defined
    

    Reference

    Here is the section in the spec: 13.12.11 Runtime Semantics: Evaluation

    5. Let blockEnv be NewDeclarativeEnvironment(oldEnv).
    6. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
    

    Where CaseBlock is the block statement of the switch case.

    This roughly translates to:

    Create a new block environment in the block of the switch statement (switch { <-here-> }) and instantiate all block level declarations (such as let, const or block level function declarations).