stmt/class: support super() calls
This commit is contained in:
parent
45b694434d
commit
28937fc384
@ -40,9 +40,9 @@ The goal is to produce idiomatic, maintainable Go code as part of a one-off conv
|
||||
- [X] Static methods
|
||||
- [X] Inheritance *partial*
|
||||
- [ ] Interfaces
|
||||
- [ ] super
|
||||
- [ ] Need to track current conversion state through into function generator, to select the concrete parent
|
||||
- [ ] parent::
|
||||
- [X] super
|
||||
- [X] Need to track current conversion state through into function generator, to select the concrete parent
|
||||
- [X] parent::
|
||||
- [ ] static, self
|
||||
- [ ] Traits / `use`
|
||||
- [ ] Abstract methods
|
||||
|
@ -7,11 +7,13 @@ class Base {
|
||||
$this->mX = $x;
|
||||
}
|
||||
|
||||
public function Val() {
|
||||
return 3;
|
||||
public function Val(): int {
|
||||
return self::TheStatic();
|
||||
}
|
||||
|
||||
static function TheStatic() {}
|
||||
static function TheStatic(): int {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends Base {
|
||||
|
91
node.go
91
node.go
@ -37,8 +37,12 @@ func (pe parseErr) Unwrap() error {
|
||||
//
|
||||
|
||||
type conversionState struct {
|
||||
currentClassName string
|
||||
currentClassParentName string
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
switch n := n_.(type) {
|
||||
|
||||
@ -106,17 +110,24 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
case *stmt.Class:
|
||||
ret := ""
|
||||
|
||||
prevClassName := this.currentClassName // almost certainly empty-string
|
||||
prevClassParentName := this.currentClassParentName
|
||||
|
||||
className := n.ClassName.(*node.Identifier).Value
|
||||
this.currentClassName = className
|
||||
memberVars := []string{}
|
||||
memberFuncs := []string{}
|
||||
|
||||
if n.Extends != nil {
|
||||
parentName, err := resolveName(n.Extends.ClassName)
|
||||
parentName, err := this.resolveName(n.Extends.ClassName)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
memberVars = append(memberVars, parentName+" // parent")
|
||||
this.currentClassParentName = parentName
|
||||
} else {
|
||||
this.currentClassParentName = ""
|
||||
}
|
||||
|
||||
// Walk all child nodes of the class
|
||||
@ -174,7 +185,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
if err != nil {
|
||||
return "", parseErr{s, err}
|
||||
}
|
||||
memberFuncStmt := "func New" + className + funcStmt + "\n"
|
||||
memberFuncStmt := "func " + constructorName(className) + funcStmt + "\n"
|
||||
memberFuncs = append(memberFuncs, memberFuncStmt)
|
||||
|
||||
} else {
|
||||
@ -213,14 +224,21 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
ret += strings.Join(memberFuncs, "\n\n")
|
||||
|
||||
// Done
|
||||
// Reinstate parent state before returning
|
||||
this.currentClassName = prevClassName
|
||||
this.currentClassParentName = prevClassParentName
|
||||
return ret, nil
|
||||
|
||||
case *stmt.Function:
|
||||
// Top-level function
|
||||
// Top-level function definition
|
||||
// TODO parse doc comment
|
||||
// FIXME is this the same as a closure?
|
||||
funcName := n.FunctionName.(*node.Identifier).Value
|
||||
|
||||
if funcName == `super` {
|
||||
return "", parseErr{n, fmt.Errorf("Function name '%s' probably will not function correctly", funcName)}
|
||||
}
|
||||
|
||||
// All top-level functions like this are public; ensure function name starts
|
||||
// with an uppercase letter
|
||||
funcName = toPublic(funcName)
|
||||
@ -379,6 +397,42 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
return "for " + cond + " " + body + "\n", nil
|
||||
|
||||
case *stmt.Expression:
|
||||
|
||||
// Special case
|
||||
if fncall, ok := n.Expr.(*expr.FunctionCall); ok {
|
||||
if fnname, err := this.resolveName(fncall.Function); err == nil && fnname == "super" {
|
||||
// Call to parent constructor
|
||||
|
||||
if this.currentClassParentName == "" {
|
||||
return "", parseErr{n, fmt.Errorf("Call to parent constructor outside of class context")}
|
||||
}
|
||||
|
||||
// We need to call the parent constructor function (NewX) with these arguments
|
||||
funcArgs, err := this.convertFuncCallArgsCommon(fncall.ArgumentList)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
// Then we need to overwrite the embedded value type with the received pointer
|
||||
// That's not 100% safe in all cases if the *this pointer is leaked
|
||||
// within the constructor, but, our generated code will never do that
|
||||
// TODO replace our NewX constructors with split NewX + newXInPlace(??)
|
||||
|
||||
// Use a child scope for the temporary name
|
||||
ret := "{\n"
|
||||
ret += "super, err := " + constructorName(this.currentClassParentName) + funcArgs + "\n"
|
||||
ret += "if err != nil {\n"
|
||||
ret += "return err\n"
|
||||
ret += "}\n"
|
||||
ret += "this." + this.currentClassParentName + " = *super\n"
|
||||
ret += "}\n"
|
||||
ret += "\n"
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Regular case
|
||||
|
||||
child, err := this.convert(n.Expr)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
@ -459,11 +513,15 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
// All our generated functions return err, but this AST node may be in a single-rvalue context
|
||||
// TODO do something more intelligent here
|
||||
// We can't necessarily hoist the whole call, in case we are on the right-hand side of a && operator
|
||||
funcName, err := resolveName(n.Function)
|
||||
funcName, err := this.resolveName(n.Function)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
|
||||
if funcName == "super" {
|
||||
return "", parseErr{n, fmt.Errorf("Unexpected use of parent constructor in rvalue context")}
|
||||
}
|
||||
|
||||
callParams, err := this.convertFuncCallArgsCommon(n.ArgumentList)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
@ -472,7 +530,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
return funcName + callParams, nil // expr only, no semicolon/newline
|
||||
|
||||
case *expr.StaticCall:
|
||||
className, err := resolveName(n.Class)
|
||||
className, err := this.resolveName(n.Class)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
@ -492,7 +550,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
case *expr.New:
|
||||
// new foo(xx)
|
||||
// Transparently convert to calling constructor function.
|
||||
nn, err := resolveName(n.Class)
|
||||
nn, err := this.resolveName(n.Class)
|
||||
if err != nil {
|
||||
return "", parseErr{n, err}
|
||||
}
|
||||
@ -560,7 +618,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||
return n.VarName.(*node.Identifier).Value, nil
|
||||
|
||||
case *expr.ConstFetch:
|
||||
return resolveName(n.Constant)
|
||||
return this.resolveName(n.Constant)
|
||||
|
||||
case *expr.Array:
|
||||
return this.convertArrayLiteralCommon(n.Items)
|
||||
@ -796,8 +854,12 @@ func toPrivate(name string) string {
|
||||
return lFirst + name[1:]
|
||||
}
|
||||
|
||||
func constructorName(className string) string {
|
||||
return `New` + className
|
||||
}
|
||||
|
||||
// resolveName turns a `*name.Name` node into a Go string.
|
||||
func resolveName(n node.Node) (string, error) {
|
||||
func (this *conversionState) resolveName(n node.Node) (string, error) {
|
||||
// TODO support namespace lookups
|
||||
|
||||
paramType := unknownVarType
|
||||
@ -808,6 +870,15 @@ func resolveName(n node.Node) (string, error) {
|
||||
paramType = vt.Parts[0].(*name.NamePart).Value
|
||||
}
|
||||
|
||||
// Handle class lookups
|
||||
if strings.ToLower(paramType) == "parent" {
|
||||
if this.currentClassParentName != "" {
|
||||
return `this.` + this.currentClassParentName, nil
|
||||
} else {
|
||||
return "", parseErr{n, fmt.Errorf("Lookup of 'parent' while not in an inherited child class context")}
|
||||
}
|
||||
}
|
||||
|
||||
return paramType, nil
|
||||
}
|
||||
|
||||
@ -926,7 +997,7 @@ func (this *conversionState) convertFunctionCommon(params []node.Node, returnTyp
|
||||
}
|
||||
|
||||
// VariableType: might be nil for untyped parameters
|
||||
paramType, err := resolveName(param.VariableType)
|
||||
paramType, err := this.resolveName(param.VariableType)
|
||||
if err != nil {
|
||||
return "", parseErr{param, err}
|
||||
}
|
||||
@ -944,7 +1015,7 @@ func (this *conversionState) convertFunctionCommon(params []node.Node, returnTyp
|
||||
}
|
||||
|
||||
// ReturnType
|
||||
funcReturn, err := resolveName(returnType)
|
||||
funcReturn, err := this.resolveName(returnType)
|
||||
if err != nil {
|
||||
return "", parseErr{returnType, err}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user