| // Copyright 2015 The Vanadium 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 golang |
| |
| import ( |
| "fmt" |
| "strconv" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/vdltool" |
| "v.io/x/ref/lib/vdl/compile" |
| ) |
| |
| func defineConst(data *goData, def *compile.ConstDef) string { |
| v := def.Value |
| return fmt.Sprintf("%s%s %s = %s%s", def.Doc, constOrVar(v.Kind()), def.Name, typedConst(data, v), def.DocSuffix) |
| } |
| |
| func constOrVar(k vdl.Kind) string { |
| switch k { |
| case vdl.Bool, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64, vdl.String, vdl.Enum: |
| return "const" |
| } |
| return "var" |
| } |
| |
| func isByteList(t *vdl.Type) bool { |
| return t.Kind() == vdl.List && t.Elem().Kind() == vdl.Byte |
| } |
| |
| func genValueOf(data *goData, v *vdl.Value) string { |
| // There's no need to convert the value to its native representation, since |
| // it'll just be converted back in vdl.ValueOf. |
| return data.Pkg("v.io/v23/vdl") + "ValueOf(" + typedConstWire(data, v) + ")" |
| } |
| |
| func typedConst(data *goData, v *vdl.Value) string { |
| if native := typedConstNative(data, v); native != "" { |
| return native |
| } |
| return typedConstWire(data, v) |
| } |
| |
| // TODO(bprosnitz): Generate the full tag name e.g. security.Read instead of |
| // security.Label(1) |
| // |
| // TODO(toddw): This is broken for optional types that can't be represented |
| // using a composite literal (e.g. optional primitives). |
| // |
| // https://github.com/vanadium/issues/issues/213 |
| func typedConstWire(data *goData, v *vdl.Value) string { |
| k, t := v.Kind(), v.Type() |
| typestr := typeGoWire(data, t) |
| if k == vdl.Optional { |
| if elem := v.Elem(); elem != nil { |
| return "&" + typedConst(data, elem) |
| } |
| return "(" + typestr + ")(nil)" // results in (*Foo)(nil) |
| } |
| valstr := untypedConstWire(data, v) |
| // Enum, TypeObject, Union, Any and non-zero []byte already include the type |
| // in their "untyped" values. Built-in bool and string are implicitly |
| // convertible from their literals. |
| if k == vdl.Enum || k == vdl.TypeObject || k == vdl.Any || (isByteList(t) && !v.IsZero()) || t == vdl.BoolType || t == vdl.StringType { |
| return valstr |
| } |
| // Everything else requires an explicit type. |
| // { } are used instead of ( ) for composites |
| switch k { |
| case vdl.Array, vdl.Struct: |
| return typestr + valstr |
| case vdl.List, vdl.Set, vdl.Map: |
| // Special-case empty variable-length collections, which we generate as a type |
| // conversion from nil |
| if !v.IsZero() { |
| return typestr + valstr |
| } |
| } |
| return typestr + "(" + valstr + ")" |
| } |
| |
| func untypedConst(data *goData, v *vdl.Value) string { |
| if native := untypedConstNative(data, v); native != "" { |
| return native |
| } |
| return untypedConstWire(data, v) |
| } |
| |
| func untypedConstWire(data *goData, v *vdl.Value) string { |
| k, t := v.Kind(), v.Type() |
| typestr := typeGoWire(data, t) |
| if isByteList(t) { |
| if v.IsZero() { |
| return "nil" |
| } |
| return typestr + "(" + strconv.Quote(string(v.Bytes())) + ")" |
| } |
| switch k { |
| case vdl.Any: |
| if elem := v.Elem(); elem != nil { |
| // For the interface{} case, we just return a Go expression representing |
| // the typed elem value. If the elem type is native, we need to return a |
| // value of the native type. |
| // |
| // Otherwise we need to generate a Go expression of type *vom.RawBytes or |
| // *vdl.Value. Since the rest of our logic can already generate the Go |
| // code for any value, we just wrap it in vom.RawBytesOf / vdl.ValueOf to |
| // produce the final result. We don't need to generate the native |
| // representation, since it'll just be converted back to a wire |
| // representation in RawBytesOf / ValueOf anyways. |
| // |
| // This may seem like a strange roundtrip, but results in less generator |
| // and generated code. |
| switch goAnyRepMode(data.Package) { |
| case goAnyRepRawBytes: |
| return data.Pkg("v.io/v23/vom") + "RawBytesOf(" + typedConstWire(data, elem) + ")" |
| case goAnyRepValue: |
| return data.Pkg("v.io/v23/vdl") + "ValueOf(" + typedConstWire(data, elem) + ")" |
| default: |
| // Special-case for error(nil) contained within an any, to avoid losing |
| // type information. Note that both of the Go expressions error(nil) |
| // and (*verror.E)(nil) are valid representations of the VDL nil error. |
| // But since error is a Go interface, we lose the type information from |
| // error(nil) when it's contained in another interface (i.e. any). So |
| // we pick a representation that doesn't lose type information. |
| if elem.Type() == vdl.ErrorType && elem.IsNil() { |
| return "(*" + data.Pkg("v.io/v23/verror") + "E)(nil)" |
| } |
| // We need the final result to be the native type. |
| return typedConst(data, elem) |
| } |
| } |
| switch goAnyRepMode(data.Package) { |
| case goAnyRepRawBytes: |
| // TODO(bprosnitz) Can this just be vom.RawBytesOf(nil) |
| return data.Pkg("v.io/v23/vom") + "RawBytesOf(" + data.Pkg("v.io/v23/vdl") + "ZeroValue(vdl.AnyType)" + ")" |
| case goAnyRepValue: |
| return data.Pkg("v.io/v23/vdl") + "ZeroValue(vdl.AnyType)" |
| default: |
| return "nil" |
| } |
| case vdl.Optional: |
| if elem := v.Elem(); elem != nil { |
| return untypedConst(data, elem) |
| } |
| return "nil" |
| case vdl.TypeObject: |
| return data.TypeOf(v.TypeObject()) |
| case vdl.Bool: |
| return strconv.FormatBool(v.Bool()) |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64: |
| return strconv.FormatUint(v.Uint(), 10) |
| case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| return strconv.FormatInt(v.Int(), 10) |
| case vdl.Float32, vdl.Float64: |
| return formatFloat(v.Float(), k) |
| case vdl.String: |
| return strconv.Quote(v.RawString()) |
| case vdl.Enum: |
| return typestr + v.EnumLabel() |
| case vdl.Array: |
| if v.IsZero() && isGoZeroValueCanonical(data, t.Elem()) { |
| // If the array is zero and the element type represents zero using the go |
| // zero value, we can special-case using the go zero value. |
| return "{}" |
| } |
| s := "{" |
| for ix := 0; ix < v.Len(); ix++ { |
| s += "\n" + untypedConst(data, v.Index(ix)) + "," |
| } |
| return s + "\n}" |
| case vdl.List: |
| if v.IsZero() { |
| return "nil" |
| } |
| s := "{" |
| for ix := 0; ix < v.Len(); ix++ { |
| s += "\n" + untypedConst(data, v.Index(ix)) + "," |
| } |
| return s + "\n}" |
| case vdl.Set, vdl.Map: |
| if v.IsZero() { |
| return "nil" |
| } |
| s := "{" |
| for _, key := range vdl.SortValuesAsString(v.Keys()) { |
| s += "\n" + untypedConst(data, key) |
| if k == vdl.Set { |
| s += ": struct{}{}," |
| } else { |
| s += ": " + untypedConst(data, v.MapIndex(key)) + "," |
| } |
| } |
| return s + "\n}" |
| case vdl.Struct: |
| s := "{" |
| hasFields := false |
| for ix := 0; ix < t.NumField(); ix++ { |
| vf := v.StructField(ix) |
| if !vf.IsZero() || !isGoZeroValueCanonical(data, vf.Type()) { |
| // Only set the field if the field isn't zero or the field type doesn't |
| // represent zero using the go zero value. Otherwise simply skip the |
| // field, letting the default go zero value occur. |
| s += "\n" + t.Field(ix).Name + ": " + fieldConst(data, vf) + "," |
| hasFields = true |
| } |
| } |
| if hasFields { |
| s += "\n" |
| } |
| return s + "}" |
| case vdl.Union: |
| ix, vf := v.UnionField() |
| var inner string |
| if !vf.IsZero() || !isGoZeroValueCanonical(data, vf.Type()) { |
| // Only set the field if the field isn't zero or the field type doesn't |
| // represent zero using the go zero value. Otherwise simply skip the |
| // field, letting the default go zero value occur. |
| inner = fieldConst(data, vf) |
| } |
| return typestr + t.Field(ix).Name + "{" + inner + "}" |
| default: |
| data.Env.Errors.Errorf("%s: %v untypedConstWire not implemented for %v %v", data.Package.Name, t, k) |
| return "INVALID" |
| } |
| } |
| |
| // fieldConst deals with a quirk regarding Go composite literals. Go allows us |
| // to elide the type from composite literal Y when the type is implied; |
| // basically when Y is contained in another composite literal X. However it |
| // requires the type for Y when X is a struct and we're filling in its fields. |
| // |
| // Thus fieldConst is called when filling in struct and union fields, and |
| // ensures typedConst is only called when the field itself will result in |
| // another Go composite literal. |
| func fieldConst(data *goData, v *vdl.Value) string { |
| if native, _, ok := findNativeType(data.Env, v.Type()); ok { |
| switch native.Kind { |
| case vdltool.GoKindArray, vdltool.GoKindSlice, vdltool.GoKindMap, vdltool.GoKindStruct: |
| return typedConst(data, v) |
| } |
| } |
| switch v.Kind() { |
| case vdl.Array, vdl.List, vdl.Set, vdl.Map, vdl.Struct, vdl.Optional: |
| return typedConst(data, v) |
| } |
| // Union is not in the list above. It *is* generated as a Go composite |
| // literal, but untypedConst already has the type of the concrete union struct |
| // attached to it, and we don't need to convert this to the union interface |
| // type, since the concrete union struct is assignable to the union interface. |
| return untypedConst(data, v) |
| } |
| |
| func formatFloat(x float64, kind vdl.Kind) string { |
| var bitSize int |
| switch kind { |
| case vdl.Float32: |
| bitSize = 32 |
| case vdl.Float64: |
| bitSize = 64 |
| default: |
| panic(fmt.Errorf("vdl: formatFloat unhandled kind: %v", kind)) |
| } |
| return strconv.FormatFloat(x, 'g', -1, bitSize) |
| } |
| |
| // typedConstNative returns a typed native constant, or returns the empty string |
| // if v isn't a native type. |
| func typedConstNative(data *goData, v *vdl.Value) string { |
| return constNative(data, v, true) |
| } |
| |
| // untypedConstNative returns an untyped native constant, or returns the empty |
| // string if v isn't a native type. |
| func untypedConstNative(data *goData, v *vdl.Value) string { |
| return constNative(data, v, false) |
| } |
| |
| func constNative(data *goData, v *vdl.Value, typed bool) string { |
| if v.Type() == vdl.ErrorType { |
| return constNativeError(data, v) |
| } |
| if native, wirePkg, ok := findNativeType(data.Env, v.Type()); ok { |
| nType := nativeType(data, native, wirePkg) |
| if native.Zero.Mode != vdltool.GoZeroModeUnknown && v.IsZero() { |
| // This is the case where the value is zero, and the zero mode is either |
| // Canonical or Unique, which means that the Go zero value of the native |
| // type is sufficient to represent the value. |
| if typed { |
| return typedConstNativeZero(native.Kind, nType) |
| } else { |
| return untypedConstNativeZero(native.Kind, nType) |
| } |
| } |
| return constNativeConversion(data, v, nType, toNative(data, native, v.Type())) |
| } |
| return "" |
| } |
| |
| func constNativeError(data *goData, v *vdl.Value) string { |
| if elem := v.Elem(); elem != nil { |
| wireError := typedConstWire(data, elem) |
| return data.Pkg("v.io/v23/verror") + "FromWire(&" + wireError + ")" |
| } |
| return "nil" |
| } |
| |
| func constNativeConversion(data *goData, v *vdl.Value, nType, toNative string) string { |
| // TODO(toddw): Change const generation to use the same style as |
| // genIsZero, which creates separate setup and expr code, so that we can |
| // handle errors without panicing. |
| return fmt.Sprintf(`func() %[1]s { |
| var native %[1]s |
| wire := %[2]s |
| if err := %[3]s(wire, &native); err != nil { |
| panic(err) |
| } |
| return native |
| }()`, nType, typedConstWire(data, v), toNative) |
| } |
| |
| func typedConstNativeZero(kind vdltool.GoKind, nType string) string { |
| zero := untypedConstNativeZero(kind, nType) |
| switch kind { |
| case vdltool.GoKindStruct, vdltool.GoKindArray: |
| return zero // untyped const is already typed, e.g. NativeType{} |
| default: |
| return nType + "(" + zero + ")" // e.g. NativeType(0) |
| } |
| } |
| |
| func untypedConstNativeZero(kind vdltool.GoKind, nType string) string { |
| switch kind { |
| case vdltool.GoKindStruct, vdltool.GoKindArray: |
| return nType + "{}" // No way to create an untyped zero struct or array. |
| case vdltool.GoKindBool: |
| return "false" |
| case vdltool.GoKindNumber: |
| return "0" |
| case vdltool.GoKindString: |
| return `""` |
| default: |
| return "nil" |
| } |
| } |