tc39 / proposal-static-class-features

The static parts of new class features, in a separate proposal
https://arai-a.github.io/ecma262-compare/?pr=1668
127 stars 27 forks source link

local lexical declarations should not be in scope for extends clause #21

Closed allenwb closed 6 years ago

allenwb commented 6 years ago

The current proposal says that:

class foo extends bar() {
   local function bar() {return Array}
}

Creates a subclass of Array. That violates a basic lexical scope principle that declarations inside of curly braces are never visible outside of the braces. In fact, a lot of work went into making sure that function parameter list expressions did not violate this principle by having visibility of function body declarations.

I suspect that the current proposed class semantics is a result of misunderstanding the purpose of classScope in ES2015 and why the extends expression was evaluated using that scope.

In ES2015, the classScope is the direct equivalent of the funcEnv used when defining function expression. It was only intended to contain a binding for the class name provided in the class definition, not for any other declarations.

The extends clause was evaluated in the classScope to catch one specific situation:

{ let Foo = whatever;
   {let bar = class Foo extends Foo {}
    }
}

Note that Foo is not defined in the scope of the inner block so normally the extends clause reference to Foo would evaluate to the outer Foo binding. But, in JS execution normally proceeds left to right and in class Foo extends Foo we have what looks a lot like a new definition of Foo immediately to the left of a reference to Foo. The TC39 consensus was that for the extends clause to access the outer Foo seems like a violation of normal left-to-right expectations and hence should be disallowed.

The way I disallowed it was by placing an uninitialized binding for the function name into classScope and then using that scope to evaluate the extends clause expression. Any reference to the class name within the extends class expression would throw because it was accessing an uninitialized binding.

But, it was never the intent that classScope would contain any other bindings (at least prior to evaluating the extends clause). It was never the intent that the extends clause would have any visibility into the class body.

The easiest fix to this is probably to introduce an additional scope, classInnerScope and to place all class body binding (include private name bindings) into that scope.

allenwb commented 6 years ago

cc @erights

littledan commented 6 years ago

Thanks for explaining the intention here. Seems like the specification should create another scope for inner lexical declarations. I don't anticipate much difficulty here.