| // Copyright 2025 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" |
| "iter" |
| "maps" |
| "reflect" |
| "regexp" |
| "slices" |
| "strconv" |
| "strings" |
| ) |
| |
| // A Domain is a non-empty set of values, all of the same kind. |
| // |
| // Domain may be a scalar: |
| // |
| // - [String] - Represents string-typed values. |
| // |
| // Or a composite: |
| // |
| // - [Def] - A mapping from fixed keys to [Domain]s. |
| // |
| // - [Tuple] - A fixed-length sequence of [Domain]s or |
| // all possible lengths repeating a [Domain]. |
| // |
| // Or top or bottom: |
| // |
| // - [Top] - Represents all possible values of all kinds. |
| // |
| // - nil - Represents no values. |
| // |
| // Or a variable: |
| // |
| // - [Var] - A value captured in the environment. |
| type Domain interface { |
| Exact() bool |
| WhyNotExact() string |
| |
| // decode stores this value in a Go value. If this value is not exact, this |
| // returns a potentially wrapped *inexactError. |
| decode(reflect.Value) error |
| } |
| |
| 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) |
| } |
| |
| // Top represents all possible values of all possible types. |
| type Top struct{} |
| |
| func (t Top) Exact() bool { return false } |
| func (t Top) WhyNotExact() string { return "is top" } |
| |
| 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 |
| } |
| |
| // A Def is a mapping from field names to [Value]s. Any fields not explicitly |
| // listed have [Value] [Top]. |
| type Def struct { |
| fields map[string]*Value |
| } |
| |
| // A DefBuilder builds a [Def] one field at a time. The zero value is an empty |
| // [Def]. |
| type DefBuilder struct { |
| fields map[string]*Value |
| } |
| |
| func (b *DefBuilder) Add(name string, v *Value) { |
| if b.fields == nil { |
| b.fields = make(map[string]*Value) |
| } |
| if old, ok := b.fields[name]; ok { |
| panic(fmt.Sprintf("duplicate field %q, added value is %v, old value is %v", name, v, old)) |
| } |
| b.fields[name] = v |
| } |
| |
| // Build constructs a [Def] from the fields added to this builder. |
| func (b *DefBuilder) Build() Def { |
| return Def{maps.Clone(b.fields)} |
| } |
| |
| // Exact returns true if all field Values are exact. |
| func (d Def) Exact() bool { |
| for _, v := range d.fields { |
| if !v.Exact() { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // WhyNotExact returns why the value is not exact |
| func (d Def) WhyNotExact() string { |
| for s, v := range d.fields { |
| if !v.Exact() { |
| w := v.WhyNotExact() |
| return "field " + s + ": " + w |
| } |
| } |
| return "" |
| } |
| |
| func (d Def) decode(rv reflect.Value) error { |
| if rv.Kind() != reflect.Struct { |
| return fmt.Errorf("cannot decode Def into %s", rv.Type()) |
| } |
| |
| var lowered map[string]string // Lower case -> canonical for d.fields. |
| rt := rv.Type() |
| for fi := range rv.NumField() { |
| fType := rt.Field(fi) |
| if fType.PkgPath != "" { |
| continue |
| } |
| v := d.fields[fType.Name] |
| if v == nil { |
| v = topValue |
| |
| // Try a case-insensitive match |
| canon, ok := d.fields[strings.ToLower(fType.Name)] |
| if ok { |
| v = canon |
| } else { |
| if lowered == nil { |
| lowered = make(map[string]string, len(d.fields)) |
| for k := range d.fields { |
| l := strings.ToLower(k) |
| if k != l { |
| lowered[l] = k |
| } |
| } |
| } |
| canon, ok := lowered[strings.ToLower(fType.Name)] |
| if ok { |
| v = d.fields[canon] |
| } |
| } |
| } |
| if err := decodeReflect(v, rv.Field(fi)); err != nil { |
| return newDecodeError(fType.Name, err) |
| } |
| } |
| return nil |
| } |
| |
| func (d Def) keys() []string { |
| return slices.Sorted(maps.Keys(d.fields)) |
| } |
| |
| func (d Def) All() iter.Seq2[string, *Value] { |
| // TODO: We call All fairly often. It's probably bad to sort this every |
| // time. |
| keys := slices.Sorted(maps.Keys(d.fields)) |
| return func(yield func(string, *Value) bool) { |
| for _, k := range keys { |
| if !yield(k, d.fields[k]) { |
| return |
| } |
| } |
| } |
| } |
| |
| // A Tuple is a sequence of Values in one of two forms: 1. a fixed-length tuple, |
| // where each Value can be different or 2. a "repeated tuple", which is a Value |
| // repeated 0 or more times. |
| type Tuple struct { |
| vs []*Value |
| |
| // repeat, if non-nil, means this Tuple consists of an element repeated 0 or |
| // more times. If repeat is non-nil, vs must be nil. This is a generator |
| // function because we don't necessarily want *exactly* the same Value |
| // repeated. For example, in YAML encoding, a !sum in a repeated tuple needs |
| // a fresh variable in each instance. |
| repeat []func(envSet) (*Value, envSet) |
| } |
| |
| func NewTuple(vs ...*Value) Tuple { |
| return Tuple{vs: vs} |
| } |
| |
| func NewRepeat(gens ...func(envSet) (*Value, envSet)) Tuple { |
| return Tuple{repeat: gens} |
| } |
| |
| func (d Tuple) Exact() bool { |
| if d.repeat != nil { |
| return false |
| } |
| for _, v := range d.vs { |
| if !v.Exact() { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func (d Tuple) WhyNotExact() string { |
| if d.repeat != nil { |
| return "d.repeat is not nil" |
| } |
| for i, v := range d.vs { |
| if !v.Exact() { |
| w := v.WhyNotExact() |
| return "index " + strconv.FormatInt(int64(i), 10) + ": " + w |
| } |
| } |
| return "" |
| } |
| |
| 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 |
| } |
| |
| // A String represents a set of strings. It can represent the intersection of a |
| // set of regexps, or a single exact string. In general, the domain of a String |
| // is non-empty, but we do not attempt to prove emptiness of a regexp value. |
| type String struct { |
| kind stringKind |
| re []*regexp.Regexp // Intersection of regexps |
| exact string |
| } |
| |
| type stringKind int |
| |
| const ( |
| stringRegex stringKind = iota |
| stringExact |
| ) |
| |
| func NewStringRegex(exprs ...string) (String, error) { |
| if len(exprs) == 0 { |
| exprs = []string{""} |
| } |
| v := String{kind: -1} |
| for _, expr := range exprs { |
| if expr == "" { |
| // Skip constructing the regexp. It won't have a "literal prefix" |
| // and so we wind up thinking this is a regexp instead of an exact |
| // (empty) string. |
| v = String{kind: stringExact, exact: ""} |
| continue |
| } |
| |
| re, err := regexp.Compile(`\A(?:` + expr + `)\z`) |
| if err != nil { |
| return String{}, fmt.Errorf("parsing value: %s", err) |
| } |
| |
| // An exact value narrows the whole domain to exact, so we're done, but |
| // should keep parsing. |
| if v.kind == stringExact { |
| continue |
| } |
| |
| if exact, complete := re.LiteralPrefix(); complete { |
| v = String{kind: stringExact, exact: exact} |
| } else { |
| v.kind = stringRegex |
| v.re = append(v.re, re) |
| } |
| } |
| return v, nil |
| } |
| |
| func NewStringExact(s string) String { |
| return String{kind: stringExact, exact: s} |
| } |
| |
| // Exact returns whether this Value is known to consist of a single string. |
| func (d String) Exact() bool { |
| return d.kind == stringExact |
| } |
| |
| func (d String) WhyNotExact() string { |
| if d.kind == stringExact { |
| return "" |
| } |
| return "string is not exact" |
| } |
| |
| 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 |
| } |