blob: 2f8cd4a2011ef0d9adf25b5cd1bc1d46d7cefe03 [file] [log] [blame] [edit]
// Copyright 2021 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 fidlgen_cpp
import (
"math"
"strconv"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
type Enum struct {
Attributes
fidlgen.Strictness
nameVariants
CodingTableType name
Enum fidlgen.Enum
Type nameVariants
Members []EnumMember
}
func (*Enum) Kind() declKind {
return Kinds.Enum
}
var _ Kinded = (*Enum)(nil)
var _ namespaced = (*Enum)(nil)
func (e Enum) UnknownValueForTmpl() interface{} {
return e.Enum.UnknownValueForTmpl()
}
// integer represents either an int or an uint.
type integer struct {
// Positive values are stored in u.
u uint64
// Negative or zero values are stored in i.
i int64
}
func (i *integer) decrement() {
if i.u > 0 {
i.u--
} else {
i.i--
}
}
func (i *integer) asUint64() uint64 {
if i.u > 0 {
return i.u
} else {
return uint64(i.i)
}
}
func readEnumValue(m fidlgen.EnumMember, typ fidlgen.PrimitiveSubtype) integer {
v := m.Value.Value
if typ.IsSigned() {
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
fidlgen.TemplateFatalf("Failed to parse enum value: %s", v)
}
if i > 0 {
return integer{
u: uint64(i),
i: 0,
}
}
return integer{
u: 0,
i: i,
}
}
u, err := strconv.ParseUint(v, 10, 64)
if err != nil {
fidlgen.TemplateFatalf("Failed to parse enum value: %s", v)
}
return integer{
u: u,
i: 0,
}
}
// UnusedEnumValueForTmpl attempts to find a numerical value that is unused in
// the enumeration. Its purpose is to support forcing users of flexible enums
// to write a `default:` case, by injecting an extra ugly enum member like
// "DO_NOT_USE_THIS = {{ UnusedEnumValueForTmpl }}" whose usage is discouraged.
//
// When an enum is fully populated (e.g. a uint8 enum with 256 members), it is
// not possible to find an unused slot. This will return the designated unknown
// value. Note that by definition there cannot be unknown values in fully
// populated enums, so it's fine if the user ends up writing a switch without a
// `default:` case in that case.
func (e Enum) UnusedEnumValueForTmpl() interface{} {
nb := e.Enum.Type.NumberOfBits()
if math.Log2(float64(len(e.Members))) >= float64(nb) {
return e.UnknownValueForTmpl()
}
// Make a set of used enum values
memberValues := make(map[integer]struct{})
for _, m := range e.Enum.Members {
i := readEnumValue(m, e.Enum.Type)
memberValues[i] = struct{}{}
}
// Find the first value that's unused.
var i integer
signed := e.Enum.Type.IsSigned()
var minusOne uint64
minusOne--
minusOne = minusOne >> (64 - nb)
if signed {
i = integer{
u: minusOne / 2,
i: 0,
}
} else {
i = integer{
u: minusOne,
i: 0,
}
}
for {
_, ok := memberValues[i]
if !ok {
return i.asUint64()
}
i.decrement()
}
}
func (e Enum) IsCompatibleWithError() bool {
return e.Enum.Type == fidlgen.Int32 || e.Enum.Type == fidlgen.Uint32
}
type EnumMember struct {
Attributes
nameVariants
Value ConstantValue
EnumMember fidlgen.EnumMember
}
func (m EnumMember) IsUnknown() bool {
return m.EnumMember.IsUnknown()
}
func (c *compiler) compileEnum(val fidlgen.Enum) *Enum {
name := c.compileNameVariants(val.Name)
r := Enum{
Attributes: Attributes{val.Attributes},
Strictness: val.Strictness,
nameVariants: name,
CodingTableType: name.Unified.ns.member(c.compileCodingTableType(val.Name)),
Enum: val,
Type: NameVariantsForPrimitive(val.Type),
}
for _, v := range val.Members {
r.Members = append(r.Members, EnumMember{
Attributes: Attributes{v.Attributes},
nameVariants: enumMemberContext.transform(v.Name),
// TODO(fxbug.dev/7660): When we expose types consistently in the IR, we
// will not need to plug this here.
Value: c.compileConstant(v.Value, nil, fidlgen.Type{
Kind: fidlgen.PrimitiveType,
PrimitiveSubtype: val.Type,
}),
EnumMember: v,
})
}
return &r
}