| // 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 ( |
| "fmt" |
| "math" |
| "sort" |
| "strconv" |
| "strings" |
| "unicode" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| type EncodedCompoundIdentifier = fidlgen.EncodedCompoundIdentifier |
| |
| type Type struct { |
| // TODO(https://fxbug.dev/42156522): Remove Resourceness once stored on fidlgen.Type. |
| fidlgen.Resourceness |
| |
| // Information extracted from fidlgen.Type. |
| Kind fidlgen.TypeKind |
| Nullable bool |
| PrimitiveSubtype fidlgen.PrimitiveSubtype |
| ElementType *Type |
| Identifier EncodedCompoundIdentifier |
| DeclType fidlgen.DeclType |
| |
| // The marker type that implements fidl::encoding::Type. |
| Fidl string |
| // The associated type fidl::encoding::Type::Owned. |
| Owned string |
| // The type to use when this occurs as a method parameter. |
| // TODO(https://fxbug.dev/42073194): Once the transition to the new types if complete, |
| // document this as being {Value,Resource}Type::Borrowed. |
| Param string |
| } |
| |
| type Alias struct { |
| fidlgen.Alias |
| Name string |
| Type Type |
| } |
| |
| type Bits struct { |
| fidlgen.Bits |
| Name string |
| UnderlyingType 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 |
| UnderlyingType string |
| Members []EnumMember |
| // Member name with the minimum value, used as an arbitrary default value |
| // in Decode::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 Type |
| Name string |
| Ordinal int |
| } |
| |
| type Struct struct { |
| fidlgen.Struct |
| ECI EncodedCompoundIdentifier |
| Derives derives |
| Name string |
| Members []StructMember |
| PaddingMarkersV2 []fidlgen.PaddingMarker |
| FlattenedPaddingMarkersV2 []fidlgen.PaddingMarker |
| SizeV2 int |
| AlignmentV2 int |
| HasPadding bool |
| // True if the struct should be encoded and decoded by memcpy. |
| UseFidlStructCopy bool |
| } |
| |
| type StructMember struct { |
| fidlgen.StructMember |
| Type Type |
| Name string |
| OffsetV2 int |
| } |
| |
| type Table struct { |
| fidlgen.Table |
| Derives derives |
| ECI EncodedCompoundIdentifier |
| Name string |
| Members []TableMember |
| } |
| |
| func (t *Table) ReversedMembers() []TableMember { |
| var r []TableMember |
| for i := len(t.Members) - 1; i >= 0; i-- { |
| r = append(r, t.Members[i]) |
| } |
| return r |
| } |
| |
| type TableMember struct { |
| fidlgen.TableMember |
| Type Type |
| Name string |
| Ordinal int |
| } |
| |
| // 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 |
| // String to use for ProtocolMarker::DEBUG_NAME. |
| DebugName string |
| // True if the protocol is marked @discoverable. |
| Discoverable bool |
| // Name of the protocol's Marker struct. |
| Marker string |
| // Name of the protocol's Proxy struct. |
| Proxy string |
| // Name of the protocol's ProxyInterface trait. |
| ProxyInterface string |
| // Name of the protocol's SynchronousProxy struct. |
| SynchronousProxy string |
| // Name of the protocol's Request enum. |
| Request string |
| // Name of the protocol's RequestStream struct. |
| RequestStream string |
| // Name of the protocol's Event enum. |
| Event string |
| // Name of the protocol's EventStream struct. |
| EventStream string |
| // Name of the protocol's ControlHandle struct. |
| ControlHandle string |
| // List of methods that are part of this protocol. |
| Methods []Method |
| } |
| |
| // 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 |
| // Name of the method's Responder struct. |
| Responder string |
| // Name of the method's ResponseFut type in the protocol's ProxyInterface trait. |
| ResponseFut string |
| |
| Request Payload |
| Response Payload |
| } |
| |
| // 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" |
| } |
| |
| // A method request or response. |
| type Payload struct { |
| // The fidl::encoding::Type type. It can be fidl::encoding::EmptyPayload, |
| // a struct, table, or union, or (only for two-way responses) one of |
| // fidl::encoding::{Result,Flexible,FlexibleResult}Type. |
| FidlType string |
| // Equivalent to <FidlType as fidl::encoding::Type>::Owned. |
| OwnedType string |
| // The user-facing owned type. This is derived from OwnedType by removing |
| // flexible wrappers (if any) and flattening structs to tuples. |
| TupleType string |
| // For methods that use error syntax, TupleType is an alias that refers |
| // to TupleTypeAliasRhs. Otherwise, TupleTypeAliasRhs is empty. |
| // TODO(https://fxbug.dev/42073194): Remove. |
| TupleTypeAliasRhs string |
| // Parameters for sending this payload. For an empty payload, this is nil. |
| // For a struct without error syntax, it contains one element per struct |
| // field. Otherwise, it contains a single element (table, union, or Result). |
| Parameters []Parameter |
| // Assuming that names from Parameters are in scope, EncodeExpr is an |
| // expression of a type implementing fidl::encoding::Encode<FidlType>. |
| EncodeExpr string |
| // ConvertToTuple converts an expression to TupleType. If OwnedType is |
| // fidl::encoding::Flexible or fidl::encoding::FlexibleResult, expects the |
| // input variable to be the Ok return value of .into_result(). Otherwise, |
| // expects the input variable to be of type OwnedType. |
| // TODO(https://fxbug.dev/42073194): Remove. |
| ConvertToTuple func(string) string |
| // Like ConvertToTuple, but uses names from Parameters for inclusion in a |
| // struct. Examples for ConvertToFields("v"): |
| // "" |
| // "param1: v.param1, param2: v.param2," |
| // "result: v.map(|x| x.param1)," |
| // TODO(https://fxbug.dev/42073194): Remove. |
| ConvertToFields func(string) string |
| } |
| |
| // A parameter in a method request or response. |
| type Parameter struct { |
| // Snake-case parameter name. |
| Name string |
| // Parameter type. |
| Type string |
| // Type to use when storing the parameter in an owning data structure. |
| OwnedType string |
| // Set only if this parameter corresponds to a struct member. |
| StructMemberType *Type |
| } |
| |
| 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 { |
| Experiments fidlgen.Experiments |
| ExternCrates []string |
| Aliases []Alias |
| Bits []Bits |
| Consts []Const |
| Enums []Enum |
| Structs []Struct |
| Unions []Union |
| Tables []Table |
| Protocols []Protocol |
| Services []Service |
| } |
| |
| 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) *Struct { |
| for i := range r.Structs { |
| if r.Structs[i].ECI == eci { |
| return &r.Structs[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) 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(https://fxbug.dev/42145610): 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.HandleSubtypeBti: "fidl::Bti", |
| fidlgen.HandleSubtypeChannel: "fidl::Channel", |
| fidlgen.HandleSubtypeClock: "fidl::Clock", |
| fidlgen.HandleSubtypeDebugLog: "fidl::DebugLog", |
| fidlgen.HandleSubtypeEvent: "fidl::Event", |
| fidlgen.HandleSubtypeEventpair: "fidl::EventPair", |
| fidlgen.HandleSubtypeException: "fidl::Exception", |
| fidlgen.HandleSubtypeFifo: "fidl::Fifo", |
| fidlgen.HandleSubtypeGuest: "fidl::Guest", |
| fidlgen.HandleSubtypeInterrupt: "fidl::Interrupt", |
| fidlgen.HandleSubtypeIommu: "fidl::Iommu", |
| fidlgen.HandleSubtypeJob: "fidl::Job", |
| fidlgen.HandleSubtypeMsi: "fidl::Msi", |
| fidlgen.HandleSubtypeNone: "fidl::Handle", |
| fidlgen.HandleSubtypePager: "fidl::Pager", |
| fidlgen.HandleSubtypePciDevice: "fidl::PciDevice", |
| fidlgen.HandleSubtypePmt: "fidl::Pmt", |
| fidlgen.HandleSubtypePort: "fidl::Port", |
| fidlgen.HandleSubtypeProcess: "fidl::Process", |
| fidlgen.HandleSubtypeProfile: "fidl::Profile", |
| fidlgen.HandleSubtypeResource: "fidl::Resource", |
| fidlgen.HandleSubtypeSocket: "fidl::Socket", |
| fidlgen.HandleSubtypeStream: "fidl::Stream", |
| fidlgen.HandleSubtypeSuspendToken: "fidl::SuspendToken", |
| fidlgen.HandleSubtypeThread: "fidl::Thread", |
| fidlgen.HandleSubtypeTimer: "fidl::Timer", |
| fidlgen.HandleSubtypeVcpu: "fidl::Vcpu", |
| fidlgen.HandleSubtypeVmar: "fidl::Vmar", |
| fidlgen.HandleSubtypeVmo: "fidl::Vmo", |
| } |
| |
| var objectTypeConsts = map[fidlgen.HandleSubtype]string{ |
| fidlgen.HandleSubtypeBti: "fidl::ObjectType::BTI", |
| fidlgen.HandleSubtypeChannel: "fidl::ObjectType::CHANNEL", |
| fidlgen.HandleSubtypeClock: "fidl::ObjectType::CLOCK", |
| fidlgen.HandleSubtypeDebugLog: "fidl::ObjectType::DEBUGLOG", |
| fidlgen.HandleSubtypeEvent: "fidl::ObjectType::EVENT", |
| fidlgen.HandleSubtypeEventpair: "fidl::ObjectType::EVENTPAIR", |
| fidlgen.HandleSubtypeException: "fidl::ObjectType::EXCEPTION", |
| fidlgen.HandleSubtypeFifo: "fidl::ObjectType::FIFO", |
| fidlgen.HandleSubtypeGuest: "fidl::ObjectType::GUEST", |
| fidlgen.HandleSubtypeInterrupt: "fidl::ObjectType::INTERRUPT", |
| fidlgen.HandleSubtypeIommu: "fidl::ObjectType::IOMMU", |
| fidlgen.HandleSubtypeJob: "fidl::ObjectType::JOB", |
| fidlgen.HandleSubtypeMsi: "fidl::ObjectType::MSI", |
| fidlgen.HandleSubtypeNone: "fidl::ObjectType::NONE", |
| fidlgen.HandleSubtypePager: "fidl::ObjectType::PAGER", |
| fidlgen.HandleSubtypePciDevice: "fidl::ObjectType::PCI_DEVICE", |
| fidlgen.HandleSubtypePmt: "fidl::ObjectType::PMT", |
| fidlgen.HandleSubtypePort: "fidl::ObjectType::PORT", |
| fidlgen.HandleSubtypeProcess: "fidl::ObjectType::PROCESS", |
| fidlgen.HandleSubtypeProfile: "fidl::ObjectType::PROFILE", |
| fidlgen.HandleSubtypeResource: "fidl::ObjectType::RESOURCE", |
| fidlgen.HandleSubtypeSocket: "fidl::ObjectType::SOCKET", |
| fidlgen.HandleSubtypeStream: "fidl::ObjectType::STREAM", |
| fidlgen.HandleSubtypeSuspendToken: "fidl::ObjectType::SUSPEND_TOKEN", |
| fidlgen.HandleSubtypeThread: "fidl::ObjectType::THREAD", |
| fidlgen.HandleSubtypeTimer: "fidl::ObjectType::TIMER", |
| fidlgen.HandleSubtypeVcpu: "fidl::ObjectType::VCPU", |
| fidlgen.HandleSubtypeVmar: "fidl::ObjectType::VMAR", |
| fidlgen.HandleSubtypeVmo: "fidl::ObjectType::VMO", |
| } |
| |
| type compiler struct { |
| decls fidlgen.DeclInfoMap |
| experiments fidlgen.Experiments |
| library fidlgen.LibraryIdentifier |
| externCrates map[string]struct{} |
| // Raw structs (including ExternalStructs), needed for |
| // flattening parameters and in computeUseFidlStructCopy. |
| structs map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct |
| } |
| |
| func (c *compiler) inExternalLibrary(eci fidlgen.EncodedCompoundIdentifier) bool { |
| return eci.LibraryName() != c.library.Encode() |
| } |
| |
| func (c *compiler) lookupDeclInfo(val fidlgen.EncodedCompoundIdentifier) fidlgen.DeclInfo { |
| if info, ok := c.decls[val]; ok { |
| return info |
| } |
| panic(fmt.Sprintf("identifier not in decl map: %s", 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, "_"))) |
| } |
| |
| // TODO(https://fxbug.dev/42145610): Escaping reserved words should happen *after* |
| // converting to CamelCase. |
| func compileCamelIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ToUpperCamelCase(changeIfReserved(val)) |
| } |
| |
| // TODO(https://fxbug.dev/42145610): Escaping reserved words should happen *after* |
| // converting to snake_case. |
| func compileSnakeIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ToSnakeCase(changeIfReserved(val)) |
| } |
| |
| // TODO(https://fxbug.dev/42145610): Escaping reserved words should happen *after* |
| // converting to SCREAMING_SNAKE_CASE. |
| func compileScreamingSnakeIdentifier(val fidlgen.Identifier) string { |
| return fidlgen.ConstNameToAllCapsSnake(changeIfReserved(val)) |
| } |
| |
| func isZirconIdentifier(ci fidlgen.CompoundIdentifier) bool { |
| return len(ci.Library) == 1 && ci.Library[0] == fidlgen.Identifier("zx") |
| } |
| |
| // We special case references to declarations from //zircon/vdso/zx:zx, since we |
| // don't generate Rust bindings for that library. |
| var zirconNames = map[fidlgen.EncodedCompoundIdentifier]string{ |
| "zx/Rights": "fidl::Rights", |
| "zx/ObjType": "fidl::ObjectType", |
| "zx/CHANNEL_MAX_MSG_BYTES": "fuchsia_zircon_types::ZX_CHANNEL_MAX_MSG_BYTES", |
| } |
| |
| // compileDeclIdentifier returns a Rust path expression referring to the given |
| // declaration. It qualifies the crate name if it is external. |
| func (c *compiler) compileDeclIdentifier(val fidlgen.EncodedCompoundIdentifier) string { |
| ci := val.Parse() |
| if ci.Member != "" { |
| panic(fmt.Sprintf("unexpected member: %s", val)) |
| } |
| if isZirconIdentifier(ci) { |
| name, ok := zirconNames[val] |
| if !ok { |
| panic(fmt.Sprintf("unexpected zircon identifier: %s", val)) |
| } |
| return name |
| } |
| var name string |
| if c.lookupDeclInfo(val).Type == fidlgen.ConstDeclType { |
| name = compileScreamingSnakeIdentifier(ci.Name) |
| } else { |
| name = compileCamelIdentifier(ci.Name) |
| } |
| // TODO(https://fxbug.dev/42145610): This is incorrect. We're calling changeIfReserved |
| // a second time, after compileScreamingSnakeIdentifier or |
| // compileCamelIdentifier already did. We should only call it once. |
| name = changeIfReserved(fidlgen.Identifier(name)) |
| if c.inExternalLibrary(val) { |
| crate := compileLibraryName(ci.Library) |
| c.externCrates[crate] = struct{}{} |
| return fmt.Sprintf("%s::%s", crate, name) |
| } |
| return name |
| } |
| |
| // compileMemberIdentifier returns a Rust path expression referring to the given |
| // declaration member. It qualifies the crate name if it is external. |
| func (c *compiler) compileMemberIdentifier(val fidlgen.EncodedCompoundIdentifier) string { |
| ci := val.Parse() |
| if ci.Member == "" { |
| panic(fmt.Sprintf("expected a member: %s", val)) |
| } |
| decl := val.DeclName() |
| declType := c.lookupDeclInfo(decl).Type |
| var member string |
| switch declType { |
| case fidlgen.BitsDeclType: |
| member = compileScreamingSnakeIdentifier(ci.Member) |
| case fidlgen.EnumDeclType: |
| if isZirconIdentifier(ci) && ci.Name == "ObjType" { |
| member = compileScreamingSnakeIdentifier(ci.Member) |
| } else { |
| member = compileCamelIdentifier(ci.Member) |
| } |
| default: |
| panic(fmt.Sprintf("unexpected decl type: %s", declType)) |
| } |
| return fmt.Sprintf("%s::%s", c.compileDeclIdentifier(decl), member) |
| } |
| |
| func (c *compiler) compileLiteral(val fidlgen.Literal, typ fidlgen.Type) string { |
| switch val.Kind { |
| case fidlgen.StringLiteral: |
| var b strings.Builder |
| b.WriteRune('"') |
| for _, r := range val.Value { |
| switch r { |
| case '\\': |
| b.WriteString(`\\`) |
| case '"': |
| b.WriteString(`\"`) |
| case '\n': |
| b.WriteString(`\n`) |
| case '\r': |
| b.WriteString(`\r`) |
| case '\t': |
| b.WriteString(`\t`) |
| default: |
| if unicode.IsPrint(r) { |
| b.WriteRune(r) |
| } else { |
| b.WriteString(fmt.Sprintf(`\u{%x}`, r)) |
| } |
| } |
| } |
| b.WriteRune('"') |
| return b.String() |
| case fidlgen.NumericLiteral: |
| if typ.Kind == fidlgen.PrimitiveType && |
| (typ.PrimitiveSubtype == fidlgen.Float32 || typ.PrimitiveSubtype == fidlgen.Float64) && |
| !strings.ContainsRune(val.Value, '.') { |
| return fmt.Sprintf("%s.0", val.Value) |
| } |
| if typ.Kind == fidlgen.IdentifierType && c.lookupDeclInfo(typ.Identifier).Type == fidlgen.BitsDeclType { |
| if val.Value != "0" { |
| panic(fmt.Sprintf("integer literal for bits const must be 0, got %s", val.Value)) |
| } |
| return fmt.Sprintf("%s::empty()", c.compileDeclIdentifier(typ.Identifier)) |
| } |
| 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) compileConstant(val fidlgen.Constant, typ fidlgen.Type) string { |
| switch val.Kind { |
| case fidlgen.IdentifierConstant: |
| declType := c.lookupDeclInfo(val.Identifier.DeclName()).Type |
| var expr string |
| switch declType { |
| case fidlgen.ConstDeclType: |
| expr = c.compileDeclIdentifier(val.Identifier) |
| case fidlgen.BitsDeclType: |
| expr = c.compileMemberIdentifier(val.Identifier) |
| if typ.Kind == fidlgen.PrimitiveType { |
| expr += ".bits()" |
| } |
| case fidlgen.EnumDeclType: |
| expr = c.compileMemberIdentifier(val.Identifier) |
| if typ.Kind == fidlgen.PrimitiveType { |
| expr += ".into_primitive()" |
| } |
| default: |
| panic(fmt.Sprintf("unexpected decl type: %s", declType)) |
| } |
| // TODO(https://fxbug.dev/42140924): fidlc allows conversions between primitive |
| // types. Ideally the JSON IR would model these conversions explicitly. |
| // In the meantime, just assume that a cast is always necessary. |
| if typ.Kind == fidlgen.PrimitiveType { |
| expr = fmt.Sprintf("%s as %s", expr, compilePrimitiveSubtype(typ.PrimitiveSubtype)) |
| } |
| return expr |
| case fidlgen.LiteralConstant: |
| return c.compileLiteral(*val.Literal, typ) |
| case fidlgen.BinaryOperator: |
| if typ.Kind == fidlgen.PrimitiveType { |
| return val.Value |
| } |
| decl := c.compileDeclIdentifier(typ.Identifier) |
| // TODO(https://github.com/rust-lang/rust/issues/67441): |
| // Use from_bits(%s).unwrap() once the const_option feature is stable. |
| 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 { |
| r := Const{ |
| Const: val, |
| Name: c.compileDeclIdentifier(val.Name), |
| Value: c.compileConstant(val.Value, val.Type), |
| } |
| if val.Type.Kind == fidlgen.StringType { |
| r.Type = "&str" |
| } else { |
| r.Type = c.compileType(val.Type).Owned |
| } |
| 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)) |
| } |
| |
| func compileObjectTypeConst(val fidlgen.HandleSubtype) string { |
| if t, ok := objectTypeConsts[val]; ok { |
| return t |
| } |
| panic(fmt.Sprintf("unknown handle type: %v", val)) |
| } |
| |
| func (c *compiler) compileType(val fidlgen.Type) Type { |
| t := Type{ |
| Resourceness: c.decls.LookupResourceness(val), |
| Kind: val.Kind, |
| Nullable: val.Nullable, |
| PrimitiveSubtype: val.PrimitiveSubtype, |
| Identifier: val.Identifier, |
| } |
| |
| switch val.Kind { |
| case fidlgen.PrimitiveType: |
| s := compilePrimitiveSubtype(val.PrimitiveSubtype) |
| t.Fidl = s |
| t.Owned = s |
| t.Param = s |
| case fidlgen.ArrayType: |
| el := c.compileType(*val.ElementType) |
| t.ElementType = &el |
| t.Fidl = fmt.Sprintf("fidl::encoding::Array<%s, %d>", el.Fidl, *val.ElementCount) |
| t.Owned = fmt.Sprintf("[%s; %d]", el.Owned, *val.ElementCount) |
| if el.IsResourceType() { |
| t.Param = t.Owned |
| } else { |
| t.Param = "&" + t.Owned |
| } |
| case fidlgen.VectorType: |
| el := c.compileType(*val.ElementType) |
| t.ElementType = &el |
| if val.ElementCount == nil { |
| t.Fidl = fmt.Sprintf("fidl::encoding::UnboundedVector<%s>", el.Fidl) |
| } else { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Vector<%s, %d>", el.Fidl, *val.ElementCount) |
| } |
| t.Owned = fmt.Sprintf("Vec<%s>", el.Owned) |
| if el.IsResourceType() { |
| t.Param = fmt.Sprintf("Vec<%s>", el.Owned) |
| } else { |
| t.Param = fmt.Sprintf("&[%s]", el.Owned) |
| } |
| if val.Nullable { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Optional<%s>", t.Fidl) |
| t.Owned = fmt.Sprintf("Option<%s>", t.Owned) |
| t.Param = fmt.Sprintf("Option<%s>", t.Param) |
| } |
| case fidlgen.StringType: |
| if val.ElementCount == nil { |
| t.Fidl = "fidl::encoding::UnboundedString" |
| } else { |
| t.Fidl = fmt.Sprintf("fidl::encoding::BoundedString<%d>", *val.ElementCount) |
| } |
| t.Owned = "String" |
| t.Param = "&str" |
| if val.Nullable { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Optional<%s>", t.Fidl) |
| t.Owned = fmt.Sprintf("Option<%s>", t.Owned) |
| t.Param = fmt.Sprintf("Option<%s>", t.Param) |
| } |
| case fidlgen.HandleType: |
| s := compileHandleSubtype(val.HandleSubtype) |
| objType := compileObjectTypeConst(val.HandleSubtype) |
| t.Fidl = fmt.Sprintf("fidl::encoding::HandleType<%s, { %s.into_raw() }, %d>", s, objType, val.HandleRights) |
| t.Owned = s |
| t.Param = s |
| if val.Nullable { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Optional<%s>", t.Fidl) |
| t.Owned = fmt.Sprintf("Option<%s>", t.Owned) |
| t.Param = fmt.Sprintf("Option<%s>", t.Param) |
| } |
| case fidlgen.RequestType: |
| s := fmt.Sprintf("fidl::endpoints::ServerEnd<%sMarker>", c.compileDeclIdentifier(val.RequestSubtype)) |
| t.Fidl = fmt.Sprintf("fidl::encoding::Endpoint<%s>", s) |
| t.Owned = s |
| t.Param = s |
| if val.Nullable { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Optional<%s>", t.Fidl) |
| t.Owned = fmt.Sprintf("Option<%s>", t.Owned) |
| t.Param = fmt.Sprintf("Option<%s>", t.Param) |
| } |
| case fidlgen.IdentifierType: |
| name := c.compileDeclIdentifier(val.Identifier) |
| declInfo := c.lookupDeclInfo(val.Identifier) |
| t.DeclType = declInfo.Type |
| switch declInfo.Type { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType: |
| t.Fidl = name |
| t.Owned = name |
| t.Param = name |
| case fidlgen.StructDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| t.Fidl = name |
| t.Owned = name |
| if t.IsResourceType() { |
| t.Param = name |
| } else { |
| t.Param = "&" + name |
| } |
| if val.Nullable { |
| switch declInfo.Type { |
| case fidlgen.StructDeclType: |
| t.Fidl = fmt.Sprintf("fidl::encoding::Boxed<%s>", t.Fidl) |
| case fidlgen.UnionDeclType: |
| t.Fidl = fmt.Sprintf("fidl::encoding::OptionalUnion<%s>", t.Fidl) |
| default: |
| panic(fmt.Sprintf("unexpected type: %s", declInfo.Type)) |
| } |
| t.Owned = fmt.Sprintf("Option<Box<%s>>", t.Owned) |
| if declInfo.IsResourceType() { |
| t.Param = fmt.Sprintf("Option<%s>", name) |
| } else { |
| t.Param = fmt.Sprintf("Option<&%s>", name) |
| } |
| } |
| case fidlgen.ProtocolDeclType: |
| s := fmt.Sprintf("fidl::endpoints::ClientEnd<%sMarker>", name) |
| t.Fidl = fmt.Sprintf("fidl::encoding::Endpoint<%s>", s) |
| t.Owned = s |
| t.Param = s |
| if val.Nullable { |
| t.Fidl = fmt.Sprintf("fidl::encoding::Optional<%s>", t.Fidl) |
| t.Owned = fmt.Sprintf("Option<%s>", t.Owned) |
| t.Param = fmt.Sprintf("Option<%s>", t.Param) |
| } |
| default: |
| panic(fmt.Sprintf("unexpected type: %v", declInfo.Type)) |
| } |
| case fidlgen.InternalType: |
| switch val.InternalSubtype { |
| case fidlgen.FrameworkErr: |
| s := "fidl::encoding::FrameworkErr" |
| t.Fidl = s |
| t.Owned = s |
| t.Param = s |
| default: |
| panic(fmt.Sprintf("unknown internal subtype: %v", val.InternalSubtype)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", val.Kind)) |
| } |
| |
| return t |
| } |
| |
| // convertParamToEncodeExpr returns an expression that converts a variable v |
| // from t.Param to a type implementing fidl::encoding::Encode<t.Fidl>. |
| // |
| // TODO(https://fxbug.dev/42073194): Remove this once the transition to the new types is |
| // complete, since parameter types will be encodable as is. |
| func convertParamToEncodeExpr(v string, t Type) string { |
| switch t.Kind { |
| case fidlgen.PrimitiveType, fidlgen.StringType, fidlgen.HandleType, fidlgen.RequestType: |
| return v |
| case fidlgen.ArrayType: |
| if t.IsResourceType() { |
| return "&mut " + v |
| } |
| return v |
| case fidlgen.VectorType: |
| if t.IsResourceType() { |
| return v + ".as_mut()" |
| } |
| return v |
| case fidlgen.IdentifierType: |
| switch t.DeclType { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType, fidlgen.ProtocolDeclType: |
| return v |
| case fidlgen.StructDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| if t.IsResourceType() { |
| if t.Nullable { |
| return v + ".as_mut()" |
| } |
| return "&mut " + v |
| } |
| return v |
| default: |
| panic(fmt.Sprintf("unexpected type: %v", t.DeclType)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", t.Kind)) |
| } |
| } |
| |
| // convertResultToEncodeExpr returns an expression that converts a variable v |
| // from Result<(p.Params[0].Type, p.Params[1].Type, ...), _> to Result<T, _> |
| // where p is payloadForType(t) and T implements fidl::encoding::Encode<p.FidlType>. |
| // If len(p.Params) == 1 then the source type is Result<p.Params[0].Type, _>. |
| func convertResultToEncodeExpr(v string, t Type, p Payload) string { |
| switch t.DeclType { |
| case fidlgen.StructDeclType: |
| var names []string |
| var exprs []string |
| transform := false |
| for _, param := range p.Parameters { |
| t := *param.StructMemberType |
| expr := convertParamToEncodeExpr(param.Name, t) |
| if expr != param.Name { |
| transform = true |
| } |
| if t.IsResourceType() { |
| expr = convertMutRefParamToEncodeExpr(param.Name, t) |
| } else { |
| expr = "*" + expr |
| } |
| names = append(names, param.Name) |
| exprs = append(exprs, expr) |
| } |
| if transform { |
| return fmt.Sprintf("%s.as_mut().map_err(|e| *e).map(|%s| %s)", v, fmtOneOrTuple(names), fmtTuple(exprs)) |
| } |
| if len(names) == 1 { |
| return fmt.Sprintf("%s.map(|%s| (%s,))", v, names[0], names[0]) |
| } |
| return v |
| case fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| if t.IsResourceType() { |
| return v + ".as_mut().map_err(|e| *e)" |
| } |
| return v |
| default: |
| panic(fmt.Sprintf("unexpected decl type: %s", t.DeclType)) |
| } |
| } |
| |
| // convertMutRefParamToEncodeExpr returns an expression that converts a variable |
| // v from &mut t.Param to a type implementing fidl::encoding::Encode<t.Fidl>. |
| // |
| // TODO(https://fxbug.dev/42073194): Remove this once the transition to the new types is |
| // complete. This is only needed for convertResultToEncodeExpr. |
| func convertMutRefParamToEncodeExpr(v string, t Type) string { |
| switch t.Kind { |
| case fidlgen.IdentifierType: |
| switch t.DeclType { |
| case fidlgen.StructDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| if t.Nullable { |
| if t.IsResourceType() { |
| return v + ".as_mut()" |
| } |
| return v + ".as_ref()" |
| } |
| } |
| } |
| return convertMutRefOwnedToEncodeExpr(v, t) |
| } |
| |
| // convertMutRefOwnedToEncodeExpr returns an expression that converts a variable |
| // v from &mut t.Owned to a type implementing fidl::encoding::Encode<t.Fidl>. |
| // |
| // TODO(https://fxbug.dev/42073194): Remove this once the transition to the new types is |
| // complete. This is only needed for convertMutRefResultToEncodeExpr. |
| func convertMutRefOwnedToEncodeExpr(v string, t Type) string { |
| switch t.Kind { |
| case fidlgen.PrimitiveType: |
| return "*" + v |
| case fidlgen.HandleType, fidlgen.RequestType: |
| if t.Nullable { |
| return fmt.Sprintf("%s.as_mut().map(|x| std::mem::replace(x, fidl::Handle::invalid().into()))", v) |
| } |
| return fmt.Sprintf("std::mem::replace(%s, fidl::Handle::invalid().into())", v) |
| case fidlgen.StringType: |
| if t.Nullable { |
| return v + ".as_deref()" |
| } |
| return v + ".as_str()" |
| case fidlgen.ArrayType: |
| if t.IsResourceType() { |
| return v |
| } |
| return "&*" + v |
| case fidlgen.VectorType: |
| if t.Nullable { |
| if t.IsResourceType() { |
| return v + ".as_deref_mut()" |
| } |
| return v + ".as_deref()" |
| } |
| if t.IsResourceType() { |
| return v + ".as_mut_slice()" |
| } |
| return v + ".as_slice()" |
| case fidlgen.IdentifierType: |
| switch t.DeclType { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType: |
| return "*" + v |
| case fidlgen.StructDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| if t.Nullable { |
| if t.IsResourceType() { |
| return v + ".as_deref_mut()" |
| } |
| return v + ".as_deref()" |
| } |
| if t.IsResourceType() { |
| return v |
| } |
| return "&*" + v |
| case fidlgen.ProtocolDeclType: |
| if t.Nullable { |
| return fmt.Sprintf("%s.as_mut().map(|x| std::mem::replace(x, fidl::Handle::invalid().into()))", v) |
| } |
| return fmt.Sprintf("std::mem::replace(%s, fidl::Handle::invalid().into())", v) |
| default: |
| panic(fmt.Sprintf("unexpected type: %v", t.DeclType)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", t.Kind)) |
| } |
| } |
| |
| func (c *compiler) compileAlias(val fidlgen.Alias) Alias { |
| return Alias{ |
| Alias: val, |
| Name: c.compileDeclIdentifier(val.Name), |
| Type: c.compileType(val.Type), |
| } |
| } |
| |
| func (c *compiler) compileBits(val fidlgen.Bits) Bits { |
| e := Bits{ |
| Bits: val, |
| Name: c.compileDeclIdentifier(val.Name), |
| UnderlyingType: c.compileType(val.Type).Owned, |
| 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.compileDeclIdentifier(val.Name), |
| UnderlyingType: 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 |
| } |
| |
| // fmtTuple formats items (types or expressions) as a Rust tuple. |
| func fmtTuple(items []string) string { |
| if len(items) == 0 { |
| panic("expected at least one item") |
| } |
| return fmt.Sprintf("(%s,)", strings.Join(items, ", ")) |
| } |
| |
| // fmtOneOrTuple is like fmtTuple but does not create 1-tuples. |
| func fmtOneOrTuple(items []string) string { |
| switch len(items) { |
| case 0: |
| panic("expected at least one item") |
| case 1: |
| return items[0] |
| default: |
| return fmt.Sprintf("(%s)", strings.Join(items, ", ")) |
| } |
| } |
| |
| func emptyPayload(fidlType string) Payload { |
| return Payload{ |
| FidlType: fidlType, |
| OwnedType: "()", |
| TupleType: "()", |
| EncodeExpr: "()", |
| ConvertToTuple: func(owned string) string { return owned }, |
| ConvertToFields: func(owned string) string { return "" }, |
| } |
| } |
| |
| func (c *compiler) payloadForType(payloadType Type) Payload { |
| typeName := c.compileDeclIdentifier(payloadType.Identifier) |
| st, ok := c.structs[payloadType.Identifier] |
| |
| // Not a struct: table or union payload. |
| if !ok { |
| paramName := "payload" |
| return Payload{ |
| FidlType: typeName, |
| OwnedType: typeName, |
| TupleType: typeName, |
| Parameters: []Parameter{{ |
| Name: paramName, |
| Type: payloadType.Param, |
| OwnedType: payloadType.Owned, |
| }}, |
| EncodeExpr: convertParamToEncodeExpr(paramName, payloadType), |
| ConvertToTuple: func(owned string) string { return owned }, |
| ConvertToFields: func(owned string) string { return fmt.Sprintf("%s: %s,", paramName, owned) }, |
| } |
| } |
| |
| // Empty struct with error syntax. |
| if len(st.Members) == 0 { |
| return emptyPayload("fidl::encoding::EmptyStruct") |
| } |
| |
| // Struct payload. Flatten it to parameters. |
| var parameters []Parameter |
| var ownedTypes, encodeExprs []string |
| for _, v := range st.Members { |
| paramName := compileSnakeIdentifier(v.Name) |
| typ := c.compileType(v.Type) |
| parameters = append(parameters, Parameter{ |
| Name: paramName, |
| Type: typ.Param, |
| OwnedType: typ.Owned, |
| StructMemberType: &typ, |
| }) |
| ownedTypes = append(ownedTypes, typ.Owned) |
| encodeExprs = append(encodeExprs, convertParamToEncodeExpr(paramName, typ)) |
| } |
| return Payload{ |
| FidlType: typeName, |
| OwnedType: typeName, |
| TupleType: fmtOneOrTuple(ownedTypes), |
| Parameters: parameters, |
| EncodeExpr: fmtTuple(encodeExprs), |
| ConvertToTuple: func(owned string) string { |
| var exprs []string |
| for _, param := range parameters { |
| exprs = append(exprs, fmt.Sprintf("%s.%s", owned, param.Name)) |
| } |
| return fmtOneOrTuple(exprs) |
| }, |
| ConvertToFields: func(owned string) string { |
| var b strings.Builder |
| for _, param := range parameters { |
| b.WriteString(fmt.Sprintf("%s: %s.%s,\n", param.Name, owned, param.Name)) |
| } |
| return b.String() |
| }, |
| } |
| } |
| |
| func (c *compiler) compileRequest(m fidlgen.Method) Payload { |
| if !m.HasRequest { |
| // Not applicable (because the method is an event). |
| return Payload{} |
| } |
| if m.RequestPayload == nil { |
| // Empty payload, e.g. the request of `Foo();`. |
| return emptyPayload("fidl::encoding::EmptyPayload") |
| } |
| // Struct, table, or union request payload. |
| return c.payloadForType(c.compileType(*m.RequestPayload)) |
| } |
| |
| func (c *compiler) compileResponse(m fidlgen.Method) Payload { |
| if !m.HasResponse { |
| // Not applicable (because the method is one-way). |
| return Payload{} |
| } |
| if m.ResponsePayload == nil { |
| // Empty payload, e.g. the response of `Foo() -> ();` or `-> Foo();`. |
| return emptyPayload("fidl::encoding::EmptyPayload") |
| } |
| if !m.HasResultUnion() { |
| // Plain payload with no flexible/error result union. |
| return c.payloadForType(c.compileType(*m.ResponsePayload)) |
| } |
| |
| innerType := c.compileType(*m.ValueType) |
| inner := c.payloadForType(innerType) |
| var errType Type |
| if m.HasError { |
| errType = c.compileType(*m.ErrorType) |
| } |
| |
| var p Payload |
| |
| // Set FidlType and OwnedType, which are different for each of the 3 cases. |
| if m.HasFrameworkError() && m.HasError { |
| p.FidlType = fmt.Sprintf("fidl::encoding::FlexibleResultType<%s, %s>", inner.FidlType, errType.Fidl) |
| p.OwnedType = fmt.Sprintf("fidl::encoding::FlexibleResult<%s, %s>", inner.OwnedType, errType.Owned) |
| } else if m.HasFrameworkError() { |
| p.FidlType = fmt.Sprintf("fidl::encoding::FlexibleType<%s>", inner.FidlType) |
| p.OwnedType = fmt.Sprintf("fidl::encoding::Flexible<%s>", inner.OwnedType) |
| } else if m.HasError { |
| p.FidlType = fmt.Sprintf("fidl::encoding::ResultType<%s, %s>", inner.FidlType, errType.Fidl) |
| p.OwnedType = fmt.Sprintf("Result<%s, %s>", inner.OwnedType, errType.Owned) |
| } else { |
| panic("should have returned earlier") |
| } |
| |
| // Set all the other fields, where all that matters is m.HasError. |
| if !m.HasError { |
| p.TupleType = inner.TupleType |
| p.Parameters = inner.Parameters |
| p.EncodeExpr = inner.EncodeExpr |
| p.ConvertToTuple = inner.ConvertToTuple |
| p.ConvertToFields = inner.ConvertToFields |
| } else { |
| paramName := "result" |
| p.TupleType = c.compileDeclIdentifier(m.ResponsePayload.Identifier) |
| p.TupleTypeAliasRhs = fmt.Sprintf("Result<%s, %s>", inner.TupleType, errType.Owned) |
| okParamType := "()" |
| if len(inner.Parameters) > 0 { |
| var paramTypes []string |
| for _, param := range inner.Parameters { |
| paramTypes = append(paramTypes, param.Type) |
| } |
| okParamType = fmtOneOrTuple(paramTypes) |
| } |
| p.Parameters = []Parameter{{ |
| Name: paramName, |
| Type: fmt.Sprintf("Result<%s, %s>", okParamType, errType.Param), |
| OwnedType: p.TupleType, |
| }} |
| p.EncodeExpr = convertResultToEncodeExpr(paramName, innerType, inner) |
| p.ConvertToTuple = func(owned string) string { |
| return fmt.Sprintf("%s.map(|x| %s)", owned, inner.ConvertToTuple("x")) |
| } |
| p.ConvertToFields = func(owned string) string { |
| return fmt.Sprintf("%s: %s.map(|x| %s),", paramName, owned, inner.ConvertToTuple("x")) |
| } |
| } |
| |
| // For FlexibleType and FlexibleResultType, we need to wrap the value before encoding. |
| if m.HasFrameworkError() { |
| name, _, _ := strings.Cut(p.OwnedType, "<") |
| p.EncodeExpr = fmt.Sprintf("%s::new(%s)", name, p.EncodeExpr) |
| } |
| |
| return p |
| } |
| |
| func (c *compiler) compileProtocol(val fidlgen.Protocol) Protocol { |
| name := c.compileDeclIdentifier(val.Name) |
| r := Protocol{ |
| Protocol: val, |
| ECI: val.Name, |
| Marker: name + "Marker", |
| Proxy: name + "Proxy", |
| ProxyInterface: name + "ProxyInterface", |
| SynchronousProxy: name + "SynchronousProxy", |
| Request: name + "Request", |
| RequestStream: name + "RequestStream", |
| Event: name + "Event", |
| EventStream: name + "EventStream", |
| ControlHandle: name + "ControlHandle", |
| } |
| if discoverableName := strings.Trim(val.GetProtocolName(), "\""); discoverableName != "" { |
| r.Discoverable = true |
| // TODO(https://fxbug.dev/42051517): Currently discoverable protocols get |
| // PROTOCOL_NAME set equal to DEBUG_NAME, and we change both to use the |
| // "fuchsia.foo.Bar" format. We should instead use distinct formats for |
| // DEBUG_NAME and PROTOCOL_NAME. |
| r.DebugName = discoverableName |
| } else { |
| // TODO(https://fxbug.dev/42051517): Include the library name in DEBUG_NAME, i.e. |
| // "fuchsia.foo/Bar" rather than just "Bar". |
| r.DebugName = "(anonymous) " + name |
| } |
| |
| for _, v := range val.Methods { |
| m := Method{ |
| Method: v, |
| Name: compileSnakeIdentifier(v.Name), |
| CamelName: compileCamelIdentifier(v.Name), |
| Request: c.compileRequest(v), |
| Response: c.compileResponse(v), |
| } |
| if v.HasRequest && v.HasResponse { |
| m.Responder = name + m.CamelName + "Responder" |
| m.ResponseFut = m.CamelName + "ResponseFut" |
| } |
| r.Methods = append(r.Methods, m) |
| } |
| |
| return r |
| } |
| |
| func (c *compiler) compileService(val fidlgen.Service) Service { |
| r := Service{ |
| Service: val, |
| Name: c.compileDeclIdentifier(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.compileDeclIdentifier(v.Type.Identifier), |
| } |
| r.Members = append(r.Members, m) |
| } |
| |
| return r |
| } |
| |
| func (c *compiler) compileStructMember(val fidlgen.StructMember) StructMember { |
| return StructMember{ |
| StructMember: val, |
| Type: c.compileType(val.Type), |
| Name: compileSnakeIdentifier(val.Name), |
| OffsetV2: val.FieldShapeV2.Offset, |
| } |
| } |
| |
| 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) { |
| return false |
| } |
| declType := c.lookupDeclInfo(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) { |
| // This behavior is matched by computeUseFullStructCopy. |
| return nil |
| } |
| if c.lookupDeclInfo(identifier).Type == fidlgen.StructDeclType { |
| st, ok := c.structs[identifier] |
| if !ok { |
| panic(fmt.Sprintf("struct not found: %v", identifier)) |
| } |
| return &st |
| } |
| return nil |
| } |
| |
| func (c *compiler) compileStruct(val fidlgen.Struct) Struct { |
| name := c.compileDeclIdentifier(val.Name) |
| r := Struct{ |
| Struct: val, |
| ECI: val.Name, |
| Name: name, |
| Members: []StructMember{}, |
| SizeV2: val.TypeShapeV2.InlineSize, |
| AlignmentV2: val.TypeShapeV2.Alignment, |
| PaddingMarkersV2: val.BuildPaddingMarkers(fidlgen.PaddingConfig{}), |
| FlattenedPaddingMarkersV2: val.BuildPaddingMarkers(fidlgen.PaddingConfig{ |
| FlattenStructs: true, |
| FlattenArrays: true, |
| ResolveStruct: c.resolveStruct, |
| }), |
| } |
| |
| for _, v := range val.Members { |
| member := c.compileStructMember(v) |
| r.Members = append(r.Members, member) |
| r.HasPadding = r.HasPadding || (v.FieldShapeV2.Padding != 0) |
| } |
| |
| r.UseFidlStructCopy = c.computeUseFidlStructCopyForStruct(val) |
| |
| return r |
| } |
| |
| func (c *compiler) compileUnion(val fidlgen.Union) Union { |
| r := Union{ |
| Union: val, |
| ECI: val.Name, |
| Name: c.compileDeclIdentifier(val.Name), |
| } |
| for _, v := range val.Members { |
| r.Members = append(r.Members, UnionMember{ |
| UnionMember: v, |
| Type: c.compileType(v.Type), |
| Name: compileCamelIdentifier(v.Name), |
| Ordinal: v.Ordinal, |
| }) |
| } |
| return r |
| } |
| |
| func (c *compiler) compileTable(table fidlgen.Table) Table { |
| r := Table{ |
| Table: table, |
| ECI: table.Name, |
| Name: c.compileDeclIdentifier(table.Name), |
| } |
| for _, member := range table.Members { |
| r.Members = append(r.Members, TableMember{ |
| TableMember: member, |
| Type: c.compileType(member.Type), |
| Name: compileSnakeIdentifier(member.Name), |
| Ordinal: member.Ordinal, |
| }) |
| } |
| return r |
| } |
| |
| type derives uint16 |
| |
| const ( |
| derivesDebug derives = 1 << iota |
| derivesCopy |
| derivesClone |
| derivesDefault |
| derivesEq |
| derivesPartialEq |
| derivesOrd |
| derivesPartialOrd |
| derivesHash |
| derivesAll derives = (1 << iota) - 1 |
| ) |
| |
| // note: keep this list in the same order as the derives definitions |
| var derivesNames = []string{ |
| // [START derived_traits] |
| "Debug", |
| "Copy", |
| "Clone", |
| "Default", |
| "Eq", |
| "PartialEq", |
| "Ord", |
| "PartialOrd", |
| "Hash", |
| // [END derived_traits] |
| } |
| |
| // Returns the derives that are allowed for a type based on resourceness |
| func allowedDerives(r fidlgen.Resourceness) derives { |
| if r.IsResourceType() { |
| return derivesAll &^ (derivesCopy | derivesClone) |
| } |
| return derivesAll |
| } |
| |
| // Returns the minimal derives we can assume a type has based on resourceness. |
| func minimalDerives(r fidlgen.Resourceness) derives { |
| if r.IsValueType() { |
| return derivesDebug | derivesPartialEq | derivesClone |
| } |
| return derivesDebug | derivesPartialEq |
| } |
| |
| // RemoveCustom removes traits by name. Templates use it when they are providing |
| // a custom impl instead of deriving. (We can't just omit the traits in |
| // fillDerives because it would recursively remove it from containing types.) |
| func (v derives) RemoveCustom(traits ...string) (derives, error) { |
| result := v |
| for _, name := range traits { |
| found := false |
| for i, n := range derivesNames { |
| if n == name { |
| found = true |
| result = result &^ (1 << i) |
| break |
| } |
| } |
| if !found { |
| return 0, fmt.Errorf("trait '%s' not found", name) |
| } |
| } |
| return result, nil |
| } |
| |
| 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&bit != 0 { |
| parts = append(parts, derivesNames[i]) |
| } |
| } |
| if len(parts) == 0 { |
| return "" |
| } |
| sort.Strings(parts) |
| 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 derives are controlled by the bitflags crate. |
| // Enums derive a constant set of traits hardcoded in enum.tmpl. |
| for _, v := range ir.Structs { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Unions { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| for _, v := range ir.Tables { |
| dc.fillDerivesForECI(v.ECI) |
| } |
| } |
| |
| // Computes derives for a struct, table, or union. |
| // Also fills in the .Derives field if the type is local to this library. |
| func (dc *derivesCompiler) fillDerivesForECI(eci EncodedCompoundIdentifier) derives { |
| declInfo := dc.lookupDeclInfo(eci) |
| |
| // TODO(https://fxbug.dev/42140082): Make external type information available here. |
| // Currently, we conservatively assume external structs, tables, and unions |
| // only derive a minimal set of traits, which includes 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) { |
| return minimalDerives(*declInfo.Resourceness) |
| } |
| |
| 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 |
| switch declInfo.Type { |
| case fidlgen.StructDeclType: |
| st := dc.root.findStruct(eci) |
| if st == nil { |
| panic(fmt.Sprintf("struct not found: %v", eci)) |
| } |
| if deriveStatus.complete { |
| derivesOut = st.Derives |
| break |
| } |
| derivesOut = allowedDerives(st.Resourceness) |
| for _, member := range st.Members { |
| derivesOut &= dc.derivesForType(member.Type) |
| } |
| derivesOut &^= derivesDefault |
| st.Derives = derivesOut |
| case fidlgen.TableDeclType: |
| table := dc.root.findTable(eci) |
| if table == nil { |
| panic(fmt.Sprintf("table not found: %v", eci)) |
| } |
| if deriveStatus.complete { |
| derivesOut = table.Derives |
| break |
| } |
| derivesOut = minimalDerives(*declInfo.Resourceness) |
| // We can always derive Default because table fields are optional. |
| derivesOut |= derivesDefault |
| table.Derives = derivesOut |
| case fidlgen.UnionDeclType: |
| union := dc.root.findUnion(eci) |
| if union == nil { |
| panic(fmt.Sprintf("union not found: %v", eci)) |
| } |
| if deriveStatus.complete { |
| derivesOut = union.Derives |
| break |
| } |
| if union.IsFlexible() { |
| derivesOut = minimalDerives(union.Resourceness) |
| } else { |
| derivesOut = allowedDerives(union.Resourceness) |
| for _, member := range union.Members { |
| derivesOut &= dc.derivesForType(member.Type) |
| } |
| } |
| derivesOut &^= derivesDefault |
| union.Derives = derivesOut |
| default: |
| panic(fmt.Sprintf("unexpected 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) derivesForType(t Type) derives { |
| switch t.Kind { |
| case fidlgen.ArrayType: |
| return dc.derivesForType(*t.ElementType) |
| case fidlgen.VectorType: |
| return derivesAll &^ derivesCopy & dc.derivesForType(*t.ElementType) |
| case fidlgen.StringType: |
| return derivesAll &^ derivesCopy |
| case fidlgen.HandleType, fidlgen.RequestType: |
| return derivesAll &^ (derivesCopy | derivesClone) |
| case fidlgen.PrimitiveType: |
| switch t.PrimitiveSubtype { |
| case fidlgen.Bool: |
| return derivesAll |
| case fidlgen.Int8, fidlgen.Int16, fidlgen.Int32, fidlgen.Int64, |
| 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 derivesAll &^ (derivesEq | derivesOrd | derivesHash) |
| default: |
| panic(fmt.Sprintf("unknown primitive type: %v", t.PrimitiveSubtype)) |
| } |
| case fidlgen.IdentifierType: |
| switch t.DeclType { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType: |
| return derivesAll |
| case fidlgen.StructDeclType, fidlgen.UnionDeclType: |
| result := dc.fillDerivesForECI(t.Identifier) |
| if t.Nullable { |
| // Nullable structs and unions gets put in Option<Box<...>>. |
| result &^= derivesCopy |
| } |
| return result |
| case fidlgen.TableDeclType: |
| return dc.fillDerivesForECI(t.Identifier) |
| case fidlgen.ProtocolDeclType: |
| // An IdentifierType referring to a Protocol is a client_end. |
| return derivesAll &^ (derivesCopy | derivesClone) |
| default: |
| panic(fmt.Sprintf("unexpected identifier type: %v", t.DeclType)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %v", t.Kind)) |
| } |
| } |
| |
| func Compile(r fidlgen.Root) Root { |
| r = r.ForBindings("rust") |
| r = r.ForTransport("Channel") |
| root := Root{ |
| Experiments: r.Experiments, |
| } |
| thisLibParsed := r.Name.Parse() |
| c := compiler{ |
| decls: r.DeclInfo(), |
| experiments: r.Experiments, |
| library: thisLibParsed, |
| externCrates: map[string]struct{}{}, |
| structs: map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct{}, |
| } |
| |
| for _, s := range r.Structs { |
| c.structs[s.Name] = s |
| } |
| |
| for _, s := range r.ExternalStructs { |
| c.structs[s.Name] = s |
| } |
| |
| for _, v := range r.Aliases { |
| root.Aliases = append(root.Aliases, c.compileAlias(v)) |
| } |
| |
| 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.Tables { |
| root.Tables = append(root.Tables, c.compileTable(v)) |
| } |
| |
| for _, v := range r.Structs { |
| if v.IsEmptySuccessStruct { |
| continue |
| } |
| root.Structs = append(root.Structs, c.compileStruct(v)) |
| } |
| |
| for _, v := range r.Unions { |
| if v.IsResult { |
| continue |
| } |
| root.Unions = append(root.Unions, c.compileUnion(v)) |
| } |
| |
| for _, v := range r.Protocols { |
| root.Protocols = append(root.Protocols, c.compileProtocol(v)) |
| } |
| |
| 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 |
| } |