blob: 1982cc085438e7f081127c66f8d9f5eed87569e0 [file] [log] [blame]
Alan Donovan312d1a52017-10-02 10:10:28 -04001// Copyright 2017 The Bazel Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
alandonovan9d977712019-01-04 13:04:59 -05005// Package starlarkstruct defines the Starlark types 'struct' and
6// 'module', both optional language extensions.
Alan Donovan6fffce72019-02-04 16:42:47 -05007//
Alan Donovan551f3002018-11-01 09:44:00 -04008package starlarkstruct // import "go.starlark.net/starlarkstruct"
Alan Donovan312d1a52017-10-02 10:10:28 -04009
alandonovan67717b52018-04-23 13:28:48 -040010// It is tempting to introduce a variant of Struct that is a wrapper
11// around a Go struct value, for stronger typing guarantees and more
12// efficient and convenient field lookup. However:
Alan Donovane3deafe2018-10-23 11:05:09 -040013// 1) all fields of Starlark structs are optional, so we cannot represent
alandonovan67717b52018-04-23 13:28:48 -040014// them using more specific types such as String, Int, *Depset, and
15// *File, as such types give no way to represent missing fields.
16// 2) the efficiency gain of direct struct field access is rather
17// marginal: finding the index of a field by binary searching on the
18// sorted list of field names is quite fast compared to the other
19// overheads.
20// 3) the gains in compactness and spatial locality are also rather
21// marginal: the array behind the []entry slice is (due to field name
22// strings) only a factor of 2 larger than the corresponding Go struct
23// would be, and, like the Go struct, requires only a single allocation.
Alan Donovan312d1a52017-10-02 10:10:28 -040024
25import (
Alan Donovan312d1a52017-10-02 10:10:28 -040026 "fmt"
27 "sort"
Josh Bleecher Snyder8cb25c82019-03-01 14:24:35 -080028 "strings"
Alan Donovan312d1a52017-10-02 10:10:28 -040029
Alan Donovan6beab7e2018-10-31 17:53:09 -040030 "go.starlark.net/starlark"
31 "go.starlark.net/syntax"
Alan Donovan312d1a52017-10-02 10:10:28 -040032)
33
34// Make is the implementation of a built-in function that instantiates
35// an immutable struct from the specified keyword arguments.
36//
Alan Donovane3deafe2018-10-23 11:05:09 -040037// An application can add 'struct' to the Starlark environment like so:
Alan Donovan312d1a52017-10-02 10:10:28 -040038//
Alan Donovane3deafe2018-10-23 11:05:09 -040039// globals := starlark.StringDict{
40// "struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
Alan Donovan312d1a52017-10-02 10:10:28 -040041// }
42//
Alan Donovane3deafe2018-10-23 11:05:09 -040043func Make(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
Alan Donovan312d1a52017-10-02 10:10:28 -040044 if len(args) > 0 {
45 return nil, fmt.Errorf("struct: unexpected positional arguments")
46 }
47 return FromKeywords(Default, kwargs), nil
48}
49
50// FromKeywords returns a new struct instance whose fields are specified by the
Alan Donovane3deafe2018-10-23 11:05:09 -040051// key/value pairs in kwargs. (Each kwargs[i][0] must be a starlark.String.)
52func FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
Alan Donovan312d1a52017-10-02 10:10:28 -040053 if constructor == nil {
54 panic("nil constructor")
55 }
56 s := &Struct{
57 constructor: constructor,
58 entries: make(entries, 0, len(kwargs)),
59 }
60 for _, kwarg := range kwargs {
Alan Donovane3deafe2018-10-23 11:05:09 -040061 k := string(kwarg[0].(starlark.String))
Alan Donovan312d1a52017-10-02 10:10:28 -040062 v := kwarg[1]
63 s.entries = append(s.entries, entry{k, v})
64 }
65 sort.Sort(s.entries)
66 return s
67}
68
69// FromStringDict returns a whose elements are those of d.
70// The constructor parameter specifies the constructor; use Default for an ordinary struct.
Alan Donovane3deafe2018-10-23 11:05:09 -040071func FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct {
Alan Donovan312d1a52017-10-02 10:10:28 -040072 if constructor == nil {
73 panic("nil constructor")
74 }
75 s := &Struct{
76 constructor: constructor,
77 entries: make(entries, 0, len(d)),
78 }
79 for k, v := range d {
80 s.entries = append(s.entries, entry{k, v})
81 }
82 sort.Sort(s.entries)
83 return s
84}
85
Alan Donovane3deafe2018-10-23 11:05:09 -040086// Struct is an immutable Starlark type that maps field names to values.
alandonovan67717b52018-04-23 13:28:48 -040087// It is not iterable and does not support len.
Alan Donovan312d1a52017-10-02 10:10:28 -040088//
89// A struct has a constructor, a distinct value that identifies a class
90// of structs, and which appears in the struct's string representation.
91//
92// Operations such as x+y fail if the constructors of the two operands
93// are not equal.
94//
95// The default constructor, Default, is the string "struct", but
96// clients may wish to 'brand' structs for their own purposes.
97// The constructor value appears in the printed form of the value,
98// and is accessible using the Constructor method.
99//
100// Use Attr to access its fields and AttrNames to enumerate them.
101type Struct struct {
Alan Donovane3deafe2018-10-23 11:05:09 -0400102 constructor starlark.Value
Alan Donovan312d1a52017-10-02 10:10:28 -0400103 entries entries // sorted by name
104}
105
106// Default is the default constructor for structs.
107// It is merely the string "struct".
Alan Donovane3deafe2018-10-23 11:05:09 -0400108const Default = starlark.String("struct")
Alan Donovan312d1a52017-10-02 10:10:28 -0400109
110type entries []entry
111
112func (a entries) Len() int { return len(a) }
113func (a entries) Less(i, j int) bool { return a[i].name < a[j].name }
114func (a entries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
115
116type entry struct {
alandonovana3e7ce02019-02-06 17:48:33 -0500117 name string
Alan Donovane3deafe2018-10-23 11:05:09 -0400118 value starlark.Value
Alan Donovan312d1a52017-10-02 10:10:28 -0400119}
120
121var (
Alan Donovane3deafe2018-10-23 11:05:09 -0400122 _ starlark.HasAttrs = (*Struct)(nil)
123 _ starlark.HasBinary = (*Struct)(nil)
Alan Donovan312d1a52017-10-02 10:10:28 -0400124)
125
126// ToStringDict adds a name/value entry to d for each field of the struct.
Alan Donovane3deafe2018-10-23 11:05:09 -0400127func (s *Struct) ToStringDict(d starlark.StringDict) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400128 for _, e := range s.entries {
129 d[e.name] = e.value
130 }
131}
132
133func (s *Struct) String() string {
Josh Bleecher Snyder8cb25c82019-03-01 14:24:35 -0800134 buf := new(strings.Builder)
alandonovancada8682018-02-26 12:59:19 -0500135 if s.constructor == Default {
alandonovan67717b52018-04-23 13:28:48 -0400136 // NB: The Java implementation always prints struct
137 // even for Bazel provider instances.
alandonovancada8682018-02-26 12:59:19 -0500138 buf.WriteString("struct") // avoid String()'s quotation
139 } else {
140 buf.WriteString(s.constructor.String())
141 }
Alan Donovan312d1a52017-10-02 10:10:28 -0400142 buf.WriteByte('(')
143 for i, e := range s.entries {
144 if i > 0 {
145 buf.WriteString(", ")
146 }
147 buf.WriteString(e.name)
148 buf.WriteString(" = ")
149 buf.WriteString(e.value.String())
150 }
151 buf.WriteByte(')')
152 return buf.String()
153}
154
155// Constructor returns the constructor used to create this struct.
Alan Donovane3deafe2018-10-23 11:05:09 -0400156func (s *Struct) Constructor() starlark.Value { return s.constructor }
Alan Donovan312d1a52017-10-02 10:10:28 -0400157
Alan Donovan551f3002018-11-01 09:44:00 -0400158func (s *Struct) Type() string { return "struct" }
Alan Donovane3deafe2018-10-23 11:05:09 -0400159func (s *Struct) Truth() starlark.Bool { return true } // even when empty
Alan Donovan312d1a52017-10-02 10:10:28 -0400160func (s *Struct) Hash() (uint32, error) {
161 // Same algorithm as Tuple.hash, but with different primes.
162 var x, m uint32 = 8731, 9839
163 for _, e := range s.entries {
Alan Donovane3deafe2018-10-23 11:05:09 -0400164 namehash, _ := starlark.String(e.name).Hash()
Alan Donovan312d1a52017-10-02 10:10:28 -0400165 x = x ^ 3*namehash
166 y, err := e.value.Hash()
167 if err != nil {
168 return 0, err
169 }
170 x = x ^ y*m
171 m += 7349
172 }
173 return x, nil
174}
175func (s *Struct) Freeze() {
176 for _, e := range s.entries {
177 e.value.Freeze()
178 }
179}
180
Alan Donovane3deafe2018-10-23 11:05:09 -0400181func (x *Struct) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400182 if y, ok := y.(*Struct); ok && op == syntax.PLUS {
Alan Donovane3deafe2018-10-23 11:05:09 -0400183 if side == starlark.Right {
Alan Donovan312d1a52017-10-02 10:10:28 -0400184 x, y = y, x
185 }
186
Alan Donovane3deafe2018-10-23 11:05:09 -0400187 if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400188 return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v",
189 x.constructor, y.constructor, err)
190 } else if !eq {
191 return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s",
192 x.constructor, y.constructor)
193 }
194
Alan Donovane3deafe2018-10-23 11:05:09 -0400195 z := make(starlark.StringDict, x.len()+y.len())
Alan Donovan312d1a52017-10-02 10:10:28 -0400196 for _, e := range x.entries {
197 z[e.name] = e.value
198 }
199 for _, e := range y.entries {
200 z[e.name] = e.value
201 }
202
203 return FromStringDict(x.constructor, z), nil
204 }
205 return nil, nil // unhandled
206}
207
alandonovana3e7ce02019-02-06 17:48:33 -0500208// Attr returns the value of the specified field.
Alan Donovane3deafe2018-10-23 11:05:09 -0400209func (s *Struct) Attr(name string) (starlark.Value, error) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400210 // Binary search the entries.
211 // This implementation is a specialization of
212 // sort.Search that avoids dynamic dispatch.
213 n := len(s.entries)
214 i, j := 0, n
215 for i < j {
216 h := int(uint(i+j) >> 1)
217 if s.entries[h].name < name {
218 i = h + 1
219 } else {
220 j = h
221 }
222 }
223 if i < n && s.entries[i].name == name {
224 return s.entries[i].value, nil
225 }
226
alandonovancada8682018-02-26 12:59:19 -0500227 var ctor string
228 if s.constructor != Default {
229 ctor = s.constructor.String() + " "
230 }
alandonovan6afa1bb2019-02-06 17:49:05 -0500231 return nil, starlark.NoSuchAttrError(
232 fmt.Sprintf("%sstruct has no .%s attribute", ctor, name))
Alan Donovan312d1a52017-10-02 10:10:28 -0400233}
234
alandonovan67717b52018-04-23 13:28:48 -0400235func (s *Struct) len() int { return len(s.entries) }
Alan Donovan312d1a52017-10-02 10:10:28 -0400236
237// AttrNames returns a new sorted list of the struct fields.
238func (s *Struct) AttrNames() []string {
239 names := make([]string, len(s.entries))
240 for i, e := range s.entries {
241 names[i] = e.name
242 }
243 return names
244}
245
Alan Donovane3deafe2018-10-23 11:05:09 -0400246func (x *Struct) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) {
Alan Donovan312d1a52017-10-02 10:10:28 -0400247 y := y_.(*Struct)
248 switch op {
249 case syntax.EQL:
250 return structsEqual(x, y, depth)
251 case syntax.NEQ:
252 eq, err := structsEqual(x, y, depth)
253 return !eq, err
254 default:
255 return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type())
256 }
257}
258
259func structsEqual(x, y *Struct, depth int) (bool, error) {
alandonovan67717b52018-04-23 13:28:48 -0400260 if x.len() != y.len() {
Alan Donovan312d1a52017-10-02 10:10:28 -0400261 return false, nil
262 }
263
Alan Donovane3deafe2018-10-23 11:05:09 -0400264 if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
alandonovan4b42bbf2017-11-10 13:22:52 -0500265 return false, fmt.Errorf("error comparing struct constructors %v and %v: %v",
Alan Donovan312d1a52017-10-02 10:10:28 -0400266 x.constructor, y.constructor, err)
267 } else if !eq {
268 return false, nil
269 }
270
alandonovan67717b52018-04-23 13:28:48 -0400271 for i, n := 0, x.len(); i < n; i++ {
Alan Donovan312d1a52017-10-02 10:10:28 -0400272 if x.entries[i].name != y.entries[i].name {
273 return false, nil
Alan Donovane3deafe2018-10-23 11:05:09 -0400274 } else if eq, err := starlark.EqualDepth(x.entries[i].value, y.entries[i].value, depth-1); err != nil {
Alan Donovan312d1a52017-10-02 10:10:28 -0400275 return false, err
276 } else if !eq {
277 return false, nil
278 }
279 }
280 return true, nil
281}