Browse Source

Support patterns in catch clause. See #305

Dmitry Panov 4 years ago
parent
commit
b8e8f56aca
6 changed files with 54 additions and 27 deletions
  1. 1 1
      ast/node.go
  2. 22 13
      compiler_stmt.go
  3. 16 0
      compiler_test.go
  4. 7 4
      parser/expression.go
  5. 3 9
      parser/statement.go
  6. 5 0
      tc39_test.go

+ 1 - 1
ast/node.go

@@ -292,7 +292,7 @@ type (
 
 
 	CatchStatement struct {
 	CatchStatement struct {
 		Catch     file.Idx
 		Catch     file.Idx
-		Parameter *Identifier
+		Parameter BindingTarget
 		Body      *BlockStatement
 		Body      *BlockStatement
 	}
 	}
 
 

+ 22 - 13
compiler_stmt.go

@@ -102,12 +102,6 @@ func (c *compiler) updateEnterBlock(enter *enterBlock) {
 }
 }
 
 
 func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
 func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
-	if c.scope.strict && v.Catch != nil && v.Catch.Parameter != nil {
-		switch v.Catch.Parameter.Name {
-		case "arguments", "eval":
-			c.throwSyntaxError(int(v.Catch.Parameter.Idx)-1, "Catch variable may not be eval or arguments in strict mode")
-		}
-	}
 	c.block = &block{
 	c.block = &block{
 		typ:   blockTry,
 		typ:   blockTry,
 		outer: c.block,
 		outer: c.block,
@@ -146,16 +140,31 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) {
 			c.newBlockScope()
 			c.newBlockScope()
 			list := v.Catch.Body.List
 			list := v.Catch.Body.List
 			funcs := c.extractFunctions(list)
 			funcs := c.extractFunctions(list)
-			c.createFunctionBindings(funcs)
-			c.scope.bindNameLexical(v.Catch.Parameter.Name, true, int(v.Catch.Parameter.Idx)-1)
-			bindings := c.scope.bindings
-			if l := len(bindings); l > 1 {
-				// make sure the catch variable always goes first
-				bindings[0], bindings[l-1] = bindings[l-1], bindings[0]
+			if _, ok := v.Catch.Parameter.(ast.Pattern); ok {
+				// add anonymous binding for the catch parameter, note it must be first
+				c.scope.addBinding(int(v.Catch.Idx0()) - 1)
 			}
 			}
-			c.compileLexicalDeclarations(list, true)
+			c.createBindings(v.Catch.Parameter, func(name unistring.String, offset int) {
+				if c.scope.strict {
+					switch name {
+					case "arguments", "eval":
+						c.throwSyntaxError(offset, "Catch variable may not be eval or arguments in strict mode")
+					}
+				}
+				c.scope.bindNameLexical(name, true, offset)
+			})
 			enter := &enterBlock{}
 			enter := &enterBlock{}
 			c.emit(enter)
 			c.emit(enter)
+			if pattern, ok := v.Catch.Parameter.(ast.Pattern); ok {
+				c.scope.bindings[0].emitGet()
+				c.emitPattern(pattern, func(target, init compiledExpr) {
+					c.emitPatternLexicalAssign(target, init, false)
+				}, false)
+			}
+			for _, decl := range funcs {
+				c.scope.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1)
+			}
+			c.compileLexicalDeclarations(list, true)
 			c.compileFunctions(funcs)
 			c.compileFunctions(funcs)
 			c.compileStatements(list, bodyNeedResult)
 			c.compileStatements(list, bodyNeedResult)
 			c.leaveScopeBlock(enter)
 			c.leaveScopeBlock(enter)

+ 16 - 0
compiler_test.go

@@ -4031,6 +4031,22 @@ func TestVariadicUseStackVars(t *testing.T) {
 	testScript1(SCRIPT, asciiString("C"), t)
 	testScript1(SCRIPT, asciiString("C"), t)
 }
 }
 
 
+func TestCatchParamPattern(t *testing.T) {
+	const SCRIPT = `
+	function f() {
+		let x = 3;
+		try {
+			throw {a: 1, b: 2};
+		} catch ({a, b, c = x}) {
+			let x = 99;
+			return ""+a+" "+b+" "+c;
+		}
+	}
+	f();
+	`
+	testScript1(SCRIPT, asciiString("1 2 3"), t)
+}
+
 /*
 /*
 func TestBabel(t *testing.T) {
 func TestBabel(t *testing.T) {
 	src, err := ioutil.ReadFile("babel7.js")
 	src, err := ioutil.ReadFile("babel7.js")

+ 7 - 4
parser/expression.go

@@ -142,11 +142,10 @@ func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral {
 	}
 	}
 }
 }
 
 
-func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression {
+func (self *_parser) parseBindingTarget() (target ast.BindingTarget) {
 	if self.token == token.LET {
 	if self.token == token.LET {
 		self.token = token.IDENTIFIER
 		self.token = token.IDENTIFIER
 	}
 	}
-	var target ast.BindingTarget
 	switch self.token {
 	switch self.token {
 	case token.IDENTIFIER:
 	case token.IDENTIFIER:
 		target = &ast.Identifier{
 		target = &ast.Identifier{
@@ -161,11 +160,15 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) a
 	default:
 	default:
 		idx := self.expect(token.IDENTIFIER)
 		idx := self.expect(token.IDENTIFIER)
 		self.nextStatement()
 		self.nextStatement()
-		return &ast.BadExpression{From: idx, To: self.idx}
+		target = &ast.BadExpression{From: idx, To: self.idx}
 	}
 	}
 
 
+	return
+}
+
+func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) ast.Expression {
 	node := &ast.Binding{
 	node := &ast.Binding{
-		Target: target,
+		Target: self.parseBindingTarget(),
 	}
 	}
 
 
 	if declarationList != nil {
 	if declarationList != nil {

+ 3 - 9
parser/statement.go

@@ -128,17 +128,11 @@ func (self *_parser) parseTryStatement() ast.Statement {
 	if self.token == token.CATCH {
 	if self.token == token.CATCH {
 		catch := self.idx
 		catch := self.idx
 		self.next()
 		self.next()
-		var parameter *ast.Identifier
+		var parameter ast.BindingTarget
 		if self.token == token.LEFT_PARENTHESIS {
 		if self.token == token.LEFT_PARENTHESIS {
 			self.next()
 			self.next()
-			if self.token != token.IDENTIFIER {
-				self.expect(token.IDENTIFIER)
-				self.nextStatement()
-				return &ast.BadStatement{From: catch, To: self.idx}
-			} else {
-				parameter = self.parseIdentifier()
-				self.expect(token.RIGHT_PARENTHESIS)
-			}
+			parameter = self.parseBindingTarget()
+			self.expect(token.RIGHT_PARENTHESIS)
 		}
 		}
 		node.Catch = &ast.CatchStatement{
 		node.Catch = &ast.CatchStatement{
 			Catch:     catch,
 			Catch:     catch,

+ 5 - 0
tc39_test.go

@@ -190,6 +190,8 @@ var (
 		"test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-class.js":                                 true,
 		"test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-class.js":                                 true,
 		"test/language/statements/for-of/dstr/const-ary-ptrn-elem-id-init-fn-name-class.js":                          true,
 		"test/language/statements/for-of/dstr/const-ary-ptrn-elem-id-init-fn-name-class.js":                          true,
 		"test/language/statements/for-of/dstr/let-ary-ptrn-elem-id-init-fn-name-class.js":                            true,
 		"test/language/statements/for-of/dstr/let-ary-ptrn-elem-id-init-fn-name-class.js":                            true,
+		"test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-class.js":                                        true,
+		"test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-class.js":                                   true,
 
 
 		// arrow-function
 		// arrow-function
 		"test/built-ins/Object/prototype/toString/proxy-function.js":                                    true,
 		"test/built-ins/Object/prototype/toString/proxy-function.js":                                    true,
@@ -247,6 +249,8 @@ var (
 		"test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-arrow.js":                    true,
 		"test/language/statements/for-of/dstr/let-obj-ptrn-id-init-fn-name-arrow.js":                    true,
 		"test/language/statements/for-of/dstr/array-elem-init-fn-name-arrow.js":                         true,
 		"test/language/statements/for-of/dstr/array-elem-init-fn-name-arrow.js":                         true,
 		"test/language/expressions/call/spread-obj-spread-order.js":                                     true,
 		"test/language/expressions/call/spread-obj-spread-order.js":                                     true,
+		"test/language/statements/try/dstr/obj-ptrn-id-init-fn-name-arrow.js":                           true,
+		"test/language/statements/try/dstr/ary-ptrn-elem-id-init-fn-name-arrow.js":                      true,
 
 
 		// template strings
 		// template strings
 		"test/built-ins/String/raw/zero-literal-segments.js":                                                       true,
 		"test/built-ins/String/raw/zero-literal-segments.js":                                                       true,
@@ -403,6 +407,7 @@ var (
 		"sec-with-statement*",
 		"sec-with-statement*",
 		"sec-switch-*",
 		"sec-switch-*",
 		"sec-try-*",
 		"sec-try-*",
+		"sec-runtime-semantics-catchclauseevaluation",
 		"sec-strict-mode-of-ecmascript",
 		"sec-strict-mode-of-ecmascript",
 		"sec-let-and-const-declarations*",
 		"sec-let-and-const-declarations*",
 		"sec-arguments-exotic-objects-defineownproperty-p-desc",
 		"sec-arguments-exotic-objects-defineownproperty-p-desc",