| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package rust |
| |
| import ( |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/ir" |
| "go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/mixer" |
| "go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/rust" |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| func buildHandleDefs(defs []ir.HandleDef) string { |
| if len(defs) == 0 { |
| return "" |
| } |
| var builder strings.Builder |
| builder.WriteString("[\n") |
| for i, d := range defs { |
| // Write indices corresponding to the .gidl file handle_defs block. |
| builder.WriteString(fmt.Sprintf( |
| `// #%d |
| HandleDef{ |
| subtype: HandleSubtype::%s, |
| rights: Rights::from_bits(%d).unwrap(), |
| }, |
| `, i, handleTypeName(d.Subtype), d.Rights)) |
| } |
| builder.WriteString("]") |
| return builder.String() |
| } |
| |
| func buildHandles(handles []ir.Handle) string { |
| var builder strings.Builder |
| builder.WriteString("[\n") |
| for i, h := range handles { |
| builder.WriteString(fmt.Sprintf("%d,", h)) |
| if i%8 == 7 { |
| builder.WriteString("\n") |
| } |
| } |
| builder.WriteString("]") |
| return builder.String() |
| } |
| |
| func buildRawHandleDispositions(handleDispositions []ir.HandleDisposition) string { |
| var builder strings.Builder |
| builder.WriteString("[") |
| for _, hd := range handleDispositions { |
| fmt.Fprintf(&builder, ` |
| zx_types::zx_handle_disposition_t { |
| operation: zx_types::ZX_HANDLE_OP_MOVE, |
| handle: handle_defs[%d].handle, |
| type_: %d, |
| rights: %d, |
| result: zx_types::ZX_OK, |
| },`, hd.Handle, hd.Type, hd.Rights) |
| } |
| builder.WriteString("]") |
| return builder.String() |
| } |
| |
| func buildRawHandles(handleDispositions []ir.HandleDisposition) string { |
| var builder strings.Builder |
| builder.WriteString("[") |
| for _, hd := range handleDispositions { |
| fmt.Fprintf(&builder, "handle_defs[%d].handle,\n", hd.Handle) |
| } |
| builder.WriteString("]") |
| return builder.String() |
| } |
| |
| func visit(value ir.Value, decl mixer.Declaration) string { |
| switch value := value.(type) { |
| case bool: |
| return strconv.FormatBool(value) |
| case int64, uint64, float64: |
| switch decl := decl.(type) { |
| case mixer.PrimitiveDeclaration: |
| suffix := primitiveTypeName(decl.Subtype()) |
| return fmt.Sprintf("%v%s", value, suffix) |
| case *mixer.BitsDecl: |
| primitive := visit(value, &decl.Underlying) |
| if decl.IsFlexible() { |
| return fmt.Sprintf("%s::from_bits_allow_unknown(%v)", declName(decl), primitive) |
| } |
| // Use from_bits_retain so that encode_failure tests work. It's |
| // not worth the effort to make the test type available here and use |
| // from_bits(...).unwrap() in success cases, since all this would do |
| // is move validation from the bindings to GIDL. |
| return fmt.Sprintf("%s::from_bits_retain(%v)", declName(decl), primitive) |
| case *mixer.EnumDecl: |
| primitive := visit(value, &decl.Underlying) |
| if decl.IsFlexible() { |
| return fmt.Sprintf("%s::from_primitive_allow_unknown(%v)", declName(decl), primitive) |
| } |
| return fmt.Sprintf("%s::from_primitive(%v).unwrap()", declName(decl), primitive) |
| } |
| case ir.RawFloat: |
| switch decl.(*mixer.FloatDecl).Subtype() { |
| case fidlgen.Float32: |
| return fmt.Sprintf("f32::from_bits(%#b)", value) |
| case fidlgen.Float64: |
| return fmt.Sprintf("f64::from_bits(%#b)", value) |
| } |
| case string: |
| var expr string |
| if fidlgen.PrintableASCII(value) { |
| expr = fmt.Sprintf("String::from(%q)", value) |
| } else { |
| expr = fmt.Sprintf("std::str::from_utf8(b\"%s\").unwrap().to_string()", rust.EscapeStr(value)) |
| } |
| return wrapNullable(decl, expr) |
| case ir.Handle: |
| expr := buildHandleValue(value) |
| return wrapNullable(decl, expr) |
| case ir.Record: |
| switch decl := decl.(type) { |
| case *mixer.StructDecl: |
| return onStruct(value, decl) |
| case *mixer.TableDecl: |
| return onTable(value, decl) |
| case *mixer.UnionDecl: |
| return onUnion(value, decl) |
| } |
| case []ir.Value: |
| switch decl := decl.(type) { |
| case *mixer.ArrayDecl: |
| return onList(value, decl) |
| case *mixer.VectorDecl: |
| return onList(value, decl) |
| } |
| case ir.DecodedRecord: |
| return onDecodedRecord(value, decl.(mixer.RecordDeclaration)) |
| case nil: |
| if !decl.IsNullable() { |
| if _, ok := decl.(*mixer.HandleDecl); ok { |
| return "Handle::invalid()" |
| } |
| panic(fmt.Sprintf("got nil for non-nullable type: %T", decl)) |
| } |
| return "None" |
| } |
| panic(fmt.Sprintf("not implemented: %T", value)) |
| } |
| |
| func declName(decl mixer.NamedDeclaration) string { |
| return identifierName(decl.Name()) |
| } |
| |
| // TODO(https://fxbug.dev/42115264): Move into a common library outside GIDL. |
| func identifierName(qualifiedName string) string { |
| parts := strings.Split(qualifiedName, "/") |
| library_parts := strings.Split(parts[0], ".") |
| return fmt.Sprintf("%s::%s", strings.Join(library_parts, "_"), |
| fidlgen.ToUpperCamelCase(parts[1])) |
| } |
| |
| func primitiveTypeName(subtype fidlgen.PrimitiveSubtype) string { |
| switch subtype { |
| case fidlgen.Bool: |
| return "bool" |
| case fidlgen.Int8: |
| return "i8" |
| case fidlgen.Uint8: |
| return "u8" |
| case fidlgen.Int16: |
| return "i16" |
| case fidlgen.Uint16: |
| return "u16" |
| case fidlgen.Int32: |
| return "i32" |
| case fidlgen.Uint32: |
| return "u32" |
| case fidlgen.Int64: |
| return "i64" |
| case fidlgen.Uint64: |
| return "u64" |
| case fidlgen.Float32: |
| return "f32" |
| case fidlgen.Float64: |
| return "f64" |
| default: |
| panic(fmt.Sprintf("unexpected subtype %v", subtype)) |
| } |
| } |
| |
| func handleTypeName(subtype fidlgen.HandleSubtype) string { |
| switch subtype { |
| case fidlgen.HandleSubtypeNone: |
| return "Handle" |
| case fidlgen.HandleSubtypeChannel: |
| return "Channel" |
| case fidlgen.HandleSubtypeEvent: |
| return "Event" |
| default: |
| panic(fmt.Sprintf("unsupported handle subtype: %s", subtype)) |
| } |
| } |
| |
| func wrapNullable(decl mixer.Declaration, valueStr string) string { |
| if !decl.IsNullable() { |
| return valueStr |
| } |
| switch decl.(type) { |
| case *mixer.ArrayDecl, *mixer.VectorDecl, *mixer.StringDecl, *mixer.HandleDecl, *mixer.ClientEndDecl, *mixer.ServerEndDecl: |
| return fmt.Sprintf("Some(%s)", valueStr) |
| case *mixer.StructDecl, *mixer.UnionDecl: |
| return fmt.Sprintf("Some(Box::new(%s))", valueStr) |
| case *mixer.BoolDecl, *mixer.IntegerDecl, *mixer.FloatDecl, *mixer.TableDecl: |
| panic(fmt.Sprintf("decl %v should not be nullable", decl)) |
| } |
| panic(fmt.Sprintf("unexpected decl %v", decl)) |
| } |
| |
| func onStruct(value ir.Record, decl *mixer.StructDecl) string { |
| var structFields []string |
| providedKeys := make(map[string]struct{}, len(value.Fields)) |
| for _, field := range value.Fields { |
| if field.Key.IsUnknown() { |
| panic("unknown field not supported") |
| } |
| providedKeys[field.Key.Name] = struct{}{} |
| fieldName := fidlgen.ToSnakeCase(field.Key.Name) |
| fieldValueStr := visit(field.Value, decl.Field(field.Key.Name)) |
| structFields = append(structFields, fmt.Sprintf("%s: %s", fieldName, fieldValueStr)) |
| } |
| for _, key := range decl.FieldNames() { |
| if _, ok := providedKeys[key]; !ok { |
| fieldName := fidlgen.ToSnakeCase(key) |
| structFields = append(structFields, fmt.Sprintf("%s: None", fieldName)) |
| } |
| } |
| valueStr := fmt.Sprintf("%s { %s }", declName(decl), strings.Join(structFields, ", ")) |
| return wrapNullable(decl, valueStr) |
| } |
| |
| func onTable(value ir.Record, decl *mixer.TableDecl) string { |
| var tableFields []string |
| for _, field := range value.Fields { |
| if field.Key.IsUnknown() { |
| panic(fmt.Sprintf("table %s: unknown ordinal %d: Rust cannot construct tables with unknown fields", |
| decl.Name(), field.Key.UnknownOrdinal)) |
| } |
| fieldName := fidlgen.ToSnakeCase(field.Key.Name) |
| fieldValueStr := visit(field.Value, decl.Field(field.Key.Name)) |
| tableFields = append(tableFields, fmt.Sprintf("%s: Some(%s)", fieldName, fieldValueStr)) |
| } |
| tableName := declName(decl) |
| tableFields = append(tableFields, "..Default::default()") |
| valueStr := fmt.Sprintf("%s { %s }", tableName, strings.Join(tableFields, ", ")) |
| return wrapNullable(decl, valueStr) |
| } |
| |
| func onUnion(value ir.Record, decl *mixer.UnionDecl) string { |
| if len(value.Fields) != 1 { |
| panic(fmt.Sprintf("union has %d fields, expected 1", len(value.Fields))) |
| } |
| field := value.Fields[0] |
| var valueStr string |
| if field.Key.IsUnknown() { |
| if field.Key.UnknownOrdinal != 0 { |
| panic(fmt.Sprintf("union %s: unknown ordinal %d: Rust can only construct unknowns with the ordinal 0", |
| decl.Name(), field.Key.UnknownOrdinal)) |
| } |
| if field.Value != nil { |
| panic(fmt.Sprintf("union %s: unknown ordinal %d: Rust cannot construct union with unknown bytes/handles", |
| decl.Name(), field.Key.UnknownOrdinal)) |
| } |
| valueStr = fmt.Sprintf("%s::unknown_variant_for_testing()", declName(decl)) |
| } else { |
| fieldName := fidlgen.ToUpperCamelCase(field.Key.Name) |
| fieldValueStr := visit(field.Value, decl.Field(field.Key.Name)) |
| valueStr = fmt.Sprintf("%s::%s(%s)", declName(decl), fieldName, fieldValueStr) |
| } |
| return wrapNullable(decl, valueStr) |
| } |
| |
| func onList(value []ir.Value, decl mixer.ListDeclaration) string { |
| var elements []string |
| elemDecl := decl.Elem() |
| for _, item := range value { |
| elements = append(elements, visit(item, elemDecl)) |
| } |
| elementsStr := strings.Join(elements, ", ") |
| var valueStr string |
| switch decl.(type) { |
| case *mixer.ArrayDecl: |
| valueStr = fmt.Sprintf("[%s]", elementsStr) |
| case *mixer.VectorDecl: |
| valueStr = fmt.Sprintf("vec![%s]", elementsStr) |
| default: |
| panic(fmt.Sprintf("unexpected decl %v", decl)) |
| } |
| return wrapNullable(decl, valueStr) |
| } |
| |
| func buildHandleValue(handle ir.Handle) string { |
| return fmt.Sprintf("copy_handle(&handle_defs[%d])", handle) |
| } |
| |
| func onDecodedRecord(value ir.DecodedRecord, decl mixer.RecordDeclaration) string { |
| if value.Encoding.WireFormat != ir.V2WireFormat { |
| panic("Rust backend only supports V2 in decode() function") |
| } |
| name := declName(decl) |
| bytes := rust.BuildBytes(value.Encoding.Bytes) |
| handles := "[]" |
| if len(value.Encoding.Handles) > 0 { |
| handles = fmt.Sprintf("select_handle_infos(&handle_defs, &%s)", |
| buildHandles(value.Encoding.Handles)) |
| } |
| context := encodingContext(value.Encoding.WireFormat) |
| return fmt.Sprintf("decode_value::<%s>(%s, &%s, &mut %s)", name, context, bytes, handles) |
| } |