stmt/class: support super() calls

This commit is contained in:
mappu 2020-04-05 19:11:05 +12:00
parent 45b694434d
commit 28937fc384
3 changed files with 89 additions and 16 deletions

View File

@ -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

View File

@ -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
View File

@ -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}
}