| // Copyright 2018 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 codegen |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| "math" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| type EncodedCompoundIdentifier = fidlgen.EncodedCompoundIdentifier |
| |
| type Bits struct { |
| fidlgen.Bits |
| Name string |
| Type string |
| Members []BitsMember |
| } |
| |
| type BitsMember struct { |
| fidlgen.BitsMember |
| Name string |
| Value string |
| } |
| |
| type Const struct { |
| fidlgen.Const |
| Name string |
| Type string |
| Value string |
| } |
| |
| type Enum struct { |
| fidlgen.Enum |
| Name string |
| Type string |
| Members []EnumMember |
| // Member name with the minimum value, used as an arbitrary default value |
| // in Decodable::new_empty for strict enums. |
| MinMember string |
| } |
| |
| type EnumMember struct { |
| fidlgen.EnumMember |
| Name string |
| Value string |
| } |
| |
| type Union struct { |
| fidlgen.Union |
| Derives derives |
| ECI EncodedCompoundIdentifier |
| Name string |
| Members []UnionMember |
| } |
| |
| type UnionMember struct { |
| fidlgen.UnionMember |
| Type string |
| OGType fidlgen.Type |
| Name string |
| Ordinal int |
| HasHandleMetadata bool |
| HandleRights string |
| HandleSubtype string |
| } |
| |
| type ResultOkEntry struct { |
| OGType fidlgen.Type |
| Type string |
| HasHandleMetadata bool |
| HandleWrapperName string |
| } |
| |
| // A Result is the result type used for a method that is flexible or uses error syntax. |
| type Result struct { |
| // Compound identifier for the result type, used for lookups. |
| ECI EncodedCompoundIdentifier |
| Derives derives |
| // Rust UpperCamelCase name for the result type used when generating or |
| // referencing it. |
| Name string |
| Ok []ResultOkEntry |
| ErrOGType *fidlgen.Type |
| ErrType *string |
| HasTransportError bool |
| } |
| |
| // HasAnyHandleWrappers returns true if any value in Ok uses a handle wrapper. |
| func (r *Result) HasAnyHandleWrappers() bool { |
| for _, okEntry := range r.Ok { |
| if okEntry.HasHandleMetadata { |
| return true |
| } |
| } |
| return false |
| } |
| |
| type Struct struct { |
| fidlgen.Struct |
| ECI EncodedCompoundIdentifier |
| Derives derives |
| Name string |
| Members []StructMember |
| PaddingMarkersV1, PaddingMarkersV2 []rustPaddingMarker |
| FlattenedPaddingMarkersV1, FlattenedPaddingMarkersV2 []rustPaddingMarker |
| SizeV1, SizeV2 int |
| AlignmentV1, AlignmentV2 int |
| HasPadding bool |
| // True iff the fidl_struct_copy! macro should be used instead of fidl_struct!. |
| UseFidlStructCopy bool |
| } |
| |
| type StructMember struct { |
| fidlgen.StructMember |
| OGType fidlgen.Type |
| Type string |
| Name string |
| OffsetV1, OffsetV2 int |
| HasDefault bool |
| DefaultValue string |
| HasHandleMetadata bool |
| HandleRights string |
| HandleSubtype string |
| } |
| |
| type Table struct { |
| fidlgen.Table |
| Derives derives |
| ECI EncodedCompoundIdentifier |
| Name string |
| Members []TableMember |
| } |
| |
| type TableMember struct { |
| fidlgen.TableMember |
| OGType fidlgen.Type |
| Type string |
| Name string |
| Ordinal int |
| HasHandleMetadata bool |
| HandleRights string |
| HandleSubtype string |
| } |
| |
| // Protocol is the definition of a protocol in the library being compiled. |
| type Protocol struct { |
| // Raw JSON IR data about this protocol. Embedded to provide access to |
| // fields common to all bindings. |
| fidlgen.Protocol |
| // Compound identifier referring to this protocol. |
| ECI EncodedCompoundIdentifier |
| // Name of the protocol as a Rust CamelCase identifier. Since only protocols |
| // from the same library are included, this will never be qualified, so it |
| // is just the CamelCase name of the protocol. |
| Name string |
| // List of methods that are part of this protocol. Processed from |
| // fidlgen.Protocol to add Rust-specific fields. |
| Methods []Method |
| // Name of this protocol for legacy (pre-RFC-0041) service discovery, if the |
| // protocol is marked as discoverable. This value does not include enclosing |
| // quote marks. |
| ProtocolName string |
| } |
| |
| // Method is a method defined in a protocol. |
| type Method struct { |
| // Raw JSON IR data about this method. Embedded to provide access to fields |
| // common to all bindings. |
| fidlgen.Method |
| // Name of the method converted to snake_case. Used when generating |
| // rust-methods associated with this method, such as proxy methods and |
| // encoder methods. |
| Name string |
| // Name of the method converted to CamelCase. Used when generating |
| // rust-types associated with this method, such as responders. |
| CamelName string |
| // Parameters to this method extracted from the request type struct. |
| Request []Parameter |
| // Arguments used for method responses. If error syntax is used, this will |
| // contain a single element for the Result enum used in rust generated code. |
| // For methods which do not use error syntax, this will contain fields |
| // extracted from the response struct. |
| // |
| // Note that since methods being strict vs flexible is not exposed in the |
| // client API, this field does not reflect whether the method is strict or |
| // flexible. For flexible, the value is still either fields extracted from |
| // the response struct or the Rust Result enum, depending only on whether |
| // error syntax was used. In the case of flexible methods without error |
| // syntax, the parameters are extracted from the success variant of the |
| // underlying result union. |
| Response []Parameter |
| // If error syntax was used, this will contain information about the result |
| // union. |
| Result *Result |
| } |
| |
| // DynamicFlags gets rust code for the DynamicFlags value that should be set for |
| // a call to this method. |
| func (m *Method) DynamicFlags() string { |
| if m.IsStrict() { |
| return "fidl::encoding::DynamicFlags::empty()" |
| } |
| return "fidl::encoding::DynamicFlags::FLEXIBLE" |
| } |
| |
| // HasErrorResult returns true if the method uses error syntax. |
| func (m *Method) HasErrorResult() bool { |
| return m.Result != nil && m.Result.ErrOGType != nil |
| } |
| |
| // A Parameter to either the requset or response of a method. Contains |
| // information to assist in generating code using borrowed types and handle |
| // wrappers. |
| type Parameter struct { |
| // The raw fidlgen type of the parameter. |
| OGType fidlgen.Type |
| // String representing the type to use for this parameter when handling it |
| // by-value. |
| Type string |
| // String representing the type to use for this parameter when receiving it |
| // as a possibly-borrowed method argument. |
| BorrowedType string |
| // Snake-case name to use for the parameter. |
| Name string |
| // Name of the wrapper type that should be used for handle validation, if |
| // HasHandleMetadata is true. |
| HandleWrapperName string |
| // True if the type of the parameter has handle metadata and so requires |
| // validation. |
| HasHandleMetadata bool |
| } |
| |
| type HandleMetadataWrapper struct { |
| Name string |
| Subtype string |
| Rights string |
| } |
| |
| type Service struct { |
| fidlgen.Service |
| Name string |
| Members []ServiceMember |
| ServiceName string |
| } |
| |
| type ServiceMember struct { |
| fidlgen.ServiceMember |
| ProtocolType string |
| Name string |
| CamelName string |
| SnakeName string |
| } |
| |
| type Root struct { |
| ExternCrates []string |
| Bits []Bits |
| Consts []Const |
| Enums []Enum |
| Structs []Struct |
| ExternalStructs []Struct |
| Unions []Union |
| // Result types for methods with error syntax. |
| Results []Result |
| Tables []Table |
| Protocols []Protocol |
| Services []Service |
| HandleMetadataWrappers []HandleMetadataWrapper |
| } |
| |
| func (r *Root) findProtocol(eci EncodedCompoundIdentifier) *Protocol { |
| for i := range r.Protocols { |
| if r.Protocols[i].ECI == eci { |
| return &r.Protocols[i] |
| } |
| } |
| return nil |
| } |
| |
| func (r *Root) findStruct(eci EncodedCompoundIdentifier, canBeExternal bool) *Struct { |
| for i := range r.Structs { |
| if r.Structs[i].ECI == eci { |
| return &r.Structs[i] |
| } |
| } |
| if canBeExternal { |
| for i := range r.ExternalStructs { |
| if r.ExternalStructs[i].ECI == eci { |
| return &r.ExternalStructs[i] |
| } |
| } |
| } |
| return nil |
| } |
| |
| func (r *Root) findTable(eci EncodedCompoundIdentifier) *Table { |
| for i := range r.Tables { |
| if r.Tables[i].ECI == eci { |
| return &r.Tables[i] |
| } |
| } |
| return nil |
| } |
| |
| func (r *Root) findResult(eci EncodedCompoundIdentifier) *Result { |
| for i := range r.Results { |
| if r.Results[i].ECI == eci { |
| return &r.Results[i] |
| } |
| } |
| return nil |
| } |
| |
| func (r *Root) findUnion(eci EncodedCompoundIdentifier) *Union { |
| for i := range r.Unions { |
| if r.Unions[i].ECI == eci { |
| return &r.Unions[i] |
| } |
| } |
| return nil |
| } |
| |
| var reservedWords = map[string]struct{}{ |
| "as": {}, |
| "box": {}, |
| "break": {}, |
| "const": {}, |
| "continue": {}, |
| "crate": {}, |
| "else": {}, |
| "enum": {}, |
| "extern": {}, |
| "false": {}, |
| "fn": {}, |
| "for": {}, |
| "if": {}, |
| "impl": {}, |
| "in": {}, |
| "let": {}, |
| "loop": {}, |
| "match": {}, |
| "mod": {}, |
| "move": {}, |
| "mut": {}, |
| "pub": {}, |
| "ref": {}, |
| "return": {}, |
| "self": {}, |
| "Self": {}, |
| "static": {}, |
| "struct": {}, |
| "super": {}, |
| "trait": {}, |
| "true": {}, |
| "type": {}, |
| "unsafe": {}, |
| "use": {}, |
| "where": {}, |
| "while": {}, |
| |
| // Keywords reserved for future use (future-proofing...) |
| "abstract": {}, |
| "alignof": {}, |
| "await": {}, |
| "become": {}, |
| "do": {}, |
| "final": {}, |
| "macro": {}, |
| "offsetof": {}, |
| "override": {}, |
| "priv": {}, |
| "proc": {}, |
| "pure": {}, |
| "sizeof": {}, |
| "typeof": {}, |
| "unsized": {}, |
| "virtual": {}, |
| "yield": {}, |
| |
| // Weak keywords (special meaning in specific contexts) |
| // These are ok in all contexts of FIDL names. |
| //"default": {}, |
| //"union": {}, |
| |
| // Things that are not keywords, but for which collisions would be very |
| // unpleasant |
| "Result": {}, |
| "Ok": {}, |
| "Err": {}, |
| "Vec": {}, |
| "Option": {}, |
| "Some": {}, |
| "None": {}, |
| "Box": {}, |
| "Future": {}, |
| "Stream": {}, |
| "Never": {}, |
| "Send": {}, |
| "fidl": {}, |
| "futures": {}, |
| "zx": {}, |
| "async": {}, |
| "on_open": {}, |
| "OnOpen": {}, |
| // TODO(fxbug.dev/66767): Remove "WaitForEvent". |
| "wait_for_event": {}, |
| "WaitForEvent": {}, |
| } |
| |
| var reservedSuffixes = []string{ |
| "Impl", |
| "Marker", |
| "Proxy", |
| "ProxyProtocol", |
| "ControlHandle", |
| "Responder", |
| "Server", |
| } |
| |
| func isReservedWord(str string) bool { |
| _, ok := reservedWords[str] |
| return ok |
| } |
| |
| // hasReservedSuffix checks if a string ends with a suffix commonly used by the |
| // bindings in generated types |
| func hasReservedSuffix(str string) bool { |
| for _, suffix := range reservedSuffixes { |
| if strings.HasSuffix(str, suffix) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // changeIfReserved adds an underscore suffix to differentiate an identifier |
| // from a reserved name |
| // |
| // Reserved names include a variety of rust keywords, commonly used rust types |
| // like Result, Vec, and Future, and any name ending in a suffix used by the |
| // bindings to identify particular generated types like -Impl, -Marker, and |
| // -Proxy. |
| func changeIfReserved(val fidlgen.Identifier) string { |
| str := string(val) |
| if hasReservedSuffix(str) || isReservedWord(str) { |
| return str + "_" |
| } |
| return str |
| } |
| |
| var primitiveTypes = map[fidlgen.PrimitiveSubtype]string{ |
| fidlgen.Bool: "bool", |
| fidlgen.Int8: "i8", |
| fidlgen.Int16: "i16", |
| fidlgen.Int32: "i32", |
| fidlgen.Int64: "i64", |
| fidlgen.Uint8: "u8", |
| fidlgen.Uint16: "u16", |
| fidlgen.Uint32: "u32", |
| fidlgen.Uint64: "u64", |
| fidlgen.Float32: "f32", |
| fidlgen.Float64: "f64", |
| } |
| |
| var handleSubtypes = map[fidlgen.HandleSubtype]string{ |
| fidlgen.Bti: "Bti", |
| fidlgen.Channel: "Channel", |
| fidlgen.Clock: "Clock", |
| fidlgen.DebugLog: "DebugLog", |
| fidlgen.Event: "Event", |
| fidlgen.Eventpair: "EventPair", |
| fidlgen.Exception: "Exception", |
| fidlgen.Fifo: "Fifo", |
| fidlgen.Guest: "Guest", |
| fidlgen.Handle: "Handle", |
| fidlgen.Interrupt: "Interrupt", |
| fidlgen.Iommu: "Iommu", |
| fidlgen.Job: "Job", |
| fidlgen.Pager: "Pager", |
| fidlgen.PciDevice: "PciDevice", |
| fidlgen.Pmt: "Pmt", |
| fidlgen.Port: "Port", |
| fidlgen.Process: "Process", |
| fidlgen.Profile: "Profile", |
| fidlgen.Resource: "Resource", |
| fidlgen.Socket: "Socket", |
| fidlgen.Stream: "Stream", |
| fidlgen.SuspendToken: "SuspendToken", |
| fidlgen.Thread: "Thread", |
| fidlgen.Time: "Timer", |
| fidlgen.Vcpu: "Vcpu", |
| fidlgen.Vmar: "Vmar", |
| fidlgen.Vmo: "Vmo", |
| } |
| |
| var handleSubtypeConsts = map[fidlgen.HandleSubtype]string{ |
| fidlgen.Bti: "BTI", |
| fidlgen.Channel: "CHANNEL", |
| fidlgen.Clock: "CLOCK", |
| fidlgen.DebugLog: "LOG", |
| fidlgen.Event: "EVENT", |
| fidlgen.Eventpair: "EVENTPAIR", |
| fidlgen.Exception: "EXCEPTION", |
| fidlgen.Fifo: "FIFO", |
| fidlgen.Guest: "GUEST", |
| fidlgen.Handle: "NONE", |
| fidlgen.Interrupt: "INTERRUPT", |
| fidlgen.Iommu: "IOMMU", |
| fidlgen.Job: "JOB", |
| fidlgen.Pager: "PAGER", |
| fidlgen.PciDevice: "PCI_DEVICE", |
| fidlgen.Pmt: "PMT", |
| fidlgen.Port: "PORT", |
| fidlgen.Process: "PROCESS", |
| fidlgen.Profile: "PROFILE", |
| fidlgen.Resource: "RESOURCE", |
| fidlgen.Socket: "SOCKET", |
| fidlgen.Stream: "STREAM", |
| fidlgen.SuspendToken: "SUSPEND_TOKEN", |
| fidlgen.Thread: "THREAD", |
| fidlgen.Time: "TIMER", |
| fidlgen.Vcpu: "VCPU", |
| fidlgen.Vmar: "VMAR", |
| fidlgen.Vmo: "VMO", |
| } |
| |
| type compiler struct { |
| decls fidlgen.DeclInfoMap |
| library fidlgen.LibraryIdentifier |
| externCrates map[string]struct{} |
| // Identifies which types are used as payload types, message body types, or |
| // both. |
| methodTypeUses fidlgen.MethodTypeUsageMap |
| // Collection of structs used as a method payload, wire result, or both, as |
| // described in the docs for fidlgen.MethodTypeUsage. This holds the |
| // compiled fidlgen.Struct because compileResultFromUnion needs to access |
| // the members from the compiled struct, and if the type is anonymous it |
| // will not appear Root.Structs. |
| methodTypeStructs map[fidlgen.EncodedCompoundIdentifier]Struct |
| structs map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct |
| results map[fidlgen.EncodedCompoundIdentifier]Result |
| handleMetadataWrappers map[string]HandleMetadataWrapper |
| } |
| |
| // inExternalLibrary returns true if the library that the given |
| // CompoundIdentifier is in is different from the one the compiler is generating |
| // code for. |
| func (c *compiler) inExternalLibrary(ci fidlgen.CompoundIdentifier) bool { |
| if len(ci.Library) != len(c.library) { |
| return true |
| } |
| for i, part := range c.library { |
| if ci.Library[i] != part { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // TODO(fxbug.dev/66767): Escaping reserved words should happen *after* |
| // converting to CamelCase. |
| func compileCamelIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ToUpperCamelCase(changeIfReserved(val)) |
| } |
| |
| func compileLibraryName(library fidlgen.LibraryIdentifier) string { |
| parts := []string{"fidl"} |
| for _, part := range library { |
| parts = append(parts, string(part)) |
| } |
| return changeIfReserved(fidlgen.Identifier(strings.Join(parts, "_"))) |
| } |
| |
| // compileSnakeIdentifier converts the identifier to snake_case and escapes it |
| // (by adding an underscore suffix) if it collides with a reserved word. |
| // TODO(fxbug.dev/66767): Escaping reserved words should happen *after* |
| // converting to snake_case. |
| func compileSnakeIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ToSnakeCase(changeIfReserved(val)) |
| } |
| |
| // TODO(fxbug.dev/66767): Escaping reserved words should happen *after* |
| // converting to SCREAMING_SNAKE_CASE. |
| func compileScreamingSnakeIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ConstNameToAllCapsSnake(changeIfReserved(val)) |
| } |
| |
| // compileCompoundIdentifier produces a string Rust identifier which can be used |
| // from the generated code to refer to the specified FIDL declaration or member. |
| // |
| // The case used in the Declaration and Member names will be unchanged. |
| // |
| // If the CompoundIdentifier is from the current library, the name will be |
| // unqualified, meaning that it is suitable for use in Rust type declarations. |
| // If it is from a different library, it will be fully qualified, and the source |
| // library will be added as a required extern-crate. |
| func (c *compiler) compileCompoundIdentifier(val fidlgen.CompoundIdentifier) string { |
| strs := []string{} |
| if c.inExternalLibrary(val) { |
| externName := compileLibraryName(val.Library) |
| c.externCrates[externName] = struct{}{} |
| strs = append(strs, externName) |
| } |
| str := changeIfReserved(val.Name) |
| strs = append(strs, str) |
| if val.Member != "" { |
| strs = append(strs, string(val.Member)) |
| } |
| return strings.Join(strs, "::") |
| } |
| |
| // compileCamelCompoundIdentifier produces a string Rust identifier which can be |
| // used from the generated code to refer to the specified FIDL declaration or |
| // member. |
| // |
| // The resulting string will have the Declaration changed to CamelCase, but |
| // Member (if any) will be unaffected by case conversion. |
| // |
| // If the CompoundIdentifier is from the current library, the name will be |
| // unqualified. If the CompoundIdentifier refers to a Declaration, that means |
| // that the name is just the declaration name converted to CamelCase, so it is |
| // suitable for use in Rust type declarations. If the CompoundIdentifier is for |
| // a member within a declaration, the member name will be qualified by the |
| // declaration name, so it is not suitable for declaring e.g. a method name. |
| // |
| // If it is from a different library, it will be fully qualified, and the source |
| // library will be added as a required extern-crate. |
| func (c *compiler) compileCamelCompoundIdentifier(eci fidlgen.EncodedCompoundIdentifier) string { |
| val := eci.Parse() |
| val.Name = fidlgen.Identifier(compileCamelIdentifier(val.Name)) |
| return c.compileCompoundIdentifier(val) |
| } |
| |
| func (c *compiler) compileSnakeCompoundIdentifier(eci fidlgen.EncodedCompoundIdentifier) string { |
| val := eci.Parse() |
| val.Name = fidlgen.Identifier(compileSnakeIdentifier(val.Name)) |
| return c.compileCompoundIdentifier(val) |
| } |
| |
| func (c *compiler) compileScreamingSnakeCompoundIdentifier(eci fidlgen.EncodedCompoundIdentifier) string { |
| val := eci.Parse() |
| val.Name = fidlgen.Identifier(compileScreamingSnakeIdentifier(val.Name)) |
| return c.compileCompoundIdentifier(val) |
| } |
| |
| func compileLiteral(val fidlgen.Literal, typ fidlgen.Type) string { |
| switch val.Kind { |
| case fidlgen.StringLiteral: |
| return fmt.Sprintf("r###\"%s\"###", val.Value) |
| case fidlgen.NumericLiteral: |
| if typ.Kind == fidlgen.PrimitiveType && |
| (typ.PrimitiveSubtype == fidlgen.Float32 || typ.PrimitiveSubtype == fidlgen.Float64) { |
| if !strings.ContainsRune(val.Value, '.') { |
| return fmt.Sprintf("%s.0", val.Value) |
| } |
| return val.Value |
| } |
| return val.Value |
| case fidlgen.BoolLiteral: |
| return val.Value |
| case fidlgen.DefaultLiteral: |
| return "::Default::default()" |
| default: |
| panic(fmt.Sprintf("unknown literal kind: %v", val.Kind)) |
| } |
| } |
| |
| func (c *compiler) identifierConstantDeclType(eci EncodedCompoundIdentifier) fidlgen.DeclType { |
| memberless := eci.Parse() |
| memberless.Member = "" |
| declInfo, ok := c.decls[memberless.Encode()] |
| if !ok { |
| panic(fmt.Sprintf("identifier not in decl map: %s", memberless.Encode())) |
| } |
| return declInfo.Type |
| } |
| |
| func (c *compiler) compileConstant(val fidlgen.Constant, typ fidlgen.Type) string { |
| switch val.Kind { |
| case fidlgen.IdentifierConstant: |
| declType := c.identifierConstantDeclType(val.Identifier) |
| parts := val.Identifier.Parse() |
| switch declType { |
| case fidlgen.ConstDeclType: |
| parts.Name = fidlgen.Identifier(compileScreamingSnakeIdentifier(parts.Name)) |
| return c.compileCompoundIdentifier(parts) |
| case fidlgen.BitsDeclType: |
| parts.Name = fidlgen.Identifier(compileCamelIdentifier(parts.Name)) |
| parts.Member = fidlgen.Identifier(compileScreamingSnakeIdentifier(parts.Member)) |
| // TODO(fxbug.dev/93195): For now we assume the primitive type |
| // matches the bits underlying type. If it doesn't the generated |
| // Rust code will not compile. |
| if typ.Kind == fidlgen.PrimitiveType { |
| return fmt.Sprintf("%s.bits()", c.compileCompoundIdentifier(parts)) |
| } |
| return c.compileCompoundIdentifier(parts) |
| case fidlgen.EnumDeclType: |
| parts.Name = fidlgen.Identifier(compileCamelIdentifier(parts.Name)) |
| parts.Member = fidlgen.Identifier(compileCamelIdentifier(parts.Member)) |
| // TODO(fxbug.dev/93195): For now we assume the primitive type |
| // matches the enum underlying type. If it doesn't the generated |
| // Rust code will not compile. |
| if typ.Kind == fidlgen.PrimitiveType { |
| return fmt.Sprintf("%s.into_primitive()", c.compileCompoundIdentifier(parts)) |
| } |
| return c.compileCompoundIdentifier(parts) |
| default: |
| panic(fmt.Sprintf("unexpected decl type %s", declType)) |
| } |
| case fidlgen.LiteralConstant: |
| return compileLiteral(val.Literal, typ) |
| case fidlgen.BinaryOperator: |
| if typ.Kind == fidlgen.PrimitiveType { |
| return val.Value |
| } |
| decl := c.compileType(typ) |
| // from_bits isn't a const function, so from_bits_truncate must be used. |
| return fmt.Sprintf("%s::from_bits_truncate(%s)", decl, val.Value) |
| default: |
| panic(fmt.Sprintf("unknown constant kind: %s", val.Kind)) |
| } |
| } |
| |
| func (c *compiler) compileConst(val fidlgen.Const) Const { |
| name := c.compileScreamingSnakeCompoundIdentifier(val.Name) |
| var r Const |
| if val.Type.Kind == fidlgen.StringType { |
| r = Const{ |
| Const: val, |
| Type: "&str", |
| Name: name, |
| Value: c.compileConstant(val.Value, val.Type), |
| } |
| } else { |
| r = Const{ |
| Const: val, |
| Type: c.compileType(val.Type), |
| Name: name, |
| Value: c.compileConstant(val.Value, val.Type), |
| } |
| } |
| return r |
| } |
| |
| func compilePrimitiveSubtype(val fidlgen.PrimitiveSubtype) string { |
| if t, ok := primitiveTypes[val]; ok { |
| return t |
| } |
| panic(fmt.Sprintf("unknown primitive type: %v", val)) |
| } |
| |
| func compileHandleSubtype(val fidlgen.HandleSubtype) string { |
| if t, ok := handleSubtypes[val]; ok { |
| return t |
| } |
| panic(fmt.Sprintf("unknown handle type: %v", val)) |
| } |
| |
| type FieldHandleInformation struct { |
| fullObjectType string |
| fullRights string |
| shortObjectType string |
| shortRights string |
| hasHandleMetadata bool |
| } |
| |
| func (c *compiler) fieldHandleInformation(val *fidlgen.Type) FieldHandleInformation { |
| if val.ElementType != nil { |
| return c.fieldHandleInformation(val.ElementType) |
| } |
| if val.Kind == fidlgen.RequestType { |
| return FieldHandleInformation{ |
| fullObjectType: "fidl::ObjectType::CHANNEL", |
| fullRights: "fidl::Rights::CHANNEL_DEFAULT", |
| shortObjectType: "CHANNEL", |
| shortRights: "CHANNEL_DEFAULT", |
| hasHandleMetadata: true, |
| } |
| } |
| if val.Kind == fidlgen.IdentifierType { |
| declInfo, ok := c.decls[val.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("unknown identifier: %v", val.Identifier)) |
| } |
| if declInfo.Type == fidlgen.ProtocolDeclType { |
| return FieldHandleInformation{ |
| fullObjectType: "fidl::ObjectType::CHANNEL", |
| fullRights: "fidl::Rights::CHANNEL_DEFAULT", |
| shortObjectType: "CHANNEL", |
| shortRights: "CHANNEL_DEFAULT", |
| hasHandleMetadata: true, |
| } |
| } |
| } |
| if val.Kind != fidlgen.HandleType { |
| return FieldHandleInformation{ |
| fullObjectType: "fidl::ObjectType::NONE", |
| fullRights: "fidl::Rights::NONE", |
| shortObjectType: "NONE", |
| shortRights: "NONE", |
| hasHandleMetadata: false, |
| } |
| } |
| subtype, ok := handleSubtypeConsts[val.HandleSubtype] |
| if !ok { |
| panic(fmt.Sprintf("unknown handle type for const: %v", val)) |
| } |
| return FieldHandleInformation{ |
| fullObjectType: fmt.Sprintf("fidl::ObjectType::%s", subtype), |
| fullRights: fmt.Sprintf("fidl::Rights::from_bits_const(%d).unwrap()", val.HandleRights), |
| shortObjectType: subtype, |
| shortRights: fmt.Sprintf("%d", val.HandleRights), |
| hasHandleMetadata: true, |
| } |
| } |
| |
| // compileType gets a string to use in generated code for the rust type used for |
| // the given FIDL type. |
| func (c *compiler) compileType(val fidlgen.Type) string { |
| var t string |
| switch val.Kind { |
| case fidlgen.ArrayType: |
| t = fmt.Sprintf("[%s; %v]", c.compileType(*val.ElementType), *val.ElementCount) |
| case fidlgen.VectorType: |
| t = fmt.Sprintf("Vec<%s>", c.compileType(*val.ElementType)) |
| case fidlgen.StringType: |
| t = "String" |
| case fidlgen.HandleType: |
| t = fmt.Sprintf("fidl::%s", compileHandleSubtype(val.HandleSubtype)) |
| case fidlgen.RequestType: |
| t = fmt.Sprintf("fidl::endpoints::ServerEnd<%sMarker>", c.compileCamelCompoundIdentifier(val.RequestSubtype)) |
| case fidlgen.PrimitiveType: |
| t = compilePrimitiveSubtype(val.PrimitiveSubtype) |
| case fidlgen.IdentifierType: |
| t = c.compileCamelCompoundIdentifier(val.Identifier) |
| declInfo, ok := c.decls[val.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("unknown identifier: %v", val.Identifier)) |
| } |
| switch declInfo.Type { |
| case fidlgen.StructDeclType, fidlgen.UnionDeclType: |
| if val.Nullable { |
| t = fmt.Sprintf("Box<%s>", t) |
| } |
| case fidlgen.ProtocolDeclType: |
| t = fmt.Sprintf("fidl::endpoints::ClientEnd<%sMarker>", t) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", val.Kind)) |
| } |
| |
| if val.Nullable { |
| return fmt.Sprintf("Option<%s>", t) |
| } |
| return t |
| } |
| |
| // compileBorrowedType gets a string to use in generated code for the rust type |
| // used when the given FIDL type is used in a method parameter where encoding is |
| // needed. Note that borrowing is only actually used for structs and collections |
| // where we want to avoid copying, primitive types are passed by value. |
| // Generated references are mutable in order to allow fuchsia handles to be |
| // moved out when encoding. |
| func (c *compiler) compileBorrowedType(val fidlgen.Type) string { |
| var t string |
| switch val.Kind { |
| case fidlgen.PrimitiveType, fidlgen.HandleType, fidlgen.RequestType: |
| return c.compileType(val) |
| case fidlgen.ArrayType: |
| t = fmt.Sprintf("&mut [%s; %v]", c.compileBorrowedType(*val.ElementType), *val.ElementCount) |
| case fidlgen.VectorType: |
| t = c.compileBorrowedType(*val.ElementType) |
| // We use slices for primitive numeric types so that encoding becomes a |
| // memcpy. Rust does not guarantee the bit patterns for bool values, so |
| // we omit them from the optimization. |
| if val.ElementType.Kind == fidlgen.PrimitiveType && val.ElementType.PrimitiveSubtype != fidlgen.Bool { |
| t = fmt.Sprintf("&[%s]", t) |
| } else { |
| t = fmt.Sprintf("&mut dyn ExactSizeIterator<Item = %s>", t) |
| } |
| case fidlgen.StringType: |
| t = "&str" |
| case fidlgen.IdentifierType: |
| t = c.compileCamelCompoundIdentifier(val.Identifier) |
| declInfo, ok := c.decls[val.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("unknown identifier: %v", val.Identifier)) |
| } |
| switch declInfo.Type { |
| case fidlgen.StructDeclType, fidlgen.UnionDeclType: |
| t = fmt.Sprintf("&mut %s", t) |
| case fidlgen.ProtocolDeclType: |
| t = fmt.Sprintf("fidl::endpoints::ClientEnd<%sMarker>", t) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", val.Kind)) |
| } |
| |
| if val.Nullable { |
| return fmt.Sprintf("Option<%s>", t) |
| } |
| return t |
| } |
| |
| func (c *compiler) compileBits(val fidlgen.Bits) Bits { |
| e := Bits{ |
| Bits: val, |
| Name: c.compileCamelCompoundIdentifier(val.Name), |
| Type: c.compileType(val.Type), |
| Members: []BitsMember{}, |
| } |
| for _, v := range val.Members { |
| e.Members = append(e.Members, BitsMember{ |
| BitsMember: v, |
| Name: compileScreamingSnakeIdentifier(v.Name), |
| Value: c.compileConstant(v.Value, val.Type), |
| }) |
| } |
| return e |
| } |
| |
| func (c *compiler) compileEnum(val fidlgen.Enum) Enum { |
| e := Enum{ |
| Enum: val, |
| Name: c.compileCamelCompoundIdentifier(val.Name), |
| Type: compilePrimitiveSubtype(val.Type), |
| Members: []EnumMember{}, |
| } |
| for _, v := range val.Members { |
| e.Members = append(e.Members, EnumMember{ |
| EnumMember: v, |
| Name: compileCamelIdentifier(v.Name), |
| Value: v.Value.Value, |
| }) |
| } |
| e.MinMember = findMinEnumMember(val.Type, e.Members).Name |
| return e |
| } |
| |
| func findMinEnumMember(typ fidlgen.PrimitiveSubtype, members []EnumMember) EnumMember { |
| var res EnumMember |
| if typ.IsSigned() { |
| min := int64(math.MaxInt64) |
| for _, m := range members { |
| v, err := strconv.ParseInt(m.Value, 10, 64) |
| if err != nil { |
| panic(fmt.Sprintf("invalid enum member value: %s", err)) |
| } |
| if v < min { |
| min = v |
| res = m |
| } |
| } |
| } else { |
| min := uint64(math.MaxUint64) |
| for _, m := range members { |
| v, err := strconv.ParseUint(m.Value, 10, 64) |
| if err != nil { |
| panic(fmt.Sprintf("invalid enum member value: %s", err)) |
| } |
| if v < min { |
| min = v |
| res = m |
| } |
| } |
| } |
| return res |
| } |
| |
| func (c *compiler) compileHandleMetadataWrapper(val *fidlgen.Type) (string, bool) { |
| hi := c.fieldHandleInformation(val) |
| name := fmt.Sprintf("HandleWrapperObjectType%sRights%s", hi.shortObjectType, hi.shortRights) |
| wrapper := HandleMetadataWrapper{ |
| Name: name, |
| Subtype: hi.fullObjectType, |
| Rights: hi.fullRights, |
| } |
| if hi.hasHandleMetadata { |
| if _, ok := c.handleMetadataWrappers[name]; !ok { |
| c.handleMetadataWrappers[name] = wrapper |
| } |
| } |
| return name, hi.hasHandleMetadata |
| } |
| |
| // compileParameterArray extracts the fields of the given method-request or |
| // method-response payload struct as method parameters, and prepares them for |
| // code generation by providing rust types and names to use. |
| func (c *compiler) compileParameterArray(payload fidlgen.Struct) []Parameter { |
| var parameters []Parameter |
| for _, v := range payload.Members { |
| wrapperName, hasHandleMetadata := c.compileHandleMetadataWrapper(&v.Type) |
| parameters = append(parameters, Parameter{ |
| OGType: v.Type, |
| Type: c.compileType(v.Type), |
| BorrowedType: c.compileBorrowedType(v.Type), |
| Name: compileSnakeIdentifier(v.Name), |
| HandleWrapperName: wrapperName, |
| HasHandleMetadata: hasHandleMetadata, |
| }) |
| } |
| return parameters |
| } |
| |
| // compileParameterSingleton converts a union or table payload into a form that |
| // is ready for code generation, providing a Rust-friendly type and name (always |
| // "payload"). Unlike compileParameterArray, this does not result in the payload |
| // layout being "flattened" into its constituent members. |
| func (c *compiler) compileParameterSingleton(val *fidlgen.Type) []Parameter { |
| wrapperName, hasHandleMetadata := c.compileHandleMetadataWrapper(val) |
| return []Parameter{{ |
| OGType: *val, |
| Type: c.compileType(*val), |
| BorrowedType: c.compileBorrowedType(*val), |
| Name: "payload", |
| HandleWrapperName: wrapperName, |
| HasHandleMetadata: hasHandleMetadata, |
| }} |
| } |
| |
| // TODO(fxbug.dev/76655): Remove this. |
| const maximumAllowedParameters = 12 |
| |
| func (c *compiler) compileProtocol(val fidlgen.Protocol) Protocol { |
| r := Protocol{ |
| Protocol: val, |
| ECI: val.Name, |
| Name: c.compileCamelCompoundIdentifier(val.Name), |
| Methods: []Method{}, |
| ProtocolName: strings.Trim(val.GetServiceName(), "\""), |
| } |
| |
| getParametersFromType := func(t *fidlgen.Type) []Parameter { |
| if _, ok := c.methodTypeUses[t.Identifier]; ok { |
| if val, ok := c.methodTypeStructs[t.Identifier]; ok { |
| return c.compileParameterArray(val.Struct) |
| } |
| return c.compileParameterSingleton(t) |
| } |
| panic(fmt.Sprintf("unknown request/response layout: %v", t.Identifier)) |
| } |
| |
| for _, v := range val.Methods { |
| var compiledRequestParameterList []Parameter |
| if v.RequestPayload != nil { |
| compiledRequestParameterList = getParametersFromType(v.RequestPayload) |
| if len(compiledRequestParameterList) > maximumAllowedParameters { |
| panic(fmt.Sprintf( |
| `Method %s.%s has %d parameters, but the FIDL Rust bindings `+ |
| `only support up to %d. See https://fxbug.dev/76655 for details.`, |
| val.Name, v.Name, len(compiledRequestParameterList), maximumAllowedParameters)) |
| } |
| } |
| |
| name := compileSnakeIdentifier(v.Name) |
| camelName := compileCamelIdentifier(v.Name) |
| var compiledResponseParameterList []Parameter |
| var foundResult *Result |
| |
| findResultType := func() *Result { |
| if result, ok := c.results[v.ResultType.Identifier]; ok { |
| return &result |
| } |
| panic(fmt.Sprintf("unknown result type: %v", v.ResultType.Identifier)) |
| } |
| |
| if v.HasError { |
| compiledResponseParameterList = getParametersFromType(v.ResponsePayload) |
| foundResult = findResultType() |
| } else if v.HasTransportError() { |
| compiledResponseParameterList = getParametersFromType(v.ValueType) |
| foundResult = findResultType() |
| } else if v.ResponsePayload != nil { |
| compiledResponseParameterList = getParametersFromType(v.ResponsePayload) |
| } |
| |
| r.Methods = append(r.Methods, Method{ |
| Method: v, |
| Name: name, |
| CamelName: camelName, |
| Request: compiledRequestParameterList, |
| Response: compiledResponseParameterList, |
| Result: foundResult, |
| }) |
| } |
| |
| return r |
| } |
| |
| func (c *compiler) compileService(val fidlgen.Service) Service { |
| r := Service{ |
| Service: val, |
| Name: c.compileCamelCompoundIdentifier(val.Name), |
| Members: []ServiceMember{}, |
| ServiceName: val.GetServiceName(), |
| } |
| |
| for _, v := range val.Members { |
| m := ServiceMember{ |
| ServiceMember: v, |
| Name: string(v.Name), |
| CamelName: compileCamelIdentifier(v.Name), |
| SnakeName: compileSnakeIdentifier(v.Name), |
| ProtocolType: c.compileCamelCompoundIdentifier(v.Type.Identifier), |
| } |
| r.Members = append(r.Members, m) |
| } |
| |
| return r |
| } |
| |
| func (c *compiler) compileStructMember(val fidlgen.StructMember) StructMember { |
| hi := c.fieldHandleInformation(&val.Type) |
| return StructMember{ |
| StructMember: val, |
| Type: c.compileType(val.Type), |
| OGType: val.Type, |
| Name: compileSnakeIdentifier(val.Name), |
| OffsetV1: val.FieldShapeV1.Offset, |
| OffsetV2: val.FieldShapeV2.Offset, |
| HasDefault: false, |
| DefaultValue: "", // TODO(cramertj) support defaults |
| HasHandleMetadata: hi.hasHandleMetadata, |
| HandleSubtype: hi.fullObjectType, |
| HandleRights: hi.fullRights, |
| } |
| } |
| |
| func (c *compiler) computeUseFidlStructCopyForStruct(st fidlgen.Struct) bool { |
| if len(st.Members) == 0 { |
| // In Rust, structs containing empty structs do not match the C++ struct layout |
| // since empty structs have size 0 in Rust -- even in repr(C). |
| return false |
| } |
| for _, member := range st.Members { |
| if !c.computeUseFidlStructCopy(&member.Type) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func (c *compiler) computeUseFidlStructCopy(typ *fidlgen.Type) bool { |
| if typ.Nullable { |
| return false |
| } |
| switch typ.Kind { |
| case fidlgen.ArrayType: |
| return c.computeUseFidlStructCopy(typ.ElementType) |
| case fidlgen.VectorType, fidlgen.StringType, fidlgen.HandleType, fidlgen.RequestType: |
| return false |
| case fidlgen.PrimitiveType: |
| switch typ.PrimitiveSubtype { |
| case fidlgen.Bool, fidlgen.Float32, fidlgen.Float64: |
| return false |
| } |
| return true |
| case fidlgen.IdentifierType: |
| if c.inExternalLibrary(typ.Identifier.Parse()) { |
| return false |
| } |
| declType := c.decls[typ.Identifier].Type |
| switch declType { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType, fidlgen.ProtocolDeclType: |
| return false |
| case fidlgen.StructDeclType: |
| st, ok := c.structs[typ.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("struct not found: %v", typ.Identifier)) |
| } |
| return c.computeUseFidlStructCopyForStruct(st) |
| default: |
| panic(fmt.Sprintf("unknown declaration type: %v", declType)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", typ.Kind)) |
| } |
| } |
| |
| func (c *compiler) resolveStruct(identifier fidlgen.EncodedCompoundIdentifier) *fidlgen.Struct { |
| if c.inExternalLibrary(identifier.Parse()) { |
| // This behavior is matched by computeUseFullStructCopy. |
| return nil |
| } |
| declType := c.decls[identifier].Type |
| if declType == fidlgen.StructDeclType { |
| st, ok := c.structs[identifier] |
| if !ok { |
| panic(fmt.Sprintf("struct not found: %v", identifier)) |
| } |
| return &st |
| } |
| return nil |
| } |
| |
| type rustPaddingMarker struct { |
| Type string |
| Offset int |
| // Mask is a string so it can be in hex. |
| Mask string |
| } |
| |
| func toRustPaddingMarker(in fidlgen.PaddingMarker) rustPaddingMarker { |
| switch len(in.Mask) { |
| case 2: |
| return rustPaddingMarker{ |
| Type: "u16", |
| Offset: in.Offset, |
| Mask: fmt.Sprintf("0x%04xu16", binary.LittleEndian.Uint16(in.Mask)), |
| } |
| case 4: |
| return rustPaddingMarker{ |
| Type: "u32", |
| Offset: in.Offset, |
| Mask: fmt.Sprintf("0x%08xu32", binary.LittleEndian.Uint32(in.Mask)), |
| } |
| case 8: |
| return rustPaddingMarker{ |
| Type: "u64", |
| Offset: in.Offset, |
| Mask: fmt.Sprintf("0x%016xu64", binary.LittleEndian.Uint64(in.Mask)), |
| } |
| default: |
| panic("unexpected mask size") |
| } |
| } |
| |
| func toRustPaddingMarkers(in []fidlgen.PaddingMarker) []rustPaddingMarker { |
| var out []rustPaddingMarker |
| for _, m := range in { |
| out = append(out, toRustPaddingMarker(m)) |
| } |
| return out |
| } |
| |
| func (c *compiler) compileStruct(val fidlgen.Struct) Struct { |
| name := c.compileCamelCompoundIdentifier(val.Name) |
| r := Struct{ |
| Struct: val, |
| ECI: val.Name, |
| Name: name, |
| Members: []StructMember{}, |
| SizeV1: val.TypeShapeV1.InlineSize, |
| SizeV2: val.TypeShapeV2.InlineSize, |
| AlignmentV1: val.TypeShapeV1.Alignment, |
| AlignmentV2: val.TypeShapeV2.Alignment, |
| PaddingMarkersV1: toRustPaddingMarkers(val.BuildPaddingMarkers(fidlgen.WireFormatVersionV1)), |
| PaddingMarkersV2: toRustPaddingMarkers(val.BuildPaddingMarkers(fidlgen.WireFormatVersionV2)), |
| FlattenedPaddingMarkersV1: toRustPaddingMarkers(val.BuildFlattenedPaddingMarkers(fidlgen.WireFormatVersionV1, c.resolveStruct)), |
| FlattenedPaddingMarkersV2: toRustPaddingMarkers(val.BuildFlattenedPaddingMarkers(fidlgen.WireFormatVersionV2, c.resolveStruct)), |
| } |
| |
| for _, v := range val.Members { |
| member := c.compileStructMember(v) |
| r.Members = append(r.Members, member) |
| r.HasPadding = r.HasPadding || (v.FieldShapeV1.Padding != 0) |
| } |
| |
| r.UseFidlStructCopy = c.computeUseFidlStructCopyForStruct(val) |
| |
| return r |
| } |
| |
| func (c *compiler) compileUnionMember(val fidlgen.UnionMember) UnionMember { |
| hi := c.fieldHandleInformation(&val.Type) |
| return UnionMember{ |
| UnionMember: val, |
| Type: c.compileType(val.Type), |
| OGType: val.Type, |
| Name: compileCamelIdentifier(val.Name), |
| Ordinal: val.Ordinal, |
| HasHandleMetadata: hi.hasHandleMetadata, |
| HandleSubtype: hi.fullObjectType, |
| HandleRights: hi.fullRights, |
| } |
| } |
| |
| func (c *compiler) compileUnion(val fidlgen.Union) Union { |
| r := Union{ |
| Union: val, |
| ECI: val.Name, |
| Name: c.compileCamelCompoundIdentifier(val.Name), |
| Members: []UnionMember{}, |
| } |
| |
| for _, v := range val.Members { |
| if v.Reserved { |
| continue |
| } |
| r.Members = append(r.Members, c.compileUnionMember(v)) |
| } |
| |
| return r |
| } |
| |
| func (c *compiler) compileResultFromUnion(m fidlgen.Method, root Root) Result { |
| // Results may be compiled more than once if they are composed. In that |
| // case, just re use the existing value. |
| if r, ok := c.results[m.ResultType.Identifier]; ok { |
| return r |
| } |
| |
| r := Result{ |
| ECI: m.ResultType.Identifier, |
| Name: c.compileCamelCompoundIdentifier(m.ResultType.Identifier), |
| HasTransportError: m.HasTransportError(), |
| } |
| |
| if m.HasError { |
| r.ErrOGType = m.ErrorType |
| errType := c.compileType(*m.ErrorType) |
| r.ErrType = &errType |
| } |
| declInfo, ok := c.decls[m.ValueType.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("declaration not found: %v", m.ValueType.Identifier)) |
| } |
| |
| switch declInfo.Type { |
| case fidlgen.StructDeclType: |
| for _, m := range c.methodTypeStructs[m.ValueType.Identifier].Members { |
| wrapperName, hasHandleMetadata := c.compileHandleMetadataWrapper(&m.OGType) |
| r.Ok = append(r.Ok, ResultOkEntry{ |
| OGType: m.OGType, |
| Type: m.Type, |
| HasHandleMetadata: hasHandleMetadata, |
| HandleWrapperName: wrapperName, |
| }) |
| } |
| case fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| wrapperName, hasHandleMetadata := c.compileHandleMetadataWrapper(m.ValueType) |
| r.Ok = append(r.Ok, ResultOkEntry{ |
| OGType: *m.ValueType, |
| Type: c.compileType(*m.ValueType), |
| HasHandleMetadata: hasHandleMetadata, |
| HandleWrapperName: wrapperName, |
| }) |
| default: |
| panic("payload must be struct, table, or union") |
| } |
| |
| c.results[r.ECI] = r |
| |
| return r |
| } |
| |
| func (c *compiler) compileTable(table fidlgen.Table) Table { |
| var members []TableMember |
| for _, member := range table.SortedMembersNoReserved() { |
| hi := c.fieldHandleInformation(&member.Type) |
| members = append(members, TableMember{ |
| TableMember: member, |
| OGType: member.Type, |
| Type: c.compileType(member.Type), |
| Name: compileSnakeIdentifier(member.Name), |
| Ordinal: member.Ordinal, |
| HasHandleMetadata: hi.hasHandleMetadata, |
| HandleSubtype: hi.fullObjectType, |
| HandleRights: hi.fullRights, |
| }) |
| } |
| return Table{ |
| Table: table, |
| ECI: table.Name, |
| Name: c.compileCamelCompoundIdentifier(table.Name), |
| Members: members, |
| } |
| } |
| |
| type derives uint16 |
| |
| const ( |
| derivesDebug derives = 1 << iota |
| derivesCopy |
| derivesClone |
| derivesEq |
| derivesPartialEq |
| derivesOrd |
| derivesPartialOrd |
| derivesHash |
| derivesAsBytes |
| derivesFromBytes |
| derivesAll derives = (1 << iota) - 1 |
| |
| derivesMinimal derives = derivesDebug | derivesPartialEq |
| derivesMinimalNonResource derives = derivesMinimal | derivesClone |
| derivesAllButZerocopy derives = derivesAll & ^derivesAsBytes & ^derivesFromBytes |
| ) |
| |
| // note: keep this list in the same order as the derives definitions |
| var derivesNames = []string{ |
| // [START derived_traits] |
| "Debug", |
| "Copy", |
| "Clone", |
| "Eq", |
| "PartialEq", |
| "Ord", |
| "PartialOrd", |
| "Hash", |
| "zerocopy::AsBytes", |
| "zerocopy::FromBytes", |
| // [END derived_traits] |
| } |
| |
| func (v derives) and(others derives) derives { |
| return v & others |
| } |
| |
| func (v derives) remove(others ...derives) derives { |
| result := v |
| for _, other := range others { |
| result &= ^other |
| } |
| return result |
| } |
| |
| func (v derives) andUnknown() derives { |
| return v.and(derivesMinimal) |
| } |
| |
| func (v derives) andUnknownNonResource() derives { |
| return v.and(derivesMinimalNonResource) |
| } |
| |
| func (v derives) contains(other derives) bool { |
| return (v & other) != 0 |
| } |
| |
| func (v derives) String() string { |
| var parts []string |
| for i, bit := 0, derives(1); bit&derivesAll != 0; i, bit = i+1, bit<<1 { |
| if v.contains(bit) { |
| parts = append(parts, derivesNames[i]) |
| } |
| } |
| if len(parts) == 0 { |
| return "" |
| } |
| return fmt.Sprintf("#[derive(%s)]", strings.Join(parts, ", ")) |
| } |
| |
| // The status of derive calculation for a particular type. |
| type deriveStatus struct { |
| // recursing indicates whether or not we've passed through this type already |
| // on a recursive descent. This is used to prevent unbounded recursion on |
| // mutually-recursive types. |
| recursing bool |
| // complete indicates whether or not the derive for the given type has |
| // already been successfully calculated and stored in the IR. |
| complete bool |
| } |
| |
| // The state of the current calculation of derives. |
| type derivesCompiler struct { |
| *compiler |
| topMostCall bool |
| didShortCircuitOnRecursion bool |
| statuses map[EncodedCompoundIdentifier]deriveStatus |
| root *Root |
| } |
| |
| // [START fill_derives] |
| // Calculates what traits should be derived for each output type, |
| // filling in all `*derives` in the IR. |
| func (c *compiler) fillDerives(ir *Root) { |
| // [END fill_derives] |
| dc := &derivesCompiler{ |
| compiler: c, |
| topMostCall: true, |
| didShortCircuitOnRecursion: false, |
| statuses: make(map[EncodedCompoundIdentifier]deriveStatus), |
| root: ir, |
| } |
| |
| // Bits and enums always derive all traits |
| for _, v := range ir.Protocols { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Structs { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.ExternalStructs { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Unions { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Results { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Tables { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| } |
| |
| func (dc *derivesCompiler) fillDerivesForECI(eci EncodedCompoundIdentifier) derives { |
| declInfo, ok := dc.decls[eci] |
| if !ok { |
| panic(fmt.Sprintf("declaration not found: %v", eci)) |
| } |
| |
| // TODO(fxbug.dev/61760): Make external type information available here. |
| // Currently, we conservatively assume external structs/tables/unions only |
| // derive the minimal set of traits, plus Clone for value types (not having |
| // Clone is especially annoying, so we put resourceness of external types |
| // into the IR as a stopgap solution). |
| if dc.inExternalLibrary(eci.Parse()) { |
| switch declInfo.Type { |
| case fidlgen.StructDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| if declInfo.IsValueType() { |
| return derivesMinimalNonResource |
| } |
| return derivesMinimal |
| } |
| } |
| |
| // Return early for declaration types that do not require recursion. |
| switch declInfo.Type { |
| case fidlgen.ConstDeclType: |
| panic("const decl should never have derives") |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType: |
| // Enums and bits are always simple, non-float primitives which |
| // implement all derivable traits except zerocopy. |
| return derivesAllButZerocopy |
| case fidlgen.ProtocolDeclType: |
| // When a protocol is used as a type, it means a ClientEnd in Rust. |
| return derivesAllButZerocopy.remove(derivesCopy, derivesClone) |
| } |
| |
| topMostCall := dc.topMostCall |
| if dc.topMostCall { |
| dc.topMostCall = false |
| } |
| deriveStatus := dc.statuses[eci] |
| if deriveStatus.recursing { |
| // If we've already seen the current type while recursing, |
| // the algorithm has already explored all of the other fields contained |
| // within the cycle, so we can return true for all derives, and the |
| // correct results will be bubbled up. |
| dc.didShortCircuitOnRecursion = true |
| return derivesAll |
| } |
| deriveStatus.recursing = true |
| dc.statuses[eci] = deriveStatus |
| |
| var derivesOut derives |
| typeSwitch: |
| switch declInfo.Type { |
| case fidlgen.StructDeclType: |
| st := dc.root.findStruct(eci, false) |
| if st == nil { |
| panic(fmt.Sprintf("struct not found: %v", eci)) |
| } |
| // Check if the derives have already been calculated |
| if deriveStatus.complete { |
| derivesOut = st.Derives |
| break typeSwitch |
| } |
| derivesOut = derivesAll |
| for _, member := range st.Members { |
| derivesOut = derivesOut.and(dc.fillDerivesForType(member.OGType)) |
| } |
| if st.HasPadding || len(st.Members) == 0 { |
| derivesOut = derivesOut.remove(derivesAsBytes, derivesFromBytes) |
| } |
| st.Derives = derivesOut |
| case fidlgen.TableDeclType: |
| table := dc.root.findTable(eci) |
| if table == nil { |
| panic(fmt.Sprintf("table not found: %v", eci)) |
| } |
| // Check if the derives have already been calculated |
| if deriveStatus.complete { |
| derivesOut = table.Derives |
| break typeSwitch |
| } |
| derivesOut = derivesAllButZerocopy |
| for _, member := range table.Members { |
| derivesOut = derivesOut.and(dc.fillDerivesForType(member.OGType)) |
| } |
| if table.IsResourceType() { |
| derivesOut = derivesOut.andUnknown() |
| } else { |
| derivesOut = derivesOut.andUnknownNonResource() |
| } |
| table.Derives = derivesOut |
| case fidlgen.UnionDeclType: |
| if result := dc.root.findResult(eci); result != nil { |
| // It's a Result, not a union |
| // Check if the derives have already been calculated |
| if deriveStatus.complete { |
| derivesOut = result.Derives |
| break typeSwitch |
| } |
| derivesOut = derivesAllButZerocopy |
| for _, ok := range result.Ok { |
| derivesOut = derivesOut.and(dc.fillDerivesForType(ok.OGType)) |
| } |
| if result.ErrOGType != nil { |
| derivesOut = derivesOut.and(dc.fillDerivesForType(*result.ErrOGType)) |
| } |
| result.Derives = derivesOut |
| } else if union := dc.root.findUnion(eci); union != nil { |
| // Check if the derives have already been calculated |
| if deriveStatus.complete { |
| derivesOut = union.Derives |
| break typeSwitch |
| } |
| derivesOut = derivesAllButZerocopy |
| for _, member := range union.Members { |
| derivesOut = derivesOut.and(dc.fillDerivesForType(member.OGType)) |
| } |
| if union.IsFlexible() { |
| if union.IsResourceType() { |
| derivesOut = derivesOut.andUnknown() |
| } else { |
| derivesOut = derivesOut.andUnknownNonResource() |
| } |
| } |
| union.Derives = derivesOut |
| } else { |
| panic(fmt.Sprintf("union not found: %v", eci)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown declaration type: %v", declInfo.Type)) |
| } |
| if topMostCall || !dc.didShortCircuitOnRecursion { |
| // Our completed result is only valid if it's either at top-level |
| // (ensuring we've fully visited all child types in the recursive |
| // substructure at least once) or if we performed no recursion-based |
| // short-circuiting, in which case results are correct and absolute. |
| // |
| // Note that non-topMostCalls are invalid if we did short-circuit |
| // on recursion, because we might have short-circuited just beneath |
| // a type without having explored all of its children at least once |
| // beneath it. |
| // |
| // For example, imagine A -> B -> C -> A. |
| // When we start on A, we go to B, then go to C, then go to A, at which |
| // point we short-circuit. The intermediate result for C is invalid |
| // for anything except the computation of A and B above, as it does |
| // not take into account that C contains A and B, only that it contains |
| // its top-level fields (other than A). In order to get a correct |
| // idea of the shape of C, we have to start with C, following through |
| // every branch until we find C again. |
| deriveStatus.complete = true |
| } |
| if topMostCall { |
| // Reset intermediate state |
| dc.topMostCall = true |
| dc.didShortCircuitOnRecursion = false |
| } |
| deriveStatus.recursing = false |
| dc.statuses[eci] = deriveStatus |
| return derivesOut |
| } |
| |
| func (dc *derivesCompiler) fillDerivesForType(ogType fidlgen.Type) derives { |
| switch ogType.Kind { |
| case fidlgen.ArrayType: |
| return dc.fillDerivesForType(*ogType.ElementType) |
| case fidlgen.VectorType: |
| return derivesAllButZerocopy.remove(derivesCopy).and(dc.fillDerivesForType(*ogType.ElementType)) |
| case fidlgen.StringType: |
| return derivesAllButZerocopy.remove(derivesCopy) |
| case fidlgen.HandleType, fidlgen.RequestType: |
| return derivesAllButZerocopy.remove(derivesCopy, derivesClone) |
| case fidlgen.PrimitiveType: |
| switch ogType.PrimitiveSubtype { |
| case fidlgen.Bool: |
| return derivesAllButZerocopy |
| case fidlgen.Int8, fidlgen.Int16, fidlgen.Int32, fidlgen.Int64: |
| return derivesAll |
| case fidlgen.Uint8, fidlgen.Uint16, fidlgen.Uint32, fidlgen.Uint64: |
| return derivesAll |
| case fidlgen.Float32, fidlgen.Float64: |
| // Floats don't have a total ordering due to NAN and its multiple representations. |
| return derivesAllButZerocopy.remove(derivesEq, derivesOrd, derivesHash) |
| default: |
| panic(fmt.Sprintf("unknown primitive type: %v", ogType.PrimitiveSubtype)) |
| } |
| case fidlgen.IdentifierType: |
| internalTypeDerives := dc.fillDerivesForECI(ogType.Identifier) |
| if ogType.Nullable { |
| // A nullable struct/union gets put in Option<Box<...>> and so |
| // cannot derive Copy, AsBytes, or FromBytes. Bits, enums, and |
| // tables are never nullable. The only other possibility for an |
| // identifier type is a protocol, which cannot derive these either. |
| return internalTypeDerives.remove(derivesCopy, derivesAsBytes, derivesFromBytes) |
| } |
| return internalTypeDerives |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", ogType.Kind)) |
| } |
| } |
| |
| func Compile(r fidlgen.Root) Root { |
| r = r.ForBindings("rust") |
| root := Root{} |
| thisLibParsed := r.Name.Parse() |
| c := compiler{ |
| decls: r.DeclsWithDependencies(), |
| library: thisLibParsed, |
| externCrates: map[string]struct{}{}, |
| methodTypeUses: r.MethodTypeUsageMap(), |
| methodTypeStructs: map[fidlgen.EncodedCompoundIdentifier]Struct{}, |
| structs: map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct{}, |
| results: map[fidlgen.EncodedCompoundIdentifier]Result{}, |
| handleMetadataWrappers: map[string]HandleMetadataWrapper{}, |
| } |
| |
| for _, s := range r.Structs { |
| c.structs[s.Name] = s |
| } |
| |
| for _, s := range r.ExternalStructs { |
| c.structs[s.Name] = s |
| } |
| |
| for _, v := range r.Bits { |
| root.Bits = append(root.Bits, c.compileBits(v)) |
| } |
| |
| for _, v := range r.Consts { |
| root.Consts = append(root.Consts, c.compileConst(v)) |
| } |
| |
| for _, v := range r.Enums { |
| root.Enums = append(root.Enums, c.compileEnum(v)) |
| } |
| |
| for _, v := range r.Services { |
| root.Services = append(root.Services, c.compileService(v)) |
| } |
| |
| for _, v := range r.Structs { |
| compiled := c.compileStruct(v) |
| if _, ok := c.methodTypeUses[v.Name]; ok { |
| c.methodTypeStructs[v.Name] = compiled |
| if v.IsAnonymous() { |
| continue |
| } |
| } |
| root.Structs = append(root.Structs, compiled) |
| } |
| |
| for _, v := range r.ExternalStructs { |
| compiled := c.compileStruct(v) |
| if _, ok := c.methodTypeUses[v.Name]; ok { |
| c.methodTypeStructs[v.Name] = compiled |
| if v.IsAnonymous() { |
| continue |
| } |
| } |
| root.ExternalStructs = append(root.ExternalStructs, compiled) |
| } |
| |
| for _, v := range r.Tables { |
| root.Tables = append(root.Tables, c.compileTable(v)) |
| } |
| |
| for _, v := range r.Protocols { |
| for _, m := range v.Methods { |
| // A result is referenced multiple times when a method is composed. |
| // We always need to compile the result from the union, because it |
| // will be referenced when compiling the method and affects how the |
| // method handles its arguments and return value, so we always run |
| // compileResultFromUnion to ensure that c.results contains the |
| // result union. |
| // |
| // However, we only want to actually generate the type aliases for |
| // the result union once, and we especially don't want to generate |
| // aliases when those already exist in another generated library, so |
| // we don't add the result type to root.Results unless this is the |
| // original non-composed method. |
| if m.ResultType != nil { |
| result := c.compileResultFromUnion(m, root) |
| if !m.IsComposed { |
| root.Results = append(root.Results, result) |
| } |
| } |
| } |
| } |
| |
| for _, v := range r.Unions { |
| if result := root.findResult(v.Name); result == nil { |
| root.Unions = append(root.Unions, c.compileUnion(v)) |
| } |
| } |
| |
| for _, v := range r.Protocols { |
| root.Protocols = append(root.Protocols, c.compileProtocol(v)) |
| } |
| |
| // Sort by wrapper name for deterministic output order. |
| handleMetadataWrapperNames := make([]string, 0, len(c.handleMetadataWrappers)) |
| for name := range c.handleMetadataWrappers { |
| handleMetadataWrapperNames = append(handleMetadataWrapperNames, name) |
| } |
| sort.Strings(handleMetadataWrapperNames) |
| for _, name := range handleMetadataWrapperNames { |
| root.HandleMetadataWrappers = append(root.HandleMetadataWrappers, c.handleMetadataWrappers[name]) |
| } |
| |
| c.fillDerives(&root) |
| |
| thisLibCompiled := compileLibraryName(thisLibParsed) |
| |
| // Sort the extern crates to make sure the generated file is |
| // consistent across builds. |
| var externCrates []string |
| for k := range c.externCrates { |
| externCrates = append(externCrates, k) |
| } |
| sort.Strings(externCrates) |
| |
| for _, k := range externCrates { |
| if k != thisLibCompiled { |
| root.ExternCrates = append(root.ExternCrates, k) |
| } |
| } |
| |
| return root |
| } |