Implement a few CIDLC improvements

* Allow `interface{}` to mean "weakly typed property bag."

* Allow slices in IDL types.

* Permit the package base as an argument.
This commit is contained in:
joeduffy 2017-04-27 15:40:51 -07:00
parent df01db5df7
commit 46227870e4
5 changed files with 70 additions and 60 deletions

View file

@ -20,6 +20,7 @@ func NewCIDLCCmd() *cobra.Command {
var name string
var outPack string
var outRPC string
var pkgBase string
var root string
var verbose int
cmd := &cobra.Command{
@ -31,6 +32,8 @@ func NewCIDLCCmd() *cobra.Command {
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
if name == "" {
return errors.New("missing required package name (--name or -n)")
} else if pkgBase == "" {
return errors.New("missing required package base (--pkg-base or -p)")
}
if root == "" {
root, _ = os.Getwd() // default to the current working directory.
@ -64,6 +67,8 @@ func NewCIDLCCmd() *cobra.Command {
&outPack, "out-pack", "", "Save generated package metadata to this directory")
cmd.PersistentFlags().StringVar(
&outRPC, "out-rpc", "", "Save generated RPC provider stubs to this directory")
cmd.PersistentFlags().StringVarP(
&pkgBase, "pkg-base", "p", "", "Specify the package base where this will be published")
cmd.PersistentFlags().StringVarP(
&root, "root", "r", "", "Pick a different root directory than `pwd` (the default)")
cmd.PersistentFlags().IntVarP(

View file

@ -291,62 +291,60 @@ func (chk *Checker) CheckStructFields(t *types.TypeName, s *types.Struct,
diag.Message("field %v.%v is marked `optional` but is not a pointer in the IDL",
t.Name(), fld.Name()))
}
// Only these types are legal:
// - Primitives: bool, float64, string
// - Other structs
// - Pointers to any of the above (if-and-only-if an optional property)
// - Pointers to other resource types (capabilities)
// - Arrays of the above things
// - Maps with string keys and any of the above as values
switch ft := fld.Type().(type) {
case *types.Basic:
if !IsPrimitive(ft) {
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v is an illegal primitive type %v; must be bool, float64, or string",
t.Name(), fld.Name(), ft))
}
case *types.Named:
// TODO: check recursively?
switch ut := ft.Underlying().(type) {
case *types.Basic:
// A named type alias of a primitive type. Ensure it is legal.
if !IsPrimitive(ut) {
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v of type %v is backed by an illegal primitive type %v; "+
"must be bool, float64, or string", t.Name(), fld.Name(), ft, ut))
}
case *types.Struct:
// OK so long as it's not a resource (these are required to be pointers).
if isres, _ := IsResource(ft.Obj(), ut); isres {
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v refers to a resource type %v by-value; field must be a pointer",
t.Name(), fld.Name(), ft))
}
default:
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v is an illegal named field type: %v",
t.Name(), fld.Name(), reflect.TypeOf(ut)))
}
case *types.Pointer:
// A pointer is OK so long as the field is either optional or a resource type.
if !opts.Optional {
if isres, _ := IsResource(nil, ft.Elem()); !isres {
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v is an illegal pointer; must be optional or of a resource type",
t.Name(), fld.Name()))
}
}
default:
contract.Failf("Unrecognized field type: %v (type=%v typetype=%v)",
fld.Name(), fld.Type(), reflect.TypeOf(fld.Type()))
if err := chk.CheckIDLType(fld.Type(), opts); err != nil {
ok = false
cmdutil.Sink().Errorf(
diag.Message("field %v.%v is an not a legal IDL type: %v", t.Name(), fld.Name(), err))
}
}
}
return ok, allprops, allopts
}
func (chk *Checker) CheckIDLType(t types.Type, opts PropertyOptions) error {
// Only these types are legal:
// - Primitives: bool, float64, string
// - Other structs
// - Pointers to any of the above (if-and-only-if an optional property)
// - Pointers to other resource types (capabilities)
// - Arrays of the above things
// - Maps with string keys and any of the above as values
switch ft := t.(type) {
case *types.Basic:
if !IsPrimitive(ft) {
return errors.Errorf("bad primitive type %v; must be bool, float64, or string", ft)
}
case *types.Interface:
// interface{} is fine and is interpreted as a weakly typed map.
return nil
case *types.Named:
switch ut := ft.Underlying().(type) {
case *types.Basic:
// A named type alias of a primitive type. Ensure it is legal.
if !IsPrimitive(ut) {
return errors.Errorf(
"typedef %v backed by bad primitive type %v; must be bool, float64, or string", ft, ut)
}
case *types.Struct:
// Struct types are okay so long as they aren't resources (these are required to be pointers).
if isres, _ := IsResource(ft.Obj(), ut); isres {
return errors.Errorf("resource type %v cannot be referenced by-value; must be a pointer", ft)
}
default:
return errors.Errorf("bad named field type: %v", reflect.TypeOf(ut))
}
case *types.Pointer:
// A pointer is OK so long as the field is either optional or a resource type.
if !opts.Optional {
if isres, _ := IsResource(nil, ft.Elem()); !isres {
return errors.New("bad pointer; must be optional or a resource type")
}
}
case *types.Slice:
// A slice is OK so long as its element type is also OK.
return chk.CheckIDLType(ft.Elem(), PropertyOptions{})
default:
contract.Failf("Unrecognized field type %v: %v", t, reflect.TypeOf(t))
}
return nil
}

View file

@ -12,7 +12,8 @@ import (
type CompileOptions struct {
Name tokens.PackageName // the package name.
Root string // the root package.
Root string // the package root on the filesystem.
PkgBase string // the base package name.
OutPack string // the package output location.
OutRPC string // the RPC output location.
}
@ -40,7 +41,7 @@ func Compile(opts CompileOptions, paths ...string) error {
}
var rpcgen *RPCGenerator
if out := opts.OutRPC; out != "" {
rpcgen = NewRPCGenerator(opts.Root, out)
rpcgen = NewRPCGenerator(opts.Root, opts.PkgBase, out)
}
for _, pkginfo := range prog.Created {
pkg, err := chk.Check(opts.Name, pkginfo)

View file

@ -308,6 +308,8 @@ func (g *PackGenerator) GenTypeName(t types.Type) string {
default:
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
}
case *types.Interface:
return "any"
case *types.Named:
// If this came from the same package; the imports will have arranged for it to be available by name.
obj := u.Obj()

View file

@ -17,14 +17,16 @@ import (
type RPCGenerator struct {
Root string
PkgBase string
Out string
Currpkg *Package // the package currently being visited.
}
func NewRPCGenerator(root string, out string) *RPCGenerator {
func NewRPCGenerator(root string, pkgbase string, out string) *RPCGenerator {
return &RPCGenerator{
Root: root,
Out: out,
Root: root,
PkgBase: pkgbase,
Out: out,
}
}
@ -397,6 +399,8 @@ func (g *RPCGenerator) GenTypeName(t types.Type) string {
default:
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
}
case *types.Interface:
return "resource.PropertyMap"
case *types.Named:
obj := u.Obj()
// For resource types, simply emit an ID, since that is what will have been serialized.
@ -415,7 +419,7 @@ func (g *RPCGenerator) GenTypeName(t types.Type) string {
// Otherwise, we will need to refer to a qualified import name.
// TODO: we will need to generate the right imports before we can emit such names.
contract.Failf("Cross-package IDL references not yet supported")
contract.Failf("Cross-package IDL references not yet supported: pkg=%v name=%v", pkg, name)
case *types.Map:
return fmt.Sprintf("map[%v]%v", g.GenTypeName(u.Key()), g.GenTypeName(u.Elem()))
case *types.Pointer: