Skip to content

Commit

Permalink
Remove ability to have a type embedded in Map and Array
Browse files Browse the repository at this point in the history
Prior to this commit, a Map or an Array could have an embedded type
that would override the default type (the type backed by the collection
itself). The reason for this was to allow "typed collections", i.e.
collections that wouldn't accept modifications beyond what its type
would allow.

The approach with typed collections led to ambiguities and overly
complex code. It was also never used in any of dgo's applications since
the most common place to check types when assigning to variables or
passing parameters. The ability to have embedded types in collections
is therefore removed by this commit.
  • Loading branch information
thallgren committed Feb 12, 2020
1 parent 7a37c29 commit 825385e
Show file tree
Hide file tree
Showing 19 changed files with 97 additions and 562 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,8 @@ to `string`, `string[5]`, `string[0,10]`, `"hello"|"goodbye"`, but it is not ass
instance of that other type.

#### Collection types
The default type for an Array or a Map can be overridden. This is particularly useful when working with
mutable collections. If an explicit type is given to the collection, it will instead ensure that any modifications
made to it will be in conformance with that type. The default type is backed by the collection and changes
dynamically when the collection is modified.
The type of a collection is just a cast of the collection itself and hence, will change dynamically when the collection
is modified.

## Immutability

Expand Down
8 changes: 0 additions & 8 deletions dgo/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,6 @@ type (
// given mapper function.
Map(mapper Mapper) Array

// MapTo is like Map but with the added ability to constrain the created Array with a given
// ArrayType.
MapTo(t ArrayType, mapper Mapper) Array

// One returns true if the predicate returns true for exactly one value of this Array.
One(predicate Predicate) bool

Expand Down Expand Up @@ -160,10 +156,6 @@ type (
// The method panics if the receiver is frozen
Set(pos int, val interface{}) Value

// SetType sets the type for this Array to the given argument which must be an ArrayType or a string that evaluates
// to an ArrayType. The Array must be mutable and an instance of the given type
SetType(t interface{})

// Slice returns a slice of this array, starting at position start and ending at position end-1
Slice(start, end int) Array

Expand Down
4 changes: 0 additions & 4 deletions dgo/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,6 @@ type (
// panic if the map is immutable.
RemoveAll(keys Array)

// SetType sets the type for this Map to the given argument which must be a MapType or a string that evaluates
// to a MapType. The Map must be mutable and an instance of the given type
SetType(t interface{})

// StringKeys returns true if this map's key type is assignable to String (i.e. if all keys are strings)
StringKeys() bool

Expand Down
15 changes: 4 additions & 11 deletions dgo_test/require.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func NotNil(t *testing.T, v interface{}) {
}

// Panic will fail unless a call to f results in a panic and the recovered value matches v
func Panic(t *testing.T, f func(), v interface{}) {
func Panic(t *testing.T, f func(), v string) {
t.Helper()
var err error

Expand All @@ -197,15 +197,8 @@ func Panic(t *testing.T, f func(), v interface{}) {
return
}

switch v := v.(type) {
case string:
if regexp.MustCompile(v).MatchString(err.Error()) {
return
}
case dgo.Value:
if v.Equals(err) {
return
}
if regexp.MustCompile(v).MatchString(err.Error()) {
return
}
t.Errorf(`recovered "%s" does not match "%v"`, err.Error(), v)
t.Errorf(`recovered "%s" does not match "%s"`, err.Error(), v)
}
170 changes: 17 additions & 153 deletions internal/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
type (
array struct {
slice []dgo.Value
typ dgo.ArrayType
frozen bool
}

Expand Down Expand Up @@ -755,37 +754,9 @@ func ArrayFromReflected(vr reflect.Value, frozen bool) dgo.Value {
return &array{slice: arr, frozen: frozen}
}

func asArrayType(typ interface{}) dgo.ArrayType {
if typ == nil {
return nil
}

parseArrayType := func(s string) dgo.ArrayType {
if t, ok := Parse(s).(dgo.ArrayType); ok {
return t
}
panic(fmt.Errorf("expression '%s' does not evaluate to an array type", s))
}

var mt dgo.ArrayType
switch typ := typ.(type) {
case dgo.ArrayType:
mt = typ
case string:
mt = parseArrayType(typ)
case dgo.String:
mt = parseArrayType(typ.GoString())
default:
mt = TypeFromReflected(reflect.TypeOf(typ)).(dgo.ArrayType)
}
return mt
}

// ArrayWithCapacity creates a new mutable array of the given type and initial capacity. The type can be nil, the
// zero value of a go slice, a dgo.ArrayType, or a dgo string that parses to a dgo.ArrayType.
func ArrayWithCapacity(capacity int, typ interface{}) dgo.Array {
mt := asArrayType(typ)
return &array{slice: make([]dgo.Value, 0, capacity), typ: mt, frozen: false}
// ArrayWithCapacity creates a new mutable array of the given type and initial capacity.
func ArrayWithCapacity(capacity int) dgo.Array {
return &array{slice: make([]dgo.Value, 0, capacity), frozen: false}
}

// WrapSlice wraps the given slice in an array. Unset entries in the slice will be replaced by Nil.
Expand Down Expand Up @@ -856,69 +827,17 @@ func Values(values []interface{}) dgo.Array {
return &array{slice: valueSlice(values, true), frozen: true}
}

func (v *array) assertType(e dgo.Value, pos int) {
if t := v.typ; t != nil {
sz := len(v.slice)
if pos >= sz {
sz++
if sz > t.Max() {
panic(IllegalSize(t, sz))
}
}
var et dgo.Type
if tp, ok := t.(dgo.TupleType); ok {
if tp.Variadic() {
lp := tp.Len() - 1
if pos < lp {
et = tp.Element(pos)
} else {
et = tp.Element(lp)
}
} else {
et = tp.Element(pos)
}
} else {
et = t.ElementType()
}
if !et.Instance(e) {
panic(IllegalAssignment(et, e))
}
}
}

func (v *array) assertTypes(values dgo.Iterable) {
if t := v.typ; t != nil {
addedSize := values.Len()
if addedSize == 0 {
return
}
sz := len(v.slice)
if sz+addedSize > t.Max() {
panic(IllegalSize(t, sz+addedSize))
}
et := t.ElementType()
values.Each(func(e dgo.Value) {
if !et.Instance(e) {
panic(IllegalAssignment(et, e))
}
})
}
}

func (v *array) Add(vi interface{}) {
if v.frozen {
panic(frozenArray(`Add`))
}
val := Value(vi)
v.assertType(val, len(v.slice))
v.slice = append(v.slice, val)
v.slice = append(v.slice, Value(vi))
}

func (v *array) AddAll(values dgo.Iterable) {
if v.frozen {
panic(frozenArray(`AddAll`))
}
v.assertTypes(values)
a := v.slice
if ar, ok := values.(*array); ok {
a = ar.AppendToSlice(a)
Expand All @@ -932,9 +851,7 @@ func (v *array) AddValues(values ...interface{}) {
if v.frozen {
panic(frozenArray(`AddValues`))
}
va := valueSlice(values, false)
v.assertTypes(&array{slice: va})
v.slice = append(v.slice, va...)
v.slice = append(v.slice, valueSlice(values, false)...)
}

func (v *array) All(predicate dgo.Predicate) bool {
Expand Down Expand Up @@ -1027,7 +944,7 @@ func (v *array) Copy(frozen bool) dgo.Array {
}
}
}
return &array{slice: cp, typ: v.typ, frozen: frozen}
return &array{slice: cp, frozen: frozen}
}

func (v *array) ContainsAll(other dgo.Iterable) bool {
Expand Down Expand Up @@ -1192,9 +1109,7 @@ func (v *array) Insert(pos int, vi interface{}) {
if v.frozen {
panic(frozenArray(`Insert`))
}
val := Value(vi)
v.assertType(val, pos)
v.slice = append(v.slice[:pos], append([]dgo.Value{val}, v.slice[pos:]...)...)
v.slice = append(v.slice[:pos], append([]dgo.Value{Value(vi)}, v.slice[pos:]...)...)
}

// InterfaceSlice returns the values held by the Array as a slice. The slice will
Expand All @@ -1212,31 +1127,6 @@ func (v *array) Len() int {
return len(v.slice)
}

func (v *array) MapTo(t dgo.ArrayType, mapper dgo.Mapper) dgo.Array {
if t == nil {
return v.Map(mapper)
}
a := v.slice
l := len(a)
if l < t.Min() {
panic(IllegalSize(t, l))
}
if l > t.Max() {
panic(IllegalSize(t, l))
}
et := t.ElementType()
vs := make([]dgo.Value, len(a))

for i := range a {
mv := Value(mapper(a[i]))
if !et.Instance(mv) {
panic(IllegalAssignment(et, mv))
}
vs[i] = mv
}
return &array{slice: vs, typ: t, frozen: v.frozen}
}

func (v *array) Map(mapper dgo.Mapper) dgo.Array {
a := v.slice
vs := make([]dgo.Value, len(a))
Expand Down Expand Up @@ -1302,11 +1192,6 @@ func (v *array) removePos(pos int) dgo.Value {
a := v.slice
if pos >= 0 && pos < len(a) {
newLen := len(a) - 1
if v.typ != nil {
if v.typ.Min() > newLen {
panic(IllegalSize(v.typ, newLen))
}
}
val := a[pos]
copy(a[pos:], a[pos+1:])
a[newLen] = nil // release to GC
Expand Down Expand Up @@ -1346,7 +1231,7 @@ func (v *array) Reject(predicate dgo.Predicate) dgo.Array {
vs = append(vs, e)
}
}
return &array{slice: vs, typ: v.typ, frozen: v.frozen}
return &array{slice: vs, frozen: v.frozen}
}

func (v *array) SameValues(other dgo.Iterable) bool {
Expand All @@ -1362,32 +1247,18 @@ func (v *array) Select(predicate dgo.Predicate) dgo.Array {
vs = append(vs, e)
}
}
return &array{slice: vs, typ: v.typ, frozen: v.frozen}
return &array{slice: vs, frozen: v.frozen}
}

func (v *array) Set(pos int, vi interface{}) dgo.Value {
if v.frozen {
panic(frozenArray(`Set`))
}
val := Value(vi)
v.assertType(val, pos)
old := v.slice[pos]
v.slice[pos] = val
v.slice[pos] = Value(vi)
return old
}

func (v *array) SetType(ti interface{}) {
if v.frozen {
panic(frozenArray(`SetType`))
}
mt := asArrayType(ti)
if mt == nil || mt.Instance(v) {
v.typ = mt
return
}
panic(IllegalAssignment(mt, v))
}

func (v *array) Slice(i, j int) dgo.Array {
if v.frozen && i == 0 && j == len(v.slice) {
return v
Expand Down Expand Up @@ -1417,7 +1288,7 @@ func (v *array) Sort() dgo.Array {
}
return a.Type().TypeIdentifier() < b.Type().TypeIdentifier()
})
return &array{slice: sorted, typ: v.typ, frozen: v.frozen}
return &array{slice: sorted, frozen: v.frozen}
}

func (v *array) String() string {
Expand Down Expand Up @@ -1495,12 +1366,9 @@ func (v *array) ToMapFromEntries() (dgo.Map, bool) {
}

func (v *array) Type() dgo.Type {
if v.typ == nil {
ea := &exactArrayType{value: v}
ea.ExactType = ea
return ea
}
return v.typ
ea := &exactArrayType{value: v}
ea.ExactType = ea
return ea
}

func (v *array) Unique() dgo.Array {
Expand Down Expand Up @@ -1530,7 +1398,7 @@ nextVal:
if ui == top {
return v
}
return &array{slice: u[:ui], typ: v.typ, frozen: v.frozen}
return &array{slice: u[:ui], frozen: v.frozen}
}

func (v *array) Pop() (dgo.Value, bool) {
Expand All @@ -1545,9 +1413,7 @@ func (v *array) Pop() (dgo.Value, bool) {
}

func (v *array) With(vi interface{}) dgo.Array {
val := Value(vi)
v.assertType(val, len(v.slice))
return &array{slice: append(v.slice, val), typ: v.typ, frozen: v.frozen}
return &array{slice: append(v.slice, Value(vi)), frozen: v.frozen}
}

func (v *array) WithAll(values dgo.Iterable) dgo.Array {
Expand All @@ -1567,9 +1433,7 @@ func (v *array) WithValues(values ...interface{}) dgo.Array {
if len(values) == 0 {
return v
}
va := valueSlice(values, v.frozen)
v.assertTypes(&array{slice: va})
return &array{slice: append(v.slice, va...), typ: v.typ, frozen: v.frozen}
return &array{slice: append(v.slice, valueSlice(values, v.frozen)...), frozen: v.frozen}
}

// ReplaceNil performs an in-place replacement of nil interfaces with the NilValue
Expand Down
Loading

0 comments on commit 825385e

Please sign in to comment.