My rough notes from today's meeting. Waldemar
-------------- DaveH: One JavaScript (Versioning debate) It's inevitable (and necessary) that ES6 will have some breaking changes around the edges. How to opt-in? DaveH's versioning proposal: A module block or file include is the only in-language ES6 opt-in. Modules can appear only at the top level or inside another module. This avoids the problem of a "use strict" nested in a function inside a with. Brendan: var obj = get_random_obj(); var x, prop = 42 with (obj) { x = function() { "use strict"; return prop; }(); } Differences between the de facto (traditional) semantics and ES6 (i.e. semantic changes instead of mere syntax additions): - ES5 strict changes - static scoping (really means static checking of variable existence; see below) - block-local functions - block-local const declarations - tail calls (yikes - it's a breaking change due to Function.caller) - typeof null - completion reform - let DaveH: Thinks we may be able to get away with enabling completion reform and let for all code. Allen: Would a class be allowed outside a module? DaveH: Yes, but it would not support static scoping, block-local functions, etc. MarkM: Classes should not be allowed in traditional semantics. If you want a class, you need a "use strict" or be inside a module. Waldemar: Given that you can't split a module into multiple script blocks, making modules be the only in-language opt-in is untenable. Programmers shouldn't have to be forced to use the broken scope/local function/etc. semantics to split a script into multiple script blocks. DaveH: Use out-of-language opt-ins. MarkM: Wants a two-way fork (non-strict vs. strict) instead of a three-way fork (non-strict vs. strict vs. ES6-in-module). MarkM: Does a failed assignment inside a non-strict module throw? DaveH: Most of the differences between strict and non-strict are code bugs. Luke, MarkM: No. Their developer colleague experience shows that there are plenty of changes to non-buggy code that need to be made to make it work under strict mode. Allen, Waldemar: It's important to support the use case of someone writing global code using the clean new semantics and not having to learn about the obsolete traditional compatibility semantics. Can "use strict" be the ES6 opt-in? What DaveH meant by static scoping (i.e. static checking): What happens when there's a free variable in a function? Nonstrict ES5 code: - Reference error if variable doesn't exist at the time it is read; creates a global if doesn't exist at the time it is written. Strict ES5 code: - Reference error if variable doesn't exist at the time it is read or written. Static checking goal for ES6 modules: - Compilation error if variable doesn't exist at the time module is compiled. - Reference error if variable doesn't exist at the time it is read or written. (It's possible to get the latter error and yet have the module compile successfully if someone deletes a global variable outside the module between when the module is compiled and when the variable is read or written at run time.) Discussion of whether it is important to support non-statically-checked binding in modules. MarkM: typeof is used to test for the existence of globals. If the test succeeds, they then proceed to use the global directly. This would then be rejected by static checks. DaveH: Doesn't see a way to do static checking with strict code (due to, for example, the "with" case illustrated by Brendan earlier). MarkM: The cost of having three modes is higher than the cost of not supporting static checking early errors. DaveH's new proposal: Other than static checking, attach the incompatible ES6 semantics to the strict mode opt-in. These semantics are upwards-compatible with ES5 strict mode (but not ES5 non-strict mode). The semantics inside a module would be the strict semantics plus static checking. Do we want other new ES6 syntax normatively backported to work in non-strict mode? Waldemar, MarkM: Not really. This requires everyone to be a language lawyer because it's introducing a very subtle new mode: ES6 with nonstrict scoping/const/local semantics. If an implementation wants to backport, the existing Chapter 16 exemption already allows it. DaveH, Brendan: Yes. People won't write "use strict". Don't want to punish people for not opting in. Alex: Split the middle. Backport new ES6 features to non-strict features where it makes sense. Waldemar, DaveH: Want to make it as easiy as possible to make a strict opt-in for an entire page instead of littering opt-ins inside each script. Allen: Backporting increases spec complexity and users' mental tax. The main costs are in making lots of divergent scoping legacy issues possible. Doug: Modules are sufficient as an opt-in, without the need for a "use strict" opt-in. Waldemar: No. Having multiple scripts on a page would require each one to create its own module, and then issues arise when they want to talk to each other -- you'd need to explicitly export const bindings, etc. MarkM: No. The typeof test won't work. Also, this would make it impossible to write code that's backwards compatible with older browsers that don't implement modules. Which ES6 features can be backported into non-strict mode? (blank: no significant issues; ?: possible issues; x: semantics conflict with de facto web) ? let (syntax issues) x const (divergent semantics) x function in block (divergent semantics) ? destructuring (syntactic conflict with array lookup) parameter default values rest parameters spread x tail calls (because of Function.caller) direct proxies simple maps and sets weak maps is / isnt (egal) iterators ? generators (interaction with scoping issues and yield keyword) generator expressions comprehensions private names quasi-literals pragmas (controversial) ? completion reform (Brendan: might be able to get away with it without breaking the web, but we don't know yet) x typeof null (Erik: It breaks the web) class super n/a modules methods in object literals <| ({[computed_name]: value}) (MarkM: what should happen with duplicate names in nonstrict mode?) Brendan: Kill typeof null. Replace it with Ojbect.isObject? How are the names introduced by generators and classes scoped in nonstrict mode? MarkM: Example of code that might accidentally work one way in all current browsers but not in ES6: (another foo is also predefined in an outer scope.) if (...) { function foo() {...} } else { function foo() {...} } foo(); // foo will call one of the two foo's defined above in ES5 nonstrict, although it's implementation-dependent which. On some browsers the first definition wins; on some the last definition wins; on some the one which corresponds to the true branch of the if wins. In ES6 strict it will call the outer scope foo. Discussion about whether we can move the web to the new local function and const semantics even in nonstrict ES5 mode. Also discussed an alternative of whether we can require nonstrict mode to support limited usage scenarios such as having the following work: if (...) { function foo() {...} ... foo(); } Waldemar: This doesn't work in current ES5 non-strict if the if is inside a with statement because an existing implementation might hoist foo across the with and then foo() could refer to a field of the with'd object. This also might not work in the presence of other definitions of foo inside the same function. Not clear if specifying such limited cases in the normative spec is useful. Current tentative decision is to support let, const, and local functions in nonstrict ES5 in the same way as in strict ES6. Fallback to either specifying limited cases or doing the ES5 nonstrict status quo (i.e. syntax error + Clause 16) if experiments show this to not be viable. We won't resolve this discussion without running some experiments. Tail calls: Luke: Remove them altogether. Waldemar: If we support them only in strict mode, the failure mode is someone copying-and-pasting code from a module to the global level and silently losing tail recursion, leading to very confusing behavior. Debated. Waldemar: We can require tail calls in non-strict mode by taking advantage of the fact that Function.caller only remembers the last invocation of each function. Thus we can do an amortized algorithm analysis that allocates the cost of storage of one stack frame link at the time we create the function. This makes it possible to implement tail calls in non-strict mode while supporting Function.caller. Tentative decision is to support tail calls in strict mode only. Resolved: Named generators behave in non-strict mode the same as in strict mode. "yield" is a contextual keyword in non-strict generators. How to resolve let[x] = 5 in nonstrict mode? Will need to do experiments. Also, do we require no-line-terminator between "let" and the identifier? Probably not, because if we do then we'd get this annoying hazard in non-strict mode: { let x = 7; if (...) { let x = 5; } // Now x is 5 because the second "let" is just a useless identifier expression with an inserted semicolon, followed by an assignment to the existing x! } Gavin: Module syntax hazard if we have no-line-terminator between "module" and the identifier: module { ... } gets interpreted as a useless expression (the identifier "module") followed by a block. Completion value reform: Let's experiment. Octal constants: Useful as arguments to chmod. Proposal for 0o123 (as well as 0b01110). MarkM: Concerned about 0O123. Waldemar: Nothing unusul here. We've lived with 36l (meaning 36 long instead of 361) in Java and C++ for a long time. Alternative for octal: 8r123 (but then we'd also want 16r123, 2r0101, and maybe more). Decided to allow 0o and 0b. Unresolved whether to allow 0O and 0B. Persistent weak feelings on both sides on the upper case forms. Use __proto__ in object literals to do a put (assuming that a __proto__ getter/setter was created in Object.prototype) instead of a defineProperty? All modes or only nonstrict mode? Allen: Make such use of __proto__ to be a synonym for <|. If a <| is already present, it's an error. DaveH: __proto__ is ugly. Don't want it in the language forever. Waldemar: What about indirect [] expressions that evaluate to "__proto__"? In Firefox they evaluate to accesses that climb the prototype chain and usually reach a magic getter/setter-that-isn't-a-getter-setter named __proto__ that sits on Object.prototype. MarkM: Likes the ability to delete __proto__ setter and thereby prevent anything in the frame from mutating prototypes. Waldemar: How do you guard against cross-frame prototype mutations? DaveH: __proto__ is in the "omg, what were we thinking" category. Waldemar: Opposed to making __proto__ mutate prototypes other than at object construction. This is getting insanely complex. Unresolved.
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss