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] Static methods
|
||||||
- [X] Inheritance *partial*
|
- [X] Inheritance *partial*
|
||||||
- [ ] Interfaces
|
- [ ] Interfaces
|
||||||
- [ ] super
|
- [X] super
|
||||||
- [ ] Need to track current conversion state through into function generator, to select the concrete parent
|
- [X] Need to track current conversion state through into function generator, to select the concrete parent
|
||||||
- [ ] parent::
|
- [X] parent::
|
||||||
- [ ] static, self
|
- [ ] static, self
|
||||||
- [ ] Traits / `use`
|
- [ ] Traits / `use`
|
||||||
- [ ] Abstract methods
|
- [ ] Abstract methods
|
||||||
|
@ -7,11 +7,13 @@ class Base {
|
|||||||
$this->mX = $x;
|
$this->mX = $x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Val() {
|
public function Val(): int {
|
||||||
return 3;
|
return self::TheStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
static function TheStatic() {}
|
static function TheStatic(): int {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Child extends Base {
|
class Child extends Base {
|
||||||
|
91
node.go
91
node.go
@ -37,8 +37,12 @@ func (pe parseErr) Unwrap() error {
|
|||||||
//
|
//
|
||||||
|
|
||||||
type conversionState struct {
|
type conversionState struct {
|
||||||
|
currentClassName string
|
||||||
|
currentClassParentName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
func (this *conversionState) convert(n_ node.Node) (string, error) {
|
func (this *conversionState) convert(n_ node.Node) (string, error) {
|
||||||
switch n := n_.(type) {
|
switch n := n_.(type) {
|
||||||
|
|
||||||
@ -106,17 +110,24 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
case *stmt.Class:
|
case *stmt.Class:
|
||||||
ret := ""
|
ret := ""
|
||||||
|
|
||||||
|
prevClassName := this.currentClassName // almost certainly empty-string
|
||||||
|
prevClassParentName := this.currentClassParentName
|
||||||
|
|
||||||
className := n.ClassName.(*node.Identifier).Value
|
className := n.ClassName.(*node.Identifier).Value
|
||||||
|
this.currentClassName = className
|
||||||
memberVars := []string{}
|
memberVars := []string{}
|
||||||
memberFuncs := []string{}
|
memberFuncs := []string{}
|
||||||
|
|
||||||
if n.Extends != nil {
|
if n.Extends != nil {
|
||||||
parentName, err := resolveName(n.Extends.ClassName)
|
parentName, err := this.resolveName(n.Extends.ClassName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
memberVars = append(memberVars, parentName+" // parent")
|
memberVars = append(memberVars, parentName+" // parent")
|
||||||
|
this.currentClassParentName = parentName
|
||||||
|
} else {
|
||||||
|
this.currentClassParentName = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk all child nodes of the class
|
// Walk all child nodes of the class
|
||||||
@ -174,7 +185,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{s, err}
|
return "", parseErr{s, err}
|
||||||
}
|
}
|
||||||
memberFuncStmt := "func New" + className + funcStmt + "\n"
|
memberFuncStmt := "func " + constructorName(className) + funcStmt + "\n"
|
||||||
memberFuncs = append(memberFuncs, memberFuncStmt)
|
memberFuncs = append(memberFuncs, memberFuncStmt)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -213,14 +224,21 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
ret += strings.Join(memberFuncs, "\n\n")
|
ret += strings.Join(memberFuncs, "\n\n")
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
|
// Reinstate parent state before returning
|
||||||
|
this.currentClassName = prevClassName
|
||||||
|
this.currentClassParentName = prevClassParentName
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
||||||
case *stmt.Function:
|
case *stmt.Function:
|
||||||
// Top-level function
|
// Top-level function definition
|
||||||
// TODO parse doc comment
|
// TODO parse doc comment
|
||||||
// FIXME is this the same as a closure?
|
// FIXME is this the same as a closure?
|
||||||
funcName := n.FunctionName.(*node.Identifier).Value
|
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
|
// All top-level functions like this are public; ensure function name starts
|
||||||
// with an uppercase letter
|
// with an uppercase letter
|
||||||
funcName = toPublic(funcName)
|
funcName = toPublic(funcName)
|
||||||
@ -379,6 +397,42 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
return "for " + cond + " " + body + "\n", nil
|
return "for " + cond + " " + body + "\n", nil
|
||||||
|
|
||||||
case *stmt.Expression:
|
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)
|
child, err := this.convert(n.Expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
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
|
// All our generated functions return err, but this AST node may be in a single-rvalue context
|
||||||
// TODO do something more intelligent here
|
// 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
|
// 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 {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
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)
|
callParams, err := this.convertFuncCallArgsCommon(n.ArgumentList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
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
|
return funcName + callParams, nil // expr only, no semicolon/newline
|
||||||
|
|
||||||
case *expr.StaticCall:
|
case *expr.StaticCall:
|
||||||
className, err := resolveName(n.Class)
|
className, err := this.resolveName(n.Class)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
@ -492,7 +550,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
case *expr.New:
|
case *expr.New:
|
||||||
// new foo(xx)
|
// new foo(xx)
|
||||||
// Transparently convert to calling constructor function.
|
// Transparently convert to calling constructor function.
|
||||||
nn, err := resolveName(n.Class)
|
nn, err := this.resolveName(n.Class)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{n, err}
|
return "", parseErr{n, err}
|
||||||
}
|
}
|
||||||
@ -560,7 +618,7 @@ func (this *conversionState) convert(n_ node.Node) (string, error) {
|
|||||||
return n.VarName.(*node.Identifier).Value, nil
|
return n.VarName.(*node.Identifier).Value, nil
|
||||||
|
|
||||||
case *expr.ConstFetch:
|
case *expr.ConstFetch:
|
||||||
return resolveName(n.Constant)
|
return this.resolveName(n.Constant)
|
||||||
|
|
||||||
case *expr.Array:
|
case *expr.Array:
|
||||||
return this.convertArrayLiteralCommon(n.Items)
|
return this.convertArrayLiteralCommon(n.Items)
|
||||||
@ -796,8 +854,12 @@ func toPrivate(name string) string {
|
|||||||
return lFirst + name[1:]
|
return lFirst + name[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func constructorName(className string) string {
|
||||||
|
return `New` + className
|
||||||
|
}
|
||||||
|
|
||||||
// resolveName turns a `*name.Name` node into a Go string.
|
// 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
|
// TODO support namespace lookups
|
||||||
|
|
||||||
paramType := unknownVarType
|
paramType := unknownVarType
|
||||||
@ -808,6 +870,15 @@ func resolveName(n node.Node) (string, error) {
|
|||||||
paramType = vt.Parts[0].(*name.NamePart).Value
|
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
|
return paramType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -926,7 +997,7 @@ func (this *conversionState) convertFunctionCommon(params []node.Node, returnTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VariableType: might be nil for untyped parameters
|
// VariableType: might be nil for untyped parameters
|
||||||
paramType, err := resolveName(param.VariableType)
|
paramType, err := this.resolveName(param.VariableType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{param, err}
|
return "", parseErr{param, err}
|
||||||
}
|
}
|
||||||
@ -944,7 +1015,7 @@ func (this *conversionState) convertFunctionCommon(params []node.Node, returnTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReturnType
|
// ReturnType
|
||||||
funcReturn, err := resolveName(returnType)
|
funcReturn, err := this.resolveName(returnType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", parseErr{returnType, err}
|
return "", parseErr{returnType, err}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user