Browse Source

Destructuring assignments, rest and spread properties, default function parameters (#303)

Closes #280, #301, #302
Dmitry Panov 4 years ago
parent
commit
a7a3a1366b
19 changed files with 2780 additions and 518 deletions
  1. 1 1
      array.go
  2. 121 43
      ast/node.go
  3. 1 0
      builtin_array.go
  4. 5 6
      builtin_function.go
  5. 189 28
      compiler.go
  6. 643 112
      compiler_expr.go
  7. 123 83
      compiler_stmt.go
  8. 467 0
      compiler_test.go
  9. 261 0
      destruct.go
  10. 2 4
      func.go
  11. 0 2
      go.sum
  12. 318 40
      parser/expression.go
  13. 19 4
      parser/lexer.go
  14. 52 45
      parser/marshal_test.go
  15. 12 0
      parser/parser_test.go
  16. 25 25
      parser/statement.go
  17. 116 92
      tc39_test.go
  18. 2 0
      token/token_const.go
  19. 423 33
      vm.go

+ 1 - 1
array.go

@@ -426,7 +426,7 @@ func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, th
 				a.propValueCount++
 			}
 		} else {
-			a.val.self.(*sparseArrayObject).add(uint32(idx), prop)
+			a.val.self.(*sparseArrayObject).add(idx, prop)
 		}
 	}
 	return ok

+ 121 - 43
ast/node.go

@@ -15,6 +15,15 @@ import (
 	"github.com/dop251/goja/unistring"
 )
 
