xref: /aosp_15_r20/external/starlark-go/starlarkstruct/struct.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
1*4947cdc7SCole Faust// Copyright 2017 The Bazel Authors. All rights reserved.
2*4947cdc7SCole Faust// Use of this source code is governed by a BSD-style
3*4947cdc7SCole Faust// license that can be found in the LICENSE file.
4*4947cdc7SCole Faust
5*4947cdc7SCole Faust// Package starlarkstruct defines the Starlark types 'struct' and
6*4947cdc7SCole Faust// 'module', both optional language extensions.
7*4947cdc7SCole Faust//
8*4947cdc7SCole Faustpackage starlarkstruct // import "go.starlark.net/starlarkstruct"
9*4947cdc7SCole Faust
10*4947cdc7SCole Faust// It is tempting to introduce a variant of Struct that is a wrapper
11*4947cdc7SCole Faust// around a Go struct value, for stronger typing guarantees and more
12*4947cdc7SCole Faust// efficient and convenient field lookup. However:
13*4947cdc7SCole Faust// 1) all fields of Starlark structs are optional, so we cannot represent
14*4947cdc7SCole Faust//    them using more specific types such as String, Int, *Depset, and
15*4947cdc7SCole Faust//    *File, as such types give no way to represent missing fields.
16*4947cdc7SCole Faust// 2) the efficiency gain of direct struct field access is rather
17*4947cdc7SCole Faust//    marginal: finding the index of a field by binary searching on the
18*4947cdc7SCole Faust//    sorted list of field names is quite fast compared to the other
19*4947cdc7SCole Faust//    overheads.
20*4947cdc7SCole Faust// 3) the gains in compactness and spatial locality are also rather
21*4947cdc7SCole Faust//    marginal: the array behind the []entry slice is (due to field name
22*4947cdc7SCole Faust//    strings) only a factor of 2 larger than the corresponding Go struct
23*4947cdc7SCole Faust//    would be, and, like the Go struct, requires only a single allocation.
24*4947cdc7SCole Faust
25*4947cdc7SCole Faustimport (
26*4947cdc7SCole Faust	"fmt"
27*4947cdc7SCole Faust	"sort"
28*4947cdc7SCole Faust	"strings"
29*4947cdc7SCole Faust
30*4947cdc7SCole Faust	"go.starlark.net/starlark"
31*4947cdc7SCole Faust	"go.starlark.net/syntax"
32*4947cdc7SCole Faust)
33*4947cdc7SCole Faust
34*4947cdc7SCole Faust// Make is the implementation of a built-in function that instantiates
35*4947cdc7SCole Faust// an immutable struct from the specified keyword arguments.
36*4947cdc7SCole Faust//
37*4947cdc7SCole Faust// An application can add 'struct' to the Starlark environment like so:
38*4947cdc7SCole Faust//
39*4947cdc7SCole Faust// 	globals := starlark.StringDict{
40*4947cdc7SCole Faust// 		"struct":  starlark.NewBuiltin("struct", starlarkstruct.Make),
41*4947cdc7SCole Faust// 	}
42*4947cdc7SCole Faust//
43*4947cdc7SCole Faustfunc Make(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
44*4947cdc7SCole Faust	if len(args) > 0 {
45*4947cdc7SCole Faust		return nil, fmt.Errorf("struct: unexpected positional arguments")
46*4947cdc7SCole Faust	}
47*4947cdc7SCole Faust	return FromKeywords(Default, kwargs), nil
48*4947cdc7SCole Faust}
49*4947cdc7SCole Faust
50*4947cdc7SCole Faust// FromKeywords returns a new struct instance whose fields are specified by the
51*4947cdc7SCole Faust// key/value pairs in kwargs.  (Each kwargs[i][0] must be a starlark.String.)
52*4947cdc7SCole Faustfunc FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
53*4947cdc7SCole Faust	if constructor == nil {
54*4947cdc7SCole Faust		panic("nil constructor")
55*4947cdc7SCole Faust	}
56*4947cdc7SCole Faust	s := &Struct{
57*4947cdc7SCole Faust		constructor: constructor,
58*4947cdc7SCole Faust		entries:     make(entries, 0, len(kwargs)),
59*4947cdc7SCole Faust	}
60*4947cdc7SCole Faust	for _, kwarg := range kwargs {
61*4947cdc7SCole Faust		k := string(kwarg[0].(starlark.String))
62*4947cdc7SCole Faust		v := kwarg[1]
63*4947cdc7SCole Faust		s.entries = append(s.entries, entry{k, v})
64*4947cdc7SCole Faust	}
65*4947cdc7SCole Faust	sort.Sort(s.entries)
66*4947cdc7SCole Faust	return s
67*4947cdc7SCole Faust}
68*4947cdc7SCole Faust
69*4947cdc7SCole Faust// FromStringDict returns a whose elements are those of d.
70*4947cdc7SCole Faust// The constructor parameter specifies the constructor; use Default for an ordinary struct.
71*4947cdc7SCole Faustfunc FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct {
72*4947cdc7SCole Faust	if constructor == nil {
73*4947cdc7SCole Faust		panic("nil constructor")
74*4947cdc7SCole Faust	}
75*4947cdc7SCole Faust	s := &Struct{
76*4947cdc7SCole Faust		constructor: constructor,
77*4947cdc7SCole Faust		entries:     make(entries, 0, len(d)),
78*4947cdc7SCole Faust	}
79*4947cdc7SCole Faust	for k, v := range d {
80*4947cdc7SCole Faust		s.entries = append(s.entries, entry{k, v})
81*4947cdc7SCole Faust	}
82*4947cdc7SCole Faust	sort.Sort(s.entries)
83*4947cdc7SCole Faust	return s
84*4947cdc7SCole Faust}
85*4947cdc7SCole Faust
86*4947cdc7SCole Faust// Struct is an immutable Starlark type that maps field names to values.
87*4947cdc7SCole Faust// It is not iterable and does not support len.
88*4947cdc7SCole Faust//
89*4947cdc7SCole Faust// A struct has a constructor, a distinct value that identifies a class
90*4947cdc7SCole Faust// of structs, and which appears in the struct's string representation.
91*4947cdc7SCole Faust//
92*4947cdc7SCole Faust// Operations such as x+y fail if the constructors of the two operands
93*4947cdc7SCole Faust// are not equal.
94*4947cdc7SCole Faust//
95*4947cdc7SCole Faust// The default constructor, Default, is the string "struct", but
96*4947cdc7SCole Faust// clients may wish to 'brand' structs for their own purposes.
97*4947cdc7SCole Faust// The constructor value appears in the printed form of the value,
98*4947cdc7SCole Faust// and is accessible using the Constructor method.
99*4947cdc7SCole Faust//
100*4947cdc7SCole Faust// Use Attr to access its fields and AttrNames to enumerate them.
101*4947cdc7SCole Fausttype Struct struct {
102*4947cdc7SCole Faust	constructor starlark.Value
103*4947cdc7SCole Faust	entries     entries // sorted by name
104*4947cdc7SCole Faust}
105*4947cdc7SCole Faust
106*4947cdc7SCole Faust// Default is the default constructor for structs.
107*4947cdc7SCole Faust// It is merely the string "struct".
108*4947cdc7SCole Faustconst Default = starlark.String("struct")
109*4947cdc7SCole Faust
110*4947cdc7SCole Fausttype entries []entry
111*4947cdc7SCole Faust
112*4947cdc7SCole Faustfunc (a entries) Len() int           { return len(a) }
113*4947cdc7SCole Faustfunc (a entries) Less(i, j int) bool { return a[i].name < a[j].name }
114*4947cdc7SCole Faustfunc (a entries) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
115*4947cdc7SCole Faust
116*4947cdc7SCole Fausttype entry struct {
117*4947cdc7SCole Faust	name  string
118*4947cdc7SCole Faust	value starlark.Value
119*4947cdc7SCole Faust}
120*4947cdc7SCole Faust
121*4947cdc7SCole Faustvar (
122*4947cdc7SCole Faust	_ starlark.HasAttrs  = (*Struct)(nil)
123*4947cdc7SCole Faust	_ starlark.HasBinary = (*Struct)(nil)
124*4947cdc7SCole Faust)
125*4947cdc7SCole Faust
126*4947cdc7SCole Faust// ToStringDict adds a name/value entry to d for each field of the struct.
127*4947cdc7SCole Faustfunc (s *Struct) ToStringDict(d starlark.StringDict) {
128*4947cdc7SCole Faust	for _, e := range s.entries {
129*4947cdc7SCole Faust		d[e.name] = e.value
130*4947cdc7SCole Faust	}
131*4947cdc7SCole Faust}
132*4947cdc7SCole Faust
133*4947cdc7SCole Faustfunc (s *Struct) String() string {
134*4947cdc7SCole Faust	buf := new(strings.Builder)
135*4947cdc7SCole Faust	if s.constructor == Default {
136*4947cdc7SCole Faust		// NB: The Java implementation always prints struct
137*4947cdc7SCole Faust		// even for Bazel provider instances.
138*4947cdc7SCole Faust		buf.WriteString("struct") // avoid String()'s quotation
139*4947cdc7SCole Faust	} else {
140*4947cdc7SCole Faust		buf.WriteString(s.constructor.String())
141*4947cdc7SCole Faust	}
142*4947cdc7SCole Faust	buf.WriteByte('(')
143*4947cdc7SCole Faust	for i, e := range s.entries {
144*4947cdc7SCole Faust		if i > 0 {
145*4947cdc7SCole Faust			buf.WriteString(", ")
146*4947cdc7SCole Faust		}
147*4947cdc7SCole Faust		buf.WriteString(e.name)
148*4947cdc7SCole Faust		buf.WriteString(" = ")
149*4947cdc7SCole Faust		buf.WriteString(e.value.String())
150*4947cdc7SCole Faust	}
151*4947cdc7SCole Faust	buf.WriteByte(')')
152*4947cdc7SCole Faust	return buf.String()
153*4947cdc7SCole Faust}
154*4947cdc7SCole Faust
155*4947cdc7SCole Faust// Constructor returns the constructor used to create this struct.
156*4947cdc7SCole Faustfunc (s *Struct) Constructor() starlark.Value { return s.constructor }
157*4947cdc7SCole Faust
158*4947cdc7SCole Faustfunc (s *Struct) Type() string         { return "struct" }
159*4947cdc7SCole Faustfunc (s *Struct) Truth() starlark.Bool { return true } // even when empty
160*4947cdc7SCole Faustfunc (s *Struct) Hash() (uint32, error) {
161*4947cdc7SCole Faust	// Same algorithm as Tuple.hash, but with different primes.
162*4947cdc7SCole Faust	var x, m uint32 = 8731, 9839
163*4947cdc7SCole Faust	for _, e := range s.entries {
164*4947cdc7SCole Faust		namehash, _ := starlark.String(e.name).Hash()
165*4947cdc7SCole Faust		x = x ^ 3*namehash
166*4947cdc7SCole Faust		y, err := e.value.Hash()
167*4947cdc7SCole Faust		if err != nil {
168*4947cdc7SCole Faust			return 0, err
169*4947cdc7SCole Faust		}
170*4947cdc7SCole Faust		x = x ^ y*m
171*4947cdc7SCole Faust		m += 7349
172*4947cdc7SCole Faust	}
173*4947cdc7SCole Faust	return x, nil
174*4947cdc7SCole Faust}
175*4947cdc7SCole Faustfunc (s *Struct) Freeze() {
176*4947cdc7SCole Faust	for _, e := range s.entries {
177*4947cdc7SCole Faust		e.value.Freeze()
178*4947cdc7SCole Faust	}
179*4947cdc7SCole Faust}
180*4947cdc7SCole Faust
181*4947cdc7SCole Faustfunc (x *Struct) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
182*4947cdc7SCole Faust	if y, ok := y.(*Struct); ok && op == syntax.PLUS {
183*4947cdc7SCole Faust		if side == starlark.Right {
184*4947cdc7SCole Faust			x, y = y, x
185*4947cdc7SCole Faust		}
186*4947cdc7SCole Faust
187*4947cdc7SCole Faust		if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
188*4947cdc7SCole Faust			return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v",
189*4947cdc7SCole Faust				x.constructor, y.constructor, err)
190*4947cdc7SCole Faust		} else if !eq {
191*4947cdc7SCole Faust			return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s",
192*4947cdc7SCole Faust				x.constructor, y.constructor)
193*4947cdc7SCole Faust		}
194*4947cdc7SCole Faust
195*4947cdc7SCole Faust		z := make(starlark.StringDict, x.len()+y.len())
196*4947cdc7SCole Faust		for _, e := range x.entries {
197*4947cdc7SCole Faust			z[e.name] = e.value
198*4947cdc7SCole Faust		}
199*4947cdc7SCole Faust		for _, e := range y.entries {
200*4947cdc7SCole Faust			z[e.name] = e.value
201*4947cdc7SCole Faust		}
202*4947cdc7SCole Faust
203*4947cdc7SCole Faust		return FromStringDict(x.constructor, z), nil
204*4947cdc7SCole Faust	}
205*4947cdc7SCole Faust	return nil, nil // unhandled
206*4947cdc7SCole Faust}
207*4947cdc7SCole Faust
208*4947cdc7SCole Faust// Attr returns the value of the specified field.
209*4947cdc7SCole Faustfunc (s *Struct) Attr(name string) (starlark.Value, error) {
210*4947cdc7SCole Faust	// Binary search the entries.
211*4947cdc7SCole Faust	// This implementation is a specialization of
212*4947cdc7SCole Faust	// sort.Search that avoids dynamic dispatch.
213*4947cdc7SCole Faust	n := len(s.entries)
214*4947cdc7SCole Faust	i, j := 0, n
215*4947cdc7SCole Faust	for i < j {
216*4947cdc7SCole Faust		h := int(uint(i+j) >> 1)
217*4947cdc7SCole Faust		if s.entries[h].name < name {
218*4947cdc7SCole Faust			i = h + 1
219*4947cdc7SCole Faust		} else {
220*4947cdc7SCole Faust			j = h
221*4947cdc7SCole Faust		}
222*4947cdc7SCole Faust	}
223*4947cdc7SCole Faust	if i < n && s.entries[i].name == name {
224*4947cdc7SCole Faust		return s.entries[i].value, nil
225*4947cdc7SCole Faust	}
226*4947cdc7SCole Faust
227*4947cdc7SCole Faust	var ctor string
228*4947cdc7SCole Faust	if s.constructor != Default {
229*4947cdc7SCole Faust		ctor = s.constructor.String() + " "
230*4947cdc7SCole Faust	}
231*4947cdc7SCole Faust	return nil, starlark.NoSuchAttrError(
232*4947cdc7SCole Faust		fmt.Sprintf("%sstruct has no .%s attribute", ctor, name))
233*4947cdc7SCole Faust}
234*4947cdc7SCole Faust
235*4947cdc7SCole Faustfunc (s *Struct) len() int { return len(s.entries) }
236*4947cdc7SCole Faust
237*4947cdc7SCole Faust// AttrNames returns a new sorted list of the struct fields.
238*4947cdc7SCole Faustfunc (s *Struct) AttrNames() []string {
239*4947cdc7SCole Faust	names := make([]string, len(s.entries))
240*4947cdc7SCole Faust	for i, e := range s.entries {
241*4947cdc7SCole Faust		names[i] = e.name
242*4947cdc7SCole Faust	}
243*4947cdc7SCole Faust	return names
244*4947cdc7SCole Faust}
245*4947cdc7SCole Faust
246*4947cdc7SCole Faustfunc (x *Struct) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) {
247*4947cdc7SCole Faust	y := y_.(*Struct)
248*4947cdc7SCole Faust	switch op {
249*4947cdc7SCole Faust	case syntax.EQL:
250*4947cdc7SCole Faust		return structsEqual(x, y, depth)
251*4947cdc7SCole Faust	case syntax.NEQ:
252*4947cdc7SCole Faust		eq, err := structsEqual(x, y, depth)
253*4947cdc7SCole Faust		return !eq, err
254*4947cdc7SCole Faust	default:
255*4947cdc7SCole Faust		return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type())
256*4947cdc7SCole Faust	}
257*4947cdc7SCole Faust}
258*4947cdc7SCole Faust
259*4947cdc7SCole Faustfunc structsEqual(x, y *Struct, depth int) (bool, error) {
260*4947cdc7SCole Faust	if x.len() != y.len() {
261*4947cdc7SCole Faust		return false, nil
262*4947cdc7SCole Faust	}
263*4947cdc7SCole Faust
264*4947cdc7SCole Faust	if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
265*4947cdc7SCole Faust		return false, fmt.Errorf("error comparing struct constructors %v and %v: %v",
266*4947cdc7SCole Faust			x.constructor, y.constructor, err)
267*4947cdc7SCole Faust	} else if !eq {
268*4947cdc7SCole Faust		return false, nil
269*4947cdc7SCole Faust	}
270*4947cdc7SCole Faust
271*4947cdc7SCole Faust	for i, n := 0, x.len(); i < n; i++ {
272*4947cdc7SCole Faust		if x.entries[i].name != y.entries[i].name {
273*4947cdc7SCole Faust			return false, nil
274*4947cdc7SCole Faust		} else if eq, err := starlark.EqualDepth(x.entries[i].value, y.entries[i].value, depth-1); err != nil {
275*4947cdc7SCole Faust			return false, err
276*4947cdc7SCole Faust		} else if !eq {
277*4947cdc7SCole Faust			return false, nil
278*4947cdc7SCole Faust		}
279*4947cdc7SCole Faust	}
280*4947cdc7SCole Faust	return true, nil
281*4947cdc7SCole Faust}
282