Fix a handful of things

* Move checking the validity of a derived class with no constructor
  from evlauation to binding (verification).  Furthermore, make it
  a verification error instead of an assert, and make the checking
  complete (i.e., don't just check for the existence of a base class,
  also check for the existence of a ctor, recursively).

* Refactor the constructor accessor out of the evaluator and make it
  a method, Ctor, on the Function abstraction.

* Add a recursive Module population case to property initialization.

* Only treat the top-most frame in a module's initializer as belonging
  to the module scope.  This avoids interpreting block scopes within
  that initializer as belonging to the module scope.  Ultimately, it's
  up to the language compiler to decide where to place scopes, but this
  gives it more precise control over module scoping.

* Assert against unsupported named/star/keyword args in CocoPy.
This commit is contained in:
joeduffy 2017-04-10 08:36:48 -07:00
parent 346bcca77c
commit 2c6ad1e331
6 changed files with 54 additions and 20 deletions

View file

@ -6,6 +6,7 @@ import (
"github.com/golang/glog"
"github.com/pulumi/coconut/pkg/compiler/ast"
"github.com/pulumi/coconut/pkg/compiler/errors"
"github.com/pulumi/coconut/pkg/compiler/symbols"
"github.com/pulumi/coconut/pkg/util/contract"
)
@ -37,6 +38,17 @@ func (b *binder) bindClassDefinition(class *symbols.Class) {
}
b.bindClassMembers(class)
if class.Ctor() == nil {
// If there is no ctor defined, ensure that all base classes have no ctor also.
ext := class.Extends
for ext != nil {
if ext.Ctor() != nil {
b.Diag().Errorf(errors.ErrorDerivedClassHasNoCtor.At(class.Node), class, ext)
}
ext = ext.Base()
}
}
}
func (b *binder) bindClassMembers(class *symbols.Class) {

View file

@ -40,4 +40,5 @@ var (
"Unexpected object target for this static or module load operation")
ErrorInvalidCast = newError(529, "Illegal cast from '%v' to '%v'; this can never succeed")
ErrorModuleAliasTargetNotFound = newError(530, "Module alias target '%v' was not found (from '%v')")
ErrorDerivedClassHasNoCtor = newError(531, "Class '%v' has no constructor, but its base class '%v' does")
)

View file

@ -3,6 +3,8 @@
package symbols
import (
"reflect"
"github.com/pulumi/coconut/pkg/compiler/ast"
"github.com/pulumi/coconut/pkg/diag"
"github.com/pulumi/coconut/pkg/tokens"
@ -39,11 +41,22 @@ func (node *Class) Base() Type { return node.Extends }
func (node *Class) TypeName() tokens.TypeName { return node.Nm }
func (node *Class) TypeToken() tokens.Type { return node.Tok }
func (node *Class) TypeMembers() ClassMemberMap { return node.Members }
func (node *Class) Sealed() bool { return node.Node.Sealed != nil && *node.Node.Sealed }
func (node *Class) Abstract() bool { return node.Node.Abstract != nil && *node.Node.Abstract }
func (node *Class) Record() bool { return node.Node.Record != nil && *node.Node.Record }
func (node *Class) Interface() bool { return node.Node.Interface != nil && *node.Node.Interface }
func (node *Class) String() string { return string(node.Token()) }
func (node *Class) Ctor() Function {
if ctor, has := node.TypeMembers()[tokens.ClassConstructorFunction]; has {
ctormeth, ismeth := ctor.(*ClassMethod)
contract.Assertf(ismeth,
"Expected ctor %v to be a class method; got %v", ctor, reflect.TypeOf(ctor))
contract.Assertf(ctormeth.Sig.Return == nil,
"Expected ctor %v to have a nil return; got %v", ctor, ctormeth.Sig.Return)
return ctormeth
}
return nil
}
func (node *Class) Sealed() bool { return node.Node.Sealed != nil && *node.Node.Sealed }
func (node *Class) Abstract() bool { return node.Node.Abstract != nil && *node.Node.Abstract }
func (node *Class) Record() bool { return node.Node.Record != nil && *node.Node.Record }
func (node *Class) Interface() bool { return node.Node.Interface != nil && *node.Node.Interface }
func (node *Class) String() string { return string(node.Token()) }
// HasInit returns true if this module has an initialzer associated with it.
func (node *Class) HasInit() bool { return node.GetInit() != nil }

View file

@ -14,6 +14,7 @@ type Type interface {
TypeName() tokens.TypeName // this type's name identifier.
TypeToken() tokens.Type // this type's (qualified) token.
TypeMembers() ClassMemberMap // this type's members.
Ctor() Function // this type's constructor (or nil if none).
Record() bool // true if this is a record type.
Interface() bool // true if this is an interface type.
}
@ -37,6 +38,7 @@ func (node *PrimitiveType) Base() Type { return nil }
func (node *PrimitiveType) TypeName() tokens.TypeName { return node.Nm }
func (node *PrimitiveType) TypeToken() tokens.Type { return tokens.Type(node.Nm) }
func (node *PrimitiveType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *PrimitiveType) Ctor() Function { return nil }
func (node *PrimitiveType) Record() bool { return false }
func (node *PrimitiveType) Interface() bool { return false }
func (node *PrimitiveType) String() string { return string(node.Token()) }
@ -63,6 +65,7 @@ func (node *PointerType) Base() Type { return nil }
func (node *PointerType) TypeName() tokens.TypeName { return node.Nm }
func (node *PointerType) TypeToken() tokens.Type { return node.Tok }
func (node *PointerType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *PointerType) Ctor() Function { return nil }
func (node *PointerType) Record() bool { return false }
func (node *PointerType) Interface() bool { return false }
func (node *PointerType) String() string { return string(node.Token()) }
@ -101,6 +104,7 @@ func (node *ArrayType) Base() Type { return nil }
func (node *ArrayType) TypeName() tokens.TypeName { return node.Nm }
func (node *ArrayType) TypeToken() tokens.Type { return node.Tok }
func (node *ArrayType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *ArrayType) Ctor() Function { return nil }
func (node *ArrayType) Record() bool { return false }
func (node *ArrayType) Interface() bool { return false }
func (node *ArrayType) String() string { return string(node.Token()) }
@ -140,6 +144,7 @@ func (node *MapType) Base() Type { return nil }
func (node *MapType) TypeName() tokens.TypeName { return node.Nm }
func (node *MapType) TypeToken() tokens.Type { return node.Tok }
func (node *MapType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *MapType) Ctor() Function { return nil }
func (node *MapType) Record() bool { return false }
func (node *MapType) Interface() bool { return false }
func (node *MapType) String() string { return string(node.Token()) }
@ -179,6 +184,7 @@ func (node *FunctionType) Base() Type { return nil }
func (node *FunctionType) TypeName() tokens.TypeName { return node.Nm }
func (node *FunctionType) TypeToken() tokens.Type { return node.Tok }
func (node *FunctionType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *FunctionType) Ctor() Function { return nil }
func (node *FunctionType) Record() bool { return false }
func (node *FunctionType) Interface() bool { return false }
func (node *FunctionType) String() string { return string(node.Token()) }
@ -234,6 +240,7 @@ func (node *ModuleType) TypeToken() tokens.Type {
return tokens.Type(string(node.Module.Token()) + ".modtype")
}
func (node *ModuleType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *ModuleType) Ctor() Function { return nil }
func (node *ModuleType) Record() bool { return false }
func (node *ModuleType) Interface() bool { return false }
func (node *ModuleType) String() string { return string(node.Token()) }
@ -270,6 +277,7 @@ func (node *PrototypeType) TypeToken() tokens.Type {
return tokens.Type(string(node.Type.Token()) + ".prototype")
}
func (node *PrototypeType) TypeMembers() ClassMemberMap { return noClassMembers }
func (node *PrototypeType) Ctor() Function { return nil }
func (node *PrototypeType) Record() bool { return false }
func (node *PrototypeType) Interface() bool { return false }
func (node *PrototypeType) String() string { return string(node.Token()) }

View file

@ -274,6 +274,10 @@ func (e *evaluator) initProperty(this *rt.Object, properties rt.PropertyMap,
}
ptr := properties.InitAddr(key, obj, false)
return ptr, m.Readonly()
case *symbols.Module:
// A module resolves to its module object.
modobj := e.getModuleObject(m)
return properties.InitAddr(key, modobj, false), false
case *symbols.Class:
// A class resolves to its prototype object.
proto := e.getPrototypeObject(m)
@ -808,7 +812,7 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
exty := ex.Type()
if types.CanConvert(thrown.Type(), exty) {
// This type matched, so this handler will catch the exception. Set the exception variable,
// evaluate the block, and swap the rt.Unwind information (thereby "handling" the in-flight exception).
// evaluate the block, and swap the rt.Unwind information ("handling" the in-flight exception).
e.pushScope(nil)
e.locals.SetValue(ex, thrown)
uw = e.evalBlock(catch.Block)
@ -1528,10 +1532,11 @@ func (e *evaluator) getDynamicNameAddr(key tokens.Name, lval bool) *rt.Pointer {
// Finally, if neither of those existed, and this is the target of a load, allocate a slot.
if pv == nil && lval {
if e.fnc.SpecialModInit() {
if e.fnc.SpecialModInit() && e.locals.Frame {
pv = globals.Properties().GetAddr(pkey, true)
} else {
loc := symbols.NewSpecialVariableSym(key, types.Dynamic)
e.ctx.Scope.MustRegister(loc)
pv = e.locals.GetValueAddr(loc, true)
}
}
@ -1552,13 +1557,7 @@ func (e *evaluator) evalNew(node diag.Diagable, t symbols.Type, args *[]ast.Expr
obj := e.newObject(node, t)
// See if there is a constructor method. If there isn't, we just return the fresh object.
if ctor, has := t.TypeMembers()[tokens.ClassConstructorFunction]; has {
ctormeth, isfunc := ctor.(*symbols.ClassMethod)
contract.Assertf(isfunc,
"Expected ctor %v to be a class method; got %v", ctor, reflect.TypeOf(ctor))
contract.Assertf(ctormeth.Sig.Return == nil,
"Expected ctor %v to have a nil return; got %v", ctor, ctormeth.Sig.Return)
if ctor := t.Ctor(); ctor != nil {
// Evaluate the arguments in order.
var argobjs []*rt.Object
if args != nil {
@ -1572,15 +1571,12 @@ func (e *evaluator) evalNew(node diag.Diagable, t symbols.Type, args *[]ast.Expr
}
// Now dispatch the function call using the fresh object as the constructor's `this` argument.
if _, uw := e.evalCall(node, ctormeth, obj, argobjs...); uw != nil {
if _, uw := e.evalCall(node, ctor, obj, argobjs...); uw != nil {
return nil, uw
}
} else {
contract.Assertf(args == nil || len(*args) == 0,
"No constructor found for %v, yet the new expression had %v args", t, len(*args))
class, isclass := t.(*symbols.Class)
contract.Assertf(!isclass || class.Extends == nil,
"No constructor found for %v, yet there is a base class; chaining must be done manually", t)
}
// Finally, ensure that all readonly properties are frozen now.

View file

@ -474,10 +474,14 @@ class Transformer:
def transform_Call(self, node):
# TODO: support named arguments, starargs, etc.
assert node.keywords is None or len(node.keywords) == 0, "Named args not yet supported"
assert node.starargs is None or len(node.starargs) == 0, "Star args not yet supported"
assert node.kwargs is None or len(node.kwargs) == 0, "KW args not yet supported"
func = self.transform_expr(node.func)
args = list()
for arg in args:
args.append(self.transform_expr(arg))
if node.args:
for arg in node.args:
args.append(self.transform_expr(arg))
return ast.InvokeFunctionExpression(func, args, loc=self.loc_from(node))
def transform_Compare(self, node):