+type PropertyKind string
+
+const (
+	PropertyKindValue  PropertyKind = "value"
+	PropertyKindGet    PropertyKind = "get"
+	PropertyKindSet    PropertyKind = "set"
+	PropertyKindMethod PropertyKind = "method"
+)
+
 // All nodes implement the Node interface.
 type Node interface {
 	Idx0() file.Idx // The index of the first character belonging to the node
@@ -32,12 +41,34 @@ type (
 		_expressionNode()
 	}
 
+	BindingTarget interface {
+		Expression
+		_bindingTarget()
+	}
+
+	Binding struct {
+		Target      BindingTarget
+		Initializer Expression
+	}
+
+	Pattern interface {
+		BindingTarget
+		_pattern()
+	}
+
 	ArrayLiteral struct {
 		LeftBracket  file.Idx
 		RightBracket file.Idx
 		Value        []Expression
 	}
 
+	ArrayPattern struct {
+		LeftBracket  file.Idx
+		RightBracket file.Idx
+		Elements     []Expression
+		Rest         Expression
+	}
+
 	AssignExpression struct {
 		Operator token.Token
 		Left     Expression
@@ -127,18 +158,40 @@ type (
 		Value      []Property
 	}
 
+	ObjectPattern struct {
+		LeftBrace  file.Idx
+		RightBrace file.Idx
+		Properties []Property
+		Rest       Expression
+	}
+
 	ParameterList struct {
 		Opening file.Idx
-		List    []*Identifier
+		List    []*Binding
+		Rest    Expression
 		Closing file.Idx
 	}
 
-	Property struct {
+	Property interface {
+		Expression
+		_property()
+	}
+
+	PropertyShort struct {
+		Name        Identifier
+		Initializer Expression
+	}
+
+	PropertyKeyed struct {
 		Key   Expression
-		Kind  string
+		Kind  PropertyKind
 		Value Expression
 	}
 
+	SpreadElement struct {
+		Expression
+	}
+
 	RegExpLiteral struct {
 		Idx     file.Idx
 		Literal string
@@ -167,12 +220,6 @@ type (
 		Postfix  bool
 	}
 
-	VariableExpression struct {
-		Name        unistring.String
-		Idx         file.Idx
-		Initializer Expression
-	}
-
 	MetaProperty struct {
 		Meta, Property *Identifier
 		Idx            file.Idx
@@ -201,8 +248,13 @@ func (*SequenceExpression) _expressionNode()    {}
 func (*StringLiteral) _expressionNode()         {}
 func (*ThisExpression) _expressionNode()        {}
 func (*UnaryExpression) _expressionNode()       {}
-func (*VariableExpression) _expressionNode()    {}
 func (*MetaProperty) _expressionNode()          {}
+func (*ObjectPattern) _expressionNode()         {}
+func (*ArrayPattern) _expressionNode()          {}
+func (*Binding) _expressionNode()               {}
+
+func (*PropertyShort) _expressionNode() {}
+func (*PropertyKeyed) _expressionNode() {}
 
 // ========= //
 // Statement //
@@ -323,13 +375,13 @@ type (
 
 	VariableStatement struct {
 		Var  file.Idx
-		List []*VariableExpression
+		List []*Binding
 	}
 
 	LexicalDeclaration struct {
 		Idx   file.Idx
 		Token token.Token
-		List  []*VariableExpression
+		List  []*Binding
 	}
 
 	WhileStatement struct {
@@ -382,7 +434,7 @@ func (*FunctionDeclaration) _statementNode() {}
 type (
 	VariableDeclaration struct {
 		Var  file.Idx
-		List []*VariableExpression
+		List []*Binding
 	}
 )
 
@@ -397,7 +449,7 @@ type (
 
 	ForLoopInitializerVarDeclList struct {
 		Var  file.Idx
-		List []*VariableExpression
+		List []*Binding
 	}
 
 	ForLoopInitializerLexicalDecl struct {
@@ -409,22 +461,13 @@ type (
 	}
 
 	ForIntoVar struct {
-		Binding *VariableExpression
-	}
-
-	ForBinding interface {
-		_forBinding()
-	}
-
-	BindingIdentifier struct {
-		Idx  file.Idx
-		Name unistring.String
+		Binding *Binding
 	}
 
 	ForDeclaration struct {
 		Idx     file.Idx
 		IsConst bool
-		Binding ForBinding
+		Target  BindingTarget
 	}
 
 	ForIntoExpression struct {
@@ -440,7 +483,19 @@ func (*ForIntoVar) _forInto()        {}
 func (*ForDeclaration) _forInto()    {}
 func (*ForIntoExpression) _forInto() {}
 
-func (*BindingIdentifier) _forBinding() {}
+func (*ArrayPattern) _pattern()       {}
+func (*ArrayPattern) _bindingTarget() {}
+
+func (*ObjectPattern) _pattern()       {}
+func (*ObjectPattern) _bindingTarget() {}
+
+func (*BadExpression) _bindingTarget() {}
+
+func (*PropertyShort) _property() {}
+func (*PropertyKeyed) _property() {}
+func (*SpreadElement) _property() {}
+
+func (*Identifier) _bindingTarget() {}
 
 // ==== //
 // Node //
@@ -459,6 +514,8 @@ type Program struct {
 // ==== //
 
 func (self *ArrayLiteral) Idx0() file.Idx          { return self.LeftBracket }
+func (self *ArrayPattern) Idx0() file.Idx          { return self.LeftBracket }
+func (self *ObjectPattern) Idx0() file.Idx         { return self.LeftBrace }
 func (self *AssignExpression) Idx0() file.Idx      { return self.Left.Idx0() }
 func (self *BadExpression) Idx0() file.Idx         { return self.From }
 func (self *BinaryExpression) Idx0() file.Idx      { return self.Left.Idx0() }
@@ -478,7 +535,6 @@ func (self *SequenceExpression) Idx0() file.Idx    { return self.Sequence[0].Idx
 func (self *StringLiteral) Idx0() file.Idx         { return self.Idx }
 func (self *ThisExpression) Idx0() file.Idx        { return self.Idx }
 func (self *UnaryExpression) Idx0() file.Idx       { return self.Idx }
-func (self *VariableExpression) Idx0() file.Idx    { return self.Idx }
 func (self *MetaProperty) Idx0() file.Idx          { return self.Idx }
 
 func (self *BadStatement) Idx0() file.Idx        { return self.From }
@@ -505,14 +561,18 @@ func (self *WhileStatement) Idx0() file.Idx      { return self.While }
 func (self *WithStatement) Idx0() file.Idx       { return self.With }
 func (self *LexicalDeclaration) Idx0() file.Idx  { return self.Idx }
 func (self *FunctionDeclaration) Idx0() file.Idx { return self.Function.Idx0() }
+func (self *Binding) Idx0() file.Idx             { return self.Target.Idx0() }
 
 func (self *ForLoopInitializerVarDeclList) Idx0() file.Idx { return self.List[0].Idx0() }
+func (self *PropertyShort) Idx0() file.Idx                 { return self.Name.Idx }
+func (self *PropertyKeyed) Idx0() file.Idx                 { return self.Key.Idx0() }
 
 // ==== //
 // Idx1 //
 // ==== //
 
-func (self *ArrayLiteral) Idx1() file.Idx          { return self.RightBracket }
+func (self *ArrayLiteral) Idx1() file.Idx          { return self.RightBracket + 1 }
+func (self *ArrayPattern) Idx1() file.Idx          { return self.RightBracket + 1 }
 func (self *AssignExpression) Idx1() file.Idx      { return self.Right.Idx1() }
 func (self *BadExpression) Idx1() file.Idx         { return self.To }
 func (self *BinaryExpression) Idx1() file.Idx      { return self.Right.Idx1() }
@@ -526,23 +586,18 @@ func (self *Identifier) Idx1() file.Idx            { return file.Idx(int(self.Id
 func (self *NewExpression) Idx1() file.Idx         { return self.RightParenthesis + 1 }
 func (self *NullLiteral) Idx1() file.Idx           { return file.Idx(int(self.Idx) + 4) } // "null"
 func (self *NumberLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *ObjectLiteral) Idx1() file.Idx         { return self.RightBrace }
+func (self *ObjectLiteral) Idx1() file.Idx         { return self.RightBrace + 1 }
+func (self *ObjectPattern) Idx1() file.Idx         { return self.RightBrace + 1 }
 func (self *RegExpLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *SequenceExpression) Idx1() file.Idx    { return self.Sequence[0].Idx1() }
+func (self *SequenceExpression) Idx1() file.Idx    { return self.Sequence[len(self.Sequence)-1].Idx1() }
 func (self *StringLiteral) Idx1() file.Idx         { return file.Idx(int(self.Idx) + len(self.Literal)) }
-func (self *ThisExpression) Idx1() file.Idx        { return self.Idx }
+func (self *ThisExpression) Idx1() file.Idx        { return self.Idx + 4 }
 func (self *UnaryExpression) Idx1() file.Idx {
 	if self.Postfix {
 		return self.Operand.Idx1() + 2 // ++ --
 	}
 	return self.Operand.Idx1()
 }
-func (self *VariableExpression) Idx1() file.Idx {
-	if self.Initializer == nil {
-		return file.Idx(int(self.Idx) + len(self.Name) + 1)
-	}
-	return self.Initializer.Idx1()
-}
 func (self *MetaProperty) Idx1() file.Idx {
 	return self.Property.Idx1()
 }
@@ -565,16 +620,39 @@ func (self *IfStatement) Idx1() file.Idx {
 	}
 	return self.Consequent.Idx1()
 }
-func (self *LabelledStatement) Idx1() file.Idx   { return self.Colon + 1 }
-func (self *Program) Idx1() file.Idx             { return self.Body[len(self.Body)-1].Idx1() }
-func (self *ReturnStatement) Idx1() file.Idx     { return self.Return }
-func (self *SwitchStatement) Idx1() file.Idx     { return self.Body[len(self.Body)-1].Idx1() }
-func (self *ThrowStatement) Idx1() file.Idx      { return self.Throw }
-func (self *TryStatement) Idx1() file.Idx        { return self.Try }
+func (self *LabelledStatement) Idx1() file.Idx { return self.Colon + 1 }
+func (self *Program) Idx1() file.Idx           { return self.Body[len(self.Body)-1].Idx1() }
+func (self *ReturnStatement) Idx1() file.Idx   { return self.Return + 6 }
+func (self *SwitchStatement) Idx1() file.Idx   { return self.Body[len(self.Body)-1].Idx1() }
+func (self *ThrowStatement) Idx1() file.Idx    { return self.Argument.Idx1() }
+func (self *TryStatement) Idx1() file.Idx {
+	if self.Finally != nil {
+		return self.Finally.Idx1()
+	}
+	if self.Catch != nil {
+		return self.Catch.Idx1()
+	}
+	return self.Body.Idx1()
+}
 func (self *VariableStatement) Idx1() file.Idx   { return self.List[len(self.List)-1].Idx1() }
 func (self *WhileStatement) Idx1() file.Idx      { return self.Body.Idx1() }
 func (self *WithStatement) Idx1() file.Idx       { return self.Body.Idx1() }
 func (self *LexicalDeclaration) Idx1() file.Idx  { return self.List[len(self.List)-1].Idx1() }
 func (self *FunctionDeclaration) Idx1() file.Idx { return self.Function.Idx1() }
+func (self *Binding) Idx1() file.Idx {
+	if self.Initializer != nil {
+		return self.Initializer.Idx1()
+	}
+	return self.Target.Idx1()
+}
 
 func (self *ForLoopInitializerVarDeclList) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() }
+
+func (self *PropertyShort) Idx1() file.Idx {
+	if self.Initializer != nil {
+		return self.Initializer.Idx1()
+	}
+	return self.Name.Idx1()
+}
+
+func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() }

+ 1 - 0
builtin_array.go

@@ -380,6 +380,7 @@ func (r *Runtime) arrayproto_splice(call FunctionCall) Value {
 			for k := int64(0); k < actualDeleteCount; k++ {
 				createDataPropertyOrThrow(a, intToValue(k), src.values[k+actualStart])
 			}
+			a.self.setOwnStr("length", intToValue(actualDeleteCount), true)
 		}
 		var values []Value
 		if itemCount < actualDeleteCount {

+ 5 - 6
builtin_function.go

@@ -34,9 +34,9 @@ repeat:
 	case *funcObject:
 		return newStringValue(f.src)
 	case *nativeFuncObject:
-		return newStringValue(fmt.Sprintf("function %s() { [native code] }", f.nameProp.get(call.This).toString()))
+		return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
 	case *boundFuncObject:
-		return newStringValue(fmt.Sprintf("function %s() { [native code] }", f.nameProp.get(call.This).toString()))
+		return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString()))
 	case *lazyObject:
 		obj.self = f.create(obj)
 		goto repeat
@@ -47,9 +47,9 @@ repeat:
 		case *funcObject:
 			name = c.src
 		case *nativeFuncObject:
-			name = nilSafe(c.nameProp.get(call.This)).toString().String()
+			name = nilSafe(f.getStr("name", nil)).toString().String()
 		case *boundFuncObject:
-			name = nilSafe(c.nameProp.get(call.This)).toString().String()
+			name = nilSafe(f.getStr("name", nil)).toString().String()
 		case *lazyObject:
 			f.target.self = c.create(obj)
 			goto repeat2
@@ -183,8 +183,7 @@ func (r *Runtime) functionproto_bind(call FunctionCall) Value {
 func (r *Runtime) initFunction() {
 	o := r.global.FunctionPrototype.self.(*nativeFuncObject)
 	o.prototype = r.global.ObjectPrototype
-	o.nameProp.value = stringEmpty
-
+	o._putProp("name", stringEmpty, false, false, true)
 	o._putProp("apply", r.newNativeFunc(r.functionproto_apply, nil, "apply", nil, 2), true, false, true)
 	o._putProp("bind", r.newNativeFunc(r.functionproto_bind, nil, "bind", nil, 1), true, false, true)
 	o._putProp("call", r.newNativeFunc(r.functionproto_call, nil, "call", nil, 1), true, false, true)

+ 189 - 28
compiler.go

@@ -2,6 +2,7 @@ package goja
 
 import (
 	"fmt"
+	"github.com/dop251/goja/token"
 	"sort"
 
 	"github.com/dop251/goja/ast"
@@ -25,9 +26,10 @@ const (
 const (
 	maskConst     = 1 << 31
 	maskVar       = 1 << 30
-	maskDeletable = maskConst
+	maskDeletable = 1 << 29
+	maskStrict    = maskDeletable
 
-	maskTyp = maskConst | maskVar
+	maskTyp = maskConst | maskVar | maskDeletable
 )
 
 type varType byte
@@ -35,6 +37,7 @@ type varType byte
 const (
 	varTypeVar varType = iota
 	varTypeLet
+	varTypeStrictConst
 	varTypeConst
 )
 
@@ -81,6 +84,7 @@ type binding struct {
 	name         unistring.String
 	accessPoints map[*scope]*[]int
 	isConst      bool
+	isStrict     bool
 	isArg        bool
 	isVar        bool
 	inStash      bool
@@ -99,6 +103,17 @@ func (b *binding) getAccessPointsForScope(s *scope) *[]int {
 	return m
 }
 
+func (b *binding) markAccessPointAt(pos int) {
+	scope := b.scope.c.scope
+	m := b.getAccessPointsForScope(scope)
+	*m = append(*m, pos-scope.base)
+}
+
+func (b *binding) markAccessPointAtScope(scope *scope, pos int) {
+	m := b.getAccessPointsForScope(scope)
+	*m = append(*m, pos-scope.base)
+}
+
 func (b *binding) markAccessPoint() {
 	scope := b.scope.c.scope
 	m := b.getAccessPointsForScope(scope)
@@ -114,6 +129,15 @@ func (b *binding) emitGet() {
 	}
 }
 
+func (b *binding) emitGetAt(pos int) {
+	b.markAccessPointAt(pos)
+	if b.isVar && !b.isArg {
+		b.scope.c.p.code[pos] = loadStash(0)
+	} else {
+		b.scope.c.p.code[pos] = loadStashLex(0)
+	}
+}
+
 func (b *binding) emitGetP() {
 	if b.isVar && !b.isArg {
 		// no-op
@@ -126,7 +150,9 @@ func (b *binding) emitGetP() {
 
 func (b *binding) emitSet() {
 	if b.isConst {
-		b.scope.c.emit(throwAssignToConst)
+		if b.isStrict || b.scope.c.scope.strict {
+			b.scope.c.emit(throwAssignToConst)
+		}
 		return
 	}
 	b.markAccessPoint()
@@ -139,7 +165,9 @@ func (b *binding) emitSet() {
 
 func (b *binding) emitSetP() {
 	if b.isConst {
-		b.scope.c.emit(throwAssignToConst)
+		if b.isStrict || b.scope.c.scope.strict {
+			b.scope.c.emit(throwAssignToConst)
+		}
 		return
 	}
 	b.markAccessPoint()
@@ -171,7 +199,11 @@ func (b *binding) emitResolveVar(strict bool) {
 	} else {
 		var typ varType
 		if b.isConst {
-			typ = varTypeConst
+			if b.isStrict {
+				typ = varTypeStrictConst
+			} else {
+				typ = varTypeConst
+			}
 		} else {
 			typ = varTypeLet
 		}
@@ -216,6 +248,8 @@ type scope struct {
 
 	// is a function or a top-level lexical environment
 	function bool
+	// is a variable environment, i.e. the target for dynamically created var bindings
+	variable bool
 	// a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically
 	dynamic bool
 	// arguments have been marked for placement in stash (functions only)
@@ -379,28 +413,33 @@ func (s *scope) ensureBoundNamesCreated() {
 	}
 }
 
-func (s *scope) bindNameLexical(name unistring.String, unique bool, offset int) (*binding, bool) {
-	if b := s.boundNames[name]; b != nil {
-		if unique {
-			s.c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name)
-		}
-		return b, false
-	}
+func (s *scope) addBinding(offset int) *binding {
 	if len(s.bindings) >= (1<<24)-1 {
 		s.c.throwSyntaxError(offset, "Too many variables")
 	}
 	b := &binding{
 		scope: s,
-		name:  name,
 	}
 	s.bindings = append(s.bindings, b)
+	return b
+}
+
+func (s *scope) bindNameLexical(name unistring.String, unique bool, offset int) (*binding, bool) {
+	if b := s.boundNames[name]; b != nil {
+		if unique {
+			s.c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name)
+		}
+		return b, false
+	}
+	b := s.addBinding(offset)
+	b.name = name
 	s.ensureBoundNamesCreated()
 	s.boundNames[name] = b
 	return b, true
 }
 
 func (s *scope) bindName(name unistring.String) (*binding, bool) {
-	if !s.function && s.outer != nil {
+	if !s.function && !s.variable && s.outer != nil {
 		return s.outer.bindName(name)
 	}
 	b, created := s.bindNameLexical(name, false, 0)
@@ -594,6 +633,9 @@ func (s *scope) makeNamesMap() map[unistring.String]uint32 {
 		idx := uint32(i)
 		if b.isConst {
 			idx |= maskConst
+			if b.isStrict {
+				idx |= maskStrict
+			}
 		}
 		if b.isVar {
 			idx |= maskVar
@@ -631,7 +673,7 @@ func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) {
 	scope.dynamic = true
 	scope.eval = eval
 	if !strict && len(in.Body) > 0 {
-		strict = c.isStrict(in.Body)
+		strict = c.isStrict(in.Body) != nil
 	}
 	scope.strict = strict
 	ownVarScope := eval && strict
@@ -712,7 +754,7 @@ func (c *compiler) compile(in *ast.Program, strict, eval, inGlobal bool) {
 
 func (c *compiler) compileDeclList(v []*ast.VariableDeclaration, inFunc bool) {
 	for _, value := range v {
-		c.compileVarDecl(value, inFunc)
+		c.createVarBindings(value, inFunc)
 	}
 }
 
@@ -746,7 +788,7 @@ func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.Function
 func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) {
 	s := c.scope
 	if s.outer != nil {
-		unique := !s.function && s.strict
+		unique := !s.function && !s.variable && s.strict
 		for _, decl := range funcs {
 			s.bindNameLexical(decl.Function.Name.Name, unique, int(decl.Function.Name.Idx1())-1)
 		}
@@ -788,14 +830,133 @@ func (c *compiler) compileFunctionsGlobal(list []*ast.FunctionDeclaration) {
 	}
 }
 
-func (c *compiler) compileVarDecl(v *ast.VariableDeclaration, inFunc bool) {
+func (c *compiler) createVarIdBinding(name unistring.String, offset int, inFunc bool) {
+	if c.scope.strict {
+		c.checkIdentifierLName(name, offset)
+		c.checkIdentifierName(name, offset)
+	}
+	if !inFunc || name != "arguments" {
+		c.scope.bindName(name)
+	}
+}
+
+func (c *compiler) createBindings(target ast.Expression, createIdBinding func(name unistring.String, offset int)) {
+	switch target := target.(type) {
+	case *ast.Identifier:
+		createIdBinding(target.Name, int(target.Idx)-1)
+	case *ast.ObjectPattern:
+		for _, prop := range target.Properties {
+			switch prop := prop.(type) {
+			case *ast.PropertyShort:
+				createIdBinding(prop.Name.Name, int(prop.Name.Idx)-1)
+			case *ast.PropertyKeyed:
+				c.createBindings(prop.Value, createIdBinding)
+			default:
+				c.throwSyntaxError(int(target.Idx0()-1), "unsupported property type in ObjectPattern: %T", prop)
+			}
+		}
+		if target.Rest != nil {
+			c.createBindings(target.Rest, createIdBinding)
+		}
+	case *ast.ArrayPattern:
+		for _, elt := range target.Elements {
+			if elt != nil {
+				c.createBindings(elt, createIdBinding)
+			}
+		}
+		if target.Rest != nil {
+			c.createBindings(target.Rest, createIdBinding)
+		}
+	case *ast.AssignExpression:
+		c.createBindings(target.Left, createIdBinding)
+	default:
+		c.throwSyntaxError(int(target.Idx0()-1), "unsupported binding target: %T", target)
+	}
+}
+
+func (c *compiler) createVarBinding(target ast.Expression, inFunc bool) {
+	c.createBindings(target, func(name unistring.String, offset int) {
+		c.createVarIdBinding(name, offset, inFunc)
+	})
+}
+
+func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) {
 	for _, item := range v.List {
-		if c.scope.strict {
-			c.checkIdentifierLName(item.Name, int(item.Idx)-1)
-			c.checkIdentifierName(item.Name, int(item.Idx)-1)
+		c.createVarBinding(item.Target, inFunc)
+	}
+}
+
+func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding {
+	if name == "let" {
+		c.throwSyntaxError(offset, "let is disallowed as a lexically bound name")
+	}
+	if c.scope.strict {
+		c.checkIdentifierLName(name, offset)
+		c.checkIdentifierName(name, offset)
+	}
+	b, _ := c.scope.bindNameLexical(name, true, offset)
+	if isConst {
+		b.isConst, b.isStrict = true, true
+	}
+	return b
+}
+
+func (c *compiler) createLexicalIdBindingFuncBody(name unistring.String, isConst bool, offset int, calleeBinding *binding) *binding {
+	if name == "let" {
+		c.throwSyntaxError(offset, "let is disallowed as a lexically bound name")
+	}
+	if c.scope.strict {
+		c.checkIdentifierLName(name, offset)
+		c.checkIdentifierName(name, offset)
+	}
+	paramScope := c.scope.outer
+	parentBinding := paramScope.boundNames[name]
+	if parentBinding != nil {
+		if parentBinding != calleeBinding && (name != "arguments" || !paramScope.argsNeeded) {
+			c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name)
 		}
-		if !inFunc || item.Name != "arguments" {
-			c.scope.bindName(item.Name)
+	}
+	b, _ := c.scope.bindNameLexical(name, true, offset)
+	if isConst {
+		b.isConst, b.isStrict = true, true
+	}
+	return b
+}
+
+func (c *compiler) createLexicalBinding(target ast.Expression, isConst bool) {
+	c.createBindings(target, func(name unistring.String, offset int) {
+		c.createLexicalIdBinding(name, isConst, offset)
+	})
+}
+
+func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) {
+	for _, d := range lex.List {
+		c.createLexicalBinding(d.Target, lex.Token == token.CONST)
+	}
+}
+
+func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool {
+	for _, st := range list {
+		if lex, ok := st.(*ast.LexicalDeclaration); ok {
+			if !scopeDeclared {
+				c.newBlockScope()
+				scopeDeclared = true
+			}
+			c.createLexicalBindings(lex)
+		}
+	}
+	return scopeDeclared
+}
+
+func (c *compiler) compileLexicalDeclarationsFuncBody(list []ast.Statement, calleeBinding *binding) {
+	for _, st := range list {
+		if lex, ok := st.(*ast.LexicalDeclaration); ok {
+			isConst := lex.Token == token.CONST
+			for _, d := range lex.List {
+				c.createBindings(d.Target, func(name unistring.String, offset int) {
+					c.createLexicalIdBindingFuncBody(name, isConst, offset, calleeBinding)
+				})
+			}
 		}
 	}
 }
@@ -836,12 +997,12 @@ func (c *compiler) throwSyntaxError(offset int, format string, args ...interface
 	})
 }
 
-func (c *compiler) isStrict(list []ast.Statement) bool {
+func (c *compiler) isStrict(list []ast.Statement) *ast.StringLiteral {
 	for _, st := range list {
 		if st, ok := st.(*ast.ExpressionStatement); ok {
 			if e, ok := st.Expression.(*ast.StringLiteral); ok {
 				if e.Literal == `"use strict"` || e.Literal == `'use strict'` {
-					return true
+					return e
 				}
 			} else {
 				break
@@ -850,14 +1011,14 @@ func (c *compiler) isStrict(list []ast.Statement) bool {
 			break
 		}
 	}
-	return false
+	return nil
 }
 
-func (c *compiler) isStrictStatement(s ast.Statement) bool {
+func (c *compiler) isStrictStatement(s ast.Statement) *ast.StringLiteral {
 	if s, ok := s.(*ast.BlockStatement); ok {
 		return c.isStrict(s.List)
 	}
-	return false
+	return nil
 }
 
 func (c *compiler) checkIdentifierName(name unistring.String, offset int) {

+ 643 - 112
compiler_expr.go

@@ -17,6 +17,7 @@ var (
 type compiledExpr interface {
 	emitGetter(putOnStack bool)
 	emitSetter(valueExpr compiledExpr, putOnStack bool)
+	emitRef()
 	emitUnary(prepare, body func(), postfix, putOnStack bool)
 	deleteExpr() compiledExpr
 	constant() bool
@@ -60,6 +61,16 @@ type compiledAssignExpr struct {
 	operator    token.Token
 }
 
+type compiledObjectAssignmentPattern struct {
+	baseCompiledExpr
+	expr *ast.ObjectPattern
+}
+
+type compiledArrayAssignmentPattern struct {
+	baseCompiledExpr
+	expr *ast.ArrayPattern
+}
+
 type deleteGlobalExpr struct {
 	baseCompiledExpr
 	name unistring.String
@@ -100,8 +111,8 @@ type compiledFunctionLiteral struct {
 	baseCompiledExpr
 	expr    *ast.FunctionLiteral
 	lhsName unistring.String
+	strict  *ast.StringLiteral
 	isExpr  bool
-	strict  bool
 }
 
 type compiledBracketExpr struct {
@@ -156,12 +167,6 @@ type compiledBinaryExpr struct {
 	operator    token.Token
 }
 
-type compiledVariableExpr struct {
-	baseCompiledExpr
-	name        unistring.String
-	initializer compiledExpr
-}
-
 type compiledEnumGetExpr struct {
 	baseCompiledExpr
 }
@@ -207,8 +212,6 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
 		return c.compileArrayLiteral(v)
 	case *ast.RegExpLiteral:
 		return c.compileRegexpLiteral(v)
-	case *ast.VariableExpression:
-		return c.compileVariableExpression(v)
 	case *ast.BinaryExpression:
 		return c.compileBinaryExpression(v)
 	case *ast.UnaryExpression:
@@ -241,6 +244,10 @@ func (c *compiler) compileExpression(v ast.Expression) compiledExpr {
 		return c.compileNewExpression(v)
 	case *ast.MetaProperty:
 		return c.compileMetaProperty(v)
+	case *ast.ObjectPattern:
+		return c.compileObjectAssignmentPattern(v)
+	case *ast.ArrayPattern:
+		return c.compileArrayAssignmentPattern(v)
 	default:
 		panic(fmt.Errorf("Unknown expression type: %T", v))
 	}
@@ -259,6 +266,10 @@ func (e *baseCompiledExpr) emitSetter(compiledExpr, bool) {
 	e.c.throwSyntaxError(e.offset, "Not a valid left-value expression")
 }
 
+func (e *baseCompiledExpr) emitRef() {
+	e.c.throwSyntaxError(e.offset, "Cannot emit reference for this type of expression")
+}
+
 func (e *baseCompiledExpr) deleteExpr() compiledExpr {
 	r := &constantExpr{
 		val: valueTrue,
@@ -391,8 +402,25 @@ func (c *compiler) emitVarSetter(name unistring.String, offset int, valueExpr co
 	})
 }
 
-func (e *compiledVariableExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) {
-	e.c.emitVarSetter(e.name, e.offset, valueExpr, putOnStack)
+func (c *compiler) emitVarRef(name unistring.String, offset int) {
+	if c.scope.strict {
+		c.checkIdentifierLName(name, offset)
+	}
+
+	b, _ := c.scope.lookupName(name)
+	if b != nil {
+		b.emitResolveVar(c.scope.strict)
+	} else {
+		if c.scope.strict {
+			c.emit(resolveVar1Strict(name))
+		} else {
+			c.emit(resolveVar1(name))
+		}
+	}
+}
+
+func (e *compiledIdentifierExpr) emitRef() {
+	e.c.emitVarRef(e.name, e.offset)
 }
 
 func (e *compiledIdentifierExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) {
@@ -476,6 +504,15 @@ func (e *compiledDotExpr) emitGetter(putOnStack bool) {
 	}
 }
 
+func (e *compiledDotExpr) emitRef() {
+	e.left.emitGetter(true)
+	if e.c.scope.strict {
+		e.c.emit(getPropRefStrict(e.name))
+	} else {
+		e.c.emit(getPropRef(e.name))
+	}
+}
+
 func (e *compiledDotExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) {
 	e.left.emitGetter(true)
 	valueExpr.emitGetter(true)
@@ -558,6 +595,16 @@ func (e *compiledBracketExpr) emitGetter(putOnStack bool) {
 	}
 }
 
+func (e *compiledBracketExpr) emitRef() {
+	e.left.emitGetter(true)
+	e.member.emitGetter(true)
+	if e.c.scope.strict {
+		e.c.emit(getElemRefStrict)
+	} else {
+		e.c.emit(getElemRef)
+	}
+}
+
 func (e *compiledBracketExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) {
 	e.left.emitGetter(true)
 	e.member.emitGetter(true)
@@ -766,6 +813,29 @@ func (e *compiledLiteral) constant() bool {
 	return true
 }
 
+func (c *compiler) compileParameterBindingIdentifier(name unistring.String, offset int) (*binding, bool) {
+	if c.scope.strict {
+		c.checkIdentifierName(name, offset)
+		c.checkIdentifierLName(name, offset)
+	}
+	b, unique := c.scope.bindNameShadow(name)
+	if !unique && c.scope.strict {
+		c.throwSyntaxError(offset, "Strict mode function may not have duplicate parameter names (%s)", name)
+		return nil, false
+	}
+	return b, unique
+}
+
+func (c *compiler) compileParameterPatternIdBinding(name unistring.String, offset int) {
+	if _, unique := c.compileParameterBindingIdentifier(name, offset); !unique {
+		c.throwSyntaxError(offset, "Duplicate parameter name not allowed in this context")
+	}
+}
+
+func (c *compiler) compileParameterPatternBinding(item ast.Expression) {
+	c.createBindings(item, c.compileParameterPatternIdBinding)
+}
+
 func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	savedPrg := e.c.p
 	e.c.p = &Program{
@@ -794,38 +864,80 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	}
 
 	if !e.c.scope.strict {
-		e.c.scope.strict = e.strict
+		e.c.scope.strict = e.strict != nil
 	}
 
-	if e.c.scope.strict {
-		for _, item := range e.expr.ParameterList.List {
-			e.c.checkIdentifierName(item.Name, int(item.Idx)-1)
-			e.c.checkIdentifierLName(item.Name, int(item.Idx)-1)
-		}
-	}
+	hasPatterns := false
+	hasInits := false
+	firstDupIdx := -1
+	length := 0
 
-	length := len(e.expr.ParameterList.List)
+	if e.expr.ParameterList.Rest != nil {
+		hasPatterns = true // strictly speaking not, but we need to activate all the checks
+	}
 
+	// First, make sure that the first bindings correspond to the formal parameters
 	for _, item := range e.expr.ParameterList.List {
-		b, unique := e.c.scope.bindNameShadow(item.Name)
-		if !unique && e.c.scope.strict {
-			e.c.throwSyntaxError(int(item.Idx)-1, "Strict mode function may not have duplicate parameter names (%s)", item.Name)
+		switch tgt := item.Target.(type) {
+		case *ast.Identifier:
+			offset := int(tgt.Idx) - 1
+			b, unique := e.c.compileParameterBindingIdentifier(tgt.Name, offset)
+			if !unique {
+				firstDupIdx = offset
+			}
+			b.isArg = true
+			b.isVar = true
+		case ast.Pattern:
+			b := e.c.scope.addBinding(int(item.Idx0()) - 1)
+			b.isArg = true
+			hasPatterns = true
+		default:
+			e.c.throwSyntaxError(int(item.Idx0())-1, "Unsupported BindingElement type: %T", item)
 			return
 		}
-		b.isArg = true
-		b.isVar = true
+		if item.Initializer != nil {
+			hasInits = true
+		}
+		if hasPatterns || hasInits {
+			if firstDupIdx >= 0 {
+				e.c.throwSyntaxError(firstDupIdx, "Duplicate parameter name not allowed in this context")
+				return
+			}
+			if e.strict != nil {
+				e.c.throwSyntaxError(int(e.strict.Idx)-1, "Illegal 'use strict' directive in function with non-simple parameter list")
+				return
+			}
+		}
+		if !hasInits {
+			length++
+		}
 	}
-	paramsCount := len(e.c.scope.bindings)
+
+	// create pattern bindings
+	if hasPatterns {
+		for _, item := range e.expr.ParameterList.List {
+			switch tgt := item.Target.(type) {
+			case *ast.Identifier:
+				// we already created those in the previous loop, skipping
+			default:
+				e.c.compileParameterPatternBinding(tgt)
+			}
+		}
+		if rest := e.expr.ParameterList.Rest; rest != nil {
+			e.c.compileParameterPatternBinding(rest)
+		}
+	}
+
+	paramsCount := len(e.expr.ParameterList.List)
+
 	e.c.scope.numArgs = paramsCount
-	e.c.compileDeclList(e.expr.DeclarationList, true)
 	body := e.expr.Body.List
 	funcs := e.c.extractFunctions(body)
-	e.c.createFunctionBindings(funcs)
 	s := e.c.scope
-	e.c.compileLexicalDeclarations(body, true)
 	var calleeBinding *binding
 	if e.isExpr && e.expr.Name != nil {
-		if b, created := s.bindName(e.expr.Name.Name); created {
+		if b, created := s.bindNameLexical(e.expr.Name.Name, false, 0); created {
+			b.isConst = true
 			calleeBinding = b
 		}
 	}
@@ -834,7 +946,91 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 
 	if calleeBinding != nil {
 		e.c.emit(loadCallee)
-		calleeBinding.emitSetP()
+		calleeBinding.emitInit()
+	}
+
+	emitArgsRestMark := -1
+	firstForwardRef := -1
+	enterFunc2Mark := -1
+
+	if hasPatterns || hasInits {
+		for i, item := range e.expr.ParameterList.List {
+			if pattern, ok := item.Target.(ast.Pattern); ok {
+				i := i
+				e.c.compilePatternInitExpr(func() {
+					if firstForwardRef == -1 {
+						s.bindings[i].emitGet()
+					} else {
+						e.c.emit(loadStackLex(-i - 1))
+					}
+				}, item.Initializer, item.Target.Idx0()).emitGetter(true)
+				e.c.emitPattern(pattern, func(target, init compiledExpr) {
+					e.c.emitPatternLexicalAssign(target, init, false)
+				}, false)
+			} else if item.Initializer != nil {
+				markGet := len(e.c.p.code)
+				e.c.emit(nil)
+				mark := len(e.c.p.code)
+				e.c.emit(nil)
+				e.c.compileExpression(item.Initializer).emitGetter(true)
+				if firstForwardRef == -1 && (s.isDynamic() || s.bindings[i].useCount() > 0) {
+					firstForwardRef = i
+				}
+				if firstForwardRef == -1 {
+					s.bindings[i].emitGetAt(markGet)
+				} else {
+					e.c.p.code[markGet] = loadStackLex(-i - 1)
+				}
+				s.bindings[i].emitInit()
+				e.c.p.code[mark] = jdefP(len(e.c.p.code) - mark)
+			} else {
+				if firstForwardRef == -1 && s.bindings[i].useCount() > 0 {
+					firstForwardRef = i
+				}
+				if firstForwardRef != -1 {
+					e.c.emit(loadStackLex(-i - 1))
+					s.bindings[i].emitInit()
+				}
+			}
+		}
+		if rest := e.expr.ParameterList.Rest; rest != nil {
+			e.c.emitAssign(rest, e.c.compileEmitterExpr(
+				func() {
+					emitArgsRestMark = len(e.c.p.code)
+					e.c.emit(createArgsRestStack(paramsCount))
+				}, rest.Idx0()),
+				func(target, init compiledExpr) {
+					e.c.emitPatternLexicalAssign(target, init, false)
+				})
+		}
+		if firstForwardRef != -1 {
+			for _, b := range s.bindings {
+				b.inStash = true
+			}
+			s.argsInStash = true
+			s.needStash = true
+		}
+
+		e.c.newBlockScope()
+		varScope := e.c.scope
+		varScope.variable = true
+		enterFunc2Mark = len(e.c.p.code)
+		e.c.emit(nil)
+		e.c.compileDeclList(e.expr.DeclarationList, true)
+		e.c.createFunctionBindings(funcs)
+		e.c.compileLexicalDeclarationsFuncBody(body, calleeBinding)
+		for _, b := range varScope.bindings {
+			if b.isVar {
+				if parentBinding := s.boundNames[b.name]; parentBinding != nil && parentBinding != calleeBinding {
+					parentBinding.emitGet()
+					b.emitSetP()
+				}
+			}
+		}
+	} else {
+		e.c.compileDeclList(e.expr.DeclarationList, true)
+		e.c.createFunctionBindings(funcs)
+		e.c.compileLexicalDeclarations(body, true)
 	}
 
 	e.c.compileFunctions(funcs)
@@ -856,23 +1052,27 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 		preambleLen += 2
 	}
 
-	if (s.argsNeeded || s.isDynamic()) && !s.argsInStash {
+	if !s.argsInStash && (s.argsNeeded || s.isDynamic()) {
 		s.moveArgsToStash()
 	}
 
 	if s.argsNeeded {
 		pos := preambleLen - 2
 		delta += 2
-		if s.strict {
-			code[pos] = createArgsStrict(length)
+		if s.strict || hasPatterns || hasInits {
+			code[pos] = createArgsUnmapped(paramsCount)
 		} else {
-			code[pos] = createArgs(length)
+			code[pos] = createArgsMapped(paramsCount)
 		}
 		pos++
-		b, _ := s.bindName("arguments")
-		e.c.p.code = code[:pos]
-		b.emitSetP()
-		e.c.p.code = code
+		b, _ := s.bindNameLexical("arguments", false, 0)
+		if s.strict {
+			b.isConst = true
+		} else {
+			b.isVar = true
+		}
+		b.markAccessPointAtScope(s, pos)
+		code[pos] = storeStashP(0)
 	}
 
 	stashSize, stackSize := s.finaliseVarAlloc(0)
@@ -885,22 +1085,60 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	delta = preambleLen - delta
 	var enter instruction
 	if stashSize > 0 || s.argsInStash {
-		enter1 := enterFunc{
-			numArgs:     uint32(paramsCount),
-			argsToStash: s.argsInStash,
-			stashSize:   uint32(stashSize),
-			stackSize:   uint32(stackSize),
-			extensible:  s.dynamic,
+		if firstForwardRef == -1 {
+			enter1 := enterFunc{
+				numArgs:     uint32(paramsCount),
+				argsToStash: s.argsInStash,
+				stashSize:   uint32(stashSize),
+				stackSize:   uint32(stackSize),
+				extensible:  s.dynamic,
+			}
+			if s.isDynamic() {
+				enter1.names = s.makeNamesMap()
+			}
+			enter = &enter1
+			if enterFunc2Mark != -1 {
+				ef2 := &enterFuncBody{
+					extensible: e.c.scope.dynamic,
+				}
+				e.c.updateEnterBlock(&ef2.enterBlock)
+				e.c.p.code[enterFunc2Mark] = ef2
+			}
+		} else {
+			enter1 := enterFunc1{
+				stashSize:  uint32(stashSize),
+				numArgs:    uint32(paramsCount),
+				argsToCopy: uint32(firstForwardRef),
+				extensible: s.dynamic,
+			}
+			if s.isDynamic() {
+				enter1.names = s.makeNamesMap()
+			}
+			enter = &enter1
+			if enterFunc2Mark != -1 {
+				ef2 := &enterFuncBody{
+					adjustStack: true,
+					extensible:  e.c.scope.dynamic,
+				}
+				e.c.updateEnterBlock(&ef2.enterBlock)
+				e.c.p.code[enterFunc2Mark] = ef2
+			}
 		}
-		if s.isDynamic() {
-			enter1.names = s.makeNamesMap()
+		if emitArgsRestMark != -1 {
+			e.c.p.code[emitArgsRestMark] = createArgsRestStash
 		}
-		enter = &enter1
 	} else {
 		enter = &enterFuncStashless{
 			stackSize: uint32(stackSize),
 			args:      uint32(paramsCount),
 		}
+		if enterFunc2Mark != -1 {
+			ef2 := &enterFuncBody{
+				extensible: e.c.scope.dynamic,
+			}
+			e.c.updateEnterBlock(&ef2.enterBlock)
+			e.c.p.code[enterFunc2Mark] = ef2
+		}
 	}
 	code[delta] = enter
 	if delta != 0 {
@@ -914,6 +1152,9 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	strict := s.strict
 	p := e.c.p
 	// e.c.p.dumpCode()
+	if enterFunc2Mark != -1 {
+		e.c.popScope()
+	}
 	e.c.popScope()
 	e.c.p = savedPrg
 	e.c.emit(&newFunc{prg: p, length: uint32(length), name: name, srcStart: uint32(e.expr.Idx0() - 1), srcEnd: uint32(e.expr.Idx1() - 1), strict: strict})
@@ -923,14 +1164,14 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 }
 
 func (c *compiler) compileFunctionLiteral(v *ast.FunctionLiteral, isExpr bool) *compiledFunctionLiteral {
-	strict := c.scope.strict || c.isStrictStatement(v.Body)
-	if v.Name != nil && strict {
+	strictBody := c.isStrictStatement(v.Body)
+	if v.Name != nil && (c.scope.strict || strictBody != nil) {
 		c.checkIdentifierLName(v.Name.Name, int(v.Name.Idx)-1)
 	}
 	r := &compiledFunctionLiteral{
 		expr:   v,
 		isExpr: isExpr,
-		strict: strict,
+		strict: strictBody,
 	}
 	r.init(c, v.Idx0())
 	return r
@@ -1373,64 +1614,84 @@ func (c *compiler) compileLogicalAnd(left, right ast.Expression, idx file.Idx) c
 	return r
 }
 
-func (e *compiledVariableExpr) emitGetter(putOnStack bool) {
-	if e.initializer != nil {
-		idExpr := &compiledIdentifierExpr{
-			name: e.name,
-		}
-		idExpr.init(e.c, file.Idx(0))
-		idExpr.emitSetter(e.initializer, putOnStack)
-	} else {
-		if putOnStack {
-			e.c.emit(loadUndef)
-		}
-	}
-}
-
-func (c *compiler) compileVariableExpression(v *ast.VariableExpression) compiledExpr {
-	r := &compiledVariableExpr{
-		name:        v.Name,
-		initializer: c.compileExpression(v.Initializer),
-	}
-	if fn, ok := r.initializer.(*compiledFunctionLiteral); ok {
-		fn.lhsName = v.Name
-	}
-	r.init(c, v.Idx0())
-	return r
-}
-
 func (e *compiledObjectLiteral) emitGetter(putOnStack bool) {
 	e.addSrcMap()
 	e.c.emit(newObject)
 	for _, prop := range e.expr.Value {
-		keyExpr := e.c.compileExpression(prop.Key)
-		cl, ok := keyExpr.(*compiledLiteral)
-		if !ok {
-			e.c.throwSyntaxError(e.offset, "non-literal properties in object literal are not supported yet")
-		}
-		key := cl.val.string()
-		valueExpr := e.c.compileExpression(prop.Value)
-		if fn, ok := valueExpr.(*compiledFunctionLiteral); ok {
-			if fn.expr.Name == nil {
-				fn.lhsName = key
+		switch prop := prop.(type) {
+		case *ast.PropertyKeyed:
+			keyExpr := e.c.compileExpression(prop.Key)
+			computed := false
+			var key unistring.String
+			switch keyExpr := keyExpr.(type) {
+			case *compiledLiteral:
+				key = keyExpr.val.string()
+			default:
+				keyExpr.emitGetter(true)
+				computed = true
+				//e.c.throwSyntaxError(e.offset, "non-literal properties in object literal are not supported yet")
 			}
-		}
-		valueExpr.emitGetter(true)
-		switch prop.Kind {
-		case "value":
-			if key == __proto__ {
-				e.c.emit(setProto)
+			valueExpr := e.c.compileExpression(prop.Value)
+			var anonFn *compiledFunctionLiteral
+			if fn, ok := valueExpr.(*compiledFunctionLiteral); ok {
+				if fn.expr.Name == nil {
+					anonFn = fn
+					fn.lhsName = key
+				}
+			}
+			if computed {
+				valueExpr.emitGetter(true)
+				switch prop.Kind {
+				case ast.PropertyKindValue, ast.PropertyKindMethod:
+					if anonFn != nil {
+						e.c.emit(setElem1Named)
+					} else {
+						e.c.emit(setElem1)
+					}
+				case ast.PropertyKindGet:
+					e.c.emit(setPropGetter1)
+				case ast.PropertyKindSet:
+					e.c.emit(setPropSetter1)
+				default:
+					panic(fmt.Errorf("unknown property kind: %s", prop.Kind))
+				}
 			} else {
-				e.c.emit(setProp1(key))
+				if anonFn != nil {
+					anonFn.lhsName = key
+				}
+				valueExpr.emitGetter(true)
+				switch prop.Kind {
+				case ast.PropertyKindValue:
+					if key == __proto__ {
+						e.c.emit(setProto)
+					} else {
+						e.c.emit(setProp1(key))
+					}
+				case ast.PropertyKindMethod:
+					e.c.emit(setProp1(key))
+				case ast.PropertyKindGet:
+					e.c.emit(setPropGetter(key))
+				case ast.PropertyKindSet:
+					e.c.emit(setPropSetter(key))
+				default:
+					panic(fmt.Errorf("unknown property kind: %s", prop.Kind))
+				}
+			}
+		case *ast.PropertyShort:
+			key := prop.Name.Name
+			if prop.Initializer != nil {
+				e.c.throwSyntaxError(int(prop.Initializer.Idx0())-1, "Invalid shorthand property initializer")
+			}
+			if e.c.scope.strict && key == "let" {
+				e.c.throwSyntaxError(e.offset, "'let' cannot be used as a shorthand property in strict mode")
 			}
-		case "method":
+			e.c.compileIdentifierExpression(&prop.Name).emitGetter(true)
 			e.c.emit(setProp1(key))
-		case "get":
-			e.c.emit(setPropGetter(key))
-		case "set":
-			e.c.emit(setPropSetter(key))
+		case *ast.SpreadElement:
+			e.c.compileExpression(prop.Expression).emitGetter(true)
+			e.c.emit(copySpread)
 		default:
-			panic(fmt.Errorf("unknown property kind: %s", prop.Kind))
+			panic(fmt.Errorf("unknown Property type: %T", prop))
 		}
 	}
 	if !putOnStack {
@@ -1448,23 +1709,28 @@ func (c *compiler) compileObjectLiteral(v *ast.ObjectLiteral) compiledExpr {
 
 func (e *compiledArrayLiteral) emitGetter(putOnStack bool) {
 	e.addSrcMap()
-	objCount := 0
+	hasSpread := false
+	mark := len(e.c.p.code)
+	e.c.emit(nil)
 	for _, v := range e.expr.Value {
-		if v != nil {
-			e.c.compileExpression(v).emitGetter(true)
-			objCount++
+		if spread, ok := v.(*ast.SpreadElement); ok {
+			hasSpread = true
+			e.c.compileExpression(spread.Expression).emitGetter(true)
+			e.c.emit(pushArraySpread)
 		} else {
-			e.c.emit(loadNil)
+			if v != nil {
+				e.c.compileExpression(v).emitGetter(true)
+			} else {
+				e.c.emit(loadNil)
+			}
+			e.c.emit(pushArrayItem)
 		}
 	}
-	if objCount == len(e.expr.Value) {
-		e.c.emit(newArray(objCount))
-	} else {
-		e.c.emit(&newArraySparse{
-			l:        len(e.expr.Value),
-			objCount: objCount,
-		})
+	var objCount uint32
+	if !hasSpread {
+		objCount = uint32(len(e.expr.Value))
 	}
+	e.c.p.code[mark] = newArray(objCount)
 	if !putOnStack {
 		e.c.emit(pop)
 	}
@@ -1523,11 +1789,14 @@ func (e *compiledCallExpr) emitGetter(putOnStack bool) {
 
 	e.addSrcMap()
 	if calleeName == "eval" {
-		foundFunc := false
+		foundFunc, foundVar := false, false
 		for sc := e.c.scope; sc != nil; sc = sc.outer {
 			if !foundFunc && sc.function {
 				foundFunc = true
 				sc.thisNeeded, sc.argsNeeded = true, true
+			}
+			if !foundVar && (sc.variable || sc.function) {
+				foundVar = true
 				if !sc.strict {
 					sc.dynamic = true
 				}
@@ -1647,3 +1916,265 @@ func (e *compiledEnumGetExpr) emitGetter(putOnStack bool) {
 		e.c.emit(pop)
 	}
 }
+
+func (c *compiler) compileObjectAssignmentPattern(v *ast.ObjectPattern) compiledExpr {
+	r := &compiledObjectAssignmentPattern{
+		expr: v,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledObjectAssignmentPattern) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.c.emit(loadUndef)
+	}
+}
+
+func (c *compiler) compileArrayAssignmentPattern(v *ast.ArrayPattern) compiledExpr {
+	r := &compiledArrayAssignmentPattern{
+		expr: v,
+	}
+	r.init(c, v.Idx0())
+	return r
+}
+
+func (e *compiledArrayAssignmentPattern) emitGetter(putOnStack bool) {
+	if putOnStack {
+		e.c.emit(loadUndef)
+	}
+}
+
+func (c *compiler) emitNamed(expr compiledExpr, name unistring.String) {
+	if en, ok := expr.(interface {
+		emitNamed(name unistring.String)
+	}); ok {
+		en.emitNamed(name)
+	} else {
+		expr.emitGetter(true)
+	}
+}
+
+func (e *compiledFunctionLiteral) emitNamed(name unistring.String) {
+	e.lhsName = name
+	e.emitGetter(true)
+}
+
+func (c *compiler) emitPattern(pattern ast.Pattern, emitter func(target, init compiledExpr), putOnStack bool) {
+	switch pattern := pattern.(type) {
+	case *ast.ObjectPattern:
+		c.emitObjectPattern(pattern, emitter, putOnStack)
+	case *ast.ArrayPattern:
+		c.emitArrayPattern(pattern, emitter, putOnStack)
+	default:
+		panic(fmt.Errorf("unsupported Pattern: %T", pattern))
+	}
+}
+
+func (c *compiler) emitAssign(target ast.Expression, init compiledExpr, emitAssignSimple func(target, init compiledExpr)) {
+	pattern, isPattern := target.(ast.Pattern)
+	if isPattern {
+		init.emitGetter(true)
+		c.emitPattern(pattern, emitAssignSimple, false)
+	} else {
+		emitAssignSimple(c.compileExpression(target), init)
+	}
+}
+
+func (c *compiler) emitObjectPattern(pattern *ast.ObjectPattern, emitAssign func(target, init compiledExpr), putOnStack bool) {
+	if pattern.Rest != nil {
+		c.emit(createDestructSrc)
+	} else {
+		c.emit(checkObjectCoercible)
+	}
+	for _, prop := range pattern.Properties {
+		switch prop := prop.(type) {
+		case *ast.PropertyShort:
+			c.emit(dup)
+			emitAssign(c.compileIdentifierExpression(&prop.Name), c.compilePatternInitExpr(func() {
+				c.emit(getProp(prop.Name.Name))
+			}, prop.Initializer, prop.Idx0()))
+		case *ast.PropertyKeyed:
+			c.emit(dup)
+			c.compileExpression(prop.Key).emitGetter(true)
+			var target ast.Expression
+			var initializer ast.Expression
+			if e, ok := prop.Value.(*ast.AssignExpression); ok {
+				target = e.Left
+				initializer = e.Right
+			} else {
+				target = prop.Value
+			}
+			c.emitAssign(target, c.compilePatternInitExpr(func() {
+				c.emit(getElem)
+			}, initializer, prop.Idx0()), emitAssign)
+		default:
+			c.throwSyntaxError(int(prop.Idx0()-1), "Unsupported AssignmentProperty type: %T", prop)
+		}
+	}
+	if pattern.Rest != nil {
+		emitAssign(c.compileExpression(pattern.Rest), c.compileEmitterExpr(func() {
+			c.emit(copyRest)
+		}, pattern.Rest.Idx0()))
+		c.emit(pop)
+	}
+	if !putOnStack {
+		c.emit(pop)
+	}
+}
+
+func (c *compiler) emitArrayPattern(pattern *ast.ArrayPattern, emitAssign func(target, init compiledExpr), putOnStack bool) {
+	var marks []int
+	c.emit(iterate)
+	for _, elt := range pattern.Elements {
+		switch elt := elt.(type) {
+		case nil:
+			marks = append(marks, len(c.p.code))
+			c.emit(nil)
+		case *ast.AssignExpression:
+			c.emitAssign(elt.Left, c.compilePatternInitExpr(func() {
+				marks = append(marks, len(c.p.code))
+				c.emit(nil, enumGet)
+			}, elt.Right, elt.Idx0()), emitAssign)
+		default:
+			c.emitAssign(elt, c.compileEmitterExpr(func() {
+				marks = append(marks, len(c.p.code))
+				c.emit(nil, enumGet)
+			}, elt.Idx0()), emitAssign)
+		}
+	}
+	if pattern.Rest != nil {
+		c.emitAssign(pattern.Rest, c.compileEmitterExpr(func() {
+			c.emit(newArrayFromIter)
+		}, pattern.Rest.Idx0()), emitAssign)
+	} else {
+		c.emit(enumPopClose)
+	}
+	mark1 := len(c.p.code)
+	c.emit(nil)
+
+	for i, elt := range pattern.Elements {
+		switch elt := elt.(type) {
+		case nil:
+			c.p.code[marks[i]] = iterNext(len(c.p.code) - marks[i])
+		case *ast.Identifier:
+			emitAssign(c.compileIdentifierExpression(elt), c.compileEmitterExpr(func() {
+				c.p.code[marks[i]] = iterNext(len(c.p.code) - marks[i])
+				c.emit(loadUndef)
+			}, elt.Idx0()))
+		case *ast.AssignExpression:
+			c.emitAssign(elt.Left, c.compileNamedEmitterExpr(func(name unistring.String) {
+				c.p.code[marks[i]] = iterNext(len(c.p.code) - marks[i])
+				c.emitNamed(c.compileExpression(elt.Right), name)
+			}, elt.Idx0()), emitAssign)
+		default:
+			c.emitAssign(elt, c.compileEmitterExpr(
+				func() {
+					c.p.code[marks[i]] = iterNext(len(c.p.code) - marks[i])
+					c.emit(loadUndef)
+				}, elt.Idx0()), emitAssign)
+		}
+	}
+	c.emit(enumPop)
+	if pattern.Rest != nil {
+		c.emitAssign(pattern.Rest, c.compileExpression(
+			&ast.ArrayLiteral{
+				LeftBracket:  pattern.Rest.Idx0(),
+				RightBracket: pattern.Rest.Idx0(),
+			}), emitAssign)
+	}
+	c.p.code[mark1] = jump(len(c.p.code) - mark1)
+
+	if !putOnStack {
+		c.emit(pop)
+	}
+}
+
+func (e *compiledObjectAssignmentPattern) emitSetter(valueExpr compiledExpr, putOnStack bool) {
+	valueExpr.emitGetter(true)
+	e.c.emitObjectPattern(e.expr, e.c.emitPatternAssign, putOnStack)
+}
+
+func (e *compiledArrayAssignmentPattern) emitSetter(valueExpr compiledExpr, putOnStack bool) {
+	valueExpr.emitGetter(true)
+	e.c.emitArrayPattern(e.expr, e.c.emitPatternAssign, putOnStack)
+}
+
+type compiledPatternInitExpr struct {
+	baseCompiledExpr
+	emitSrc func()
+	def     compiledExpr
+}
+
+func (e *compiledPatternInitExpr) emitGetter(putOnStack bool) {
+	if !putOnStack {
+		return
+	}
+	e.emitSrc()
+	if e.def != nil {
+		mark := len(e.c.p.code)
+		e.c.emit(nil)
+		e.def.emitGetter(true)
+		e.c.p.code[mark] = jdef(len(e.c.p.code) - mark)
+	}
+}
+
+func (e *compiledPatternInitExpr) emitNamed(name unistring.String) {
+	e.emitSrc()
+	if e.def != nil {
+		mark := len(e.c.p.code)
+		e.c.emit(nil)
+		e.c.emitNamed(e.def, name)
+		e.c.p.code[mark] = jdef(len(e.c.p.code) - mark)
+	}
+}
+
+func (c *compiler) compilePatternInitExpr(emitSrc func(), def ast.Expression, idx file.Idx) compiledExpr {
+	r := &compiledPatternInitExpr{
+		emitSrc: emitSrc,
+		def:     c.compileExpression(def),
+	}
+	r.init(c, idx)
+	return r
+}
+
+type compiledEmitterExpr struct {
+	baseCompiledExpr
+	emitter      func()
+	namedEmitter func(name unistring.String)
+}
+
+func (e *compiledEmitterExpr) emitGetter(putOnStack bool) {
+	if e.emitter != nil {
+		e.emitter()
+	} else {
+		e.namedEmitter("")
+	}
+	if !putOnStack {
+		e.c.emit(pop)
+	}
+}
+
+func (e *compiledEmitterExpr) emitNamed(name unistring.String) {
+	if e.namedEmitter != nil {
+		e.namedEmitter(name)
+	} else {
+		e.emitter()
+	}
+}
+
+func (c *compiler) compileEmitterExpr(emitter func(), idx file.Idx) *compiledEmitterExpr {
+	r := &compiledEmitterExpr{
+		emitter: emitter,
+	}
+	r.init(c, idx)
+	return r
+}
+
+func (c *compiler) compileNamedEmitterExpr(namedEmitter func(unistring.String), idx file.Idx) *compiledEmitterExpr {
+	r := &compiledEmitterExpr{
+		namedEmitter: namedEmitter,
+	}
+	r.init(c, idx)
+	return r
+}

+ 123 - 83
compiler_stmt.go

@@ -252,7 +252,7 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
 		enterIterBlock = c.compileForHeadLexDecl(&init.LexicalDeclaration, needResult)
 	case *ast.ForLoopInitializerVarDeclList:
 		for _, expr := range init.List {
-			c.compileVariableExpression(expr).emitGetter(false)
+			c.compileVarBinding(expr)
 		}
 	case *ast.ForLoopInitializerExpression:
 		c.compileExpression(init.Expression).emitGetter(false)
@@ -347,10 +347,15 @@ func (c *compiler) compileForInto(into ast.ForInto, needResult bool) (enter *ent
 		if c.scope.strict && into.Binding.Initializer != nil {
 			c.throwSyntaxError(int(into.Binding.Initializer.Idx0())-1, "for-in loop variable declaration may not have an initializer.")
 		}
-		c.compileIdentifierExpression(&ast.Identifier{
-			Name: into.Binding.Name,
-			Idx:  into.Binding.Idx0(),
-		}).emitSetter(&c.enumGetExpr, false)
+		switch target := into.Binding.Target.(type) {
+		case *ast.Identifier:
+			c.compileIdentifierExpression(target).emitSetter(&c.enumGetExpr, false)
+		case ast.Pattern:
+			c.emit(enumGet)
+			c.emitPattern(target, c.emitPatternVarAssign, false)
+		default:
+			c.throwSyntaxError(int(target.Idx0()-1), "unsupported for-in var target: %T", target)
+		}
 	case *ast.ForDeclaration:
 
 		c.block = &block{
@@ -362,12 +367,19 @@ func (c *compiler) compileForInto(into ast.ForInto, needResult bool) (enter *ent
 		c.newBlockScope()
 		enter = &enterBlock{}
 		c.emit(enter)
-		if binding, ok := into.Binding.(*ast.BindingIdentifier); ok {
-			b := c.createLexicalBinding(binding.Name, into.IsConst, int(into.Idx)-1)
-			c.enumGetExpr.emitGetter(true)
+		switch target := into.Target.(type) {
+		case *ast.Identifier:
+			b := c.createLexicalIdBinding(target.Name, into.IsConst, int(into.Idx)-1)
+			c.emit(enumGet)
 			b.emitInit()
-		} else {
-			c.throwSyntaxError(int(into.Idx)-1, "Unsupported ForBinding: %T", into.Binding)
+		case ast.Pattern:
+			c.createLexicalBinding(target, into.IsConst)
+			c.emit(enumGet)
+			c.emitPattern(target, func(target, init compiledExpr) {
+				c.emitPatternLexicalAssign(target, init, into.IsConst)
+			}, false)
+		default:
+			c.throwSyntaxError(int(into.Idx)-1, "Unsupported ForBinding: %T", into.Target)
 		}
 	default:
 		panic(fmt.Sprintf("Unsupported for-into: %T", into))
@@ -385,19 +397,15 @@ func (c *compiler) compileLabeledForInOfStatement(into ast.ForInto, source ast.E
 	}
 	enterPos := -1
 	if forDecl, ok := into.(*ast.ForDeclaration); ok {
-		if binding, ok := forDecl.Binding.(*ast.BindingIdentifier); ok {
-			c.block = &block{
-				typ:        blockScope,
-				outer:      c.block,
-				needResult: false,
-			}
-			c.newBlockScope()
-			enterPos = len(c.p.code)
-			c.emit(jump(1))
-			c.createLexicalBinding(binding.Name, forDecl.IsConst, int(forDecl.Idx)-1)
-		} else {
-			c.throwSyntaxError(int(forDecl.Idx)-1, "Unsupported ForBinding: %T", forDecl.Binding)
+		c.block = &block{
+			typ:        blockScope,
+			outer:      c.block,
+			needResult: false,
 		}
+		c.newBlockScope()
+		enterPos = len(c.p.code)
+		c.emit(jump(1))
+		c.createLexicalBinding(forDecl.Target, forDecl.IsConst)
 	}
 	c.compileExpression(source).emitGetter(true)
 	if enterPos != -1 {
@@ -421,7 +429,7 @@ func (c *compiler) compileLabeledForInOfStatement(into ast.ForInto, source ast.E
 		c.popScope()
 	}
 	if iter {
-		c.emit(iterate)
+		c.emit(iterateP)
 	} else {
 		c.emit(enumerate)
 	}
@@ -711,43 +719,102 @@ func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
 	c.emit(ret)
 }
 
+func (c *compiler) checkVarConflict(name unistring.String, offset int) {
+	for sc := c.scope; sc != nil; sc = sc.outer {
+		if b, exists := sc.boundNames[name]; exists && !b.isVar {
+			c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name)
+		}
+		if sc.function {
+			break
+		}
+	}
+}
+
+func (c *compiler) emitVarAssign(name unistring.String, offset int, init compiledExpr) {
+	c.checkVarConflict(name, offset)
+	if init != nil {
+		c.emitVarRef(name, offset)
+		c.emitNamed(init, name)
+		c.emit(putValueP)
+	}
+}
+
+func (c *compiler) compileVarBinding(expr *ast.Binding) {
+	switch target := expr.Target.(type) {
+	case *ast.Identifier:
+		c.emitVarAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer))
+	case ast.Pattern:
+		c.compileExpression(expr.Initializer).emitGetter(true)
+		c.emitPattern(target, c.emitPatternVarAssign, false)
+	default:
+		c.throwSyntaxError(int(target.Idx0()-1), "unsupported variable binding target: %T", target)
+	}
+}
+
+func (c *compiler) emitLexicalAssign(name unistring.String, offset int, init compiledExpr, isConst bool) {
+	b := c.scope.boundNames[name]
+	if b == nil {
+		panic("Lexical declaration for an unbound name")
+	}
+	if init != nil {
+		c.emitNamed(init, name)
+	} else {
+		if isConst {
+			c.throwSyntaxError(offset, "Missing initializer in const declaration")
+		}
+		c.emit(loadUndef)
+	}
+	if c.scope.outer != nil {
+		b.emitInit()
+	} else {
+		c.emit(initGlobal(name))
+	}
+}
+
+func (c *compiler) emitPatternVarAssign(target, init compiledExpr) {
+	id := target.(*compiledIdentifierExpr)
+	c.emitVarAssign(id.name, id.offset, init)
+}
+
+func (c *compiler) emitPatternLexicalAssign(target, init compiledExpr, isConst bool) {
+	id := target.(*compiledIdentifierExpr)
+	c.emitLexicalAssign(id.name, id.offset, init, isConst)
+}
+
+func (c *compiler) emitPatternAssign(target, init compiledExpr) {
+	target.emitRef()
+	if id, ok := target.(*compiledIdentifierExpr); ok {
+		c.emitNamed(init, id.name)
+	} else {
+		init.emitGetter(true)
+	}
+	c.emit(putValueP)
+}
+
+func (c *compiler) compileLexicalBinding(expr *ast.Binding, isConst bool) {
+	switch target := expr.Target.(type) {
+	case *ast.Identifier:
+		c.emitLexicalAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer), isConst)
+	case ast.Pattern:
+		c.compileExpression(expr.Initializer).emitGetter(true)
+		c.emitPattern(target, func(target, init compiledExpr) {
+			c.emitPatternLexicalAssign(target, init, isConst)
+		}, false)
+	default:
+		c.throwSyntaxError(int(target.Idx0()-1), "unsupported lexical binding target: %T", target)
+	}
+}
+
 func (c *compiler) compileVariableStatement(v *ast.VariableStatement) {
 	for _, expr := range v.List {
-		for sc := c.scope; sc != nil; sc = sc.outer {
-			if b, exists := sc.boundNames[expr.Name]; exists && !b.isVar {
-				c.throwSyntaxError(int(expr.Idx)-1, "Identifier '%s' has already been declared", expr.Name)
-			}
-			if sc.function {
-				break
-			}
-		}
-		c.compileExpression(expr).emitGetter(false)
+		c.compileVarBinding(expr)
 	}
 }
 
 func (c *compiler) compileLexicalDeclaration(v *ast.LexicalDeclaration) {
+	isConst := v.Token == token.CONST
 	for _, e := range v.List {
-		b := c.scope.boundNames[e.Name]
-		if b == nil {
-			panic("Lexical declaration for an unbound name")
-		}
-		if e.Initializer != nil {
-			initializer := c.compileExpression(e.Initializer)
-			if fn, ok := initializer.(*compiledFunctionLiteral); ok {
-				fn.lhsName = e.Name
-			}
-			initializer.emitGetter(true)
-		} else {
-			if v.Token == token.CONST {
-				c.throwSyntaxError(int(e.Idx1())-1, "Missing initializer in const declaration")
-			}
-			c.emit(loadUndef)
-		}
-		if c.scope.outer != nil {
-			b.emitInit()
-		} else {
-			c.emit(initGlobal(e.Name))
-		}
+		c.compileLexicalBinding(e, isConst)
 	}
 }
 
@@ -845,34 +912,6 @@ func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bo
 	c.leaveBlock()
 }
 
-func (c *compiler) createLexicalBinding(name unistring.String, isConst bool, offset int) *binding {
-	if name == "let" {
-		c.throwSyntaxError(offset, "let is disallowed as a lexically bound name")
-	}
-	b, _ := c.scope.bindNameLexical(name, true, offset)
-	b.isConst = isConst
-	return b
-}
-
-func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) {
-	for _, d := range lex.List {
-		c.createLexicalBinding(d.Name, lex.Token == token.CONST, int(d.Idx)-1)
-	}
-}
-
-func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool {
-	for _, st := range list {
-		if lex, ok := st.(*ast.LexicalDeclaration); ok {
-			if !scopeDeclared {
-				c.newBlockScope()
-				scopeDeclared = true
-			}
-			c.createLexicalBindings(lex)
-		}
-	}
-	return scopeDeclared
-}
-
 func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool) {
 	var scopeDeclared bool
 	funcs := c.extractFunctions(v.List)
@@ -978,8 +1017,9 @@ func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult boo
 		}
 		copy(bb[1:], bindings)
 		db = &binding{
-			scope:   c.scope,
-			isConst: true,
+			scope:    c.scope,
+			isConst:  true,
+			isStrict: true,
 		}
 		bb[0] = db
 		c.scope.bindings = bb

+ 467 - 0
compiler_test.go

@@ -3418,6 +3418,15 @@ func TestLexicalConstModifyFromEval(t *testing.T) {
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 }
 
+func TestLexicalStrictNames(t *testing.T) {
+	const SCRIPT = `let eval = 1;`
+
+	_, err := Compile("", SCRIPT, true)
+	if err == nil {
+		t.Fatal("Expected an error")
+	}
+}
+
 func TestAssignAfterStackExpand(t *testing.T) {
 	// make sure the reference to the variable x does not remain stale after the stack is copied
 	const SCRIPT = `
@@ -3479,6 +3488,464 @@ func TestLoadMixedLex(t *testing.T) {
 	testScript1(SCRIPT, valueTrue, t)
 }
 
+func TestObjectLiteralSpread(t *testing.T) {
+	const SCRIPT = `
+	let src = {prop1: 1};
+	Object.defineProperty(src, "prop2", {value: 2, configurable: true});
+	Object.defineProperty(src, "prop3", {value: 3, enumerable: true, configurable: true});
+	let target = {prop4: 4, ...src};
+	assert(deepEqual(target, {prop1: 1, prop3: 3, prop4: 4}));
+	`
+	testScript1(TESTLIBX+SCRIPT, _undefined, t)
+}
+
+func TestArrayLiteralSpread(t *testing.T) {
+	const SCRIPT = `
+	let a1 = [1, 2];
+	let a2 = [3, 4];
+	let a = [...a1, 0, ...a2, 1];
+	assert(compareArray(a, [1, 2, 0, 3, 4, 1]));
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestObjectAssignmentPattern(t *testing.T) {
+	const SCRIPT = `
+	let a, b, c;
+	({a, b, c=3} = {a: 1, b: 2});
+	assert.sameValue(a, 1, "a");
+	assert.sameValue(b, 2, "b");
+	assert.sameValue(c, 3, "c");
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestObjectAssignmentPatternNested(t *testing.T) {
+	const SCRIPT = `
+	let a, b, c, d;
+	({a, b, c: {d} = 3} = {a: 1, b: 2, c: {d: 4}});
+	assert.sameValue(a, 1, "a");
+	assert.sameValue(b, 2, "b");
+	assert.sameValue(c, undefined, "c");
+	assert.sameValue(d, 4, "d");
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestObjectAssignmentPatternEvalOrder(t *testing.T) {
+	const SCRIPT = `
+	let trace = "";
+	let target_obj = {};
+
+	function src() {
+	    trace += "src(),";
+		return {
+			get a() {
+				trace += "get a,";
+				return "a";
+			}
+		}
+	}
+	
+	function prop1() {
+		trace += "prop1(),"
+		return "a";
+	}
+	
+	function prop2() {
+		trace += "prop2(),";
+		return "b";
+	}
+	
+	function target() {
+		trace += "target(),"
+		return target_obj;
+	}
+	
+	let a, b;
+	
+	({[prop1()]: target().a, [prop2()]: b} = src());
+	if (target_obj.a !== "a") {
+		throw new Error("target_obj.a="+target_obj.a);
+	}
+	trace;
+	`
+	testScript1(SCRIPT, asciiString("src(),prop1(),target(),get a,prop2(),"), t)
+}
+
+func TestArrayAssignmentPatternEvalOrder(t *testing.T) {
+	const SCRIPT = `
+	let trace = "";
+
+	let src_arr = {
+		[Symbol.iterator]: function() {
+			let done = false;
+			return {
+				next: function() {
+					trace += "next,";
+					if (!done) {
+						done = true;
+						return {value: 0};
+					}
+					return {done: true};
+				},
+				return: function() {
+					trace += "return,";
+				}
+			}
+		}
+	}
+
+	function src() {
+		trace += "src(),";
+		return src_arr;
+	}
+
+	let tgt = {
+		get a() {
+			trace += "get a,";
+			return "a";
+		},
+		get b() {
+			trace += "get b,";
+			return "b";
+		}
+	}
+
+	function target() {
+		trace += "target(),";
+		return tgt;
+	}
+
+	function default_a() {
+		trace += "default a,";
+		return "def_a";
+	}
+
+	function default_b() {
+		trace += "default b,";
+		return "def_b";
+	}
+
+	([target().a = default_a(), target().b = default_b()] = src());
+	trace;
+	`
+	testScript1(SCRIPT, asciiString("src(),target(),next,target(),next,default b,"), t)
+}
+
+func TestObjectAssignPatternRest(t *testing.T) {
+	const SCRIPT = `
+	let a, b, c, d;
+	({a, b, c, ...d} = {a: 1, b: 2, d: 4});
+	assert.sameValue(a, 1, "a");
+	assert.sameValue(b, 2, "b");
+	assert.sameValue(c, undefined, "c");
+	assert(deepEqual(d, {d: 4}), "d");
+	`
+	testScript1(TESTLIBX+SCRIPT, _undefined, t)
+}
+
+func TestObjectBindPattern(t *testing.T) {
+	const SCRIPT = `
+	let {a, b, c, ...d} = {a: 1, b: 2, d: 4};
+	assert.sameValue(a, 1, "a");
+	assert.sameValue(b, 2, "b");
+	assert.sameValue(c, undefined, "c");
+	assert(deepEqual(d, {d: 4}), "d");
+
+	var { x: y, } = { x: 23 };
+	
+	assert.sameValue(y, 23);
+	
+	assert.throws(ReferenceError, function() {
+	  x;
+	});
+	`
+	testScript1(TESTLIBX+SCRIPT, _undefined, t)
+}
+
+func TestObjLiteralShorthandWithInitializer(t *testing.T) {
+	const SCRIPT = `
+	o = {a=1};
+	`
+	_, err := Compile("", SCRIPT, false)
+	if err == nil {
+		t.Fatal("Expected an error")
+	}
+}
+
+func TestObjLiteralShorthandLetStringLit(t *testing.T) {
+	const SCRIPT = `
+	o = {"let"};
+	`
+	_, err := Compile("", SCRIPT, false)
+	if err == nil {
+		t.Fatal("Expected an error")
+	}
+}
+
+func TestObjLiteralComputedKeys(t *testing.T) {
+	const SCRIPT = `
+	let o = {
+		get [Symbol.toString]() {
+		}
+	}
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestArrayAssignPattern(t *testing.T) {
+	const SCRIPT = `
+	let a, b;
+	([a, b] = [1, 2]);
+	a === 1 && b === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayAssignPattern1(t *testing.T) {
+	const SCRIPT = `
+	let a, b;
+	([a = 3, b = 2] = [1]);
+	a === 1 && b === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayAssignPatternLHS(t *testing.T) {
+	const SCRIPT = `
+	let a = {};
+	[ a.b, a['c'] = 2 ] = [1];
+	a.b === 1 && a.c === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayAssignPatternElision(t *testing.T) {
+	const SCRIPT = `
+	let a, b;
+	([a,, b] = [1, 4, 2]);
+	a === 1 && b === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayAssignPatternRestPattern(t *testing.T) {
+	const SCRIPT = `
+	let a, b, z;
+	[ z, ...[a, b] ] = [0, 1, 2];
+	z === 0 && a === 1 && b === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayBindingPattern(t *testing.T) {
+	const SCRIPT = `
+	let [a, b] = [1, 2];
+	a === 1 && b === 2;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestObjectPatternShorthandInit(t *testing.T) {
+	const SCRIPT = `
+	[...{ x = 1 }] = [];
+	x;
+	`
+	testScript1(SCRIPT, valueInt(1), t)
+}
+
+func TestArrayBindingPatternRestPattern(t *testing.T) {
+	const SCRIPT = `
+	const [a, b, ...[c, d]] = [1, 2, 3, 4];
+	a === 1 && b === 2 && c === 3 && d === 4;
+	`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestForVarPattern(t *testing.T) {
+	const SCRIPT = `
+	var o = {a: 1};
+	var trace = "";
+	for (var [key, value] of Object.entries(o)) {
+		trace += key+":"+value;
+	}
+	trace;
+	`
+	testScript1(SCRIPT, asciiString("a:1"), t)
+}
+
+func TestForLexPattern(t *testing.T) {
+	const SCRIPT = `
+	var o = {a: 1};
+	var trace = "";
+	for (const [key, value] of Object.entries(o)) {
+		trace += key+":"+value;
+	}
+	trace;
+	`
+	testScript1(SCRIPT, asciiString("a:1"), t)
+}
+
+func TestBindingPatternRestTrailingComma(t *testing.T) {
+	const SCRIPT = `
+	const [a, b, ...rest,] = [];
+	`
+	_, err := Compile("", SCRIPT, false)
+	if err == nil {
+		t.Fatal("Expected an error")
+	}
+}
+
+func TestAssignPatternRestTrailingComma(t *testing.T) {
+	const SCRIPT = `
+	([a, b, ...rest,] = []);
+	`
+	_, err := Compile("", SCRIPT, false)
+	if err == nil {
+		t.Fatal("Expected an error")
+	}
+}
+
+func TestFuncParamInitializerSimple(t *testing.T) {
+	const SCRIPT = `
+	function f(a = 1) {
+		return a;
+	}
+	""+f()+f(2);
+	`
+	testScript1(SCRIPT, asciiString("12"), t)
+}
+
+func TestFuncParamObjectPatternSimple(t *testing.T) {
+	const SCRIPT = `
+	function f({a, b} = {a: 1, b: 2}) {
+		return "" + a + b;
+	}
+	""+f()+" "+f({a: 3, b: 4});
+	`
+	testScript1(SCRIPT, asciiString("12 34"), t)
+}
+
+func TestFuncParamRestStackSimple(t *testing.T) {
+	const SCRIPT = `
+	function f(arg1, ...rest) {
+		return rest;
+	}
+	let ar = f(1, 2, 3);
+	ar.join(",");
+	`
+	testScript1(SCRIPT, asciiString("2,3"), t)
+}
+
+func TestFuncParamRestStashSimple(t *testing.T) {
+	const SCRIPT = `
+	function f(arg1, ...rest) {
+		eval("true");
+		return rest;
+	}
+	let ar = f(1, 2, 3);
+	ar.join(",");
+	`
+	testScript1(SCRIPT, asciiString("2,3"), t)
+}
+
+func TestFuncParamRestPattern(t *testing.T) {
+	const SCRIPT = `
+	function f(arg1, ...{0: rest1, 1: rest2}) {
+		return ""+arg1+" "+rest1+" "+rest2;
+	}
+	f(1, 2, 3);
+	`
+	testScript1(SCRIPT, asciiString("1 2 3"), t)
+}
+
+func TestFuncParamForwardRef(t *testing.T) {
+	const SCRIPT = `
+	function f(a = b + 1, b) {
+		return ""+a+" "+b;
+	}
+	f(1, 2);
+	`
+	testScript1(SCRIPT, asciiString("1 2"), t)
+}
+
+func TestFuncParamForwardRefMissing(t *testing.T) {
+	const SCRIPT = `
+	function f(a = b + 1, b) {
+		return ""+a+" "+b;
+	}
+	assert.throws(ReferenceError, function() {
+		f();
+	});
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestFuncParamInnerRef(t *testing.T) {
+	const SCRIPT = `
+	function f(a = inner) {
+		var inner = 42;
+		return a;
+	}
+	assert.throws(ReferenceError, function() {
+		f();
+	});
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestFuncParamInnerRefEval(t *testing.T) {
+	const SCRIPT = `
+	function f(a = eval("inner")) {
+		var inner = 42;
+		return a;
+	}
+	assert.throws(ReferenceError, function() {
+		f();
+	});
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestFuncParamCalleeName(t *testing.T) {
+	const SCRIPT = `
+	function f(a = f) {
+		var f;
+		return f;
+	}
+	typeof f();
+	`
+	testScript1(SCRIPT, asciiString("undefined"), t)
+}
+
+func TestFuncParamVarCopy(t *testing.T) {
+	const SCRIPT = `
+	function f(a = f) {
+		var a;
+		return a;
+	}
+	typeof f();
+	`
+	testScript1(SCRIPT, asciiString("function"), t)
+}
+
+func TestFuncParamScope(t *testing.T) {
+	const SCRIPT = `
+	var x = 'outside';
+	var probe1, probe2;
+	
+	function f(
+		_ = probe1 = function() { return x; },
+		__ = (eval('var x = "inside";'), probe2 = function() { return x; })
+	) {
+	}
+	f();
+	probe1()+" "+probe2();
+	`
+	testScript1(SCRIPT, asciiString("inside inside"), t)
+}
+
 /*
 func TestBabel(t *testing.T) {
 	src, err := ioutil.ReadFile("babel7.js")

+ 261 - 0
destruct.go

@@ -0,0 +1,261 @@
+package goja
+
+import (
+	"github.com/dop251/goja/unistring"
+	"reflect"
+)
+
+type destructKeyedSource struct {
+	r        *Runtime
+	wrapped  Value
+	usedKeys map[unistring.String]struct{}
+}
+
+func newDestructKeyedSource(r *Runtime, wrapped Value) *destructKeyedSource {
+	return &destructKeyedSource{
+		r:       r,
+		wrapped: wrapped,
+	}
+}
+
+func (r *Runtime) newDestructKeyedSource(wrapped Value) *Object {
+	return &Object{
+		runtime: r,
+		self:    newDestructKeyedSource(r, wrapped),
+	}
+}
+
+func (d *destructKeyedSource) w() objectImpl {
+	return d.wrapped.ToObject(d.r).self
+}
+
+func (d *destructKeyedSource) recordKey(key unistring.String) {
+	if d.usedKeys == nil {
+		d.usedKeys = make(map[unistring.String]struct{})
+	}
+	d.usedKeys[key] = struct{}{}
+}
+
+func (d *destructKeyedSource) sortLen() int64 {
+	return d.w().sortLen()
+}
+
+func (d *destructKeyedSource) sortGet(i int64) Value {
+	return d.w().sortGet(i)
+}
+
+func (d *destructKeyedSource) swap(i int64, i2 int64) {
+	d.w().swap(i, i2)
+}
+
+func (d *destructKeyedSource) className() string {
+	return d.w().className()
+}
+
+func (d *destructKeyedSource) getStr(p unistring.String, receiver Value) Value {
+	d.recordKey(p)
+	return d.w().getStr(p, receiver)
+}
+
+func (d *destructKeyedSource) getIdx(p valueInt, receiver Value) Value {
+	d.recordKey(p.string())
+	return d.w().getIdx(p, receiver)
+}
+
+func (d *destructKeyedSource) getSym(p *Symbol, receiver Value) Value {
+	return d.w().getSym(p, receiver)
+}
+
+func (d *destructKeyedSource) getOwnPropStr(u unistring.String) Value {
+	d.recordKey(u)
+	return d.w().getOwnPropStr(u)
+}
+
+func (d *destructKeyedSource) getOwnPropIdx(v valueInt) Value {
+	d.recordKey(v.string())
+	return d.w().getOwnPropIdx(v)
+}
+
+func (d *destructKeyedSource) getOwnPropSym(symbol *Symbol) Value {
+	return d.w().getOwnPropSym(symbol)
+}
+
+func (d *destructKeyedSource) setOwnStr(p unistring.String, v Value, throw bool) bool {
+	return d.w().setOwnStr(p, v, throw)
+}
+
+func (d *destructKeyedSource) setOwnIdx(p valueInt, v Value, throw bool) bool {
+	return d.w().setOwnIdx(p, v, throw)
+}
+
+func (d *destructKeyedSource) setOwnSym(p *Symbol, v Value, throw bool) bool {
+	return d.w().setOwnSym(p, v, throw)
+}
+
+func (d *destructKeyedSource) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) {
+	return d.w().setForeignStr(p, v, receiver, throw)
+}
+
+func (d *destructKeyedSource) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) {
+	return d.w().setForeignIdx(p, v, receiver, throw)
+}
+
+func (d *destructKeyedSource) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) {
+	return d.w().setForeignSym(p, v, receiver, throw)
+}
+
+func (d *destructKeyedSource) hasPropertyStr(u unistring.String) bool {
+	return d.w().hasPropertyStr(u)
+}
+
+func (d *destructKeyedSource) hasPropertyIdx(idx valueInt) bool {
+	return d.w().hasPropertyIdx(idx)
+}
+
+func (d *destructKeyedSource) hasPropertySym(s *Symbol) bool {
+	return d.w().hasPropertySym(s)
+}
+
+func (d *destructKeyedSource) hasOwnPropertyStr(u unistring.String) bool {
+	return d.w().hasOwnPropertyStr(u)
+}
+
+func (d *destructKeyedSource) hasOwnPropertyIdx(v valueInt) bool {
+	return d.w().hasOwnPropertyIdx(v)
+}
+
+func (d *destructKeyedSource) hasOwnPropertySym(s *Symbol) bool {
+	return d.w().hasOwnPropertySym(s)
+}
+
+func (d *destructKeyedSource) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool {
+	return d.w().defineOwnPropertyStr(name, desc, throw)
+}
+
+func (d *destructKeyedSource) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool {
+	return d.w().defineOwnPropertyIdx(name, desc, throw)
+}
+
+func (d *destructKeyedSource) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool {
+	return d.w().defineOwnPropertySym(name, desc, throw)
+}
+
+func (d *destructKeyedSource) deleteStr(name unistring.String, throw bool) bool {
+	return d.w().deleteStr(name, throw)
+}
+
+func (d *destructKeyedSource) deleteIdx(idx valueInt, throw bool) bool {
+	return d.w().deleteIdx(idx, throw)
+}
+
+func (d *destructKeyedSource) deleteSym(s *Symbol, throw bool) bool {
+	return d.w().deleteSym(s, throw)
+}
+
+func (d *destructKeyedSource) toPrimitiveNumber() Value {
+	return d.w().toPrimitiveNumber()
+}
+
+func (d *destructKeyedSource) toPrimitiveString() Value {
+	return d.w().toPrimitiveString()
+}
+
+func (d *destructKeyedSource) toPrimitive() Value {
+	return d.w().toPrimitive()
+}
+
+func (d *destructKeyedSource) assertCallable() (call func(FunctionCall) Value, ok bool) {
+	return d.w().assertCallable()
+}
+
+func (d *destructKeyedSource) assertConstructor() func(args []Value, newTarget *Object) *Object {
+	return d.w().assertConstructor()
+}
+
+func (d *destructKeyedSource) proto() *Object {
+	return d.w().proto()
+}
+
+func (d *destructKeyedSource) setProto(proto *Object, throw bool) bool {
+	return d.w().setProto(proto, throw)
+}
+
+func (d *destructKeyedSource) hasInstance(v Value) bool {
+	return d.w().hasInstance(v)
+}
+
+func (d *destructKeyedSource) isExtensible() bool {
+	return d.w().isExtensible()
+}
+
+func (d *destructKeyedSource) preventExtensions(throw bool) bool {
+	return d.w().preventExtensions(throw)
+}
+
+type destructKeyedSourceIter struct {
+	d       *destructKeyedSource
+	wrapped iterNextFunc
+}
+
+func (i *destructKeyedSourceIter) next() (propIterItem, iterNextFunc) {
+	for {
+		item, next := i.wrapped()
+		if next == nil {
+			return item, nil
+		}
+		i.wrapped = next
+		if _, exists := i.d.usedKeys[item.name]; !exists {
+			return item, i.next
+		}
+	}
+}
+
+func (d *destructKeyedSource) enumerateOwnKeys() iterNextFunc {
+	return (&destructKeyedSourceIter{
+		d:       d,
+		wrapped: d.w().enumerateOwnKeys(),
+	}).next
+}
+
+func (d *destructKeyedSource) export(ctx *objectExportCtx) interface{} {
+	return d.w().export(ctx)
+}
+
+func (d *destructKeyedSource) exportType() reflect.Type {
+	return d.w().exportType()
+}
+
+func (d *destructKeyedSource) equal(impl objectImpl) bool {
+	return d.w().equal(impl)
+}
+
+func (d *destructKeyedSource) ownKeys(all bool, accum []Value) []Value {
+	var next iterNextFunc
+	if all {
+		next = d.enumerateOwnKeys()
+	} else {
+		next = (&enumerableIter{
+			wrapped: d.enumerateOwnKeys(),
+		}).next
+	}
+	for item, next := next(); next != nil; item, next = next() {
+		accum = append(accum, stringValueFromRaw(item.name))
+	}
+	return accum
+}
+
+func (d *destructKeyedSource) ownSymbols(all bool, accum []Value) []Value {
+	return d.w().ownSymbols(all, accum)
+}
+
+func (d *destructKeyedSource) ownPropertyKeys(all bool, accum []Value) []Value {
+	return d.ownSymbols(all, d.ownKeys(all, accum))
+}
+
+func (d *destructKeyedSource) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
+	return d.w()._putProp(name, value, writable, enumerable, configurable)
+}
+
+func (d *destructKeyedSource) _putSym(s *Symbol, prop Value) {
+	d.w()._putSym(s, prop)
+}

+ 2 - 4
func.go

@@ -9,7 +9,7 @@ import (
 type baseFuncObject struct {
 	baseObject
 
-	nameProp, lenProp valueProperty
+	lenProp valueProperty
 }
 
 type funcObject struct {
@@ -184,9 +184,7 @@ func (f *baseFuncObject) init(name unistring.String, length int) {
 	f.baseObject.init()
 
 	if name != "" {
-		f.nameProp.configurable = true
-		f.nameProp.value = stringValueFromRaw(name)
-		f._put("name", &f.nameProp)
+		f._putProp("name", stringValueFromRaw(name), false, false, true)
 	}
 
 	f.lenProp.configurable = true

+ 0 - 2
go.sum

@@ -8,14 +8,12 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

+ 318 - 40
parser/expression.go

@@ -1,6 +1,8 @@
 package parser
 
 import (
+	"strings"
+
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
@@ -140,22 +142,30 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
 	}
 }
 
-func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableExpression) ast.Expression {
+func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression {
 	if self.token == token.LET {
 		self.token = token.IDENTIFIER
 	}
-	if self.token != token.IDENTIFIER {
+	var target ast.BindingTarget
+	switch self.token {
+	case token.IDENTIFIER:
+		target = &ast.Identifier{
+			Name: self.parsedLiteral,
+			Idx:  self.idx,
+		}
+		self.next()
+	case token.LEFT_BRACKET:
+		target = self.parseArrayBindingPattern()
+	case token.LEFT_BRACE:
+		target = self.parseObjectBindingPattern()
+	default:
 		idx := self.expect(token.IDENTIFIER)
 		self.nextStatement()
 		return &ast.BadExpression{From: idx, To: self.idx}
 	}
 
-	name := self.parsedLiteral
-	idx := self.idx
-	self.next()
-	node := &ast.VariableExpression{
-		Name: name,
-		Idx:  idx,
+	node := &ast.Binding{
+		Target: target,
 	}
 
 	if declarationList != nil {
@@ -170,7 +180,7 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.VariableEx
 	return node
 }
 
-func (self *_parser) parseVariableDeclarationList() (declarationList []*ast.VariableExpression) {
+func (self *_parser) parseVariableDeclarationList() (declarationList []*ast.Binding) {
 	for {
 		self.parseVariableDeclaration(&declarationList)
 		if self.token != token.COMMA {
@@ -181,7 +191,7 @@ func (self *_parser) parseVariableDeclarationList() (declarationList []*ast.Vari
 	return
 }
 
-func (self *_parser) parseVarDeclarationList(var_ file.Idx) []*ast.VariableExpression {
+func (self *_parser) parseVarDeclarationList(var_ file.Idx) []*ast.Binding {
 	declarationList := self.parseVariableDeclarationList()
 
 	self.scope.declare(&ast.VariableDeclaration{
@@ -193,6 +203,12 @@ func (self *_parser) parseVarDeclarationList(var_ file.Idx) []*ast.VariableExpre
 }
 
 func (self *_parser) parseObjectPropertyKey() (unistring.String, ast.Expression, token.Token) {
+	if self.token == token.LEFT_BRACKET {
+		self.next()
+		expr := self.parseAssignmentExpression()
+		self.expect(token.RIGHT_BRACKET)
+		return "", expr, token.ILLEGAL
+	}
 	idx, tkn, literal, parsedLiteral := self.idx, self.token, self.literal, self.parsedLiteral
 	var value ast.Expression
 	self.next()
@@ -228,15 +244,21 @@ func (self *_parser) parseObjectPropertyKey() (unistring.String, ast.Expression,
 				Literal: literal,
 				Value:   unistring.String(literal),
 			}
-			tkn = token.STRING
+			tkn = token.KEYWORD
 		}
 	}
 	return parsedLiteral, value, tkn
 }
 
 func (self *_parser) parseObjectProperty() ast.Property {
+	if self.token == token.ELLIPSIS {
+		self.next()
+		return &ast.SpreadElement{
+			Expression: self.parseAssignmentExpression(),
+		}
+	}
 	literal, value, tkn := self.parseObjectPropertyKey()
-	if tkn == token.IDENTIFIER || tkn == token.STRING {
+	if tkn == token.IDENTIFIER || tkn == token.STRING || tkn == token.KEYWORD || tkn == token.ILLEGAL {
 		switch {
 		case self.token == token.LEFT_PARENTHESIS:
 			idx := self.idx
@@ -248,19 +270,27 @@ func (self *_parser) parseObjectProperty() ast.Property {
 			}
 			self.parseFunctionBlock(node)
 
-			return ast.Property{
+			return &ast.PropertyKeyed{
 				Key:   value,
-				Kind:  "method",
+				Kind:  ast.PropertyKindMethod,
 				Value: node,
 			}
-		case self.token == token.COMMA || self.token == token.RIGHT_BRACE: // shorthand property
-			return ast.Property{
-				Key:  value,
-				Kind: "value",
-				Value: &ast.Identifier{
-					Name: literal,
-					Idx:  self.idx,
-				},
+		case self.token == token.COMMA || self.token == token.RIGHT_BRACE || self.token == token.ASSIGN: // shorthand property
+			if tkn == token.IDENTIFIER || tkn == token.KEYWORD && literal == "let" {
+				var initializer ast.Expression
+				if self.token == token.ASSIGN {
+					// allow the initializer syntax here in case the object literal
+					// needs to be reinterpreted as an assignment pattern, enforce later if it doesn't.
+					self.next()
+					initializer = self.parseAssignmentExpression()
+				}
+				return &ast.PropertyShort{
+					Name: ast.Identifier{
+						Name: literal,
+						Idx:  value.Idx0(),
+					},
+					Initializer: initializer,
+				}
 			}
 		case literal == "get" && self.token != token.COLON:
 			idx := self.idx
@@ -272,9 +302,9 @@ func (self *_parser) parseObjectProperty() ast.Property {
 				ParameterList: parameterList,
 			}
 			self.parseFunctionBlock(node)
-			return ast.Property{
+			return &ast.PropertyKeyed{
 				Key:   value,
-				Kind:  "get",
+				Kind:  ast.PropertyKindGet,
 				Value: node,
 			}
 		case literal == "set" && self.token != token.COLON:
@@ -289,9 +319,9 @@ func (self *_parser) parseObjectProperty() ast.Property {
 
 			self.parseFunctionBlock(node)
 
-			return ast.Property{
+			return &ast.PropertyKeyed{
 				Key:   value,
-				Kind:  "set",
+				Kind:  ast.PropertyKindSet,
 				Value: node,
 			}
 		}
@@ -299,14 +329,14 @@ func (self *_parser) parseObjectProperty() ast.Property {
 
 	self.expect(token.COLON)
 
-	return ast.Property{
+	return &ast.PropertyKeyed{
 		Key:   value,
-		Kind:  "value",
+		Kind:  ast.PropertyKindValue,
 		Value: self.parseAssignmentExpression(),
 	}
 }
 
-func (self *_parser) parseObjectLiteral() ast.Expression {
+func (self *_parser) parseObjectLiteral() *ast.ObjectLiteral {
 	var value []ast.Property
 	idx0 := self.expect(token.LEFT_BRACE)
 	for self.token != token.RIGHT_BRACE && self.token != token.EOF {
@@ -327,7 +357,7 @@ func (self *_parser) parseObjectLiteral() ast.Expression {
 	}
 }
 
-func (self *_parser) parseArrayLiteral() ast.Expression {
+func (self *_parser) parseArrayLiteral() *ast.ArrayLiteral {
 
 	idx0 := self.expect(token.LEFT_BRACKET)
 	var value []ast.Expression
@@ -337,7 +367,14 @@ func (self *_parser) parseArrayLiteral() ast.Expression {
 			value = append(value, nil)
 			continue
 		}
-		value = append(value, self.parseAssignmentExpression())
+		if self.token == token.ELLIPSIS {
+			self.next()
+			value = append(value, &ast.SpreadElement{
+				Expression: self.parseAssignmentExpression(),
+			})
+		} else {
+			value = append(value, self.parseAssignmentExpression())
+		}
 		if self.token != token.RIGHT_BRACKET {
 			self.expect(token.COMMA)
 		}
@@ -816,18 +853,31 @@ func (self *_parser) parseAssignmentExpression() ast.Expression {
 	if operator != 0 {
 		idx := self.idx
 		self.next()
-		switch left.(type) {
+		ok := false
+		switch l := left.(type) {
 		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
-		default:
-			self.error(left.Idx0(), "Invalid left-hand side in assignment")
-			self.nextStatement()
-			return &ast.BadExpression{From: idx, To: self.idx}
+			ok = true
+		case *ast.ArrayLiteral:
+			if operator == token.ASSIGN {
+				left = self.reinterpretAsArrayAssignmentPattern(l)
+				ok = true
+			}
+		case *ast.ObjectLiteral:
+			if operator == token.ASSIGN {
+				left = self.reinterpretAsObjectAssignmentPattern(l)
+				ok = true
+			}
 		}
-		return &ast.AssignExpression{
-			Left:     left,
-			Operator: operator,
-			Right:    self.parseAssignmentExpression(),
+		if ok {
+			return &ast.AssignExpression{
+				Left:     left,
+				Operator: operator,
+				Right:    self.parseAssignmentExpression(),
+			}
 		}
+		self.error(left.Idx0(), "Invalid left-hand side in assignment")
+		self.nextStatement()
+		return &ast.BadExpression{From: idx, To: self.idx}
 	}
 
 	return left
@@ -856,3 +906,231 @@ func (self *_parser) parseExpression() ast.Expression {
 
 	return left
 }
+
+func (self *_parser) checkComma(from, to file.Idx) {
+	if pos := strings.IndexByte(self.str[int(from)-self.base:int(to)-self.base], ','); pos >= 0 {
+		self.error(from+file.Idx(pos), "Comma is not allowed here")
+	}
+}
+
+func (self *_parser) reinterpretAsArrayAssignmentPattern(left *ast.ArrayLiteral) *ast.ArrayPattern {
+	value := left.Value
+	var rest ast.Expression
+	for i, item := range value {
+		if spread, ok := item.(*ast.SpreadElement); ok {
+			if i != len(value)-1 {
+				self.error(item.Idx0(), "Rest element must be last element")
+				return nil
+			}
+			self.checkComma(spread.Expression.Idx1(), left.RightBracket)
+			rest = self.reinterpretAsDestructAssignTarget(spread.Expression)
+			value = value[:len(value)-1]
+		} else {
+			value[i] = self.reinterpretAsAssignmentElement(item)
+		}
+	}
+	return &ast.ArrayPattern{
+		LeftBracket:  left.LeftBracket,
+		RightBracket: left.RightBracket,
+		Elements:     value,
+		Rest:         rest,
+	}
+}
+
+func (self *_parser) reinterpretArrayAssignPatternAsBinding(pattern *ast.ArrayPattern) *ast.ArrayPattern {
+	for i, item := range pattern.Elements {
+		pattern.Elements[i] = self.reinterpretAsDestructBindingTarget(item)
+	}
+	if pattern.Rest != nil {
+		pattern.Rest = self.reinterpretAsDestructBindingTarget(pattern.Rest)
+	}
+	return pattern
+}
+
+func (self *_parser) reinterpretAsArrayBindingPattern(left *ast.ArrayLiteral) *ast.ArrayPattern {
+	value := left.Value
+	var rest ast.Expression
+	for i, item := range value {
+		if spread, ok := item.(*ast.SpreadElement); ok {
+			if i != len(value)-1 {
+				self.error(item.Idx0(), "Rest element must be last element")
+				return nil
+			}
+			self.checkComma(spread.Expression.Idx1(), left.RightBracket)
+			rest = self.reinterpretAsDestructBindingTarget(spread.Expression)
+			value = value[:len(value)-1]
+		} else {
+			value[i] = self.reinterpretAsBindingElement(item)
+		}
+	}
+	return &ast.ArrayPattern{
+		LeftBracket:  left.LeftBracket,
+		RightBracket: left.RightBracket,
+		Elements:     value,
+		Rest:         rest,
+	}
+}
+
+func (self *_parser) parseArrayBindingPattern() *ast.ArrayPattern {
+	return self.reinterpretAsArrayBindingPattern(self.parseArrayLiteral())
+}
+
+func (self *_parser) parseObjectBindingPattern() *ast.ObjectPattern {
+	return self.reinterpretAsObjectBindingPattern(self.parseObjectLiteral())
+}
+
+func (self *_parser) reinterpretArrayObjectPatternAsBinding(pattern *ast.ObjectPattern) *ast.ObjectPattern {
+	for _, prop := range pattern.Properties {
+		if keyed, ok := prop.(*ast.PropertyKeyed); ok {
+			keyed.Value = self.reinterpretAsBindingElement(keyed.Value)
+		}
+	}
+	if pattern.Rest != nil {
+		pattern.Rest = self.reinterpretAsBindingRestElement(pattern.Rest)
+	}
+	return pattern
+}
+
+func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) *ast.ObjectPattern {
+	var rest ast.Expression
+	value := expr.Value
+	for i, prop := range value {
+		ok := false
+		switch prop := prop.(type) {
+		case *ast.PropertyKeyed:
+			if prop.Kind == ast.PropertyKindValue {
+				prop.Value = self.reinterpretAsBindingElement(prop.Value)
+				ok = true
+			}
+		case *ast.PropertyShort:
+			ok = true
+		case *ast.SpreadElement:
+			if i != len(expr.Value)-1 {
+				self.error(prop.Idx0(), "Rest element must be last element")
+				return nil
+			}
+			// TODO make sure there is no trailing comma
+			rest = self.reinterpretAsBindingRestElement(prop.Expression)
+			value = value[:i]
+			ok = true
+		}
+		if !ok {
+			self.error(prop.Idx0(), "Invalid destructuring binding target")
+			return nil
+		}
+	}
+	return &ast.ObjectPattern{
+		LeftBrace:  expr.LeftBrace,
+		RightBrace: expr.RightBrace,
+		Properties: value,
+		Rest:       rest,
+	}
+}
+
+func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) *ast.ObjectPattern {
+	var rest ast.Expression
+	value := l.Value
+	for i, prop := range value {
+		ok := false
+		switch prop := prop.(type) {
+		case *ast.PropertyKeyed:
+			if prop.Kind == ast.PropertyKindValue {
+				prop.Value = self.reinterpretAsAssignmentElement(prop.Value)
+				ok = true
+			}
+		case *ast.PropertyShort:
+			ok = true
+		case *ast.SpreadElement:
+			if i != len(l.Value)-1 {
+				self.error(prop.Idx0(), "Rest element must be last element")
+				return nil
+			}
+			// TODO make sure there is no trailing comma
+			rest = prop.Expression
+			value = value[:i]
+			ok = true
+		}
+		if !ok {
+			self.error(prop.Idx0(), "Invalid destructuring assignment target")
+			return nil
+		}
+	}
+	return &ast.ObjectPattern{
+		LeftBrace:  l.LeftBrace,
+		RightBrace: l.RightBrace,
+		Properties: value,
+		Rest:       rest,
+	}
+}
+
+func (self *_parser) reinterpretAsAssignmentElement(expr ast.Expression) ast.Expression {
+	switch expr := expr.(type) {
+	case *ast.AssignExpression:
+		if expr.Operator == token.ASSIGN {
+			expr.Left = self.reinterpretAsDestructAssignTarget(expr.Left)
+			return expr
+		} else {
+			self.error(expr.Idx0(), "Invalid destructuring assignment target")
+			return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()}
+		}
+	default:
+		return self.reinterpretAsDestructAssignTarget(expr)
+	}
+}
+
+func (self *_parser) reinterpretAsBindingElement(expr ast.Expression) ast.Expression {
+	switch expr := expr.(type) {
+	case *ast.AssignExpression:
+		if expr.Operator == token.ASSIGN {
+			expr.Left = self.reinterpretAsDestructBindingTarget(expr.Left)
+			return expr
+		} else {
+			self.error(expr.Idx0(), "Invalid destructuring assignment target")
+			return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()}
+		}
+	default:
+		return self.reinterpretAsDestructBindingTarget(expr)
+	}
+}
+
+func (self *_parser) reinterpretAsDestructAssignTarget(item ast.Expression) ast.Expression {
+	switch item := item.(type) {
+	case nil:
+		return nil
+	case *ast.ArrayLiteral:
+		return self.reinterpretAsArrayAssignmentPattern(item)
+	case *ast.ObjectLiteral:
+		return self.reinterpretAsObjectAssignmentPattern(item)
+	case ast.Pattern, *ast.Identifier, *ast.DotExpression, *ast.BracketExpression:
+		return item
+	}
+	self.error(item.Idx0(), "Invalid destructuring assignment target")
+	return &ast.BadExpression{From: item.Idx0(), To: item.Idx1()}
+}
+
+func (self *_parser) reinterpretAsDestructBindingTarget(item ast.Expression) ast.BindingTarget {
+	switch item := item.(type) {
+	case nil:
+		return nil
+	case *ast.ArrayPattern:
+		return self.reinterpretArrayAssignPatternAsBinding(item)
+	case *ast.ObjectPattern:
+		return self.reinterpretArrayObjectPatternAsBinding(item)
+	case *ast.ArrayLiteral:
+		return self.reinterpretAsArrayBindingPattern(item)
+	case *ast.ObjectLiteral:
+		return self.reinterpretAsObjectBindingPattern(item)
+	case *ast.Identifier:
+		return item
+	}
+	self.error(item.Idx0(), "Invalid destructuring binding target")
+	return &ast.BadExpression{From: item.Idx0(), To: item.Idx1()}
+}
+
+func (self *_parser) reinterpretAsBindingRestElement(expr ast.Expression) ast.Expression {
+	if _, ok := expr.(*ast.Identifier); ok {
+		return expr
+	}
+	self.error(expr.Idx0(), "Invalid binding rest")
+	return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()}
+}

+ 19 - 4
parser/lexer.go

@@ -297,7 +297,17 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
 					insertSemicolon = true
 					tkn, literal = self.scanNumericLiteral(true)
 				} else {
-					tkn = token.PERIOD
+					if self.chr == '.' {
+						self.read()
+						if self.chr == '.' {
+							self.read()
+							tkn = token.ELLIPSIS
+						} else {
+							tkn = token.ILLEGAL
+						}
+					} else {
+						tkn = token.PERIOD
+					}
 				}
 			case ',':
 				tkn = token.COMMA
@@ -351,10 +361,15 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
 			case '>':
 				tkn = self.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN)
 			case '=':
-				tkn = self.switch2(token.ASSIGN, token.EQUAL)
-				if tkn == token.EQUAL && self.chr == '=' {
+				if self.chr == '>' {
 					self.read()
-					tkn = token.STRICT_EQUAL
+					tkn = token.ARROW
+				} else {
+					tkn = self.switch2(token.ASSIGN, token.EQUAL)
+					if tkn == token.EQUAL && self.chr == '=' {
+						self.read()
+						tkn = token.STRICT_EQUAL
+					}
 				}
 			case '!':
 				tkn = self.switch2(token.NOT, token.NOT_EQUAL)

+ 52 - 45
parser/marshal_test.go

@@ -98,8 +98,9 @@ func testMarshalNode(node interface{}) interface{} {
 	case *ast.StringLiteral:
 		return marshal("Literal", node.Literal)
 
-	case *ast.VariableExpression:
-		return []interface{}{node.Name, testMarshalNode(node.Initializer)}
+	case *ast.Binding:
+		return marshal("Binding", "Target", testMarshalNode(node.Target),
+			"Initializer", testMarshalNode(node.Initializer))
 
 	// Statement
 
@@ -143,7 +144,7 @@ func testMarshalNode(node interface{}) interface{} {
 			"Name", node.Label.Name,
 			"Statement", testMarshalNode(node.Statement),
 		)
-	case ast.Property:
+	case *ast.PropertyKeyed:
 		return marshal("",
 			"Key", node.Key,
 			"Value", testMarshalNode(node.Value),
@@ -163,13 +164,11 @@ func testMarshalNode(node interface{}) interface{} {
 
 	// Special
 	case *ast.ForDeclaration:
-		return marshal("For-Into-Decl", testMarshalNode(node.Binding))
+		return marshal("For-Into-Decl", testMarshalNode(node.Target))
 
 	case *ast.ForIntoVar:
 		return marshal("For-Into-Var", testMarshalNode(node.Binding))
 
-	case *ast.BindingIdentifier:
-		return marshal("Binding-Identifier", "Id", node.Name)
 	}
 
 	{
@@ -715,32 +714,36 @@ func TestParserAST(t *testing.T) {
         })()
         ---
 [
-  {
-    "Var": [
-      [
-        "abc",
-        {
-          "Call": {
-            "ArgumentList": [],
-            "Callee": {
-              "Call": {
-                "ArgumentList": [
-                  {
-                    "Function": {
-                      "BlockStatement": []
-                    }
+   {
+      "Var": [
+         {
+            "Binding": {
+               "Initializer": {
+                  "Call": {
+                     "ArgumentList": [],
+                     "Callee": {
+                        "Call": {
+                           "ArgumentList": [
+                              {
+                                 "Function": {
+                                    "BlockStatement": []
+                                 }
+                              }
+                           ],
+                           "Callee": {
+                              "Literal": 1
+                           }
+                        }
+                     }
                   }
-                ],
-                "Callee": {
-                  "Literal": 1
-                }
-              }
+               },
+               "Target": {
+                  "Identifier": "abc"
+               }
             }
-          }
-        }
+         }
       ]
-    ]
-  }
+   }
 ]
         `)
 
@@ -819,22 +822,26 @@ func TestParserAST(t *testing.T) {
         }
         ---
 [
-  {
-    "ForIn": {
-      "Body": {
-        "BlockStatement": []
-      },
-      "Into": {
-		"For-Into-Var": [
-           "abc",
-           null
-        ]
-      },
-      "Source": {
-        "Identifier": "def"
-      }
-    }
-  }
+   {
+	  "ForIn": {
+		 "Body": {
+			"BlockStatement": []
+		 },
+		 "Into": {
+			"For-Into-Var": {
+			   "Binding": {
+				  "Initializer": null,
+				  "Target": {
+					 "Identifier": "abc"
+				  }
+			   }
+			}
+		 },
+		 "Source": {
+			"Identifier": "def"
+		 }
+	  }
+   }
 ]
         `)
 

+ 12 - 0
parser/parser_test.go

@@ -876,9 +876,21 @@ func TestParser(t *testing.T) {
         `, nil)
 
 		test("'ё\\\u2029'", nil)
+
+		test(`[a, b] = [1, 2]`, nil)
+		test(`({"a b": {}} = {})`, nil)
 	})
 }
 
+func TestParseDestruct(t *testing.T) {
+	parser := newParser("", `({a: (a.b), ...spread,} = {})`)
+	prg, err := parser.parse()
+	if err != nil {
+		t.Fatal(err)
+	}
+	_ = prg
+}
+
 func Test_parseStringLiteral(t *testing.T) {
 	tt(t, func() {
 		test := func(have string, want unistring.String) {

+ 25 - 25
parser/statement.go

@@ -162,13 +162,15 @@ func (self *_parser) parseTryStatement() ast.Statement {
 
 func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
 	opening := self.expect(token.LEFT_PARENTHESIS)
-	var list []*ast.Identifier
+	var list []*ast.Binding
+	var rest ast.Expression
 	for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF {
-		if self.token != token.IDENTIFIER {
-			self.expect(token.IDENTIFIER)
-		} else {
-			list = append(list, self.parseIdentifier())
+		if self.token == token.ELLIPSIS {
+			self.next()
+			rest = self.reinterpretAsDestructBindingTarget(self.parseAssignmentExpression())
+			break
 		}
+		self.parseVariableDeclaration(&list)
 		if self.token != token.RIGHT_PARENTHESIS {
 			self.expect(token.COMMA)
 		}
@@ -178,24 +180,11 @@ func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
 	return &ast.ParameterList{
 		Opening: opening,
 		List:    list,
+		Rest:    rest,
 		Closing: closing,
 	}
 }
 
-func (self *_parser) parseParameterList() (list []string) {
-	for self.token != token.EOF {
-		if self.token != token.IDENTIFIER {
-			self.expect(token.IDENTIFIER)
-		}
-		list = append(list, self.literal)
-		self.next()
-		if self.token != token.EOF {
-			self.expect(token.COMMA)
-		}
-	}
-	return
-}
-
 func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
 
 	node := &ast.FunctionLiteral{
@@ -452,7 +441,7 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 		if tok == token.VAR || tok == token.LET || tok == token.CONST {
 			idx := self.idx
 			self.next()
-			var list []*ast.VariableExpression
+			var list []*ast.Binding
 			if tok == token.VAR {
 				list = self.parseVarDeclarationList(idx)
 			} else {
@@ -479,13 +468,11 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 					into = &ast.ForDeclaration{
 						Idx:     idx,
 						IsConst: tok == token.CONST,
-						Binding: &ast.BindingIdentifier{
-							Name: list[0].Name,
-							Idx:  list[0].Idx,
-						},
+						Target:  list[0].Target,
 					}
 				}
 			} else {
+				self.ensurePatternInit(list)
 				if tok == token.VAR {
 					initializer = &ast.ForLoopInitializerVarDeclList{
 						List: list,
@@ -511,7 +498,7 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 			}
 			if forIn || forOf {
 				switch expr.(type) {
-				case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.VariableExpression:
+				case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.Binding:
 					// These are all acceptable
 				default:
 					self.error(idx, "Invalid left-hand side in for-in or for-of")
@@ -541,11 +528,23 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 	return self.parseFor(idx, initializer)
 }
 
+func (self *_parser) ensurePatternInit(list []*ast.Binding) {
+	for _, item := range list {
+		if _, ok := item.Target.(ast.Pattern); ok {
+			if item.Initializer == nil {
+				self.error(item.Idx1(), "Missing initializer in destructuring declaration")
+				break
+			}
+		}
+	}
+}
+
 func (self *_parser) parseVariableStatement() *ast.VariableStatement {
 
 	idx := self.expect(token.VAR)
 
 	list := self.parseVarDeclarationList(idx)
+	self.ensurePatternInit(list)
 	self.semicolon()
 
 	return &ast.VariableStatement{
@@ -561,6 +560,7 @@ func (self *_parser) parseLexicalDeclaration(tok token.Token) *ast.LexicalDeclar
 	}
 
 	list := self.parseVariableDeclarationList()
+	self.ensurePatternInit(list)
 	self.semicolon()
 
 	return &ast.LexicalDeclaration{

+ 116 - 92
tc39_test.go

@@ -34,6 +34,28 @@ var (
 
 var (
 	skipList = map[string]bool{
+
+		// Obsolete tests (see https://github.com/tc39/test262/pull/2445)
+		//TODO: remove this after upgrading the test suite past the above PR
+		"test/language/statements/function/scope-param-rest-elem-var-open.js":         true,
+		"test/language/statements/function/scope-param-rest-elem-var-close.js":        true,
+		"test/language/statements/function/scope-param-elem-var-open.js":              true,
+		"test/language/function-code/eval-param-env-with-prop-initializer.js":         true,
+		"test/language/function-code/eval-param-env-with-computed-key.js":             true,
+		"test/language/expressions/object/scope-meth-param-rest-elem-var-open.js":     true,
+		"test/language/statements/function/scope-param-elem-var-close.js":             true,
+		"test/language/expressions/object/scope-meth-param-rest-elem-var-close.js":    true,
+		"test/language/expressions/object/scope-meth-param-elem-var-open.js":          true,
+		"test/language/expressions/object/scope-meth-param-elem-var-close.js":         true,
+		"test/language/expressions/function/scope-param-rest-elem-var-open.js":        true,
+		"test/language/expressions/function/scope-param-rest-elem-var-close.js":       true,
+		"test/language/expressions/function/scope-param-elem-var-open.js":             true,
+		"test/language/expressions/function/scope-param-elem-var-close.js":            true,
+		"test/language/expressions/arrow-function/scope-param-rest-elem-var-open.js":  true,
+		"test/language/expressions/arrow-function/scope-param-rest-elem-var-close.js": true,
+		"test/language/expressions/arrow-function/scope-param-elem-var-close.js":      true,
+		"test/language/expressions/arrow-function/scope-param-elem-var-open.js":       true,
+
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true, // timezone
 		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, // timezone
@@ -115,80 +137,92 @@ var (
 		"test/language/expressions/object/setter-super-prop.js":                                                      true,
 		"test/language/expressions/object/getter-super-prop.js":                                                      true,
 		"test/language/expressions/delete/super-property.js":                                                         true,
-
-		// object literals
-		"test/built-ins/Array/from/source-object-iterator-1.js":                                                true,
-		"test/built-ins/Array/from/source-object-iterator-2.js":                                                true,
-		"test/built-ins/TypedArrays/of/argument-number-value-throws.js":                                        true,
-		"test/built-ins/TypedArrays/from/set-value-abrupt-completion.js":                                       true,
-		"test/built-ins/TypedArrays/from/property-abrupt-completion.js":                                        true,
-		"test/built-ins/DataView/custom-proto-access-throws-sab.js":                                            true,
-		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":                 true,
-		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":               true,
-		"test/built-ins/Array/prototype/splice/property-traps-order-with-species.js":                           true,
-		"test/built-ins/String/prototype/indexOf/position-tointeger-errors.js":                                 true,
-		"test/built-ins/String/prototype/indexOf/position-tointeger-toprimitive.js":                            true,
-		"test/built-ins/String/prototype/indexOf/position-tointeger-wrapped-values.js":                         true,
-		"test/built-ins/String/prototype/indexOf/searchstring-tostring-errors.js":                              true,
-		"test/built-ins/String/prototype/indexOf/searchstring-tostring-toprimitive.js":                         true,
-		"test/built-ins/String/prototype/indexOf/searchstring-tostring-wrapped-values.js":                      true,
-		"test/built-ins/String/prototype/split/separator-undef-limit-zero.js":                                  true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-cannot-convert-to-primitive-err.js":         true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-toprimitive-call-err.js":                    true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-toprimitive-meth-err.js":                    true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-toprimitive-meth-priority.js":               true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-toprimitive-returns-object-err.js":          true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-tostring-call-err.js":                       true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-tostring-meth-err.js":                       true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-tostring-meth-priority.js":                  true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-tostring-returns-object-err.js":             true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-valueof-call-err.js":                        true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-valueof-meth-err.js":                        true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-valueof-meth-priority.js":                   true,
-		"test/built-ins/String/prototype/trimEnd/this-value-object-valueof-returns-object-err.js":              true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-cannot-convert-to-primitive-err.js":       true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-toprimitive-call-err.js":                  true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-toprimitive-meth-err.js":                  true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-toprimitive-meth-priority.js":             true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-toprimitive-returns-object-err.js":        true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-tostring-call-err.js":                     true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-tostring-meth-err.js":                     true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-tostring-meth-priority.js":                true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-tostring-returns-object-err.js":           true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-valueof-call-err.js":                      true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-valueof-meth-err.js":                      true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-valueof-meth-priority.js":                 true,
-		"test/built-ins/String/prototype/trimStart/this-value-object-valueof-returns-object-err.js":            true,
-		"test/built-ins/TypedArray/prototype/sort/sort-tonumber.js":                                            true,
-		"test/built-ins/Array/prototype/flatMap/array-like-objects.js":                                         true,
-		"test/built-ins/Array/prototype/flatMap/array-like-objects-poisoned-length.js":                         true,
-		"test/built-ins/Array/prototype/flatMap/this-value-ctor-object-species.js":                             true,
-		"test/built-ins/Array/prototype/flatMap/this-value-ctor-object-species-custom-ctor.js":                 true,
-		"test/built-ins/Array/prototype/flatMap/this-value-ctor-object-species-custom-ctor-poisoned-throws.js": true,
-		"test/built-ins/Array/prototype/flatMap/this-value-ctor-object-species-bad-throws.js":                  true,
-		"test/built-ins/Proxy/getPrototypeOf/instanceof-target-not-extensible-not-same-proto-throws.js":        true,
-		"test/language/statements/class/definition/fn-name-method.js":                                          true,
+		"test/language/statements/let/dstr/obj-ptrn-id-init-fn-name-class.js":                                        true,
+		"test/language/statements/let/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                                   true,
+		"test/language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-class.js":                                    true,
+		"test/language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-class.js":                               true,
+		"test/language/statements/for/dstr/let-ary-ptrn-elem-id-init-fn-name-class.js":                               true,
+		"test/language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-class.js":                                    true,
+		"test/language/statements/const/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                                 true,
+		"test/language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-class.js":                                  true,
+		"test/language/statements/const/dstr/obj-ptrn-id-init-fn-name-class.js":                                      true,
+		"test/language/statements/for/dstr/const-ary-ptrn-elem-id-init-fn-name-class.js":                             true,
+		"test/language/statements/variable/dstr/obj-ptrn-id-init-fn-name-class.js":                                   true,
+		"test/language/statements/variable/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                              true,
+		"test/language/expressions/object/method-definition/name-name-prop-symbol.js":                                true,
+		"test/language/expressions/function/dstr/dflt-obj-ptrn-id-init-fn-name-class.js":                             true,
+		"test/language/expressions/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-class.js":                        true,
+		"test/language/expressions/function/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                             true,
+		"test/language/expressions/function/dstr/obj-ptrn-id-init-fn-name-class.js":                                  true,
+		"test/language/statements/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-class.js":                         true,
+		"test/language/statements/function/dstr/obj-ptrn-id-init-fn-name-class.js":                                   true,
+		"test/language/statements/function/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                              true,
+		"test/language/statements/function/dstr/dflt-obj-ptrn-id-init-fn-name-class.js":                              true,
+		"test/language/statements/class/scope-static-setter-paramsbody-var-open.js":                                  true,
+		"test/language/statements/class/scope-static-setter-paramsbody-var-close.js":                                 true,
+		"test/language/statements/class/scope-static-meth-paramsbody-var-open.js":                                    true,
+		"test/language/statements/class/scope-static-meth-paramsbody-var-close.js":                                   true,
+		"test/language/statements/class/scope-setter-paramsbody-var-open.js":                                         true,
+		"test/language/statements/class/scope-setter-paramsbody-var-close.js":                                        true,
+		"test/language/statements/class/scope-meth-paramsbody-var-open.js":                                           true,
+		"test/language/statements/class/scope-meth-paramsbody-var-close.js":                                          true,
+		"test/language/expressions/class/scope-static-setter-paramsbody-var-open.js":                                 true,
+		"test/language/expressions/class/scope-static-setter-paramsbody-var-close.js":                                true,
+		"test/language/expressions/class/scope-static-meth-paramsbody-var-open.js":                                   true,
+		"test/language/expressions/class/scope-static-meth-paramsbody-var-close.js":                                  true,
+		"test/language/expressions/class/scope-setter-paramsbody-var-open.js":                                        true,
+		"test/language/expressions/class/scope-setter-paramsbody-var-close.js":                                       true,
+		"test/language/expressions/class/scope-meth-paramsbody-var-open.js":                                          true,
+		"test/language/expressions/class/scope-meth-paramsbody-var-close.js":                                         true,
+		"test/language/expressions/arrow-function/scope-paramsbody-var-open.js":                                      true,
+		"test/language/expressions/arrow-function/scope-paramsbody-var-close.js":                                     true,
+		"test/language/expressions/arrow-function/scope-body-lex-distinct.js":                                        true,
 
 		// arrow-function
-		"test/built-ins/Object/prototype/toString/proxy-function.js":            true,
-		"test/built-ins/Array/prototype/pop/throws-with-string-receiver.js":     true,
-		"test/built-ins/Array/prototype/push/throws-with-string-receiver.js":    true,
-		"test/built-ins/Array/prototype/shift/throws-with-string-receiver.js":   true,
-		"test/built-ins/Array/prototype/unshift/throws-with-string-receiver.js": true,
-		"test/built-ins/Date/prototype/toString/non-date-receiver.js":           true,
-		"test/built-ins/Number/prototype/toExponential/range.js":                true,
-		"test/built-ins/Number/prototype/toFixed/range.js":                      true,
-		"test/built-ins/Number/prototype/toPrecision/range.js":                  true,
-		"test/built-ins/TypedArray/prototype/sort/stability.js":                 true,
-		"test/built-ins/RegExp/named-groups/functional-replace-global.js":       true,
-		"test/built-ins/RegExp/named-groups/functional-replace-non-global.js":   true,
-		"test/built-ins/Array/prototype/sort/stability-513-elements.js":         true,
-		"test/built-ins/Array/prototype/sort/stability-5-elements.js":           true,
-		"test/built-ins/Array/prototype/sort/stability-2048-elements.js":        true,
-		"test/built-ins/Array/prototype/sort/stability-11-elements.js":          true,
-		"test/language/statements/variable/fn-name-arrow.js":                    true,
-		"test/language/statements/let/fn-name-arrow.js":                         true,
-		"test/language/statements/const/fn-name-arrow.js":                       true,
+		"test/built-ins/Object/prototype/toString/proxy-function.js":                                    true,
+		"test/built-ins/Array/prototype/pop/throws-with-string-receiver.js":                             true,
+		"test/built-ins/Array/prototype/push/throws-with-string-receiver.js":                            true,
+		"test/built-ins/Array/prototype/shift/throws-with-string-receiver.js":                           true,
+		"test/built-ins/Array/prototype/unshift/throws-with-string-receiver.js":                         true,
+		"test/built-ins/Date/prototype/toString/non-date-receiver.js":                                   true,
+		"test/built-ins/Number/prototype/toExponential/range.js":                                        true,
+		"test/built-ins/Number/prototype/toFixed/range.js":                                              true,
+		"test/built-ins/Number/prototype/toPrecision/range.js":                                          true,
+		"test/built-ins/TypedArray/prototype/sort/stability.js":                                         true,
+		"test/built-ins/RegExp/named-groups/functional-replace-global.js":                               true,
+		"test/built-ins/RegExp/named-groups/functional-replace-non-global.js":                           true,
+		"test/built-ins/Array/prototype/sort/stability-513-elements.js":                                 true,
+		"test/built-ins/Array/prototype/sort/stability-5-elements.js":                                   true,
+		"test/built-ins/Array/prototype/sort/stability-2048-elements.js":                                true,
+		"test/built-ins/Array/prototype/sort/stability-11-elements.js":                                  true,
+		"test/language/statements/variable/fn-name-arrow.js":                                            true,
+		"test/language/statements/let/fn-name-arrow.js":                                                 true,
+		"test/language/statements/const/fn-name-arrow.js":                                               true,
+		"test/built-ins/Proxy/getPrototypeOf/instanceof-target-not-extensible-not-same-proto-throws.js": true,
+		"test/language/statements/let/dstr/obj-ptrn-id-init-fn-name-arrow.js":                           true,
+		"test/language/statements/let/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                      true,
+		"test/language/statements/for/dstr/var-obj-ptrn-id-init-fn-name-arrow.js":                       true,
+		"test/language/statements/for/dstr/var-ary-ptrn-elem-id-init-fn-name-arrow.js":                  true,
+		"test/language/statements/for/dstr/let-ary-ptrn-elem-id-init-fn-name-arrow.js":                  true,
+		"test/language/statements/for/dstr/let-obj-ptrn-id-init-fn-name-arrow.js":                       true,
+		"test/language/statements/const/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                    true,
+		"test/language/statements/for/dstr/const-obj-ptrn-id-init-fn-name-arrow.js":                     true,
+		"test/language/statements/const/dstr/obj-ptrn-id-init-fn-name-arrow.js":                         true,
+		"test/language/statements/for/dstr/const-ary-ptrn-elem-id-init-fn-name-arrow.js":                true,
+		"test/language/statements/variable/dstr/obj-ptrn-id-init-fn-name-arrow.js":                      true,
+		"test/language/statements/variable/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                 true,
+		"test/language/expressions/assignment/dstr/obj-prop-elem-init-fn-name-arrow.js":                 true,
+		"test/language/expressions/assignment/dstr/obj-id-init-fn-name-arrow.js":                        true,
+		"test/language/expressions/assignment/dstr/obj-rest-order.js":                                   true,
+		"test/language/expressions/assignment/dstr/array-elem-init-fn-name-arrow.js":                    true,
+		"test/language/expressions/function/dstr/obj-ptrn-id-init-fn-name-arrow.js":                     true,
+		"test/language/expressions/function/dstr/dflt-obj-ptrn-id-init-fn-name-arrow.js":                true,
+		"test/language/expressions/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-arrow.js":           true,
+		"test/language/expressions/function/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                true,
+		"test/language/statements/function/dstr/dflt-ary-ptrn-elem-id-init-fn-name-arrow.js":            true,
+		"test/language/statements/function/dstr/obj-ptrn-id-init-fn-name-arrow.js":                      true,
+		"test/language/statements/function/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                 true,
+		"test/language/statements/function/dstr/dflt-obj-ptrn-id-init-fn-name-arrow.js":                 true,
 
 		// template strings
 		"test/built-ins/String/raw/zero-literal-segments.js":                                           true,
@@ -196,6 +230,8 @@ var (
 		"test/built-ins/String/raw/special-characters.js":                                              true,
 		"test/built-ins/String/raw/return-the-string-value-from-template.js":                           true,
 		"test/built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js": true,
+		"test/built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit.js":       true,
+		"test/built-ins/Array/prototype/slice/length-exceeding-integer-limit-proxied-array.js":         true,
 
 		// restricted unicode regexp syntax
 		"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js":         true,
@@ -251,23 +287,6 @@ var (
 
 		// generators
 		"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true,
-
-		// computed properties
-		"test/language/expressions/object/__proto__-permitted-dup.js":                     true,
-		"test/language/expressions/object/method-definition/name-name-prop-symbol.js":     true,
-		"test/language/expressions/object/method-definition/name-prop-name-eval-error.js": true,
-		"test/language/expressions/object/accessor-name-computed-yield-id.js":             true,
-		"test/language/expressions/object/accessor-name-computed-in.js":                   true,
-
-		// get [Symbol.*]
-		"test/language/expressions/object/prop-def-id-eval-error.js": true,
-
-		// destructing binding
-		"test/language/statements/for-of/head-var-bound-names-dup.js": true,
-		"test/language/statements/for-of/head-let-destructuring.js":   true,
-		"test/language/statements/for-in/head-var-bound-names-dup.js": true,
-		"test/language/statements/for/head-let-destructuring.js":      true,
-		"test/language/statements/for-in/head-let-destructuring.js":   true,
 	}
 
 	featuresBlackList = []string{
@@ -275,11 +294,8 @@ var (
 		"async-iteration",
 		"BigInt",
 		"class",
-		"destructuring-binding",
 		"generators",
 		"String.prototype.replaceAll",
-		"computed-property-names",
-		"default-parameters",
 		"super",
 	}
 
@@ -365,6 +381,14 @@ var (
 		"sec-let-and-const-declarations*",
 		"sec-arguments-exotic-objects-defineownproperty-p-desc",
 		"sec-other-properties-of-the-global-object-globalthis",
+		"sec-variable-statement-runtime-semantics-evaluation",
+		"sec-function-definitions",
+		"sec-function-definitions-runtime-semantics-evaluation",
+		"sec-function-definitions-runtime-semantics-instantiatefunctionobject",
+		"sec-function-definitions-runtime-semantics-iteratorbindinginitialization",
+		"sec-function-definitions-static-semantics-early-errors",
+		"sec-functiondeclarationinstantiation",
+		"sec-functiondeclarations-in-ifstatement-statement-clauses",
 	}
 )
 

+ 2 - 0
token/token_const.go

@@ -71,6 +71,8 @@ const (
 	SEMICOLON         // ;
 	COLON             // :
 	QUESTION_MARK     // ?
+	ARROW             // =>
+	ELLIPSIS          // ...
 
 	firstKeyword
 	IF

+ 423 - 33
vm.go

@@ -25,7 +25,9 @@ type stash struct {
 
 	outer *stash
 
-	function bool
+	// true if this stash is a VariableEnvironment, i.e. dynamic var declarations created
+	// by direct eval go here.
+	variable bool
 }
 
 type context struct {
@@ -90,10 +92,13 @@ func (r *stashRefLex) set(v Value) {
 
 type stashRefConst struct {
 	stashRefLex
+	strictConst bool
 }
 
 func (r *stashRefConst) set(v Value) {
-	panic(errAssignToConst)
+	if r.strictConst {
+		panic(errAssignToConst)
+	}
 }
 
 type objRef struct {
@@ -325,6 +330,7 @@ func (s *stash) getRefByName(name unistring.String, strict bool) ref {
 								idx: int(idx &^ maskTyp),
 							},
 						},
+						strictConst: strict || (idx&maskStrict != 0),
 					}
 				}
 			} else {
@@ -360,7 +366,7 @@ func (s *stash) createLexBinding(name unistring.String, isConst bool) {
 	if _, exists := s.names[name]; !exists {
 		idx := uint32(len(s.names))
 		if isConst {
-			idx |= maskConst
+			idx |= maskConst | maskStrict
 		}
 		s.names[name] = idx
 		s.values = append(s.values, nil)
@@ -771,9 +777,10 @@ func (vm *vm) storeStack1Lex(s int) {
 
 func (vm *vm) initStack(s int) {
 	if s <= 0 {
-		panic("Illegal stack var index")
+		vm.stack[vm.sb-s] = vm.stack[vm.sp-1]
+	} else {
+		vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1]
 	}
-	vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1]
 	vm.pc++
 }
 
@@ -1227,6 +1234,37 @@ func (j jump) exec(vm *vm) {
 	vm.pc += int(j)
 }
 
+type _getElemRef struct{}
+
+var getElemRef _getElemRef
+
+func (_getElemRef) exec(vm *vm) {
+	obj := vm.stack[vm.sp-2].ToObject(vm.r)
+	propName := toPropertyKey(vm.stack[vm.sp-1])
+	vm.refStack = append(vm.refStack, &objRef{
+		base: obj.self,
+		name: propName.string(),
+	})
+	vm.sp -= 2
+	vm.pc++
+}
+
+type _getElemRefStrict struct{}
+
+var getElemRefStrict _getElemRefStrict
+
+func (_getElemRefStrict) exec(vm *vm) {
+	obj := vm.stack[vm.sp-2].ToObject(vm.r)
+	propName := toPropertyKey(vm.stack[vm.sp-1])
+	vm.refStack = append(vm.refStack, &objRef{
+		base:   obj.self,
+		name:   propName.string(),
+		strict: true,
+	})
+	vm.sp -= 2
+	vm.pc++
+}
+
 type _setElem struct{}
 
 var setElem _setElem
@@ -1243,6 +1281,39 @@ func (_setElem) exec(vm *vm) {
 	vm.pc++
 }
 
+type _setElem1 struct{}
+
+var setElem1 _setElem1
+
+func (_setElem1) exec(vm *vm) {
+	obj := vm.stack[vm.sp-3].ToObject(vm.r)
+	propName := toPropertyKey(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+
+	obj.setOwn(propName, val, true)
+
+	vm.sp -= 2
+	vm.pc++
+}
+
+type _setElem1Named struct{}
+
+var setElem1Named _setElem1Named
+
+func (_setElem1Named) exec(vm *vm) {
+	obj := vm.stack[vm.sp-3].ToObject(vm.r)
+	propName := toPropertyKey(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
+		Value:        propName,
+		Configurable: FLAG_TRUE,
+	}, true)
+	obj.setOwn(propName, val, true)
+
+	vm.sp -= 2
+	vm.pc++
+}
+
 type _setElemP struct{}
 
 var setElemP _setElemP
@@ -1339,6 +1410,29 @@ func (d deletePropStrict) exec(vm *vm) {
 	vm.pc++
 }
 
+type getPropRef unistring.String
+
+func (p getPropRef) exec(vm *vm) {
+	vm.refStack = append(vm.refStack, &objRef{
+		base: vm.stack[vm.sp-1].ToObject(vm.r).self,
+		name: unistring.String(p),
+	})
+	vm.sp--
+	vm.pc++
+}
+
+type getPropRefStrict unistring.String
+
+func (p getPropRefStrict) exec(vm *vm) {
+	vm.refStack = append(vm.refStack, &objRef{
+		base:   vm.stack[vm.sp-1].ToObject(vm.r).self,
+		name:   unistring.String(p),
+		strict: true,
+	})
+	vm.sp--
+	vm.pc++
+}
+
 type setProp unistring.String
 
 func (p setProp) exec(vm *vm) {
@@ -1407,6 +1501,10 @@ type setPropGetter unistring.String
 func (s setPropGetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
+	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
+		Value:        asciiString("get ").concat(stringValueFromRaw(val.string())),
+		Configurable: FLAG_TRUE,
+	}, true)
 
 	descr := PropertyDescriptor{
 		Getter:       val,
@@ -1426,6 +1524,11 @@ func (s setPropSetter) exec(vm *vm) {
 	obj := vm.r.toObject(vm.stack[vm.sp-2])
 	val := vm.stack[vm.sp-1]
 
+	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
+		Value:        asciiString("set ").concat(stringValueFromRaw(val.string())),
+		Configurable: FLAG_TRUE,
+	}, true)
+
 	descr := PropertyDescriptor{
 		Setter:       val,
 		Configurable: FLAG_TRUE,
@@ -1438,6 +1541,57 @@ func (s setPropSetter) exec(vm *vm) {
 	vm.pc++
 }
 
+type _setPropGetter1 struct{}
+
+var setPropGetter1 _setPropGetter1
+
+func (s _setPropGetter1) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-3])
+	propName := toPropertyKey(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
+		Value:        asciiString("get ").concat(stringValueFromRaw(val.string())),
+		Configurable: FLAG_TRUE,
+	}, true)
+
+	descr := PropertyDescriptor{
+		Getter:       val,
+		Configurable: FLAG_TRUE,
+		Enumerable:   FLAG_TRUE,
+	}
+
+	obj.defineOwnProperty(propName, descr, false)
+
+	vm.sp -= 2
+	vm.pc++
+}
+
+type _setPropSetter1 struct{}
+
+var setPropSetter1 _setPropSetter1
+
+func (s _setPropSetter1) exec(vm *vm) {
+	obj := vm.r.toObject(vm.stack[vm.sp-3])
+	propName := toPropertyKey(vm.stack[vm.sp-2])
+	val := vm.stack[vm.sp-1]
+
+	vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{
+		Value:        asciiString("set ").concat(stringValueFromRaw(val.string())),
+		Configurable: FLAG_TRUE,
+	}, true)
+
+	descr := PropertyDescriptor{
+		Setter:       val,
+		Configurable: FLAG_TRUE,
+		Enumerable:   FLAG_TRUE,
+	}
+
+	obj.defineOwnProperty(propName, descr, false)
+
+	vm.sp -= 2
+	vm.pc++
+}
+
 type getProp unistring.String
 
 func (g getProp) exec(vm *vm) {
@@ -1544,32 +1698,64 @@ func (_newObject) exec(vm *vm) {
 type newArray uint32
 
 func (l newArray) exec(vm *vm) {
-	values := make([]Value, l)
-	if l > 0 {
-		copy(values, vm.stack[vm.sp-int(l):vm.sp])
-	}
-	obj := vm.r.newArrayValues(values)
-	if l > 0 {
-		vm.sp -= int(l) - 1
-		vm.stack[vm.sp-1] = obj
+	values := make([]Value, 0, l)
+	vm.push(vm.r.newArrayValues(values))
+	vm.pc++
+}
+
+type _pushArrayItem struct{}
+
+var pushArrayItem _pushArrayItem
+
+func (_pushArrayItem) exec(vm *vm) {
+	arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject)
+	if arr.length < math.MaxUint32 {
+		arr.length++
 	} else {
-		vm.push(obj)
+		panic(vm.r.newError(vm.r.global.RangeError, "Invalid array length"))
 	}
+	val := vm.stack[vm.sp-1]
+	arr.values = append(arr.values, val)
+	if val != nil {
+		arr.objCount++
+	}
+	vm.sp--
 	vm.pc++
 }
 
-type newArraySparse struct {
-	l, objCount int
+type _pushArraySpread struct{}
+
+var pushArraySpread _pushArraySpread
+
+func (_pushArraySpread) exec(vm *vm) {
+	arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject)
+	vm.r.iterate(vm.r.getIterator(vm.stack[vm.sp-1], nil), func(val Value) {
+		if arr.length < math.MaxUint32 {
+			arr.length++
+		} else {
+			panic(vm.r.newError(vm.r.global.RangeError, "Invalid array length"))
+		}
+		arr.values = append(arr.values, val)
+		arr.objCount++
+	})
+	vm.sp--
+	vm.pc++
 }
 
-func (n *newArraySparse) exec(vm *vm) {
-	values := make([]Value, n.l)
-	copy(values, vm.stack[vm.sp-int(n.l):vm.sp])
-	arr := vm.r.newArrayObject()
-	setArrayValues(arr, values)
-	arr.objCount = n.objCount
-	vm.sp -= int(n.l) - 1
-	vm.stack[vm.sp-1] = arr.val
+type _newArrayFromIter struct{}
+
+var newArrayFromIter _newArrayFromIter
+
+func (_newArrayFromIter) exec(vm *vm) {
+	var values []Value
+	l := len(vm.iterStack) - 1
+	iter := vm.iterStack[l].iter
+	vm.iterStack[l] = iterStackItem{}
+	vm.iterStack = vm.iterStack[:l]
+	vm.r.iterate(iter, func(val Value) {
+		values = append(values, val)
+	})
+	vm.push(vm.r.newArrayValues(values))
 	vm.pc++
 }
 
@@ -2032,7 +2218,7 @@ func newStashRef(typ varType, name unistring.String, v *[]Value, idx int) ref {
 				idx: idx,
 			},
 		}
-	case varTypeConst:
+	case varTypeConst, varTypeStrictConst:
 		return &stashRefConst{
 			stashRefLex: stashRefLex{
 				stashRef: stashRef{
@@ -2041,6 +2227,7 @@ func newStashRef(typ varType, name unistring.String, v *[]Value, idx int) ref {
 					idx: idx,
 				},
 			},
+			strictConst: typ == varTypeStrictConst,
 		}
 	}
 	panic("unsupported var type")
@@ -2353,7 +2540,7 @@ func (vm *vm) _nativeCall(f *nativeFuncObject, n int) {
 	if f.f != nil {
 		vm.pushCtx()
 		vm.prg = nil
-		vm.funcName = f.nameProp.get(nil).string()
+		vm.funcName = nilSafe(f.getStr("name", nil)).string()
 		ret := f.f(FunctionCall{
 			Arguments: vm.stack[vm.sp-n : vm.sp],
 			This:      vm.stack[vm.sp-n-2],
@@ -2470,7 +2657,7 @@ func (e *enterFunc) exec(vm *vm) {
 	vm.sb = sp - vm.args - 1
 	vm.newStash()
 	stash := vm.stash
-	stash.function = true
+	stash.variable = true
 	stash.values = make([]Value, e.stashSize)
 	if len(e.names) > 0 {
 		if e.extensible {
@@ -2522,6 +2709,98 @@ func (e *enterFunc) exec(vm *vm) {
 	vm.pc++
 }
 
+// Similar to enterFunc, but for when arguments may be accessed before they are initialised,
+// e.g. by an eval() code or from a closure, or from an earlier initialiser code.
+// In this case the arguments remain on stack, first argsToCopy of them are copied to the stash.
+type enterFunc1 struct {
+	names      map[unistring.String]uint32
+	stashSize  uint32
+	numArgs    uint32
+	argsToCopy uint32
+	extensible bool
+}
+
+func (e *enterFunc1) exec(vm *vm) {
+	sp := vm.sp
+	vm.sb = sp - vm.args - 1
+	vm.newStash()
+	stash := vm.stash
+	stash.variable = true
+	stash.values = make([]Value, e.stashSize)
+	if len(e.names) > 0 {
+		if e.extensible {
+			m := make(map[unistring.String]uint32, len(e.names))
+			for name, idx := range e.names {
+				m[name] = idx
+			}
+			stash.names = m
+		} else {
+			stash.names = e.names
+		}
+	}
+	offset := vm.args - int(e.argsToCopy)
+	if offset > 0 {
+		copy(stash.values, vm.stack[sp-vm.args:sp-offset])
+		if offset := vm.args - int(e.numArgs); offset > 0 {
+			vm.stash.extraArgs = make([]Value, offset)
+			copy(stash.extraArgs, vm.stack[sp-offset:])
+		}
+	} else {
+		copy(stash.values, vm.stack[sp-vm.args:sp])
+		if int(e.argsToCopy) > vm.args {
+			vv := stash.values[vm.args:e.argsToCopy]
+			for i := range vv {
+				vv[i] = _undefined
+			}
+		}
+	}
+
+	vm.pc++
+}
+
+// Finalises the initialisers section and starts the function body which has its own
+// scope. When used in conjunction with enterFunc1 adjustStack is set to true which
+// causes the arguments to be removed from the stack.
+type enterFuncBody struct {
+	enterBlock
+	extensible  bool
+	adjustStack bool
+}
+
+func (e *enterFuncBody) exec(vm *vm) {
+	if e.stashSize > 0 || e.extensible {
+		vm.newStash()
+		stash := vm.stash
+		stash.variable = true
+		stash.values = make([]Value, e.stashSize)
+		if len(e.names) > 0 {
+			if e.extensible {
+				m := make(map[unistring.String]uint32, len(e.names))
+				for name, idx := range e.names {
+					m[name] = idx
+				}
+				stash.names = m
+			} else {
+				stash.names = e.names
+			}
+		}
+	}
+	sp := vm.sp
+	if e.adjustStack {
+		sp -= vm.args
+	}
+	nsp := sp + int(e.stackSize)
+	if e.stackSize > 0 {
+		vm.stack.expand(nsp - 1)
+		vv := vm.stack[sp:nsp]
+		for i := range vv {
+			vv[i] = nil
+		}
+	}
+	sp = nsp
+	vm.pc++
+}
+
 type _ret struct{}
 
 var ret _ret
@@ -2752,7 +3031,7 @@ func (d *bindVars) exec(vm *vm) {
 			if idx, exists := s.names[name]; exists && idx&maskVar == 0 {
 				panic(vm.alreadyDeclared(name))
 			}
-			if s.function {
+			if s.variable {
 				target = s
 				break
 			}
@@ -2834,6 +3113,28 @@ func (j jneq1) exec(vm *vm) {
 	}
 }
 
+type jdef int32
+
+func (j jdef) exec(vm *vm) {
+	if vm.stack[vm.sp-1] != _undefined {
+		vm.pc += int(j)
+	} else {
+		vm.sp--
+		vm.pc++
+	}
+}
+
+type jdefP int32
+
+func (j jdefP) exec(vm *vm) {
+	if vm.stack[vm.sp-1] != _undefined {
+		vm.pc += int(j)
+	} else {
+		vm.pc++
+	}
+	vm.sp--
+}
+
 type _not struct{}
 
 var not _not
@@ -3177,9 +3478,9 @@ func (_typeof) exec(vm *vm) {
 	vm.pc++
 }
 
-type createArgs uint32
+type createArgsMapped uint32
 
-func (formalArgs createArgs) exec(vm *vm) {
+func (formalArgs createArgsMapped) exec(vm *vm) {
 	v := &Object{runtime: vm.r}
 	args := &argumentsObject{}
 	args.extensible = true
@@ -3216,9 +3517,9 @@ func (formalArgs createArgs) exec(vm *vm) {
 	vm.pc++
 }
 
-type createArgsStrict uint32
+type createArgsUnmapped uint32
 
-func (formalArgs createArgsStrict) exec(vm *vm) {
+func (formalArgs createArgsUnmapped) exec(vm *vm) {
 	args := vm.r.newBaseObject(vm.r.global.ObjectPrototype, "Arguments")
 	i := 0
 	c := int(formalArgs)
@@ -3332,6 +3633,17 @@ func (_enumPopClose) exec(vm *vm) {
 	vm.pc++
 }
 
+type _iterateP struct{}
+
+var iterateP _iterateP
+
+func (_iterateP) exec(vm *vm) {
+	iter := vm.r.getIterator(vm.stack[vm.sp-1], nil)
+	vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter})
+	vm.sp--
+	vm.pc++
+}
+
 type _iterate struct{}
 
 var iterate _iterate
@@ -3339,7 +3651,6 @@ var iterate _iterate
 func (_iterate) exec(vm *vm) {
 	iter := vm.r.getIterator(vm.stack[vm.sp-1], nil)
 	vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter})
-	vm.sp--
 	vm.pc++
 }
 
@@ -3394,3 +3705,82 @@ var throwAssignToConst _throwAssignToConst
 func (_throwAssignToConst) exec(vm *vm) {
 	panic(errAssignToConst)
 }
+
+func (r *Runtime) copyDataProperties(target, source Value) {
+	targetObj := r.toObject(target)
+	if source == _null || source == _undefined {
+		return
+	}
+	sourceObj := source.ToObject(r)
+	iter := &enumerableIter{
+		wrapped: sourceObj.self.enumerateOwnKeys(),
+	}
+
+	for item, next := iter.next(); next != nil; item, next = next() {
+		v := nilSafe(sourceObj.self.getStr(item.name, nil))
+		createDataPropertyOrThrow(targetObj, stringValueFromRaw(item.name), v)
+	}
+}
+
+type _copySpread struct{}
+
+var copySpread _copySpread
+
+func (_copySpread) exec(vm *vm) {
+	vm.r.copyDataProperties(vm.stack[vm.sp-2], vm.stack[vm.sp-1])
+	vm.sp--
+	vm.pc++
+}
+
+type _copyRest struct{}
+
+var copyRest _copyRest
+
+func (_copyRest) exec(vm *vm) {
+	vm.push(vm.r.NewObject())
+	vm.r.copyDataProperties(vm.stack[vm.sp-1], vm.stack[vm.sp-2])
+	vm.pc++
+}
+
+type _createDestructSrc struct{}
+
+var createDestructSrc _createDestructSrc
+
+func (_createDestructSrc) exec(vm *vm) {
+	v := vm.stack[vm.sp-1]
+	vm.r.checkObjectCoercible(v)
+	vm.push(vm.r.newDestructKeyedSource(v))
+	vm.pc++
+}
+
+type _checkObjectCoercible struct{}
+
+var checkObjectCoercible _checkObjectCoercible
+
+func (_checkObjectCoercible) exec(vm *vm) {
+	vm.r.checkObjectCoercible(vm.stack[vm.sp-1])
+	vm.pc++
+}
+
+type createArgsRestStack int
+
+func (n createArgsRestStack) exec(vm *vm) {
+	var values []Value
+	delta := vm.args - int(n)
+	if delta > 0 {
+		values = make([]Value, delta)
+		copy(values, vm.stack[vm.sb+int(n)+1:])
+	}
+	vm.push(vm.r.newArrayValues(values))
+	vm.pc++
+}
+
+type _createArgsRestStash struct{}
+
+var createArgsRestStash _createArgsRestStash
+
+func (_createArgsRestStash) exec(vm *vm) {
+	vm.push(vm.r.newArrayValues(vm.stash.extraArgs))
+	vm.stash.extraArgs = nil
+	vm.pc++
+}