| // Copyright 2026 The Go 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 unify |
| |
| import ( |
| "fmt" |
| "reflect" |
| "regexp" |
| "strconv" |
| "strings" |
| "sync" |
| "unicode" |
| ) |
| |
| // Decode decodes v into a Go value. |
| // |
| // v must be exact, except that it can include Top. into must be a pointer. |
| // [Def]s are decoded into structs. [Tuple]s are decoded into slices. [String]s |
| // are decoded into strings or ints. Any field can itself be a pointer to one of |
| // these types. Top can be decoded into a pointer-typed field and will set the |
| // field to nil. Anything else will allocate a value if necessary. |
| // |
| // Any type may implement [DecoderEncoder], in which case its DecodeUnified |
| // method will be called instead of using the default decoding scheme. |
| func (v *Value) Decode(into any) error { |
| rv := reflect.ValueOf(into) |
| if rv.Kind() != reflect.Pointer { |
| return fmt.Errorf("cannot decode into non-pointer %T", into) |
| } |
| return decodeReflect(v, rv.Elem()) |
| } |
| |
| // Encode constructs a Value from a Go value. It is the inverse of |
| // [Value.Decode]. |
| // |
| // If a struct has an "Encode<Field>" field, it will be used for encoding the |
| // field named by <Field>, overriding the default field. This behavior is useful |
| // when a single type is used as an input and an output from unification, but |
| // the input and output have different requirements (e.g., optionality or |
| // strings vs regexps). |
| func Encode(gv any) *Value { |
| rv := reflect.ValueOf(gv) |
| return encodeReflect(rv) |
| } |
| |
| // DecoderEncoder can be implemented by types as a custom implementation of |
| // [Decode] and [Encode] for that type. These must be inverses. |
| type DecoderEncoder interface { |
| DecodeUnified(v *Value) error |
| EncodeUnified() *Value |
| } |
| |
| var decoderEncoder = reflect.TypeFor[DecoderEncoder]() |
| |
| func decodeReflect(v *Value, rv reflect.Value) error { |
| var ptr reflect.Value |
| if rv.Kind() == reflect.Pointer { |
| if rv.IsNil() { |
| // Transparently allocate through pointers, *except* for Top, which |
| // wants to set the pointer to nil. |
| // |
| // TODO: Drop this condition if I switch to an explicit Optional[T] |
| // or move the Top logic into Def. |
| if _, ok := v.Domain.(Top); !ok { |
| // Allocate the value to fill in, but don't actually store it in |
| // the pointer until we successfully decode. |
| ptr = rv |
| rv = reflect.New(rv.Type().Elem()).Elem() |
| } |
| } else { |
| rv = rv.Elem() |
| } |
| } |
| |
| var err error |
| if reflect.PointerTo(rv.Type()).Implements(decoderEncoder) { |
| // Use the custom decoder. |
| err = rv.Addr().Interface().(DecoderEncoder).DecodeUnified(v) |
| } else { |
| err = v.Domain.decode(rv) |
| } |
| if err == nil && ptr.IsValid() { |
| ptr.Set(rv.Addr()) |
| } |
| return err |
| } |
| |
| type inexactError struct { |
| valueType string |
| goType string |
| } |
| |
| func (e *inexactError) Error() string { |
| return fmt.Sprintf("cannot store inexact %s value in %s", e.valueType, e.goType) |
| } |
| |
| type decodeError struct { |
| path string |
| err error |
| } |
| |
| func newDecodeError(path string, err error) *decodeError { |
| if err, ok := err.(*decodeError); ok { |
| return &decodeError{path: path + "." + err.path, err: err.err} |
| } |
| return &decodeError{path: path, err: err} |
| } |
| |
| func (e *decodeError) Unwrap() error { |
| return e.err |
| } |
| |
| func (e *decodeError) Error() string { |
| return fmt.Sprintf("%s: %s", e.path, e.err) |
| } |
| |
| func (d Var) decode(rv reflect.Value) error { |
| return &inexactError{"var", rv.Type().String()} |
| } |
| |
| func encodeReflect(rv reflect.Value) *Value { |
| if rv.Kind() == reflect.Pointer { |
| // Transparently read through non-nil pointers |
| if rv.IsNil() { |
| return topValue |
| } |
| if re, ok := rv.Interface().(*regexp.Regexp); ok { |
| if exact, complete := re.LiteralPrefix(); complete { |
| return NewValue(NewStringExact(exact)) |
| } |
| return NewValue(String{kind: stringRegex, re: []*regexp.Regexp{re}}) |
| } |
| rv = rv.Elem() |
| } |
| |
| if reflect.PointerTo(rv.Type()).Implements(decoderEncoder) { |
| // Use the custom encoder. |
| return rv.Addr().Interface().(DecoderEncoder).EncodeUnified() |
| } |
| |
| switch rv.Kind() { |
| default: |
| panic(fmt.Sprintf("cannot encode type %s to a unify.Value", rv.Type())) |
| |
| case reflect.Struct: |
| var db DefBuilder |
| fieldMap := canonStructFields(rv.Type()) |
| for defName, f := range fieldMap { |
| if f.encode.Index == nil { |
| continue |
| } |
| fVal := rv.FieldByIndex(f.encode.Index) |
| if fVal.Kind() == reflect.Pointer && fVal.IsNil() { |
| // Omit nil pointers from def (equivalent to setting them to |
| // "top") |
| continue |
| } |
| uv := encodeReflect(fVal) |
| db.Add(defName, uv) |
| } |
| return NewValue(db.Build()) |
| |
| case reflect.Slice: |
| uvs := make([]*Value, rv.Len()) |
| for i := range rv.Len() { |
| uvs[i] = encodeReflect(rv.Index(i)) |
| } |
| return NewValue(NewTuple(uvs...)) |
| |
| case reflect.String, reflect.Int, reflect.Bool: |
| return NewValue(NewStringExact(fmt.Sprint(rv))) |
| } |
| } |
| |
| func (t Top) decode(rv reflect.Value) error { |
| // We can decode Top into a pointer-typed value as nil. |
| if rv.Kind() != reflect.Pointer { |
| return &inexactError{"top", rv.Type().String()} |
| } |
| rv.SetZero() |
| return nil |
| } |
| |
| func (d Def) decode(rv reflect.Value) error { |
| if rv.Kind() != reflect.Struct { |
| return fmt.Errorf("cannot decode Def into %s", rv.Type()) |
| } |
| |
| fieldMap := canonStructFields(rv.Type()) |
| for defName, f := range fieldMap { |
| if f.decode.Index == nil { |
| continue |
| } |
| v := d.fields[defName] |
| if v == nil { |
| v = topValue |
| } |
| if err := decodeReflect(v, rv.FieldByIndex(f.decode.Index)); err != nil { |
| return newDecodeError(f.decode.Name, err) |
| } |
| } |
| return nil |
| } |
| |
| type structFieldPair struct { |
| decode reflect.StructField |
| encode reflect.StructField |
| } |
| |
| var structFieldsCache sync.Map /*[reflect.Type, map[string]structFieldPair]*/ |
| |
| // canonStructFields canonicalizes the name of all exported fields in rt from |
| // Go-style exported names to YAML-style lower-case names. If the Go name starts |
| // with N upper-case letters, then if N==1, it lower-cases just the first |
| // letter; if N=len, it lower-cases the whole name; otherwise it lower-cases the |
| // first N-1 letters. |
| // |
| // It produces two maps: one for encoding and one for decoding. By default, |
| // these are the same, but if a field has form "EncodeX", then it's used for |
| // encoding x. |
| // |
| // It returns a map from Def field name to a pair of decode/encode struct |
| // fields. The mapping between Go field names and Def names is a bijection, so |
| // it can be used for encoding and decoding. |
| // |
| // For example: |
| // |
| // YAML field Go field |
| // asmPos <=> AsmPos |
| // cpuFeatures <=> CPUFeatures |
| // goarch <=> GOARCH |
| // bits <=> Bits (decode) & EncodeBits (encode) |
| // |
| // rt must be a struct type. |
| func canonStructFields(rt reflect.Type) map[string]structFieldPair { |
| type fieldMap = map[string]structFieldPair |
| if fields, ok := structFieldsCache.Load(rt); ok { |
| return fields.(fieldMap) |
| } |
| |
| fm := make(fieldMap) |
| for f := range rt.Fields() { |
| if !f.IsExported() { |
| continue |
| } |
| if isEncodeOverride(f.Name) { |
| defName := lowerGoName(f.Name[6:]) |
| pair := fm[defName] |
| pair.encode = f |
| fm[defName] = pair |
| } else { |
| defName := lowerGoName(f.Name) |
| pair := fm[defName] |
| if pair.decode.Index != nil { |
| panic(fmt.Sprintf("multiple fields in type %s map to %q", rt, defName)) |
| } |
| pair.decode = f |
| if pair.encode.Index == nil { |
| pair.encode = f |
| } |
| fm[defName] = pair |
| } |
| } |
| |
| res, _ := structFieldsCache.LoadOrStore(rt, fm) |
| return res.(fieldMap) |
| } |
| |
| func isEncodeOverride(name string) bool { |
| return strings.HasPrefix(name, "Encode") && len(name) > 6 && unicode.IsUpper(rune(name[6])) |
| } |
| |
| func lowerGoName(goName string) string { |
| prefixBytes := -1 |
| prevBytes := 0 |
| allUpper := true |
| for pos, ch := range goName { |
| if !unicode.IsUpper(ch) { |
| allUpper = false |
| prefixBytes = pos |
| break |
| } |
| prevBytes = pos |
| } |
| if allUpper { |
| // The whole name is upper-case. |
| return strings.ToLower(goName) |
| } |
| if prevBytes == 0 { |
| // The name starts with a single upper-case letter. Lower-case just it. |
| prevBytes = prefixBytes |
| } |
| // Lower case the first n-1 upper-case letters. |
| return strings.ToLower(goName[:prevBytes]) + goName[prevBytes:] |
| } |
| |
| func (d Tuple) decode(rv reflect.Value) error { |
| if d.repeat != nil { |
| return &inexactError{"repeated tuple", rv.Type().String()} |
| } |
| // TODO: We could also do arrays. |
| if rv.Kind() != reflect.Slice { |
| return fmt.Errorf("cannot decode Tuple into %s", rv.Type()) |
| } |
| if rv.IsNil() || rv.Cap() < len(d.vs) { |
| rv.Set(reflect.MakeSlice(rv.Type(), len(d.vs), len(d.vs))) |
| } else { |
| rv.SetLen(len(d.vs)) |
| } |
| for i, v := range d.vs { |
| if err := decodeReflect(v, rv.Index(i)); err != nil { |
| return newDecodeError(fmt.Sprintf("%d", i), err) |
| } |
| } |
| return nil |
| } |
| |
| func (d String) decode(rv reflect.Value) error { |
| if d.kind != stringExact { |
| return &inexactError{"regex", rv.Type().String()} |
| } |
| switch rv.Kind() { |
| default: |
| return fmt.Errorf("cannot decode String into %s", rv.Type()) |
| case reflect.String: |
| rv.SetString(d.exact) |
| case reflect.Int: |
| i, err := strconv.Atoi(d.exact) |
| if err != nil { |
| return fmt.Errorf("cannot decode String into %s: %s", rv.Type(), err) |
| } |
| rv.SetInt(int64(i)) |
| case reflect.Bool: |
| b, err := strconv.ParseBool(d.exact) |
| if err != nil { |
| return fmt.Errorf("cannot decode String into %s: %s", rv.Type(), err) |
| } |
| rv.SetBool(b) |
| } |
| return nil |
| } |