blob: c8432e43ddd1ebc028a7df1956506552071eadbf [file] [log] [blame] [edit]
// 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"
"log"
"regexp"
"strconv"
"strings"
"unicode"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
// Documented is embedded in structs for declarations that may hold documentation.
type Documented struct {
Doc []string
}
// Type represents a FIDL datatype.
type Type struct {
fidlgen.Type
Decl string // type in traditional bindings
SyncDecl string // type in async bindings when referring to traditional bindings
AsyncDecl string // type in async bindings when referring to async bindings
OptionalDecl string // type when the value is optional
Nullable bool
declType fidlgen.DeclType
typedDataDecl string
typeExpr string
}
// Const represents a constant declaration.
type Const struct {
Type Type
Name string
Value string
Documented
}
// Enum represents an enum declaration.
type Enum struct {
fidlgen.Enum
Name string
Members []EnumMember
TypeSymbol string
TypeExpr string
Documented
}
// EnumMember represents a member of an enum declaration.
type EnumMember struct {
fidlgen.EnumMember
Name string
Value string
Documented
}
// Bits represents a bits declaration.
type Bits struct {
fidlgen.Bits
Name string
Members []BitsMember
TypeSymbol string
TypeExpr string
Mask uint64
Documented
}
// BitsMember represents a member of a bits declaration.
type BitsMember struct {
Name string
Value string
Documented
}
type PayloadableName struct {
Name string
}
// AsParameters default behavior for all payloadable types (Struct, Table, and
// Union) is to simple make a single "parameter" of the type in question (aka
// the "unflattend" representation), always named `value`. Note that *Struct has
// its own override of `AsParameters` which does in fact perform flattening.
func (u *PayloadableName) AsParameters(ty Type) []Parameter {
return []Parameter{
{
Type: ty,
TypeSymbol: ty.typeExpr,
Name: unflattenedPayloadName,
Flattened: false,
typeExpr: fmt.Sprintf("$fidl.MemberType<%s>(type: %s, offset: 0)", ty.Decl, ty.typeExpr),
},
}
}
// Union represents a union declaration.
type Union struct {
fidlgen.Union
PayloadableName
TagName string
Members []UnionMember
TypeSymbol string
TypeExpr string
OptTypeSymbol string
OptTypeExpr string
Documented
}
// UnionMember represents a member of a Union declaration.
type UnionMember struct {
Ordinal uint64
Type Type
Name string
CtorName string
Tag string
Documented
}
// Struct represents a struct declaration.
type Struct struct {
fidlgen.Struct
PayloadableName
Members []StructMember
Paddings []StructPadding
TypeSymbol string
TypeExpr string
HasNullableField bool
Documented
isEmptyStruct bool
}
// AsParameters produces a "flattened" representation of the Struct's members,
// so that they may be used in certain contexts, like rendering method
// signatures. Thus, if we have a FIDL payload like:
//
// type MyStruct = struct {
// a bool;
// b int8;
// };
// protocol MyProtocol {
// MyMethod(MyStruct);
// };
//
// It will produce a "flattened" Dart function signature like:
//
// Future<void> myMethod(bool a, int b);
//
// Rather than an "unflattened" one like:
//
// Future<void> myMethod(MyStruct value);
func (s *Struct) AsParameters(_ Type) []Parameter {
var parameters []Parameter
if s.isEmptyStruct {
return parameters
}
for _, v := range s.Members {
parameters = append(parameters, Parameter{
Type: v.Type,
TypeSymbol: v.typeExpr,
Name: v.Name,
Flattened: true,
typeExpr: v.typeExpr,
})
}
return parameters
}
// StructMember represents a member of a struct declaration.
type StructMember struct {
Type Type
TypeSymbol string
Name string
DefaultValue string
OffsetV2 int
typeExpr string
Documented
}
type StructPadding struct {
OffsetV2 int
PaddingV2 int
}
// Table represents a table declaration.
type Table struct {
fidlgen.Table
PayloadableName
Members []TableMember
TypeSymbol string
TypeExpr string
Documented
}
// TableMember represents a member of a table declaration.
type TableMember struct {
Ordinal int
Index int
Type Type
Name string
DefaultValue string
typeExpr string
Documented
}
// Protocol represents an protocol declaration.
type Protocol struct {
fidlgen.Protocol
Name string
ServiceData string
ProxyName string
BindingName string
ServerName string
EventsName string
Methods []Method
HasEvents bool
Documented
}
// Parameter represents a method request/response payload parameter. In the
// "unflattened" case (valid for union/table payloads), there is always one
// parameter per payload, representing the entire union/table being transported.
// In the "flattened" case, each parameter represents a single member of the
// struct in question.
type Parameter struct {
Type Type
TypeSymbol string
Name string
Flattened bool
typeExpr string
}
type MethodResponse struct {
// WireParameters represent the parameters of the top level response struct
// that is sent on the wire
WireParameters []Parameter
// MethodParameters represent the parameters that the user interacts with
// when using generated methods. When HasError is false, this is the same as
// WireParameters. When HasError is true, MethodParameters corresponds to the
// fields of a successful response.
MethodParameters []Parameter
HasError bool
HasTransportError bool
ResultTypeName string
ResultTypeTagName string
ValueType Type
ErrorType Type
}
// Method represents a method declaration within an protocol declaration.
type Method struct {
fidlgen.Method
Ordinal uint64
OrdinalName string
Name string
HasRequest bool
Request []Parameter
HasResponse bool
Response MethodResponse
// AsyncResponseClass is a named tuple that wraps the MethodParameters of
// a response, and is only generated when there is more than one parameter
AsyncResponseClass string
AsyncResponseType string
CallbackType string
TypeSymbol string
TypeExpr string
Transitional bool
Overflowable fidlgen.Overflowable
Documented
}
// ResponseMessageType is the Dart type returned by the "DecodeResponse" template.
func (m Method) ResponseMessageType() string {
if !m.HasResponse {
return "void"
}
if m.Response.HasError || m.Response.HasTransportError {
return m.Response.ResultTypeName
}
return m.AsyncResponseClass
}
// AsyncResultCompleter provides the appropriate value to $completer.complete
// based on the types of the arguments and whether the method uses error syntax.
func (m Method) AsyncResponseCompleter() string {
if !m.HasResponse || (!m.Response.HasError && !m.Response.HasTransportError) {
return "$completer.complete($response);"
}
var out strings.Builder
fmt.Fprintf(&out, "if ($response.$tag == %s.response) {\n", m.Response.ResultTypeTagName)
out.WriteString("$completer.complete(")
switch len(m.Response.MethodParameters) {
case 0:
out.WriteString("null);")
case 1:
param := m.Response.MethodParameters[0]
if param.Flattened {
fmt.Fprintf(&out, "$response.response!.%s);", param.Name)
} else {
out.WriteString("$response.response!);")
}
default:
out.WriteString(m.AsyncResponseClass + "(")
for _, p := range m.Response.MethodParameters {
fmt.Fprintf(&out, "$response.response!.%s, ", p.Name)
}
out.WriteString("));")
}
if m.Response.HasError && m.Response.HasTransportError {
fmt.Fprintf(&out, "} else if ($response.$tag == %s.err) {", m.Response.ResultTypeTagName)
out.WriteString(`
$completer.completeError($fidl.MethodException($response.err));
} else {
$completer.completeError($fidl.FidlError.fromTransportErr($response.transportErr!));
}`)
} else if m.Response.HasError {
out.WriteString(`} else {
$completer.completeError($fidl.MethodException($response.err));
}`)
} else { // m.Response.HasTransportError
out.WriteString(`} else {
$completer.completeError($fidl.FidlError.fromTransportErr($response.transportErr!));
}`)
}
return out.String()
}
// AsyncResultResponse produces code to convert the result of an error-syntax
// method into the appropriate result union.
func (m Method) AsyncResultResponse() string {
if !m.HasResponse || (!m.Response.HasError && !m.Response.HasTransportError) {
return ""
}
var out strings.Builder
fmt.Fprintf(&out, "%s.withResponse(\n", m.Response.ResultTypeName)
switch len(m.Response.MethodParameters) {
case 0:
fmt.Fprintf(&out, `%s()`, m.Response.ValueType.Decl)
case 1:
param := m.Response.MethodParameters[0]
if param.Flattened {
fmt.Fprintf(&out, "%s(%s: $responseValue)", m.Response.ValueType.Decl, param.Name)
} else {
out.WriteString("$responseValue")
}
default:
fmt.Fprintf(&out, `%s(`, m.Response.ValueType.Decl)
for _, p := range m.Response.MethodParameters {
fmt.Fprintf(&out, "%s: $responseValue.%s, ", p.Name, p.Name)
}
out.WriteString(")")
}
out.WriteString(")")
return out.String()
}
// AddEventResponse provides the appropriate value to the EventStreamController
// for an event, based on whether the event uses error syntax.
func (m Method) AddEventResponse() string {
if !m.HasResponse || !m.Response.HasError {
return fmt.Sprintf("_%sEventStreamController.add($response);", m.Name)
}
var out strings.Builder
fmt.Fprintf(&out, `if ($response.$tag == %s.response) {
_%sEventStreamController.add(`, m.Response.ResultTypeTagName, m.Name)
switch len(m.Response.MethodParameters) {
case 0:
out.WriteString("null);")
case 1:
param := m.Response.MethodParameters[0]
if param.Flattened {
fmt.Fprintf(&out, "$response.response!.%s);", param.Name)
} else {
out.WriteString("$response.response!);")
}
default:
out.WriteString(m.AsyncResponseClass + "(")
for _, p := range m.Response.MethodParameters {
fmt.Fprintf(&out, "$response.response!.%s, ", p.Name)
}
out.WriteString("));")
}
fmt.Fprintf(&out, `} else {
_%sEventStreamController.addError($fidl.MethodException($response.err));
}`, m.Name)
return out.String()
}
// MessageHeaderStrictness setting to use for encodeMessageHeader arguments.
func (m Method) MessageHeaderStrictness() string {
if m.IsStrict() {
return "$fidl.CallStrictness.strict"
}
return "$fidl.CallStrictness.flexible"
}
// Import describes another FIDL library that will be imported.
type Import struct {
URL string
LocalName string
AsyncURL string
}
// Root holds all of the declarations for a FIDL library.
type Root struct {
Experiments fidlgen.Experiments
LibraryName string
Imports []Import
Consts []Const
Enums []Enum
Bits []Bits
Protocols []Protocol
Structs []Struct
Tables []Table
Unions []Union
ExternalStructs []Struct
}
type nameContext struct{ fidlgen.NameContext }
func newNameContext() nameContext {
return nameContext{fidlgen.NewNameContext()}
}
func (ctx nameContext) changeIfReserved(name string) string {
if ctx.IsReserved(name) {
return name + "$"
}
return name
}
var (
// Name of a bits member
bitsMemberContext = newNameContext()
// Name of an enum member
enumMemberContext = newNameContext()
// Name of a struct member
structMemberContext = newNameContext()
// Name of a table member
tableMemberContext = newNameContext()
// Name of a union member
unionMemberContext = newNameContext()
// Tag of a union member
unionMemberTagContext = newNameContext()
// Name of a constant
constantContext = newNameContext()
// Name of a top-level declaration (other than a constant)
declarationContext = newNameContext()
// Name of a method
methodContext = newNameContext()
// For unflattened payloads, always use the same name in function signatures.
unflattenedPayloadName = "payload"
)
func init() {
// Reserve names that are universally reserved:
for _, ctx := range []nameContext{
enumMemberContext, structMemberContext, tableMemberContext,
unionMemberContext, unionMemberTagContext, constantContext,
declarationContext, methodContext, bitsMemberContext,
} {
ctx.ReserveNames([]string{"assert", "async", "await", "break", "case",
"catch", "class", "const", "continue", "default", "do", "else",
"enum", "extends", "false", "final", "finally", "for", "if", "in",
"is", "new", "null", "override", "rethrow", "return", "String",
"super", "switch", "this", "throw", "true", "try", "var", "void",
"while", "with", "yield"})
}
constantContext.ReserveNames([]string{"dynamic", "int", "num"})
declarationContext.ReserveNames([]string{"List", "Map", "Never", "None",
"Null", "Object", "Some"})
methodContext.ReserveNames([]string{"dynamic", "hashCode", "int",
"noSuchMethod", "num", "runtimeType", "toString"})
bitsMemberContext.ReserveNames([]string{"dynamic", "hashCode", "int",
"toString"})
enumMemberContext.ReserveNames([]string{"bool", "dynamic", "hashCode",
"int", "noSuchMethod", "num", "runtimeType", "toString"})
structMemberContext.ReserveNames([]string{"bool", "double", "dynamic",
"hashCode", "int", "noSuchMethod", "num", "runtimeType", "toString"})
tableMemberContext.ReserveNames([]string{"bool", "double", "dynamic",
"hashCode", "int", "noSuchMethod", "num", "runtimeType", "toString"})
unionMemberContext.ReserveNames([]string{"bool", "dynamic", "hashCode", "int",
"noSuchMethod", "num", "runtimeType", "toString"})
unionMemberTagContext.ReserveNames([]string{"index", "values"})
}
var declForPrimitiveType = map[fidlgen.PrimitiveSubtype]string{
fidlgen.Bool: "bool",
fidlgen.Int8: "int",
fidlgen.Int16: "int",
fidlgen.Int32: "int",
fidlgen.Int64: "int",
fidlgen.Uint8: "int",
fidlgen.Uint16: "int",
fidlgen.Uint32: "int",
fidlgen.Uint64: "int",
fidlgen.Float32: "double",
fidlgen.Float64: "double",
}
var typedDataDecl = map[fidlgen.PrimitiveSubtype]string{
fidlgen.Int8: "Int8List",
fidlgen.Int16: "Int16List",
fidlgen.Int32: "Int32List",
fidlgen.Int64: "Int64List",
fidlgen.Uint8: "Uint8List",
fidlgen.Uint16: "Uint16List",
fidlgen.Uint32: "Uint32List",
fidlgen.Uint64: "Uint64List",
fidlgen.Float32: "Float32List",
fidlgen.Float64: "Float64List",
}
var typeForPrimitiveSubtype = map[fidlgen.PrimitiveSubtype]string{
fidlgen.Bool: "BoolType",
fidlgen.Int8: "Int8Type",
fidlgen.Int16: "Int16Type",
fidlgen.Int32: "Int32Type",
fidlgen.Int64: "Int64Type",
fidlgen.Uint8: "Uint8Type",
fidlgen.Uint16: "Uint16Type",
fidlgen.Uint32: "Uint32Type",
fidlgen.Uint64: "Uint64Type",
fidlgen.Float32: "Float32Type",
fidlgen.Float64: "Float64Type",
}
var declForInternalType = map[fidlgen.InternalSubtype]string{
fidlgen.TransportErr: "$fidl.TransportErr",
}
var typeForInternalSubtype = map[fidlgen.InternalSubtype]string{
fidlgen.TransportErr: "TransportErrType",
}
func docStringLink(nameWithBars string) string {
return fmt.Sprintf("[%s]", nameWithBars[1:len(nameWithBars)-1])
}
var reLink = regexp.MustCompile("\\|([^\\|]+)\\|")
// TODO(fxbug.dev/118283): rethink how we depend on the fidlgen package.
type Annotated interface {
LookupAttribute(fidlgen.Identifier) (fidlgen.Attribute, bool)
}
func docString(node Annotated) Documented {
attribute, ok := node.LookupAttribute("doc")
if !ok {
return Documented{nil}
}
value, ok := attribute.LookupArg("value")
if !ok {
return Documented{nil}
}
var docs []string
lines := strings.Split(value.ValueString(), "\n")
if len(lines[len(lines)-1]) == 0 {
// Remove the blank line at the end
lines = lines[:len(lines)-1]
}
for _, line := range lines {
// Turn |something| into [something]
line = reLink.ReplaceAllStringFunc(line, docStringLink)
docs = append(docs, line)
}
return Documented{docs}
}
func formatBool(val bool) string {
return strconv.FormatBool(val)
}
func formatInt(val *int) string {
if val == nil {
return "null"
}
return strconv.Itoa(*val)
}
func formatParameterList(params []Parameter) string {
if len(params) == 0 {
return "[]"
}
lines := []string{}
for _, p := range params {
lines = append(lines, fmt.Sprintf(" %s,\n", p.typeExpr))
}
return fmt.Sprintf("<$fidl.MemberType>[\n%s ]", strings.Join(lines, ""))
}
func formatTableMemberList(members []TableMember) string {
if len(members) == 0 {
return "[]"
}
lines := []string{}
for _, member := range members {
for len(lines) < member.Ordinal-1 {
lines = append(lines, fmt.Sprintf(" null,\n"))
}
lines = append(lines, fmt.Sprintf(" %s,\n", member.Type.typeExpr))
}
return fmt.Sprintf("[\n%s ]", strings.Join(lines, ""))
}
func formatUnionMemberList(members []UnionMember) string {
if len(members) == 0 {
return "<int, $fidl.FidlType>{}"
}
var lines []string
for _, v := range members {
lines = append(lines, fmt.Sprintf(" %d: %s,\n", v.Ordinal, v.Type.typeExpr))
}
return fmt.Sprintf("<int, $fidl.FidlType>{\n%s }", strings.Join(lines, ""))
}
func formatLibraryName(library fidlgen.LibraryIdentifier) string {
parts := []string{}
for _, part := range library {
parts = append(parts, string(part))
}
return strings.Join(parts, "_")
}
func typeExprForPrimitiveSubtype(val fidlgen.PrimitiveSubtype) string {
t, ok := typeForPrimitiveSubtype[val]
if !ok {
log.Fatal("Unknown primitive subtype: ", val)
}
return fmt.Sprintf("$fidl.%s()", t)
}
func typeExprForInternalSubtype(val fidlgen.InternalSubtype) string {
t, ok := typeForInternalSubtype[val]
if !ok {
log.Fatal("Unknown internal subtype: ", val)
}
return fmt.Sprintf("$fidl.%s()", t)
}
func libraryPrefix(library fidlgen.LibraryIdentifier) string {
return fmt.Sprintf("lib$%s", formatLibraryName(library))
}
type compiler struct {
Root
decls fidlgen.DeclInfoMap
experiments fidlgen.Experiments
library fidlgen.LibraryIdentifier
typesRoot fidlgen.Root
paramableTypes map[fidlgen.EncodedCompoundIdentifier]Parameterizer
}
// Parameterizer should be implemented by all "payloadable" layout types (that
// is, Struct, Table, and Union) so that method signatures and encoding/decoding
// type lists may be built out for each of them.
type Parameterizer interface {
AsParameters(Type) []Parameter
}
// Assert that parameterizable layouts conform to the payloader interface.
var _ = []Parameterizer{
(*PayloadableName)(nil),
(*Table)(nil),
(*Struct)(nil),
(*Union)(nil),
}
func (c *compiler) compileParameters(t *fidlgen.Type) []Parameter {
ty := c.compileType(*t)
val, ok := c.paramableTypes[ty.Identifier]
if !ok {
panic(fmt.Sprintf("Unknown request/response layout: %s", ty.Identifier))
}
return val.AsParameters(ty)
}
func (c *compiler) typeExprForMethod(val fidlgen.Method, request []Parameter, response []Parameter, name string) string {
var (
requestSizeV2 = 0
responseSizeV2 = 0
)
if val.RequestPayload != nil {
requestSizeV2 = val.RequestPayload.TypeShapeV2.InlineSize
}
if val.ResponsePayload != nil {
responseSizeV2 = val.ResponsePayload.TypeShapeV2.InlineSize
}
// request/response and requestInlineSize/responseInlineSize are null/0 for
// both empty payloads and when there are no request/responses. The HasRequest
// and HasResponse fields are used to distinguish between these two cases
// during codegen.
return fmt.Sprintf(`$fidl.MethodType(
request: %s,
response: %s,
name: r"%s",
requestInlineSizeV2: %d,
responseInlineSizeV2: %d,
)`, formatParameterList(request), formatParameterList(response), name,
requestSizeV2, responseSizeV2)
}
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
}
func (c *compiler) typeSymbolForCompoundIdentifier(ident fidlgen.CompoundIdentifier) string {
return c._typeSymbolForCompoundIdentifier(ident, "Type")
}
func (c *compiler) optTypeSymbolForCompoundIdentifier(ident fidlgen.CompoundIdentifier) string {
return c._typeSymbolForCompoundIdentifier(ident, "OptType")
}
func (c *compiler) _typeSymbolForCompoundIdentifier(ident fidlgen.CompoundIdentifier, suffix string) string {
t := fmt.Sprintf("k%s_%s", ident.Name, suffix)
if c.inExternalLibrary(ident) {
return fmt.Sprintf("%s.%s", libraryPrefix(ident.Library), t)
}
return t
}
func (c *compiler) compileUpperCamelIdentifier(val fidlgen.Identifier, context nameContext) string {
return context.changeIfReserved(fidlgen.ToUpperCamelCase(string(val)))
}
func (c *compiler) compileLowerCamelIdentifier(val fidlgen.Identifier, context nameContext) string {
return context.changeIfReserved(fidlgen.ToLowerCamelCase(string(val)))
}
func (c *compiler) compileCompoundIdentifier(val fidlgen.CompoundIdentifier, context nameContext) string {
strs := []string{}
if c.inExternalLibrary(val) {
strs = append(strs, libraryPrefix(val.Library))
}
strs = append(strs, context.changeIfReserved(string(val.Name)))
if val.Member != "" {
strs = append(strs, context.changeIfReserved(string(val.Member)))
}
return strings.Join(strs, ".")
}
func (c *compiler) compileUpperCamelCompoundIdentifier(val fidlgen.CompoundIdentifier, ext string, context nameContext) string {
str := fidlgen.ToUpperCamelCase(string(val.Name)) + ext
val.Name = fidlgen.Identifier(str)
return c.compileCompoundIdentifier(val, context)
}
func (c *compiler) compileLowerCamelCompoundIdentifier(val fidlgen.CompoundIdentifier, ext string, context nameContext) string {
str := fidlgen.ToLowerCamelCase(string(val.Name)) + ext
val.Name = fidlgen.Identifier(str)
return c.compileCompoundIdentifier(val, context)
}
func (c *compiler) compileConstantIdentifier(val fidlgen.CompoundIdentifier, context nameContext) string {
if val.Member != "" {
// val.Name here is the type.
// Format: Type.memberIdentifier
val.Name = fidlgen.Identifier(fidlgen.ToUpperCamelCase(string(val.Name)))
val.Member = fidlgen.Identifier(fidlgen.ToLowerCamelCase(string(val.Member)))
} else {
// Format: valueIdentifier
val.Name = fidlgen.Identifier(fidlgen.ToLowerCamelCase(string(val.Name)))
}
return c.compileCompoundIdentifier(val, context)
}
func (c *compiler) compileLiteral(val fidlgen.Literal) 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`)
case '$':
b.WriteString(`\$`)
default:
if unicode.IsPrint(r) {
b.WriteRune(r)
} else {
b.WriteString(fmt.Sprintf(`\u{%x}`, r))
}
}
}
b.WriteRune('\'')
return b.String()
case fidlgen.NumericLiteral:
// TODO(fxbug.dev/7810): Once we expose resolved constants for defaults, e.g.
// in structs, we will not need ignore hex and binary values.
if strings.HasPrefix(val.Value, "0x") || strings.HasPrefix(val.Value, "0b") {
return val.Value
}
// No special handling of floats.
if strings.ContainsRune(val.Value, '.') {
return val.Value
}
// For numbers larger than int64, they must be emitted as hex numbers.
// We simply do this for all positive numbers.
if strings.HasPrefix(val.Value, "-") {
return val.Value
}
num, err := strconv.ParseUint(val.Value, 10, 64)
if err != nil {
panic(fmt.Sprintf("JSON IR contains invalid numeric literal: %s", val.Value))
}
return fmt.Sprintf("%#x", num)
case fidlgen.BoolLiteral:
return val.Value
case fidlgen.DefaultLiteral:
return "default"
default:
log.Fatal("Unknown literal kind: ", val.Kind)
return ""
}
}
func (c *compiler) compileConstant(val fidlgen.Constant, t *Type) string {
switch val.Kind {
case fidlgen.IdentifierConstant:
return c.compileConstantIdentifier(val.Identifier.Parse(), constantContext)
case fidlgen.LiteralConstant:
return c.compileLiteral(*val.Literal)
case fidlgen.BinaryOperator:
if t.declType == fidlgen.BitsDeclType {
return fmt.Sprintf("%s._(%s)", t.Decl, val.Value)
}
return fmt.Sprintf("%s", val.Value)
default:
log.Fatal("Unknown constant kind: ", val.Kind)
return ""
}
}
func (c *compiler) compilePrimitiveSubtype(val fidlgen.PrimitiveSubtype) string {
if t, ok := declForPrimitiveType[val]; ok {
return t
}
log.Fatal("Unknown primitive type: ", val)
return ""
}
func (c *compiler) compileInternalSubtype(val fidlgen.InternalSubtype) string {
if t, ok := declForInternalType[val]; ok {
return t
}
log.Fatal("Unknown internal type: ", val)
return ""
}
func (c *compiler) compileType(val fidlgen.Type) Type {
nullablePrefix := ""
if val.Nullable {
nullablePrefix = "Nullable"
}
r := Type{
Type: val,
}
r.Nullable = val.Nullable
switch val.Kind {
case fidlgen.ArrayType:
t := c.compileType(*val.ElementType)
if len(t.typedDataDecl) > 0 {
r.Decl = t.typedDataDecl
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
} else {
r.Decl = fmt.Sprintf("List<%s>", t.Decl)
r.SyncDecl = fmt.Sprintf("List<%s>", t.SyncDecl)
r.AsyncDecl = fmt.Sprintf("List<%s>", t.AsyncDecl)
}
elementStr := fmt.Sprintf("element: %s", t.typeExpr)
elementCountStr := fmt.Sprintf("elementCount: %s", formatInt(val.ElementCount))
r.typeExpr = fmt.Sprintf("$fidl.ArrayType<%s, %s>(%s, %s)", t.Decl, r.Decl, elementStr, elementCountStr)
case fidlgen.VectorType:
t := c.compileType(*val.ElementType)
if len(t.typedDataDecl) > 0 {
r.Decl = t.typedDataDecl
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
} else {
r.Decl = fmt.Sprintf("List<%s>", t.Decl)
r.SyncDecl = fmt.Sprintf("List<%s>", t.SyncDecl)
r.AsyncDecl = fmt.Sprintf("List<%s>", t.AsyncDecl)
}
elementStr := fmt.Sprintf("element: %s", t.typeExpr)
maybeElementCountStr := fmt.Sprintf("maybeElementCount: %s", formatInt(val.ElementCount))
r.typeExpr = fmt.Sprintf("$fidl.%sVectorType<%s, %s>(%s, %s)",
nullablePrefix, t.Decl, r.Decl, elementStr, maybeElementCountStr)
case fidlgen.StringType:
r.Decl = "String"
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
r.typeExpr = fmt.Sprintf("$fidl.%sStringType(maybeElementCount: %s)",
nullablePrefix, formatInt(val.ElementCount))
case fidlgen.HandleType:
var subtype string
switch val.HandleSubtype {
case "channel":
subtype = "Channel"
case "eventpair":
subtype = "EventPair"
case "socket":
subtype = "Socket"
case "vmo":
subtype = "Vmo"
default:
subtype = "Handle"
}
r.Decl = "$zircon." + subtype
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
baseType := fmt.Sprintf("$fidl.%sType(objectType: %d, rights: %d)",
subtype, val.ObjType, val.HandleRights)
if val.Nullable {
r.typeExpr = fmt.Sprintf("$fidl.NullableHandleType(%s)", baseType)
} else {
r.typeExpr = baseType
}
case fidlgen.RequestType:
compound := val.RequestSubtype.Parse()
t := c.compileUpperCamelCompoundIdentifier(compound, "", declarationContext)
r.Decl = fmt.Sprintf("$fidl.InterfaceRequest<%s>", t)
if c.inExternalLibrary(compound) {
r.SyncDecl = fmt.Sprintf("$fidl.InterfaceRequest<sync$%s>", t)
} else {
r.SyncDecl = fmt.Sprintf("$fidl.InterfaceRequest<$sync.%s>", t)
}
r.AsyncDecl = r.Decl
r.typeExpr = fmt.Sprintf("$fidl.%sInterfaceRequestType<%s>()",
nullablePrefix, t)
case fidlgen.PrimitiveType:
r.Decl = c.compilePrimitiveSubtype(val.PrimitiveSubtype)
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
r.typedDataDecl = typedDataDecl[val.PrimitiveSubtype]
r.typeExpr = typeExprForPrimitiveSubtype(val.PrimitiveSubtype)
case fidlgen.InternalType:
r.Decl = c.compileInternalSubtype(val.InternalSubtype)
r.SyncDecl = r.Decl
r.AsyncDecl = r.Decl
r.typeExpr = typeExprForInternalSubtype(val.InternalSubtype)
case fidlgen.IdentifierType:
compound := val.Identifier.Parse()
t := c.compileUpperCamelCompoundIdentifier(compound, "", declarationContext)
declInfo, ok := c.decls[val.Identifier]
if !ok {
log.Fatal("Unknown identifier: ", val.Identifier)
}
r.declType = declInfo.Type
switch r.declType {
case fidlgen.ConstDeclType:
fallthrough
case fidlgen.EnumDeclType:
fallthrough
case fidlgen.BitsDeclType:
fallthrough
case fidlgen.StructDeclType:
fallthrough
case fidlgen.TableDeclType:
fallthrough
case fidlgen.UnionDeclType:
r.Decl = t
if c.inExternalLibrary(compound) {
r.SyncDecl = fmt.Sprintf("sync$%s", t)
} else {
r.SyncDecl = fmt.Sprintf("$sync.%s", t)
}
r.AsyncDecl = r.SyncDecl
if val.Nullable {
switch r.declType {
case fidlgen.UnionDeclType:
r.typeExpr = c.optTypeSymbolForCompoundIdentifier(val.Identifier.Parse())
default:
r.typeExpr = fmt.Sprintf("$fidl.PointerType<%s>(element: %s)",
t, c.typeSymbolForCompoundIdentifier(val.Identifier.Parse()))
}
} else {
r.typeExpr = c.typeSymbolForCompoundIdentifier(val.Identifier.Parse())
}
case fidlgen.ProtocolDeclType:
r.Decl = fmt.Sprintf("$fidl.InterfaceHandle<%s>", t)
if c.inExternalLibrary(compound) {
r.SyncDecl = fmt.Sprintf("$fidl.InterfaceHandle<sync$%s>", t)
} else {
r.SyncDecl = fmt.Sprintf("$fidl.InterfaceHandle<$sync.%s>", t)
}
r.AsyncDecl = r.Decl
r.typeExpr = fmt.Sprintf("$fidl.%sInterfaceHandleType<%s>()", nullablePrefix, t)
default:
log.Fatal("Unknown declaration type: ", r.declType)
}
default:
log.Fatal("Unknown type kind: ", val.Kind)
}
if r.AsyncDecl == "" {
log.Fatalf("No AsyncDecl for %s", r.Decl)
}
if r.SyncDecl == "" {
log.Fatalf("No SyncDecl for %s", r.Decl)
}
if r.Nullable {
r.Decl = r.Decl + "?"
r.OptionalDecl = r.Decl
} else {
r.OptionalDecl = r.Decl + "?"
}
return r
}
func (c *compiler) compileConst(val fidlgen.Const) Const {
t := c.compileType(val.Type)
r := Const{
Type: t,
Name: c.compileLowerCamelCompoundIdentifier(val.Name.Parse(), "", constantContext),
Value: c.compileConstant(val.Value, &t),
Documented: docString(val),
}
return r
}
func (c *compiler) membersAsMapToNull(members []fidlgen.EnumMember) string {
var values []string
for _, member := range members {
values = append(values, fmt.Sprintf("%s:null", c.compileConstant(member.Value, nil)))
}
return strings.Join(values, ",")
}
func (c *compiler) compileEnum(val fidlgen.Enum) Enum {
ci := val.Name.Parse()
n := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
e := Enum{
Enum: val,
Name: n,
TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
TypeExpr: fmt.Sprintf("$fidl.EnumType<%s>(type: %s, values: {%s}, ctor: %s._ctor)",
n, typeExprForPrimitiveSubtype(val.Type), c.membersAsMapToNull(val.Members), n),
Documented: docString(val),
}
for _, v := range val.Members {
e.Members = append(e.Members, EnumMember{
EnumMember: v,
Name: c.compileLowerCamelIdentifier(v.Name, enumMemberContext),
Value: c.compileConstant(v.Value, nil),
Documented: docString(v),
})
}
return e
}
func (c *compiler) compileBits(val fidlgen.Bits) Bits {
ci := val.Name.Parse()
n := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
if val.Type.Kind != fidlgen.PrimitiveType {
panic("unexpected, only primitives are allowed for bits declarations")
}
subtype := val.Type.PrimitiveSubtype
// TODO(fxbug.dev/59044): Mask should be an int
maskVal, err := strconv.ParseUint(val.Mask, 10, 64)
if err != nil {
panic(fmt.Sprintf("JSON IR contains invalid mask value: %s", val.Mask))
}
b := Bits{
Bits: val,
Name: n,
TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
TypeExpr: fmt.Sprintf("$fidl.BitsType<%s>(type: %s, ctor: %s._ctor)", n, typeExprForPrimitiveSubtype(subtype), n),
Mask: maskVal,
Documented: docString(val),
}
for _, v := range val.Members {
b.Members = append(b.Members, BitsMember{
Name: c.compileLowerCamelIdentifier(v.Name, bitsMemberContext),
Value: c.compileConstant(v.Value, nil),
Documented: docString(v),
})
}
return b
}
func (c *compiler) compileMethodResponse(method fidlgen.Method) MethodResponse {
if method.HasError || method.HasTransportError() {
response := MethodResponse{
WireParameters: c.compileParameters(method.ResponsePayload),
MethodParameters: c.compileParameters(method.ValueType),
HasError: method.HasError,
HasTransportError: method.HasTransportError(),
ResultTypeName: c.compileUpperCamelCompoundIdentifier(method.ResultType.Identifier.Parse(), "", declarationContext),
ResultTypeTagName: c.compileUpperCamelCompoundIdentifier(method.ResultType.Identifier.Parse(), "Tag", declarationContext),
ValueType: c.compileType(*method.ValueType),
}
if method.HasError {
response.ErrorType = c.compileType(*method.ErrorType)
}
return response
}
response := c.compileParameters(method.ResponsePayload)
return MethodResponse{
WireParameters: response,
MethodParameters: response,
}
}
func (c *compiler) compileMethod(val fidlgen.Method, protocol Protocol, fidlProtocol fidlgen.Protocol) Method {
var (
name = c.compileLowerCamelIdentifier(val.Name, methodContext)
request []Parameter
response MethodResponse
asyncResponseClass string
asyncResponseType string
)
if val.RequestPayload != nil {
request = c.compileParameters(val.RequestPayload)
}
if val.ResponsePayload != nil {
response = c.compileMethodResponse(val)
}
if val.HasResponse {
switch len(response.MethodParameters) {
case 0:
asyncResponseType = "void"
case 1:
asyncResponseType = response.MethodParameters[0].Type.Decl
default:
asyncResponseType = fmt.Sprintf("%s$%s$Response", protocol.Name, val.Name)
asyncResponseClass = asyncResponseType
}
}
_, transitional := val.LookupAttribute("transitional")
return Method{
Method: val,
Ordinal: val.Ordinal,
OrdinalName: fmt.Sprintf("_k%s_%s_Ordinal", protocol.Name, val.Name),
Name: name,
HasRequest: val.HasRequest,
Request: request,
HasResponse: val.HasResponse,
Response: response,
AsyncResponseClass: asyncResponseClass,
AsyncResponseType: asyncResponseType,
CallbackType: fmt.Sprintf("%s%sCallback", protocol.Name, c.compileUpperCamelIdentifier(val.Name, methodContext)),
TypeSymbol: fmt.Sprintf("_k%s_%s_Type", protocol.Name, val.Name),
TypeExpr: c.typeExprForMethod(val, request, response.WireParameters, fmt.Sprintf("%s.%s", protocol.Name, val.Name)),
Transitional: transitional,
Documented: docString(val),
Overflowable: val.GetOverflowable(fidlProtocol, c.experiments),
}
}
func (c *compiler) compileProtocol(val fidlgen.Protocol) Protocol {
ci := val.Name.Parse()
r := Protocol{
Protocol: val,
Name: c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext),
ServiceData: c.compileUpperCamelCompoundIdentifier(ci, "Data", declarationContext),
ProxyName: c.compileUpperCamelCompoundIdentifier(ci, "Proxy", declarationContext),
BindingName: c.compileUpperCamelCompoundIdentifier(ci, "Binding", declarationContext),
ServerName: c.compileUpperCamelCompoundIdentifier(ci, "Server", declarationContext),
EventsName: c.compileUpperCamelCompoundIdentifier(ci, "Events", declarationContext),
Methods: []Method{},
HasEvents: false,
Documented: docString(val),
}
if !val.OneWayUnknownInteractions() {
r.ServerName = r.Name
}
for _, v := range val.Methods {
m := c.compileMethod(v, r, val)
r.Methods = append(r.Methods, m)
if !v.HasRequest && v.HasResponse {
r.HasEvents = true
}
}
return r
}
func (c *compiler) compileStructMember(val fidlgen.StructMember) StructMember {
t := c.compileType(val.Type)
var defaultValue string
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t)
}
return StructMember{
Type: t,
TypeSymbol: t.typeExpr,
Name: c.compileLowerCamelIdentifier(val.Name, structMemberContext),
DefaultValue: defaultValue,
OffsetV2: val.FieldShapeV2.Offset,
typeExpr: fmt.Sprintf("$fidl.MemberType<%s>(type: %s, offset: %v)",
t.Decl, t.typeExpr, val.FieldShapeV2.Offset),
Documented: docString(val),
}
}
func (c *compiler) compileStruct(val fidlgen.Struct) Struct {
ci := val.Name.Parse()
name := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
r := Struct{
PayloadableName: PayloadableName{name},
Struct: val,
TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
TypeExpr: fmt.Sprintf(
`$fidl.StructType<%s>(inlineSize: %v, structDecode: %s._structDecode)`,
name, val.TypeShapeV2.InlineSize, name),
Documented: docString(val),
}
// Early exit for empty struct case.
if len(val.Members) == 0 {
r.isEmptyStruct = true
r.Members = []StructMember{
c.compileStructMember(fidlgen.EmptyStructMember("reserved")),
}
r.Paddings = []StructPadding{
{
OffsetV2: 0,
PaddingV2: 1,
},
}
return r
}
var (
isFirst = true
previousPaddingV2 int
)
for _, v := range val.Members {
member := c.compileStructMember(v)
if member.Type.Nullable {
r.HasNullableField = true
}
r.Members = append(r.Members, member)
if isFirst {
isFirst = false
} else {
r.Paddings = append(r.Paddings, StructPadding{
OffsetV2: v.FieldShapeV2.Offset - previousPaddingV2,
PaddingV2: previousPaddingV2,
})
}
previousPaddingV2 = v.FieldShapeV2.Padding
}
r.Paddings = append(r.Paddings, StructPadding{
OffsetV2: val.TypeShapeV2.InlineSize - previousPaddingV2,
PaddingV2: previousPaddingV2,
})
return r
}
func (c *compiler) compileTableMember(val fidlgen.TableMember) TableMember {
t := c.compileType(*val.Type)
defaultValue := ""
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t)
}
return TableMember{
Ordinal: val.Ordinal,
Index: val.Ordinal - 1,
Type: t,
Name: c.compileLowerCamelIdentifier(val.Name, tableMemberContext),
DefaultValue: defaultValue,
Documented: docString(val),
}
}
func (c *compiler) compileTable(val fidlgen.Table) Table {
ci := val.Name.Parse()
r := Table{
Table: val,
PayloadableName: PayloadableName{c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)},
TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
Documented: docString(val),
}
for _, v := range val.SortedMembersNoReserved() {
r.Members = append(r.Members, c.compileTableMember(v))
}
r.TypeExpr = fmt.Sprintf(`$fidl.TableType<%s>(
inlineSize: %v,
members: %s,
ctor: %s._ctor,
resource: %t,
)`, r.Name, val.TypeShapeV2.InlineSize, formatTableMemberList(r.Members), r.Name, r.IsResourceType())
return r
}
func (c *compiler) compileUnion(val fidlgen.Union) Union {
var members []UnionMember
for _, member := range val.Members {
if member.Reserved {
continue
}
memberType := c.compileType(*member.Type)
members = append(members, UnionMember{
Ordinal: uint64(member.Ordinal),
Type: memberType,
Name: c.compileLowerCamelIdentifier(member.Name, unionMemberContext),
CtorName: c.compileUpperCamelIdentifier(member.Name, unionMemberContext),
Tag: c.compileLowerCamelIdentifier(member.Name, unionMemberTagContext),
Documented: docString(member),
})
}
ci := val.Name.Parse()
name := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
r := Union{
Union: val,
PayloadableName: PayloadableName{name},
TagName: c.compileUpperCamelCompoundIdentifier(ci, "Tag", declarationContext),
TypeSymbol: c.typeSymbolForCompoundIdentifier(ci),
OptTypeSymbol: c.optTypeSymbolForCompoundIdentifier(ci),
Members: members,
Documented: docString(val),
}
r.TypeExpr = fmt.Sprintf(`$fidl.UnionType<%s>(
members: %s,
ctor: %s._ctor,
flexible: %t,
resource: %t,
)`, r.Name, formatUnionMemberList(r.Members), r.Name, r.IsFlexible(), r.IsResourceType())
r.OptTypeExpr = fmt.Sprintf(`$fidl.NullableUnionType<%s>(
members: %s,
ctor: %s._ctor,
flexible: %t,
resource: %t,
)`, r.Name, formatUnionMemberList(r.Members), r.Name, r.IsFlexible(), r.IsResourceType())
return r
}
// isParamableType checks to see if a type is ever used as either a message body
// type or a payload type. If it is, we will need to render the type as a list
// of parameters in some contexts (for example, for a method-calling function
// signature), which will require special treatment depending on whether or not
// the underlying FIDL layout is a `struct`.
//
// By way of example, consider the following FIDL:
//
// MyMethod(struct{...}) -> (struct{...}) error uint32;
// |-----A-----| |-----B-----| |-----C-----|
// |------------D------------|
//
// Types `A` and `B` are payloads (included in the `methodTypes` set). These
// types, or their parameterized representations, are ones that users of the
// Dart bindings will interact with when sending and replying to FIDL method
// calls.
//
// Types `A` and `C` are message bodies (included in the `wireTypes` set). These
// are the types that are actually sent over the wire, and may need to be
// internally constructed from their constituent parts ("unflattened").
//
// If the `error` syntax were not used in the example above, the message body
// type name and payload type name sets for the method would be identical.
func isParamableType(name fidlgen.EncodedCompoundIdentifier, wireTypes fidlgen.EncodedCompoundIdentifierSet, methodTypes fidlgen.EncodedCompoundIdentifierSet) bool {
if _, ok := wireTypes[name]; ok {
return true
}
if _, ok := methodTypes[name]; ok {
return true
}
return false
}
// Compile the language independent type definition into the Dart-specific representation.
func Compile(r fidlgen.Root) Root {
r = r.ForBindings("dart")
c := compiler{
decls: r.DeclInfo(),
experiments: r.Experiments,
library: r.Name.Parse(),
typesRoot: r,
paramableTypes: map[fidlgen.EncodedCompoundIdentifier]Parameterizer{},
}
// Do a first pass of the protocols, creating a set of all names of types that
// are used as a transactional message payloads and/or wire bodies.
mtum := r.MethodTypeUsageMap()
c.Root.Experiments = r.Experiments
c.Root.LibraryName = fmt.Sprintf("fidl_%s", formatLibraryName(c.library))
for _, v := range r.Consts {
c.Root.Consts = append(c.Root.Consts, c.compileConst(v))
}
for _, v := range r.Enums {
c.Root.Enums = append(c.Root.Enums, c.compileEnum(v))
}
for _, v := range r.Bits {
c.Root.Bits = append(c.Root.Bits, c.compileBits(v))
}
for _, v := range r.Structs {
compiled := c.compileStruct(v)
if k, ok := mtum[v.Name]; ok {
c.paramableTypes[v.Name] = &compiled
if v.IsAnonymous() && k != fidlgen.UsedOnlyAsPayload {
// Because anonymous payload struct definitions are always exposed to
// the user in "flattened" form as parameter lists consisting of the
// struct's members, it is pointless to generate a Dart class for this
// unusable Struct definition.
continue
}
}
c.Root.Structs = append(c.Root.Structs, compiled)
}
for _, v := range r.ExternalStructs {
compiled := c.compileStruct(v)
if k, ok := mtum[v.Name]; ok {
c.paramableTypes[v.Name] = &compiled
if v.IsAnonymous() && k != fidlgen.UsedOnlyAsPayload {
// Because anonymous payload struct definitions are always exposed to
// the user in "flattened" form as parameter lists consisting of the
// struct's members, it is pointless to generate a Dart class for this
// unusable Struct definition.
continue
}
}
c.Root.ExternalStructs = append(c.Root.ExternalStructs, compiled)
}
for _, v := range r.Tables {
t := c.compileTable(v)
if _, ok := mtum[v.Name]; ok {
c.paramableTypes[v.Name] = &t
}
c.Root.Tables = append(c.Root.Tables, t)
}
for _, v := range r.Unions {
u := c.compileUnion(v)
if _, ok := mtum[v.Name]; ok {
c.paramableTypes[v.Name] = &u
}
c.Root.Unions = append(c.Root.Unions, u)
}
// For imported table and union payloads, generate the appropriate payloadable
// names.
for _, v := range r.Libraries {
for n, d := range v.Decls {
if d.Type == fidlgen.TableDeclType || d.Type == fidlgen.UnionDeclType {
if _, ok := mtum[n]; ok {
ci := n.Parse()
name := c.compileUpperCamelCompoundIdentifier(ci, "", declarationContext)
pn := PayloadableName{name}
c.paramableTypes[n] = &pn
}
}
}
}
for _, v := range r.Protocols {
c.Root.Protocols = append(c.Root.Protocols, c.compileProtocol(v))
}
for _, l := range r.Libraries {
if l.Name == r.Name {
// We don't need to import our own package.
continue
}
library := l.Name.Parse()
c.Root.Imports = append(c.Root.Imports, Import{
LocalName: libraryPrefix(library),
AsyncURL: fmt.Sprintf("package:fidl_%s/fidl_async.dart", formatLibraryName(library)),
})
}
return c.Root
}