blob: 60233731839e5d18617070b2e84a2a885e4c9a7b [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 mixer
import (
"fmt"
"math"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
// ValueVisitor is an API that walks GIDL values.
type ValueVisitor interface {
OnBool(value bool)
OnInt64(value int64, typ fidlgen.PrimitiveSubtype)
OnUint64(value uint64, typ fidlgen.PrimitiveSubtype)
OnFloat64(value float64, typ fidlgen.PrimitiveSubtype)
OnString(value string, decl *StringDecl)
OnHandle(value gidlir.Handle, decl *HandleDecl)
OnBits(value interface{}, decl *BitsDecl)
OnEnum(value interface{}, decl *EnumDecl)
OnStruct(value gidlir.Record, decl *StructDecl)
OnTable(value gidlir.Record, decl *TableDecl)
OnUnion(value gidlir.Record, decl *UnionDecl)
OnArray(value []interface{}, decl *ArrayDecl)
OnVector(value []interface{}, decl *VectorDecl)
OnNull(decl Declaration)
}
// Visit is the entry point into visiting a value, it dispatches appropriately
// into the visitor.
func Visit(visitor ValueVisitor, value interface{}, decl Declaration) {
switch value := value.(type) {
case bool:
visitor.OnBool(value)
case int64:
switch decl := decl.(type) {
case *BitsDecl:
visitor.OnBits(value, decl)
case *EnumDecl:
visitor.OnEnum(value, decl)
case *IntegerDecl:
visitor.OnInt64(value, decl.Subtype())
default:
panic(fmt.Sprintf("int64 value has non-integer decl: %T", decl))
}
case uint64:
switch decl := decl.(type) {
case *BitsDecl:
visitor.OnBits(value, decl)
case *EnumDecl:
visitor.OnEnum(value, decl)
case *IntegerDecl:
visitor.OnUint64(value, decl.Subtype())
default:
panic(fmt.Sprintf("uint64 value has non-integer decl: %T", decl))
}
case float64:
visitor.OnFloat64(value, decl.(*FloatDecl).Subtype())
case string:
switch decl := decl.(type) {
case *StringDecl:
visitor.OnString(value, decl)
default:
panic(fmt.Sprintf("string value has non-string decl: %T", decl))
}
case gidlir.HandleWithRights:
switch decl := decl.(type) {
case *HandleDecl:
visitor.OnHandle(value.Handle, decl)
default:
panic(fmt.Sprintf("handle value has non-handle decl: %T", decl))
}
case gidlir.Record:
switch decl := decl.(type) {
case *StructDecl:
visitor.OnStruct(value, decl)
case *TableDecl:
visitor.OnTable(value, decl)
case *UnionDecl:
visitor.OnUnion(value, decl)
default:
panic(fmt.Sprintf("expected %T, got %T: %v", decl, value, value))
}
case []interface{}:
switch decl := decl.(type) {
case *ArrayDecl:
visitor.OnArray(value, decl)
case *VectorDecl:
visitor.OnVector(value, decl)
default:
panic(fmt.Sprintf("not implemented: %T", decl))
}
case nil:
if !decl.IsNullable() {
panic(fmt.Sprintf("got nil for non-nullable type: %T", decl))
}
visitor.OnNull(decl)
default:
panic(fmt.Sprintf("not implemented: %T", value))
}
}
// Declaration is the GIDL-level concept of a FIDL type. It is more convenient
// to work with in GIDL backends than fidl.Type. It also provides logic for
// testing if a GIDL value conforms to the declaration.
type Declaration interface {
// IsNullable returns true for nullable types. For example, it returns false
// for string and true for string?.
IsNullable() bool
// conforms verifies that the value conforms to this declaration.
conforms(value interface{}, ctx context) error
}
// Assert that wrappers conform to the Declaration interface.
var _ = []Declaration{
(*BoolDecl)(nil),
(*IntegerDecl)(nil),
(*FloatDecl)(nil),
(*StringDecl)(nil),
(*HandleDecl)(nil),
(*BitsDecl)(nil),
(*EnumDecl)(nil),
(*StructDecl)(nil),
(*TableDecl)(nil),
(*UnionDecl)(nil),
(*ArrayDecl)(nil),
(*VectorDecl)(nil),
}
// context stores the external information needed to determine if a value
// conforms to a declaration.
type context struct {
// Handle definitions in scope, used for HandleDecl conformance.
handleDefs []gidlir.HandleDef
// If true, handle types are ignored in conforms(). This allows us to
// express EncodeSuccess tests with incorrect handle types (which pass
// because bindings do not check handles during encoding).
ignoreWrongHandleType bool
}
type PrimitiveDeclaration interface {
Declaration
// Subtype returns the primitive subtype (bool, uint32, float64, etc.).
Subtype() fidlgen.PrimitiveSubtype
}
// Assert that wrappers conform to the PrimitiveDeclaration interface.
var _ = []PrimitiveDeclaration{
(*BoolDecl)(nil),
(*IntegerDecl)(nil),
(*FloatDecl)(nil),
}
type NamedDeclaration interface {
Declaration
// Name returns the fully qualified name of this declaration, e.g.
// "the.library.name/TheTypeName".
// TODO(fxbug.dev/39407): Return common.DeclName.
Name() string
}
// Assert that wrappers conform to the NamedDeclaration interface.
var _ = []NamedDeclaration{
(*BitsDecl)(nil),
(*EnumDecl)(nil),
(*StructDecl)(nil),
(*TableDecl)(nil),
(*UnionDecl)(nil),
}
type RecordDeclaration interface {
NamedDeclaration
// IsResourceType returns true if the type is marked as a resource, meaning
// it may contain handles.
IsResourceType() bool
// AllFields returns the names of all fields in the type.
FieldNames() []string
// Field returns the declaration for the field with the given name. It
// returns false if no field with that name exists.
Field(name string) (Declaration, bool)
}
// Assert that wrappers conform to the RecordDeclaration interface.
var _ = []RecordDeclaration{
(*StructDecl)(nil),
(*TableDecl)(nil),
(*UnionDecl)(nil),
}
type ListDeclaration interface {
Declaration
// Elem returns the declaration for the list's element type.
Elem() Declaration
}
// Assert that wrappers conform to the ListDeclaration interface.
var _ = []ListDeclaration{
(*ArrayDecl)(nil),
(*VectorDecl)(nil),
}
// Helper struct for implementing IsNullable on types that are never nullable.
type NeverNullable struct{}
func (NeverNullable) IsNullable() bool {
return false
}
type BoolDecl struct {
NeverNullable
}
func (decl *BoolDecl) Subtype() fidlgen.PrimitiveSubtype {
return fidlgen.Bool
}
func (decl *BoolDecl) conforms(value interface{}, _ context) error {
switch value.(type) {
default:
return fmt.Errorf("expecting bool, found %T (%v)", value, value)
case bool:
return nil
}
}
type IntegerDecl struct {
NeverNullable
subtype fidlgen.PrimitiveSubtype
lower int64
upper uint64
}
func (decl *IntegerDecl) Subtype() fidlgen.PrimitiveSubtype {
return decl.subtype
}
func (decl *IntegerDecl) conforms(value interface{}, _ context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting int64 or uint64, found %T (%v)", value, value)
case int64:
if value < 0 {
if value < decl.lower {
return fmt.Errorf("%d is out of range", value)
}
} else {
if decl.upper < uint64(value) {
return fmt.Errorf("%d is out of range", value)
}
}
return nil
case uint64:
if decl.upper < value {
return fmt.Errorf("%d is out of range", value)
}
return nil
}
}
type FloatDecl struct {
NeverNullable
subtype fidlgen.PrimitiveSubtype
}
func (decl *FloatDecl) Subtype() fidlgen.PrimitiveSubtype {
return decl.subtype
}
func (decl *FloatDecl) conforms(value interface{}, _ context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting float64, found %T (%s)", value, value)
case float64:
if math.IsNaN(value) {
return fmt.Errorf("must use raw_float for NaN: %v", value)
}
if math.IsInf(value, 0) {
return fmt.Errorf("must use raw_float for infinity: %v", value)
}
return nil
case gidlir.RawFloat:
if decl.subtype == fidlgen.Float32 && value > math.MaxUint32 {
return fmt.Errorf("raw_float out of range for float32: %v", value)
}
return nil
}
}
type StringDecl struct {
bound *int
nullable bool
}
func (decl *StringDecl) IsNullable() bool {
return decl.nullable
}
func (decl *StringDecl) conforms(value interface{}, _ context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting string, found %T (%v)", value, value)
case string:
if decl.bound == nil {
return nil
}
if bound := *decl.bound; bound < len(value) {
return fmt.Errorf(
"string '%s' is too long, expecting %d but was %d", value,
bound, len(value))
}
return nil
case nil:
if decl.nullable {
return nil
}
return fmt.Errorf("expecting non-null string, found nil")
}
}
type HandleDecl struct {
subtype fidlgen.HandleSubtype
// TODO(fxbug.dev/41920): Add a field for handle rights.
nullable bool
}
func (decl *HandleDecl) Subtype() fidlgen.HandleSubtype {
return decl.subtype
}
func (decl *HandleDecl) IsNullable() bool {
return decl.nullable
}
func (decl *HandleDecl) conforms(value interface{}, ctx context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting handle, found %T (%v)", value, value)
case gidlir.HandleWithRights:
if v := int(value.Handle); v < 0 || v >= len(ctx.handleDefs) {
return fmt.Errorf("handle #%d out of range", value.Handle)
}
if !ctx.ignoreWrongHandleType {
if decl.subtype == fidlgen.Handle {
// The declaration is an untyped handle. Any subtype conforms.
return nil
}
if subtype := ctx.handleDefs[value.Handle].Subtype; subtype != decl.subtype {
return fmt.Errorf("expecting handle:%s, found handle:%s", decl.subtype, subtype)
}
}
return nil
case nil:
if decl.nullable {
return nil
}
return fmt.Errorf("expecting non-null handle, found nil")
}
}
type BitsDecl struct {
NeverNullable
Underlying IntegerDecl
bitsDecl fidlgen.Bits
}
func (decl *BitsDecl) Name() string {
return string(decl.bitsDecl.Name)
}
func (decl *BitsDecl) IsFlexible() bool {
return decl.bitsDecl.IsFlexible()
}
func (decl *BitsDecl) conforms(value interface{}, ctx context) error {
// TODO(fxbug.dev/7847): Require a valid bits member when strict
return decl.Underlying.conforms(value, ctx)
}
type EnumDecl struct {
NeverNullable
Underlying IntegerDecl
enumDecl fidlgen.Enum
}
func (decl *EnumDecl) Name() string {
return string(decl.enumDecl.Name)
}
func (decl *EnumDecl) IsFlexible() bool {
return decl.enumDecl.IsFlexible()
}
func (decl *EnumDecl) conforms(value interface{}, ctx context) error {
// TODO(fxbug.dev/7847): Require a valid enum member when strict
return decl.Underlying.conforms(value, ctx)
}
// StructDecl describes a struct declaration.
type StructDecl struct {
structDecl fidlgen.Struct
nullable bool
schema Schema
}
func (decl *StructDecl) IsNullable() bool {
return decl.nullable
}
func (decl *StructDecl) IsResourceType() bool {
return decl.structDecl.IsResourceType()
}
func (decl *StructDecl) Name() string {
return string(decl.structDecl.Name)
}
func (decl *StructDecl) FieldNames() []string {
var names []string
for _, member := range decl.structDecl.Members {
names = append(names, string(member.Name))
}
return names
}
func (decl *StructDecl) Field(name string) (Declaration, bool) {
for _, member := range decl.structDecl.Members {
if string(member.Name) == name {
return decl.schema.lookupDeclByType(member.Type)
}
}
return nil, false
}
// recordConforms is a helper function for implementing Declarations.conforms on
// types that expect a gidlir.Record value. It takes the kind ("struct", etc.),
// expected identifier, schema, and nullability, and returns the record or an
// error. It can also return (nil, nil) when value is nil and nullable is true.
func recordConforms(value interface{}, kind string, decl NamedDeclaration, schema Schema) (*gidlir.Record, error) {
switch value := value.(type) {
default:
return nil, fmt.Errorf("expecting %s, found %T (%v)", kind, value, value)
case gidlir.Record:
if name := schema.qualifyName(value.Name); name != decl.Name() {
return nil, fmt.Errorf("expecting %s %s, found %s", kind, decl.Name(), name)
}
return &value, nil
case nil:
if decl.IsNullable() {
return nil, nil
}
return nil, fmt.Errorf("expecting non-null %s %s, found nil", kind, decl.Name())
}
}
func (decl *StructDecl) conforms(value interface{}, ctx context) error {
record, err := recordConforms(value, "struct", decl, decl.schema)
if err != nil {
return err
}
if record == nil {
return nil
}
provided := make(map[string]struct{}, len(record.Fields))
for _, field := range record.Fields {
if field.Key.IsUnknown() {
return fmt.Errorf("field %d: ordinal keys are invalid in structs", field.Key.UnknownOrdinal)
}
if fieldDecl, ok := decl.Field(field.Key.Name); !ok {
return fmt.Errorf("field %s: unknown", field.Key.Name)
} else if err := fieldDecl.conforms(field.Value, ctx); err != nil {
return fmt.Errorf("field %s: %s", field.Key.Name, err)
}
provided[field.Key.Name] = struct{}{}
}
for _, member := range decl.structDecl.Members {
// TODO(fxbug.dev/49939) Allow omitted non-nullable fields that have defaults.
if _, ok := provided[string(member.Name)]; !ok && !member.Type.Nullable {
panic(fmt.Sprintf("missing non-nullable field %s in struct %s",
member.Name, decl.Name()))
}
}
return nil
}
// TableDecl describes a table declaration.
type TableDecl struct {
NeverNullable
tableDecl fidlgen.Table
schema Schema
}
func (decl *TableDecl) IsResourceType() bool {
return decl.tableDecl.IsResourceType()
}
func (decl *TableDecl) Name() string {
return string(decl.tableDecl.Name)
}
func (decl *TableDecl) FieldNames() []string {
var names []string
for _, member := range decl.tableDecl.Members {
if !member.Reserved {
names = append(names, string(member.Name))
}
}
return names
}
func (decl *TableDecl) Field(name string) (Declaration, bool) {
for _, member := range decl.tableDecl.Members {
if string(member.Name) == name {
return decl.schema.lookupDeclByType(member.Type)
}
}
return nil, false
}
func (decl *TableDecl) fieldByOrdinal(ordinal uint64) (Declaration, bool) {
for _, member := range decl.tableDecl.Members {
if uint64(member.Ordinal) == ordinal {
// Ignore reserved members. This means that it is valid to specify
// an unknown value for a reserved member, since they are treated
// the same as unknown ordinals.
if member.Reserved {
continue
}
return decl.schema.lookupDeclByType(member.Type)
}
}
return nil, false
}
func (decl *TableDecl) conforms(value interface{}, ctx context) error {
record, err := recordConforms(value, "table", decl, decl.schema)
if err != nil {
return err
}
if record == nil {
panic("tables cannot be nullable")
}
for _, field := range record.Fields {
// The mixer explicitly allows using unknown keys for strict unions to
// enable encode failure tests. Bindings that do not allow constructing
// a strict union with an unknown variant should be denylisted from these
// tests.
if field.Key.IsUnknown() {
if _, ok := decl.fieldByOrdinal(field.Key.UnknownOrdinal); ok {
return fmt.Errorf("field name must be used rather than ordinal %d", field.Key.UnknownOrdinal)
}
continue
}
if fieldDecl, ok := decl.Field(field.Key.Name); !ok {
return fmt.Errorf("field %s: unknown", field.Key.Name)
} else if err := fieldDecl.conforms(field.Value, ctx); err != nil {
return fmt.Errorf("field %s: %s", field.Key.Name, err)
}
}
return nil
}
// UnionDecl describes a union declaration.
type UnionDecl struct {
unionDecl fidlgen.Union
nullable bool
schema Schema
}
func (decl *UnionDecl) IsNullable() bool {
return decl.nullable
}
func (decl *UnionDecl) IsResourceType() bool {
return decl.unionDecl.IsResourceType()
}
func (decl *UnionDecl) Name() string {
return string(decl.unionDecl.Name)
}
func (decl *UnionDecl) FieldNames() []string {
var names []string
for _, member := range decl.unionDecl.Members {
names = append(names, string(member.Name))
}
return names
}
func (decl *UnionDecl) Field(name string) (Declaration, bool) {
for _, member := range decl.unionDecl.Members {
if string(member.Name) == name {
return decl.schema.lookupDeclByType(member.Type)
}
}
return nil, false
}
func (decl *UnionDecl) fieldByOrdinal(ordinal uint64) (Declaration, bool) {
for _, member := range decl.unionDecl.Members {
if uint64(member.Ordinal) == ordinal {
return decl.schema.lookupDeclByType(member.Type)
}
}
return nil, false
}
func (decl *UnionDecl) conforms(value interface{}, ctx context) error {
record, err := recordConforms(value, "union", decl, decl.schema)
if err != nil {
return err
}
if record == nil {
return nil
}
if num := len(record.Fields); num != 1 {
return fmt.Errorf("must have one field, found %d", num)
}
for _, field := range record.Fields {
if field.Key.IsUnknown() {
if _, ok := decl.fieldByOrdinal(field.Key.UnknownOrdinal); ok {
return fmt.Errorf("field name must be used rather than ordinal %d", field.Key.UnknownOrdinal)
}
if decl.unionDecl.IsStrict() {
return fmt.Errorf("cannot use unknown ordinal in a strict union")
}
continue
}
if fieldDecl, ok := decl.Field(field.Key.Name); !ok {
return fmt.Errorf("field %s: unknown", field.Key.Name)
} else if err := fieldDecl.conforms(field.Value, ctx); err != nil {
return fmt.Errorf("field %s: %s", field.Key.Name, err)
}
}
return nil
}
type ArrayDecl struct {
NeverNullable
// The array has type `typ`, and it contains `typ.ElementType` elements.
typ fidlgen.Type
schema Schema
}
func (decl *ArrayDecl) Elem() Declaration {
elemType := *decl.typ.ElementType
elemDecl, ok := decl.schema.lookupDeclByType(elemType)
if !ok {
panic(fmt.Sprintf("array element type %v not found in schema", elemType))
}
return elemDecl
}
func (decl *ArrayDecl) Size() int {
return *decl.typ.ElementCount
}
func (decl *ArrayDecl) conforms(value interface{}, ctx context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting array, found %T (%v)", value, value)
case []interface{}:
if len(value) != decl.Size() {
return fmt.Errorf("expecting %d elements, got %d", decl.Size(), len(value))
}
elemDecl := decl.Elem()
for i, elem := range value {
if err := elemDecl.conforms(elem, ctx); err != nil {
return fmt.Errorf("[%d]: %s", i, err)
}
}
return nil
}
}
type VectorDecl struct {
// The vector has type `typ`, and it contains `typ.ElementType` elements.
typ fidlgen.Type
schema Schema
}
func (decl *VectorDecl) IsNullable() bool {
return decl.typ.Nullable
}
func (decl *VectorDecl) Elem() Declaration {
elemType := *decl.typ.ElementType
elemDecl, ok := decl.schema.lookupDeclByType(elemType)
if !ok {
panic(fmt.Sprintf("vector element type %v not found in schema", elemType))
}
return elemDecl
}
func (decl *VectorDecl) MaxSize() (int, bool) {
if decl.typ.ElementCount != nil {
return *decl.typ.ElementCount, true
}
return 0, false
}
func (decl *VectorDecl) conforms(value interface{}, ctx context) error {
switch value := value.(type) {
default:
return fmt.Errorf("expecting vector, found %T (%v)", value, value)
case []interface{}:
if maxSize, ok := decl.MaxSize(); ok && len(value) > maxSize {
return fmt.Errorf("expecting at most %d elements, got %d", maxSize, len(value))
}
elemDecl := decl.Elem()
for i, elem := range value {
if err := elemDecl.conforms(elem, ctx); err != nil {
return fmt.Errorf("[%d]: %s", i, err)
}
}
return nil
case nil:
if decl.typ.Nullable {
return nil
}
return fmt.Errorf("expecting non-nullable vector, got nil")
}
}
// Schema is the GIDL-level concept of a FIDL library. It provides functions to
// lookup types and return the corresponding Declaration.
type Schema struct {
// TODO(fxbug.dev/39407): Use common.LibraryName.
libraryName string
// Maps fully qualified type names to fidl data structures:
// *fidl.Struct, *fidl.Table, or *fidl.Union.
// TODO(fxbug.dev/39407): Use common.DeclName.
types map[string]interface{}
}
// BuildSchema builds a Schema from a FIDL library and handle definitions.
// Note: The returned schema contains pointers into fidl.
func BuildSchema(fidl fidlgen.Root) Schema {
total := len(fidl.Bits) + len(fidl.Enums) + len(fidl.Structs) + len(fidl.Tables) + len(fidl.Unions)
types := make(map[string]interface{}, total)
// These loops must use fidl.Structs[i], fidl.Tables[i], etc. rather than
// iterating `for i, decl := ...` and using &decl, because that would store
// the same local variable address in every entry.
for i := range fidl.Bits {
decl := &fidl.Bits[i]
types[string(decl.Name)] = decl
}
for i := range fidl.Enums {
decl := &fidl.Enums[i]
types[string(decl.Name)] = decl
}
for i := range fidl.Structs {
decl := &fidl.Structs[i]
types[string(decl.Name)] = decl
}
for i := range fidl.Tables {
decl := &fidl.Tables[i]
types[string(decl.Name)] = decl
}
for i := range fidl.Unions {
decl := &fidl.Unions[i]
types[string(decl.Name)] = decl
}
return Schema{libraryName: string(fidl.Name), types: types}
}
// ExtractDeclaration extract the top-level declaration for the provided value,
// and ensures the value conforms to the schema. It also takes a list of handle
// definitions in scope, which can be nil if there are no handles.
func (s Schema) ExtractDeclaration(value interface{}, handleDefs []gidlir.HandleDef) (*StructDecl, error) {
decl, err := s.ExtractDeclarationUnsafe(value)
if err != nil {
return nil, err
}
if err := decl.conforms(value, context{handleDefs: handleDefs}); err != nil {
return nil, fmt.Errorf("value %v failed to conform to declaration (type %T): %v", value, decl, err)
}
return decl, nil
}
// ExtractDeclarationEncodeSuccess extract the top-level declaration for the
// provided value, and ensures the value conforms to the schema based on the
// rules for EncodeSuccess. It also takes a list of handle definitions in
// scope, which can be nil if there are no handles.
func (s Schema) ExtractDeclarationEncodeSuccess(value interface{}, handleDefs []gidlir.HandleDef) (*StructDecl, error) {
decl, err := s.ExtractDeclarationUnsafe(value)
if err != nil {
return nil, err
}
if err := decl.conforms(value, context{handleDefs: handleDefs, ignoreWrongHandleType: true}); err != nil {
return nil, fmt.Errorf("value %v failed to conform to declaration (type %T): %v", value, decl, err)
}
return decl, nil
}
// ExtractDeclarationUnsafe extracts the top-level declaration for the provided
// value, but does not ensure the value conforms to the schema. This is used in
// cases where conformance is too strict (e.g. failure cases).
func (s Schema) ExtractDeclarationUnsafe(value interface{}) (*StructDecl, error) {
switch value := value.(type) {
case gidlir.Record:
return s.ExtractDeclarationByName(value.Name)
default:
return nil, fmt.Errorf("top-level message must be a struct; got %s (%T)", value, value)
}
}
// ExtractDeclarationByName extracts the top-level declaration for the given
// unqualified type name. This is used in cases where only the type name is
// provided in the test (e.g. decoding-only tests).
func (s Schema) ExtractDeclarationByName(unqualifiedName string) (*StructDecl, error) {
decl, ok := s.lookupDeclByName(unqualifiedName, false)
if !ok {
return nil, fmt.Errorf("unknown declaration %s", unqualifiedName)
}
structDecl, ok := decl.(*StructDecl)
if !ok {
return nil, fmt.Errorf("top-level message must be a struct; got %s (%T)",
unqualifiedName, decl)
}
return structDecl, nil
}
func (s Schema) lookupDeclByName(unqualifiedName string, nullable bool) (Declaration, bool) {
return s.lookupDeclByQualifiedName(s.qualifyName(unqualifiedName), nullable)
}
// TODO(fxbug.dev/39407): Take common.MemberName, return common.DeclName.
func (s Schema) qualifyName(unqualifiedName string) string {
return fmt.Sprintf("%s/%s", s.libraryName, unqualifiedName)
}
// TODO(fxbug.dev/39407): Take common.DeclName.
func (s Schema) lookupDeclByQualifiedName(name string, nullable bool) (Declaration, bool) {
typ, ok := s.types[name]
if !ok {
return nil, false
}
switch typ := typ.(type) {
case *fidlgen.Bits:
return &BitsDecl{
bitsDecl: *typ,
Underlying: *lookupDeclByPrimitive(typ.Type.PrimitiveSubtype).(*IntegerDecl),
}, true
case *fidlgen.Enum:
return &EnumDecl{
enumDecl: *typ,
Underlying: *lookupDeclByPrimitive(typ.Type).(*IntegerDecl),
}, true
case *fidlgen.Struct:
return &StructDecl{
structDecl: *typ,
nullable: nullable,
schema: s,
}, true
case *fidlgen.Table:
if nullable {
panic(fmt.Sprintf("nullable table %s is not allowed", typ.Name))
}
return &TableDecl{
tableDecl: *typ,
schema: s,
}, true
case *fidlgen.Union:
return &UnionDecl{
unionDecl: *typ,
nullable: nullable,
schema: s,
}, true
}
return nil, false
}
// LookupDeclByPrimitive looks up a message declaration by primitive subtype.
func lookupDeclByPrimitive(subtype fidlgen.PrimitiveSubtype) PrimitiveDeclaration {
switch subtype {
case fidlgen.Bool:
return &BoolDecl{}
case fidlgen.Int8:
return &IntegerDecl{subtype: subtype, lower: math.MinInt8, upper: math.MaxInt8}
case fidlgen.Int16:
return &IntegerDecl{subtype: subtype, lower: math.MinInt16, upper: math.MaxInt16}
case fidlgen.Int32:
return &IntegerDecl{subtype: subtype, lower: math.MinInt32, upper: math.MaxInt32}
case fidlgen.Int64:
return &IntegerDecl{subtype: subtype, lower: math.MinInt64, upper: math.MaxInt64}
case fidlgen.Uint8:
return &IntegerDecl{subtype: subtype, lower: 0, upper: math.MaxUint8}
case fidlgen.Uint16:
return &IntegerDecl{subtype: subtype, lower: 0, upper: math.MaxUint16}
case fidlgen.Uint32:
return &IntegerDecl{subtype: subtype, lower: 0, upper: math.MaxUint32}
case fidlgen.Uint64:
return &IntegerDecl{subtype: subtype, lower: 0, upper: math.MaxUint64}
case fidlgen.Float32:
return &FloatDecl{subtype: subtype}
case fidlgen.Float64:
return &FloatDecl{subtype: subtype}
default:
panic(fmt.Sprintf("unsupported primitive subtype: %s", subtype))
}
}
func (s Schema) lookupDeclByType(typ fidlgen.Type) (Declaration, bool) {
switch typ.Kind {
case fidlgen.StringType:
return &StringDecl{
bound: typ.ElementCount,
nullable: typ.Nullable,
}, true
case fidlgen.HandleType:
return &HandleDecl{
subtype: typ.HandleSubtype,
nullable: typ.Nullable,
}, true
case fidlgen.PrimitiveType:
return lookupDeclByPrimitive(typ.PrimitiveSubtype), true
case fidlgen.IdentifierType:
return s.lookupDeclByQualifiedName(string(typ.Identifier), typ.Nullable)
case fidlgen.ArrayType:
return &ArrayDecl{schema: s, typ: typ}, true
case fidlgen.VectorType:
return &VectorDecl{schema: s, typ: typ}, true
default:
// TODO(fxbug.dev/36441): HandleType, RequestType
panic("not implemented")
}
}