| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package vdl |
| |
| import ( |
| "errors" |
| "fmt" |
| "strconv" |
| "strings" |
| "sync" |
| ) |
| |
| var ( |
| errNameNonEmpty = errors.New("any and typeobject cannot be renamed") |
| errNoLabels = errors.New("no enum labels") |
| errLabelEmpty = errors.New("empty enum label") |
| errHasLabels = errors.New("labels only valid for enum") |
| errLenZero = errors.New("negative or zero array length") |
| errLenNonZero = errors.New("length only valid for array") |
| errElemNil = errors.New("nil elem type") |
| errElemNonNil = errors.New("elem only valid for array, list and map") |
| errKeyNil = errors.New("nil key type") |
| errKeyNonNil = errors.New("key only valid for set and map") |
| errFieldTypeNil = errors.New("nil field type") |
| errFieldNameEmpty = errors.New("empty field name") |
| errNoFields = errors.New("no union fields") |
| errHasFields = errors.New("fields only valid for struct or union") |
| errBaseNil = errors.New("nil base type for named type") |
| errBaseCycle = errors.New("invalid named type cycle") |
| errNotBuilt = errors.New("TypeBuilder.Build must be called before Pending.Built") |
| ) |
| |
| // Primitive types, the basis for all other types. All have empty names. |
| var ( |
| AnyType = primitiveType(Any) |
| BoolType = primitiveType(Bool) |
| ByteType = primitiveType(Byte) |
| Uint16Type = primitiveType(Uint16) |
| Uint32Type = primitiveType(Uint32) |
| Uint64Type = primitiveType(Uint64) |
| Int8Type = primitiveType(Int8) |
| Int16Type = primitiveType(Int16) |
| Int32Type = primitiveType(Int32) |
| Int64Type = primitiveType(Int64) |
| Float32Type = primitiveType(Float32) |
| Float64Type = primitiveType(Float64) |
| Complex64Type = primitiveType(Complex64) |
| Complex128Type = primitiveType(Complex128) |
| StringType = primitiveType(String) |
| TypeObjectType = primitiveType(TypeObject) |
| ) |
| |
| // ErrorType describes the built-in error type. |
| var ErrorType = OptionalType(NamedType("error", StructType( |
| Field{"Id", StringType}, |
| Field{"RetryCode", EnumType("NoRetry", "RetryConnection", "RetryRefetch", "RetryBackoff")}, |
| Field{"Msg", StringType}, |
| Field{"ParamList", ListType(AnyType)}, |
| ))) |
| |
| // The ErrorType above must be kept in-sync with WireError. |
| |
| func primitiveType(k Kind) *Type { |
| prim, err := typeCons(&Type{kind: k}) |
| if err != nil { |
| panic(err) |
| } |
| return prim |
| } |
| |
| // TypeOrPending only allows *Type or Pending values; other values cause a |
| // compile-time error. It's used as the argument type for TypeBuilder methods, |
| // to allow either fully built *Type values or Pending values as subtypes. |
| type TypeOrPending interface { |
| // ptype returns the pending type, which may be only partially built. |
| ptype() *Type |
| } |
| |
| // PendingType represents a type that's being built by the TypeBuilder. |
| type PendingType interface { |
| TypeOrPending |
| // Built returns the final built and hash-consed type. Build must be called |
| // on the TypeBuilder before Built is called on any pending type. If any |
| // pending type has a build error, Built returns a nil type for all pending |
| // types, and returns non-nil errors for at least one pending type. |
| Built() (*Type, error) |
| } |
| |
| // PendingOptional represents an Optional type that is being built. Given a base |
| // type that is non-optional, you can build a new type that is optional. |
| type PendingOptional interface { |
| PendingType |
| // AssignElem assigns the Optional elem type. |
| AssignElem(elem TypeOrPending) PendingOptional |
| } |
| |
| // PendingEnum represents an Enum type that is being built. |
| type PendingEnum interface { |
| PendingType |
| // AppendLabel appends an Enum label. Every Enum must have at least one |
| // label, and each label must not be empty. |
| AppendLabel(label string) PendingEnum |
| } |
| |
| // PendingArray represents an Array type that is being built. |
| type PendingArray interface { |
| PendingType |
| // AssignLen assigns the Array length. |
| AssignLen(len int) PendingArray |
| // AssignElem assigns the Array element type. |
| AssignElem(elem TypeOrPending) PendingArray |
| } |
| |
| // PendingList represents a List type that is being built. |
| type PendingList interface { |
| PendingType |
| // AssignElem assigns the List element type. |
| AssignElem(elem TypeOrPending) PendingList |
| } |
| |
| // PendingSet represents a Set type that is being built. |
| type PendingSet interface { |
| PendingType |
| // AssignKey assigns the Set key type. |
| AssignKey(key TypeOrPending) PendingSet |
| } |
| |
| // PendingMap represents a Map type that is being built. |
| type PendingMap interface { |
| PendingType |
| // AssignKey assigns the Map key type. |
| AssignKey(key TypeOrPending) PendingMap |
| // AssignElem assigns the Map element type. |
| AssignElem(elem TypeOrPending) PendingMap |
| } |
| |
| // PendingStruct represents a Struct type that is being built. |
| type PendingStruct interface { |
| PendingType |
| // AppendField appends the Struct field with the given name and t. The name |
| // must not be empty. The ordering of fields is preserved; different |
| // orderings create different types. |
| AppendField(name string, t TypeOrPending) PendingStruct |
| // NumField returns the number of fields appended so far. |
| NumField() int |
| } |
| |
| // PendingUnion represents a Union type that is being built. |
| type PendingUnion interface { |
| PendingType |
| // AppendField appends the Union field with the given name and t. The name |
| // must not be empty. The ordering of fields is preserved; different |
| // orderings create different types. |
| AppendField(name string, t TypeOrPending) PendingUnion |
| // NumField returns the number of fields appended so far. |
| NumField() int |
| } |
| |
| // PendingNamed represents a named type that is being built. Given a base type |
| // you can build a new type with an identical underlying structure, but a |
| // different name. |
| type PendingNamed interface { |
| PendingType |
| // AssignBase assigns the base type of the named type. The resulting built |
| // type will have the same underlying structure as base, but with the given |
| // name. |
| AssignBase(base TypeOrPending) PendingNamed |
| } |
| |
| type ( |
| // pending implements common functionality for all pending objects. |
| pending struct { |
| *Type // Holds pending type pre-Build, and the result post-Build. |
| err error // Build error for this pending type. |
| } |
| |
| // Each pending object holds a *Type that it fills in as the user calls |
| // methods to describe the type. When Build is called, the type is |
| // hash-consed to the final result. |
| pendingOptional struct{ *pending } |
| pendingEnum struct{ *pending } |
| pendingArray struct{ *pending } |
| pendingList struct{ *pending } |
| pendingSet struct{ *pending } |
| pendingMap struct{ *pending } |
| pendingStruct struct{ *pending } |
| pendingUnion struct{ *pending } |
| pendingNamed struct{ *pending } |
| ) |
| |
| func (p pendingOptional) AssignElem(elem TypeOrPending) PendingOptional { |
| p.elem = elem.ptype() |
| return p |
| } |
| |
| func (p pendingEnum) AppendLabel(label string) PendingEnum { |
| p.labels = append(p.labels, label) |
| return p |
| } |
| |
| func (p pendingArray) AssignLen(len int) PendingArray { |
| p.len = len |
| return p |
| } |
| |
| func (p pendingArray) AssignElem(elem TypeOrPending) PendingArray { |
| p.elem = elem.ptype() |
| return p |
| } |
| |
| func (p pendingList) AssignElem(elem TypeOrPending) PendingList { |
| p.elem = elem.ptype() |
| return p |
| } |
| |
| func (p pendingSet) AssignKey(key TypeOrPending) PendingSet { |
| p.key = key.ptype() |
| return p |
| } |
| |
| func (p pendingMap) AssignKey(key TypeOrPending) PendingMap { |
| p.key = key.ptype() |
| return p |
| } |
| |
| func (p pendingMap) AssignElem(elem TypeOrPending) PendingMap { |
| p.elem = elem.ptype() |
| return p |
| } |
| |
| func (p pendingStruct) AppendField(name string, t TypeOrPending) PendingStruct { |
| p.fields = append(p.fields, Field{name, t.ptype()}) |
| return p |
| } |
| |
| func (p pendingStruct) NumField() int { |
| return len(p.fields) |
| } |
| |
| func (p pendingUnion) AppendField(name string, t TypeOrPending) PendingUnion { |
| p.fields = append(p.fields, Field{name, t.ptype()}) |
| return p |
| } |
| |
| func (p pendingUnion) NumField() int { |
| return len(p.fields) |
| } |
| |
| func (p pendingNamed) AssignBase(base TypeOrPending) PendingNamed { |
| // Pending named types are special - they have the internalNamed kind, and put |
| // the base type in elem. See pending.finalize() for the extra logic. |
| p.elem = base.ptype() |
| return p |
| } |
| |
| // TypeBuilder builds Types. There are two phases: 1) Create Pending* objects |
| // and describe each type, and 2) call Build. When Build is called, all types |
| // are created and may be retrieved by calling Built on the pending type. This |
| // two-phase building enables support for recursive types, and also makes it |
| // easy to construct a group of dependent types without determining their |
| // dependency ordering. The separation between Build and Built allows |
| // individual errors to be returned for each pending type, and easily associated |
| // with additional information for the pending type, e.g. position information |
| // in a compiler. |
| // |
| // Each TypeBuilder instance enforces the rule that type names are unique; each |
| // named type must be represented by exactly one Type or PendingType object. |
| // E.g. you can't create an enum "Foo" and a struct "Foo" via the same |
| // TypeBuilder, nor can you create two structs named "Foo", even if they have |
| // the same fields. This rule simplifies the hash consing logic. |
| // |
| // There is no enforcement of unique names across TypeBuilder instances; the val |
| // package allows different types with the same names. This allows support for |
| // a single named type with multiple versions, all handled within a single |
| // address space. |
| // |
| // The zero TypeBuilder represents an empty builder. |
| type TypeBuilder struct { |
| ptypes []*pending |
| } |
| |
| func (b *TypeBuilder) add(t *Type) *pending { |
| // Every pending object starts with the errNotBuilt error, which will be |
| // overridden when the type is actually built. |
| p := &pending{Type: t, err: errNotBuilt} |
| b.ptypes = append(b.ptypes, p) |
| return p |
| } |
| |
| // Optional returns PendingOptional, used to describe an Optional type. |
| func (b *TypeBuilder) Optional() PendingOptional { |
| return pendingOptional{b.add(&Type{kind: Optional})} |
| } |
| |
| // Enum returns PendingEnum, used to describe an Enum type. |
| func (b *TypeBuilder) Enum() PendingEnum { |
| return pendingEnum{b.add(&Type{kind: Enum})} |
| } |
| |
| // Array returns PendingArray, used to describe an Array type. |
| func (b *TypeBuilder) Array() PendingArray { |
| return pendingArray{b.add(&Type{kind: Array})} |
| } |
| |
| // List returns PendingList, used to describe a List type. |
| func (b *TypeBuilder) List() PendingList { |
| return pendingList{b.add(&Type{kind: List})} |
| } |
| |
| // Set returns PendingSet, used to describe a Set type. |
| func (b *TypeBuilder) Set() PendingSet { |
| return pendingSet{b.add(&Type{kind: Set})} |
| } |
| |
| // Map returns PendingMap, used to describe a Map type. |
| func (b *TypeBuilder) Map() PendingMap { |
| return pendingMap{b.add(&Type{kind: Map})} |
| } |
| |
| // Struct returns PendingStruct, used to describe a Struct type. |
| func (b *TypeBuilder) Struct() PendingStruct { |
| return pendingStruct{b.add(&Type{kind: Struct})} |
| } |
| |
| // Union returns PendingUnion, used to describe a Union type. |
| func (b *TypeBuilder) Union() PendingUnion { |
| return pendingUnion{b.add(&Type{kind: Union})} |
| } |
| |
| // Named returns PendingNamed, used to describe a named type based on another |
| // type. |
| func (b *TypeBuilder) Named(name string) PendingNamed { |
| return pendingNamed{b.add(&Type{kind: internalNamed, name: name})} |
| } |
| |
| // Build builds all pending types. Build must be called before Built may be |
| // called on each pending type to retrieve the final result. |
| // |
| // Build guarantees that either all pending types are successfully built, or |
| // none of them are. I.e. all calls to Built will either return a non-nil Type |
| // and nil error, or nil Type. The pending type(s) that had build errors will |
| // return non-nil errors. |
| func (b *TypeBuilder) Build() { |
| // First finalize all types, indicating no more mutations will occur. |
| for _, p := range b.ptypes { |
| p.err = p.finalize() |
| } |
| // Now enforce the rule that type names are unique. This must occur before we |
| // hash cons anything, to catch tricky cases where hash consing is difficult. |
| // See uniqueTypeStr for more info. |
| names := make(map[string]*Type) |
| for _, p := range b.ptypes { |
| if err := enforceUniqueNames(p.Type, names); err != nil && p.err == nil { |
| p.err = err |
| } |
| } |
| // Now hash cons each pending type. |
| for _, p := range b.ptypes { |
| if p.err != nil { |
| continue // skip this type since it already has an error |
| } |
| p.Type, p.err = typeCons(p.Type) |
| } |
| // If any pending type has a build error, make sure all built types are nil. |
| for _, p := range b.ptypes { |
| if p.err != nil { |
| for _, q := range b.ptypes { |
| q.Type = nil |
| } |
| return |
| } |
| } |
| } |
| |
| func (p *pending) ptype() *Type { return p.Type } |
| |
| // finalize indicates Build has been called, and the pending type will not be |
| // mutated anymore. |
| func (p *pending) finalize() error { |
| if p.Type.kind == internalNamed { |
| // Now that the mutations have finished, we can copy the base type into |
| // p.Type, keeping the name of p.Type. |
| name, base := p.Type.name, p.Type.elem |
| if name == "" { |
| return fmt.Errorf("PendingNamed used to build unnamed type based on %v", base) |
| } |
| // There may be a chain of named types, in which case we'll need to follow |
| // the chain to the first type that's not internalNamed. Watch out for |
| // cycles, which could arise due to corrupt input. |
| seen := map[*Type]bool{p.Type: true} |
| for { |
| if base == nil { |
| return errBaseNil |
| } |
| if base.kind != internalNamed { |
| break |
| } |
| if seen[base] { |
| return errBaseCycle |
| } |
| seen[base] = true |
| base = base.elem |
| } |
| *p.Type = *base |
| p.Type.name = name |
| p.Type.unique = "" |
| } |
| return nil |
| } |
| |
| func (p *pending) Built() (*Type, error) { |
| return p.Type, p.err |
| } |
| |
| func checkedBuild(b TypeBuilder, p PendingType) *Type { |
| b.Build() |
| t, err := p.Built() |
| if err != nil { |
| panic(err) |
| } |
| return t |
| } |
| |
| // OptionalType is a helper using TypeBuilder to create a single Optional type. |
| // Panics on all errors. |
| func OptionalType(elem *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.Optional().AssignElem(elem)) |
| } |
| |
| // EnumType is a helper using TypeBuilder to create a single Enum type. |
| // Panics on all errors. |
| func EnumType(labels ...string) *Type { |
| var b TypeBuilder |
| e := b.Enum() |
| for _, l := range labels { |
| e.AppendLabel(l) |
| } |
| return checkedBuild(b, e) |
| } |
| |
| // ArrayType is a helper using TypeBuilder to create a single Array type. |
| // Panics on all errors. |
| func ArrayType(len int, elem *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.Array().AssignLen(len).AssignElem(elem)) |
| } |
| |
| // ListType is a helper using TypeBuilder to create a single List type. Panics |
| // on all errors. |
| func ListType(elem *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.List().AssignElem(elem)) |
| } |
| |
| // SetType is a helper using TypeBuilder to create a single Set type. Panics on |
| // all errors. |
| func SetType(key *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.Set().AssignKey(key)) |
| } |
| |
| // MapType is a helper using TypeBuilder to create a single Map type. Panics |
| // on all errors. |
| func MapType(key, elem *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.Map().AssignKey(key).AssignElem(elem)) |
| } |
| |
| // StructType is a helper using TypeBuilder to create a single Struct type. |
| // Panics on all errors. |
| func StructType(fields ...Field) *Type { |
| var b TypeBuilder |
| s := b.Struct() |
| for _, f := range fields { |
| s.AppendField(f.Name, f.Type) |
| } |
| return checkedBuild(b, s) |
| } |
| |
| // UnionType is a helper using TypeBuilder to create a single Union type. |
| // Panics on all errors. |
| func UnionType(fields ...Field) *Type { |
| var b TypeBuilder |
| o := b.Union() |
| for _, f := range fields { |
| o.AppendField(f.Name, f.Type) |
| } |
| return checkedBuild(b, o) |
| } |
| |
| // NamedType is a helper using TypeBuilder to create a single named type based |
| // on another type. Panics on all errors. |
| func NamedType(name string, base *Type) *Type { |
| var b TypeBuilder |
| return checkedBuild(b, b.Named(name).AssignBase(base)) |
| } |
| |
| // enforceUniqueNames ensures that t and its subtypes contain unique type names; |
| // every non-empty type name corresponds to exactly one *Type. |
| func enforceUniqueNames(t *Type, names map[string]*Type) error { |
| if t == nil || t.name == "" { |
| return nil |
| } |
| if found := names[t.name]; found != nil { |
| if found != t { |
| return fmt.Errorf("duplicate type names %q and %q", found, t) |
| } |
| return nil |
| } |
| // First time seeing this type, put it in names and call recursively. |
| names[t.name] = t |
| if err := enforceUniqueNames(t.elem, names); err != nil { |
| return err |
| } |
| if err := enforceUniqueNames(t.key, names); err != nil { |
| return err |
| } |
| for _, x := range t.fields { |
| if err := enforceUniqueNames(x.Type, names); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // uniqueTypeStr returns a unique string representing t, which is also its |
| // human-readable representation. Invariant: two types A and B have the same |
| // unique string iff they are equal, even if they haven't been hash-consed yet. |
| // |
| // Think of each type as a graph, where nodes represent each type, and edges |
| // point from composite type to subtype. Recursive types form a cycle in this |
| // graph. If two type graphs are the same, the two types are equal. |
| // |
| // There is a subtlety. Since we haven't hash-consed the types yet, it's |
| // possible that two different graphs also represent equal types. E.g. consider |
| // the type: |
| // type A struct {x []string;y []string} |
| // |
| // There are two different representations: |
| // A (not consed) A (hash-consed) |
| // x/ \y x/ \y |
| // / \ \ / |
| // []string []string []string |
| // |
| // Both of these representations must return the same unique string. To |
| // accomplish this, we recursively traverse the graph and dump the semantic |
| // contents of each type node. The seen map breaks infinite loops from |
| // recursive types. Since type cycles may only be created via named types, we |
| // keep track of seen types and only dump their names. |
| func uniqueTypeStr(t *Type, seen map[*Type]bool) string { |
| if seen[t] && t.name != "" { |
| return t.name |
| } |
| seen[t] = true |
| s := t.name |
| if s != "" { |
| s += " " |
| } |
| switch t.kind { |
| case Optional: |
| return s + "?" + uniqueTypeStr(t.elem, seen) |
| case Enum: |
| return s + "enum{" + strings.Join(t.labels, ";") + "}" |
| case Array: |
| return s + "[" + strconv.Itoa(t.len) + "]" + uniqueTypeStr(t.elem, seen) |
| case List: |
| return s + "[]" + uniqueTypeStr(t.elem, seen) |
| case Set: |
| return s + "set[" + uniqueTypeStr(t.key, seen) + "]" |
| case Map: |
| return s + "map[" + uniqueTypeStr(t.key, seen) + "]" + uniqueTypeStr(t.elem, seen) |
| case Struct, Union: |
| if t.kind == Struct { |
| s += "struct{" |
| } else { |
| s += "union{" |
| } |
| for index, f := range t.fields { |
| if index > 0 { |
| s += ";" |
| } |
| s += f.Name + " " + uniqueTypeStr(f.Type, seen) |
| } |
| return s + "}" |
| default: |
| return s + t.kind.String() |
| } |
| } |
| |
| var ( |
| // typeReg holds a global set of hash-consed types. Hash-consing is based on |
| // the string representation of the type. See comments in uniqueType for an |
| // explanation of subtleties. |
| typeReg = map[string]*Type{} |
| typeRegMu sync.Mutex |
| ) |
| |
| // typeCons returns the hash-consed Type for a given Type t. |
| func typeCons(t *Type) (*Type, error) { |
| if err := validType(t); err != nil { |
| return nil, err |
| } |
| typeRegMu.Lock() |
| cons := typeConsLocked(t) |
| typeRegMu.Unlock() |
| return cons, nil |
| } |
| |
| func typeConsLocked(t *Type) *Type { |
| if t == nil { |
| return nil |
| } |
| // Look for the type in our registry, based on its unique string. |
| if t.unique == "" { |
| t.unique = uniqueTypeStr(t, make(map[*Type]bool)) |
| } |
| if found := typeReg[t.unique]; found != nil { |
| return found |
| } |
| // Not found in the registry, add it and recursively cons subtypes. We cons |
| // the outer type first to deal with recursive types; otherwise we'd have an |
| // infinite loop. |
| typeReg[t.unique] = t |
| t.containsKind.Set(t.kind) |
| t.elem = typeConsLocked(t.elem) |
| if t.elem != nil { |
| t.containsKind |= t.elem.containsKind |
| } |
| t.key = typeConsLocked(t.key) |
| if t.key != nil { |
| t.containsKind |= t.key.containsKind |
| } |
| for index := range t.fields { |
| t.fields[index].Type = typeConsLocked(t.fields[index].Type) |
| t.containsKind |= t.fields[index].Type.containsKind |
| } |
| return t |
| } |
| |
| // validType returns a nil error iff t and all subtypes are valid. |
| func validType(t *Type) error { |
| allTypes := make(map[*Type]bool) |
| if err := verifyAndCollectAllTypes(t, allTypes); err != nil { |
| return err |
| } |
| if err := existsStrictCycle(allTypes); err != nil { |
| return err |
| } |
| if err := existsInvalidKey(allTypes); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // isValidKey returns true iff t is a valid set or map key. |
| func isValidKey(t *Type, seen map[*Type]bool) bool { |
| if seen[t] { |
| return true |
| } |
| seen[t] = true |
| switch t.kind { |
| case Any, List, Map, Optional, Set, TypeObject: |
| return false |
| case Array: |
| return isValidKey(t.elem, seen) |
| case Struct, Union: |
| for _, x := range t.fields { |
| if !isValidKey(x.Type, seen) { |
| return false |
| } |
| } |
| } |
| return true |
| } |
| |
| // existsInvalidKey returns a nil error iff the given Types all have valid set |
| // and map keys. |
| func existsInvalidKey(allTypes map[*Type]bool) error { |
| seen := make(map[*Type]bool) |
| for t, _ := range allTypes { |
| if (t.kind == Map || t.kind == Set) && !isValidKey(t.key, seen) { |
| return fmt.Errorf("invalid key %q in %q", t.key, t) |
| } |
| } |
| return nil |
| } |
| |
| // typeInStrictCycle returns a subtype that belongs to a strict cycle or nil if |
| // there are no strict cycles |
| func typeInStrictCycle(t *Type, seen map[*Type]int) *Type { |
| if seen[t] == 1 { |
| return t |
| } |
| if seen[t] == 2 { |
| return nil |
| } |
| seen[t] = 1 |
| switch t.kind { |
| case Array: |
| if typeInCycle := typeInStrictCycle(t.elem, seen); typeInCycle != nil { |
| return typeInCycle |
| } |
| case Struct, Union: |
| for _, x := range t.fields { |
| if typeInCycle := typeInStrictCycle(x.Type, seen); typeInCycle != nil { |
| return typeInCycle |
| } |
| } |
| } |
| seen[t] = 2 |
| return nil |
| } |
| |
| // existsStrictCycle returns a nil error iff the given type set has no strict |
| // cycles (e.g. type A struct{Elem: A}) |
| func existsStrictCycle(allTypes map[*Type]bool) error { |
| seen := make(map[*Type]int) |
| for t, _ := range allTypes { |
| if typeInCycle := typeInStrictCycle(t, seen); typeInCycle != nil { |
| return fmt.Errorf("type %q is inside of a strict cycle", typeInCycle) |
| } |
| } |
| return nil |
| } |
| |
| // verifyAndCollectAllTypes returns a nil error iff t and all subtypes are |
| // correctly defined. If all subtypes are correctly defined allTypes will be |
| // filled with all subtypes of the given Type t. |
| func verifyAndCollectAllTypes(t *Type, allTypes map[*Type]bool) error { |
| if t == nil || allTypes[t] { |
| return nil |
| } |
| allTypes[t] = true |
| // Check kind |
| switch t.kind { |
| case internalNamed: |
| return fmt.Errorf("internal kind %d used in verifyAndCollectAllTypes", t.kind) |
| } |
| // Check name |
| switch t.kind { |
| case Any, TypeObject: |
| if t.name != "" { |
| return errNameNonEmpty |
| } |
| } |
| // Check len |
| switch t.kind { |
| case Array: |
| if t.len <= 0 { |
| return errLenZero |
| } |
| default: |
| if t.len != 0 { |
| return errLenNonZero |
| } |
| } |
| // Check elem |
| switch t.kind { |
| case Array, List, Map: |
| if t.elem == nil { |
| return errElemNil |
| } |
| case Optional: |
| if t.elem == nil { |
| return errElemNil |
| } |
| if !t.elem.CanBeOptional() { |
| return fmt.Errorf("invalid optional type %q", t) |
| } |
| default: |
| if t.elem != nil { |
| return errElemNonNil |
| } |
| } |
| // Check key |
| switch t.kind { |
| case Set, Map: |
| if t.key == nil { |
| return errKeyNil |
| } |
| default: |
| if t.key != nil { |
| return errKeyNonNil |
| } |
| } |
| // Check labels |
| switch t.kind { |
| case Enum: |
| if len(t.labels) == 0 { |
| return errNoLabels |
| } |
| for _, l := range t.labels { |
| if l == "" { |
| return errLabelEmpty |
| } |
| } |
| default: |
| if len(t.labels) > 0 { |
| return errHasLabels |
| } |
| } |
| // Check fields |
| switch t.kind { |
| case Struct, Union: |
| seenFields := make(map[string]bool, len(t.fields)) |
| for _, f := range t.fields { |
| if f.Type == nil { |
| return errFieldTypeNil |
| } |
| if f.Name == "" { |
| return errFieldNameEmpty |
| } |
| if seenFields[f.Name] { |
| return fmt.Errorf("%q has duplicate field name %q", t.name, f.Name) |
| } |
| seenFields[f.Name] = true |
| } |
| // We allow struct{} but not union{}; we rely on union having at least one |
| // field, and we special-case field 0. E.g. the zero value of union is the |
| // zero value of the type of field 0. |
| if t.kind == Union && len(t.fields) == 0 { |
| return errNoFields |
| } |
| default: |
| if len(t.fields) > 0 { |
| return errHasFields |
| } |
| } |
| // Check subtypes recursively. |
| if err := verifyAndCollectAllTypes(t.elem, allTypes); err != nil { |
| return err |
| } |
| if err := verifyAndCollectAllTypes(t.key, allTypes); err != nil { |
| return err |
| } |
| for _, x := range t.fields { |
| if err := verifyAndCollectAllTypes(x.Type, allTypes); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |