node: initial try/catch support, track the error handler (WIP)
This commit is contained in:
parent
0d7024dbee
commit
52ccea5d65
16
fixtures/0008-try-catch-finally.php
Normal file
16
fixtures/0008-try-catch-finally.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
function Foo(): int {
|
||||
throw new \Exception('error');
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
echo Foo();
|
||||
} catch (\BarException | \BazException $e) {
|
||||
echo "bar or baz exception " . $e->getMessage() . "\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "other exception " . $e->getMessage() . "\n";
|
||||
} finally {
|
||||
echo "finally";
|
||||
}
|
110
node.go
110
node.go
@ -38,6 +38,7 @@ func (pe parseErr) Unwrap() error {
|
||||
type conversionState struct {
|
||||
currentClassName string
|
||||
currentClassParentName string
|
||||
currentErrHandler string
|
||||
}
|
||||
|
||||
//
|
||||
@ -100,6 +101,15 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
statements := []string{}
|
||||
|
||||
for _, s := range n.Stmts {
|
||||
|
||||
switch s.(type) {
|
||||
case *stmt.Class, *stmt.Function, *stmt.Interface:
|
||||
this.currentErrHandler = "return nil, err\n"
|
||||
|
||||
default:
|
||||
this.currentErrHandler = "panic(err)\n" // top-level init/main behaviour
|
||||
}
|
||||
|
||||
sm, err := this.convert(s)
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
@ -511,7 +521,7 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
// super() is a reserved name in PHP anyway
|
||||
ret := "super, err := " + constructorName(this.currentClassParentName) + funcArgs + "\n"
|
||||
ret += "if err != nil {\n"
|
||||
ret += "return err\n"
|
||||
ret += this.currentErrHandler //"return err\n"
|
||||
ret += "}\n"
|
||||
ret += "this." + this.currentClassParentName + " = *super // copy by value\n"
|
||||
return ret, nil
|
||||
@ -611,6 +621,76 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Try:
|
||||
|
||||
// Generate an if err != nil {} block that we can bring up whenever
|
||||
// an error is raised in a body function call
|
||||
// TODO This will produce shadowed error handlers - need to make sure we
|
||||
// use err2, err3 etc based on our current level of nesting
|
||||
|
||||
// TODO put finally() code somewhere (before/after err checking??)
|
||||
|
||||
handler := ""
|
||||
if len(n.Catches) == 0 {
|
||||
handler = "// suppress error"
|
||||
} else {
|
||||
|
||||
catchVars := ""
|
||||
catchIfs := []string{}
|
||||
|
||||
for _, catch_ := range n.Catches {
|
||||
catch, ok := catch_.(*stmt.Catch)
|
||||
if !ok {
|
||||
return "", parseErr{n, fmt.Errorf("expected stmt.Catch")}
|
||||
}
|
||||
|
||||
catchVar := catch.Variable.(*expr.Variable).VarName.(*node.Identifier).Value
|
||||
|
||||
catchBody, err := this.convert(stmt.NewStmtList(catch.Stmts))
|
||||
if err != nil {
|
||||
return "", parseErr{catch_, err}
|
||||
}
|
||||
|
||||
for _, typename_ := range catch.Types {
|
||||
typename, err := this.resolveName(typename_)
|
||||
if !ok {
|
||||
return "", parseErr{catch_, err}
|
||||
}
|
||||
|
||||
tempCatchVar := catchVar
|
||||
if len(catch.Types) > 1 {
|
||||
tempCatchVar += "_" + typename
|
||||
}
|
||||
|
||||
catchVars += "var " + tempCatchVar + "*" + typename + "\n"
|
||||
|
||||
catchStmt := "if errors.As(err, &" + tempCatchVar + ") {\n"
|
||||
if len(catch.Types) > 1 {
|
||||
catchStmt += catchVar + " := " + tempCatchVar + " // rename\n"
|
||||
}
|
||||
catchStmt += catchBody // contains its own {}
|
||||
catchStmt += "} " // but without trailing NL
|
||||
|
||||
catchIfs = append(catchIfs, catchStmt)
|
||||
}
|
||||
}
|
||||
|
||||
handler = catchVars + strings.Join(catchIfs, "else") + "\n"
|
||||
}
|
||||
|
||||
// Store the handler to be used going forwards
|
||||
previousErrHandler := this.currentErrHandler
|
||||
this.currentErrHandler = handler
|
||||
|
||||
body, err := this.convert(stmt.NewStmtList(n.Stmts))
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// Reinstate parent error handler
|
||||
this.currentErrHandler = previousErrHandler
|
||||
return body, nil // Try/catch behaviour is transparently encoded in err handlers
|
||||
|
||||
case *stmt.Nop:
|
||||
return "", nil
|
||||
|
||||
@ -670,6 +750,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// FIXME need to support `this.currentErrHandler`
|
||||
|
||||
return funcName + callParams, nil // expr only, no semicolon/newline
|
||||
|
||||
case *expr.StaticCall:
|
||||
@ -697,6 +779,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// FIXME need to support `this.currentErrHandler`
|
||||
|
||||
return callTarget + callParams, nil // expr only, no semicolon/newline
|
||||
|
||||
case *expr.New:
|
||||
@ -750,6 +834,8 @@ func (this *conversionState) convertNoFreeFloating(n_ node.Node) (string, error)
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// FIXME need to support `this.currentErrHandler`
|
||||
|
||||
return parent + "." + child + args, nil
|
||||
|
||||
case *expr.PropertyFetch:
|
||||
@ -1021,11 +1107,25 @@ func (this *conversionState) resolveName(n node.Node) (string, error) {
|
||||
// TODO support namespace lookups
|
||||
|
||||
paramType := unknownVarType
|
||||
if vt, ok := n.(*name.Name); ok {
|
||||
if len(vt.Parts) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(vt.Parts))}
|
||||
if n == nil || n == node.Node(nil) {
|
||||
return paramType, nil
|
||||
}
|
||||
paramType = vt.Parts[0].(*name.NamePart).Value
|
||||
|
||||
switch n := n.(type) {
|
||||
case *name.FullyQualified:
|
||||
if len(n.Parts) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(n.Parts))}
|
||||
}
|
||||
paramType = n.Parts[0].(*name.NamePart).Value
|
||||
|
||||
case *name.Name:
|
||||
if len(n.Parts) != 1 {
|
||||
return "", parseErr{n, fmt.Errorf("name has %d parts, expected 1", len(n.Parts))}
|
||||
}
|
||||
paramType = n.Parts[0].(*name.NamePart).Value
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected name type %#v", n)
|
||||
}
|
||||
|
||||
// Handle class lookups
|
||||
|
Loading…
Reference in New Issue
Block a user