From b8e8f56aca191dd134b28ded780a86c94d6be5f4 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sun, 25 Jul 2021 11:14:10 +0100 Subject: [PATCH] Support patterns in catch clause. See #305 --- ast/node.go | 2 +- compiler_stmt.go | 35 ++++++++++++++++++++++------------- compiler_test.go | 16 ++++++++++++++++ parser/expression.go | 11 +++++++---- parser/statement.go | 12 +++--------- tc39_test.go | 5 +++++ 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/ast/node.go b/ast/node.go index ac71f0c9..9ae2836c 100644 --- a/ast/node.go +++ b/ast/node.go @@ -292,7 +292,7 @@ type ( CatchStatement struct { Catch file.Idx - Parameter *Identifier + Parameter BindingTarget Body *BlockStatement } diff --git a/compiler_stmt.go b/compiler_stmt.go index d472f837..dfc4bc9a 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -102,12 +102,6 @@ func (c *compiler) updateEnterBlock(enter *enterBlock) { } 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{ typ: blockTry, outer: c.block, @@ -146,16 +140,31 @@ func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { c.newBlockScope() list := v.Catch.Body.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{} 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.compileStatements(list, bodyNeedResult) c.leaveScopeBlock(enter) diff --git a/compiler_test.go b/compiler_test.go index 88d516f6..c6ab798a 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -4031,6 +4031,22 @@ func TestVariadicUseStackVars(t *testing.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) { src, err := ioutil.ReadFile("babel7.js") diff --git a/parser/expression.go b/parser/expression.go index dfae1e1a..21ac15d8 100644 --- a/parser/expression.go +++ b/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 { self.token = token.IDENTIFIER } - var target ast.BindingTarget switch self.token { case token.IDENTIFIER: target = &ast.Identifier{ @@ -161,11 +160,15 @@ func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) a default: idx := self.expect(token.IDENTIFIER) 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{ - Target: target, + Target: self.parseBindingTarget(), } if declarationList != nil { diff --git a/parser/statement.go b/parser/statement.go index c1ba76f3..cc3cb0ce 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -128,17 +128,11 @@ func (self *_parser) parseTryStatement() ast.Statement { if self.token == token.CATCH { catch := self.idx self.next() - var parameter *ast.Identifier + var parameter ast.BindingTarget if self.token == token.LEFT_PARENTHESIS { 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{ Catch: catch, diff --git a/tc39_test.go b/tc39_test.go index 11b93259..6b37137c 100644 --- a/tc39_test.go +++ b/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/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/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 "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/array-elem-init-fn-name-arrow.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 "test/built-ins/String/raw/zero-literal-segments.js": true, @@ -403,6 +407,7 @@ var ( "sec-with-statement*", "sec-switch-*", "sec-try-*", + "sec-runtime-semantics-catchclauseevaluation", "sec-strict-mode-of-ecmascript", "sec-let-and-const-declarations*", "sec-arguments-exotic-objects-defineownproperty-p-desc",