whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,43 @@
package starlarkstruct
import (
"fmt"
"go.starlark.net/starlark"
)
// A Module is a named collection of values,
// typically a suite of functions imported by a load statement.
//
// It differs from Struct primarily in that its string representation
// does not enumerate its fields.
type Module struct {
Name string
Members starlark.StringDict
}
var _ starlark.HasAttrs = (*Module)(nil)
func (m *Module) Attr(name string) (starlark.Value, error) { return m.Members[name], nil }
func (m *Module) AttrNames() []string { return m.Members.Keys() }
func (m *Module) Freeze() { m.Members.Freeze() }
func (m *Module) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", m.Type()) }
func (m *Module) String() string { return fmt.Sprintf("<module %q>", m.Name) }
func (m *Module) Truth() starlark.Bool { return true }
func (m *Module) Type() string { return "module" }
// MakeModule may be used as the implementation of a Starlark built-in
// function, module(name, **kwargs). It returns a new module with the
// specified name and members.
func MakeModule(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var name string
if err := starlark.UnpackPositionalArgs(b.Name(), args, nil, 1, &name); err != nil {
return nil, err
}
members := make(starlark.StringDict, len(kwargs))
for _, kwarg := range kwargs {
k := string(kwarg[0].(starlark.String))
members[k] = kwarg[1]
}
return &Module{name, members}, nil
}
@@ -0,0 +1,282 @@
// Copyright 2017 The Bazel 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 starlarkstruct defines the Starlark types 'struct' and
// 'module', both optional language extensions.
//
package starlarkstruct // import "go.starlark.net/starlarkstruct"
// It is tempting to introduce a variant of Struct that is a wrapper
// around a Go struct value, for stronger typing guarantees and more
// efficient and convenient field lookup. However:
// 1) all fields of Starlark structs are optional, so we cannot represent
// them using more specific types such as String, Int, *Depset, and
// *File, as such types give no way to represent missing fields.
// 2) the efficiency gain of direct struct field access is rather
// marginal: finding the index of a field by binary searching on the
// sorted list of field names is quite fast compared to the other
// overheads.
// 3) the gains in compactness and spatial locality are also rather
// marginal: the array behind the []entry slice is (due to field name
// strings) only a factor of 2 larger than the corresponding Go struct
// would be, and, like the Go struct, requires only a single allocation.
import (
"fmt"
"sort"
"strings"
"go.starlark.net/starlark"
"go.starlark.net/syntax"
)
// Make is the implementation of a built-in function that instantiates
// an immutable struct from the specified keyword arguments.
//
// An application can add 'struct' to the Starlark environment like so:
//
// globals := starlark.StringDict{
// "struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
// }
//
func Make(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if len(args) > 0 {
return nil, fmt.Errorf("struct: unexpected positional arguments")
}
return FromKeywords(Default, kwargs), nil
}
// FromKeywords returns a new struct instance whose fields are specified by the
// key/value pairs in kwargs. (Each kwargs[i][0] must be a starlark.String.)
func FromKeywords(constructor starlark.Value, kwargs []starlark.Tuple) *Struct {
if constructor == nil {
panic("nil constructor")
}
s := &Struct{
constructor: constructor,
entries: make(entries, 0, len(kwargs)),
}
for _, kwarg := range kwargs {
k := string(kwarg[0].(starlark.String))
v := kwarg[1]
s.entries = append(s.entries, entry{k, v})
}
sort.Sort(s.entries)
return s
}
// FromStringDict returns a new struct instance whose elements are those of d.
// The constructor parameter specifies the constructor; use Default for an ordinary struct.
func FromStringDict(constructor starlark.Value, d starlark.StringDict) *Struct {
if constructor == nil {
panic("nil constructor")
}
s := &Struct{
constructor: constructor,
entries: make(entries, 0, len(d)),
}
for k, v := range d {
s.entries = append(s.entries, entry{k, v})
}
sort.Sort(s.entries)
return s
}
// Struct is an immutable Starlark type that maps field names to values.
// It is not iterable and does not support len.
//
// A struct has a constructor, a distinct value that identifies a class
// of structs, and which appears in the struct's string representation.
//
// Operations such as x+y fail if the constructors of the two operands
// are not equal.
//
// The default constructor, Default, is the string "struct", but
// clients may wish to 'brand' structs for their own purposes.
// The constructor value appears in the printed form of the value,
// and is accessible using the Constructor method.
//
// Use Attr to access its fields and AttrNames to enumerate them.
type Struct struct {
constructor starlark.Value
entries entries // sorted by name
}
// Default is the default constructor for structs.
// It is merely the string "struct".
const Default = starlark.String("struct")
type entries []entry
func (a entries) Len() int { return len(a) }
func (a entries) Less(i, j int) bool { return a[i].name < a[j].name }
func (a entries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type entry struct {
name string
value starlark.Value
}
var (
_ starlark.HasAttrs = (*Struct)(nil)
_ starlark.HasBinary = (*Struct)(nil)
)
// ToStringDict adds a name/value entry to d for each field of the struct.
func (s *Struct) ToStringDict(d starlark.StringDict) {
for _, e := range s.entries {
d[e.name] = e.value
}
}
func (s *Struct) String() string {
buf := new(strings.Builder)
switch constructor := s.constructor.(type) {
case starlark.String:
// NB: The Java implementation always prints struct
// even for Bazel provider instances.
buf.WriteString(constructor.GoString()) // avoid String()'s quotation
default:
buf.WriteString(s.constructor.String())
}
buf.WriteByte('(')
for i, e := range s.entries {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(e.name)
buf.WriteString(" = ")
buf.WriteString(e.value.String())
}
buf.WriteByte(')')
return buf.String()
}
// Constructor returns the constructor used to create this struct.
func (s *Struct) Constructor() starlark.Value { return s.constructor }
func (s *Struct) Type() string { return "struct" }
func (s *Struct) Truth() starlark.Bool { return true } // even when empty
func (s *Struct) Hash() (uint32, error) {
// Same algorithm as Tuple.hash, but with different primes.
var x, m uint32 = 8731, 9839
for _, e := range s.entries {
namehash, _ := starlark.String(e.name).Hash()
x = x ^ 3*namehash
y, err := e.value.Hash()
if err != nil {
return 0, err
}
x = x ^ y*m
m += 7349
}
return x, nil
}
func (s *Struct) Freeze() {
for _, e := range s.entries {
e.value.Freeze()
}
}
func (x *Struct) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
if y, ok := y.(*Struct); ok && op == syntax.PLUS {
if side == starlark.Right {
x, y = y, x
}
if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
return nil, fmt.Errorf("in %s + %s: error comparing constructors: %v",
x.constructor, y.constructor, err)
} else if !eq {
return nil, fmt.Errorf("cannot add structs of different constructors: %s + %s",
x.constructor, y.constructor)
}
z := make(starlark.StringDict, x.len()+y.len())
for _, e := range x.entries {
z[e.name] = e.value
}
for _, e := range y.entries {
z[e.name] = e.value
}
return FromStringDict(x.constructor, z), nil
}
return nil, nil // unhandled
}
// Attr returns the value of the specified field.
func (s *Struct) Attr(name string) (starlark.Value, error) {
// Binary search the entries.
// This implementation is a specialization of
// sort.Search that avoids dynamic dispatch.
n := len(s.entries)
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1)
if s.entries[h].name < name {
i = h + 1
} else {
j = h
}
}
if i < n && s.entries[i].name == name {
return s.entries[i].value, nil
}
var ctor string
if s.constructor != Default {
ctor = s.constructor.String() + " "
}
return nil, starlark.NoSuchAttrError(
fmt.Sprintf("%sstruct has no .%s attribute", ctor, name))
}
func (s *Struct) len() int { return len(s.entries) }
// AttrNames returns a new sorted list of the struct fields.
func (s *Struct) AttrNames() []string {
names := make([]string, len(s.entries))
for i, e := range s.entries {
names[i] = e.name
}
return names
}
func (x *Struct) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) {
y := y_.(*Struct)
switch op {
case syntax.EQL:
return structsEqual(x, y, depth)
case syntax.NEQ:
eq, err := structsEqual(x, y, depth)
return !eq, err
default:
return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y.Type())
}
}
func structsEqual(x, y *Struct, depth int) (bool, error) {
if x.len() != y.len() {
return false, nil
}
if eq, err := starlark.Equal(x.constructor, y.constructor); err != nil {
return false, fmt.Errorf("error comparing struct constructors %v and %v: %v",
x.constructor, y.constructor, err)
} else if !eq {
return false, nil
}
for i, n := 0, x.len(); i < n; i++ {
if x.entries[i].name != y.entries[i].name {
return false, nil
} else if eq, err := starlark.EqualDepth(x.entries[i].value, y.entries[i].value, depth-1); err != nil {
return false, err
} else if !eq {
return false, nil
}
}
return true, nil
}
@@ -0,0 +1,69 @@
// Copyright 2018 The Bazel 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 starlarkstruct_test
import (
"fmt"
"path/filepath"
"testing"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"go.starlark.net/starlarktest"
)
func Test(t *testing.T) {
testdata := starlarktest.DataFile("starlarkstruct", ".")
thread := &starlark.Thread{Load: load}
starlarktest.SetReporter(thread, t)
filename := filepath.Join(testdata, "testdata/struct.star")
predeclared := starlark.StringDict{
"struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
"gensym": starlark.NewBuiltin("gensym", gensym),
}
if _, err := starlark.ExecFile(thread, filename, nil, predeclared); err != nil {
if err, ok := err.(*starlark.EvalError); ok {
t.Fatal(err.Backtrace())
}
t.Fatal(err)
}
}
// load implements the 'load' operation as used in the evaluator tests.
func load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
if module == "assert.star" {
return starlarktest.LoadAssertModule()
}
return nil, fmt.Errorf("load not implemented")
}
// gensym is a built-in function that generates a unique symbol.
func gensym(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var name string
if err := starlark.UnpackArgs("gensym", args, kwargs, "name", &name); err != nil {
return nil, err
}
return &symbol{name: name}, nil
}
// A symbol is a distinct value that acts as a constructor of "branded"
// struct instances, like a class symbol in Python or a "provider" in Bazel.
type symbol struct{ name string }
var _ starlark.Callable = (*symbol)(nil)
func (sym *symbol) Name() string { return sym.name }
func (sym *symbol) String() string { return sym.name }
func (sym *symbol) Type() string { return "symbol" }
func (sym *symbol) Freeze() {} // immutable
func (sym *symbol) Truth() starlark.Bool { return starlark.True }
func (sym *symbol) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", sym.Type()) }
func (sym *symbol) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if len(args) > 0 {
return nil, fmt.Errorf("%s: unexpected positional arguments", sym)
}
return starlarkstruct.FromKeywords(sym, kwargs), nil
}
@@ -0,0 +1,63 @@
# Tests of Starlark 'struct' extension.
# This is not a standard feature and the Go and Starlark APIs may yet change.
load("assert.star", "assert")
assert.eq(str(struct), "<built-in function struct>")
# struct is a constructor for "unbranded" structs.
s = struct(host = "localhost", port = 80)
assert.eq(s, s)
assert.eq(s, struct(host = "localhost", port = 80))
assert.ne(s, struct(host = "localhost", port = 81))
assert.eq(type(s), "struct")
assert.eq(str(s), 'struct(host = "localhost", port = 80)')
assert.eq(s.host, "localhost")
assert.eq(s.port, 80)
assert.fails(lambda : s.protocol, "struct has no .protocol attribute")
assert.eq(dir(s), ["host", "port"])
# Use gensym to create "branded" struct types.
hostport = gensym(name = "hostport")
assert.eq(type(hostport), "symbol")
assert.eq(str(hostport), "hostport")
# Call the symbol to instantiate a new type.
http = hostport(host = "localhost", port = 80)
assert.eq(type(http), "struct")
assert.eq(str(http), 'hostport(host = "localhost", port = 80)') # includes name of constructor
assert.eq(http, http)
assert.eq(http, hostport(host = "localhost", port = 80))
assert.ne(http, hostport(host = "localhost", port = 443))
assert.eq(http.host, "localhost")
assert.eq(http.port, 80)
assert.fails(lambda : http.protocol, "hostport struct has no .protocol attribute")
person = gensym(name = "person")
bob = person(name = "bob", age = 50)
alice = person(name = "alice", city = "NYC")
assert.ne(http, bob) # different constructor symbols
assert.ne(bob, alice) # different fields
hostport2 = gensym(name = "hostport")
assert.eq(hostport, hostport)
assert.ne(hostport, hostport2) # same name, different symbol
assert.ne(http, hostport2(host = "localhost", port = 80)) # equal fields but different ctor symbols
# dir
assert.eq(dir(alice), ["city", "name"])
assert.eq(dir(bob), ["age", "name"])
assert.eq(dir(http), ["host", "port"])
# hasattr, getattr
assert.true(hasattr(alice, "city"))
assert.eq(hasattr(alice, "ageaa"), False)
assert.eq(getattr(alice, "city"), "NYC")
# +
assert.eq(bob + bob, bob)
assert.eq(bob + alice, person(age = 50, city = "NYC", name = "alice"))
assert.eq(alice + bob, person(age = 50, city = "NYC", name = "bob")) # not commutative! a misfeature
assert.fails(lambda : alice + 1, "struct \\+ int")
assert.eq(http + http, http)
assert.fails(lambda : http + bob, "different constructors: hostport \\+ person")