Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Commit 8a653ef

Browse files
m4ns0urtrotterdylan
authored andcommitted
Implement dict.setdefault (#365)
1 parent 7a87e7a commit 8a653ef

4 files changed

Lines changed: 61 additions & 8 deletions

File tree

runtime/dict.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func (d *Dict) incVersion() {
326326
// DelItem removes the entry associated with key from d. It returns true if an
327327
// item was removed, or false if it did not exist in d.
328328
func (d *Dict) DelItem(f *Frame, key *Object) (bool, *BaseException) {
329-
originValue, raised := d.putItem(f, key, nil)
329+
originValue, raised := d.putItem(f, key, nil, true)
330330
if raised != nil {
331331
return false, raised
332332
}
@@ -365,7 +365,7 @@ func (d *Dict) GetItemString(f *Frame, key string) (*Object, *BaseException) {
365365
// Pop looks up key in d, returning and removing the associalted value if exist,
366366
// or nil if key is not present in d.
367367
func (d *Dict) Pop(f *Frame, key *Object) (*Object, *BaseException) {
368-
return d.putItem(f, key, nil)
368+
return d.putItem(f, key, nil, true)
369369
}
370370

371371
// Keys returns a list containing all the keys in d.
@@ -390,7 +390,7 @@ func (d *Dict) Len() int {
390390

391391
// putItem associates value with key in d, returning the old associated value if
392392
// the key was added, or nil if it was not already present in d.
393-
func (d *Dict) putItem(f *Frame, key, value *Object) (*Object, *BaseException) {
393+
func (d *Dict) putItem(f *Frame, key, value *Object, overwrite bool) (*Object, *BaseException) {
394394
hash, raised := Hash(f, key)
395395
if raised != nil {
396396
return nil, raised
@@ -413,7 +413,7 @@ func (d *Dict) putItem(f *Frame, key, value *Object) (*Object, *BaseException) {
413413
d.table.incUsed(-1)
414414
d.incVersion()
415415
}
416-
} else {
416+
} else if overwrite || entry == nil {
417417
newEntry := &dictEntry{hash.Value(), key, value}
418418
if newTable, ok := t.writeEntry(f, index, newEntry); ok {
419419
if newTable != nil {
@@ -435,7 +435,7 @@ func (d *Dict) putItem(f *Frame, key, value *Object) (*Object, *BaseException) {
435435

436436
// SetItem associates value with key in d.
437437
func (d *Dict) SetItem(f *Frame, key, value *Object) *BaseException {
438-
_, raised := d.putItem(f, key, value)
438+
_, raised := d.putItem(f, key, value, true)
439439
return raised
440440
}
441441

@@ -755,6 +755,36 @@ func dictRepr(f *Frame, o *Object) (*Object, *BaseException) {
755755
return NewStr(buf.String()).ToObject(), nil
756756
}
757757

758+
func dictSetDefault(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
759+
argc := len(args)
760+
if argc == 1 {
761+
return nil, f.RaiseType(TypeErrorType, "setdefault expected at least 1 arguments, got 0")
762+
}
763+
if argc > 3 {
764+
return nil, f.RaiseType(TypeErrorType, fmt.Sprintf("setdefault expected at most 2 arguments, got %v", argc-1))
765+
}
766+
expectedTypes := []*Type{DictType, ObjectType, ObjectType}
767+
if argc == 2 {
768+
expectedTypes = expectedTypes[:2]
769+
}
770+
if raised := checkMethodArgs(f, "setdefault", args, expectedTypes...); raised != nil {
771+
return nil, raised
772+
}
773+
d := toDictUnsafe(args[0])
774+
key := args[1]
775+
var value *Object
776+
if argc > 2 {
777+
value = args[2]
778+
} else {
779+
value = None
780+
}
781+
originValue, raised := d.putItem(f, key, value, false)
782+
if originValue != nil {
783+
return originValue, raised
784+
}
785+
return value, raised
786+
}
787+
758788
func dictSetItem(f *Frame, o, key, value *Object) *BaseException {
759789
return toDictUnsafe(o).SetItem(f, key, value)
760790
}
@@ -803,6 +833,7 @@ func initDictType(dict map[string]*Object) {
803833
dict["itervalues"] = newBuiltinFunction("itervalues", dictIterValues).ToObject()
804834
dict["keys"] = newBuiltinFunction("keys", dictKeys).ToObject()
805835
dict["pop"] = newBuiltinFunction("pop", dictPop).ToObject()
836+
dict["setdefault"] = newBuiltinFunction("setdefault", dictSetDefault).ToObject()
806837
dict["update"] = newBuiltinFunction("update", dictUpdate).ToObject()
807838
dict["values"] = newBuiltinFunction("values", dictValues).ToObject()
808839
DictType.slots.Contains = &binaryOpSlot{dictContains}

runtime/dict_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,30 @@ func TestDictNewRaises(t *testing.T) {
615615
}
616616
}
617617

618+
func TestDictSetDefault(t *testing.T) {
619+
setDefaultMethod := mustNotRaise(GetAttr(NewRootFrame(), DictType.ToObject(), NewStr("setdefault"), nil))
620+
setDefault := newBuiltinFunction("TestDictSetDefault", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
621+
i, raised := setDefaultMethod.Call(f, args, kwargs)
622+
if raised != nil {
623+
return nil, raised
624+
}
625+
return NewTuple(i, args[0]).ToObject(), nil
626+
}).ToObject()
627+
cases := []invokeTestCase{
628+
{args: wrapArgs(NewDict(), "foo"), want: newTestTuple(None, newTestDict("foo", None)).ToObject()},
629+
{args: wrapArgs(NewDict(), "foo", 42), want: newTestTuple(42, newTestDict("foo", 42)).ToObject()},
630+
{args: wrapArgs(newTestDict("foo", 42), "foo"), want: newTestTuple(42, newTestDict("foo", 42)).ToObject()},
631+
{args: wrapArgs(newTestDict("foo", 42), "foo", 43), want: newTestTuple(42, newTestDict("foo", 42)).ToObject()},
632+
{args: wrapArgs(NewDict()), wantExc: mustCreateException(TypeErrorType, "setdefault expected at least 1 arguments, got 0")},
633+
{args: wrapArgs(NewDict(), "foo", "bar", "baz"), wantExc: mustCreateException(TypeErrorType, "setdefault expected at most 2 arguments, got 3")},
634+
}
635+
for _, cas := range cases {
636+
if err := runInvokeTestCase(setDefault, &cas); err != "" {
637+
t.Error(err)
638+
}
639+
}
640+
}
641+
618642
func TestDictSetItem(t *testing.T) {
619643
setItem := newBuiltinFunction("TestDictSetItem", func(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
620644
if raised := checkFunctionArgs(f, "TestDictSetItem", args, DictType, ObjectType, ObjectType); raised != nil {

runtime/set.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func toSetUnsafe(o *Object) *Set {
8181

8282
// Add inserts key into s. If key already exists then does nothing.
8383
func (s *Set) Add(f *Frame, key *Object) (bool, *BaseException) {
84-
origin, raised := s.dict.putItem(f, key, None)
84+
origin, raised := s.dict.putItem(f, key, None, true)
8585
if raised != nil {
8686
return false, raised
8787
}

third_party/stdlib/test/test_dict.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ def test_get(self):
288288
self.assertRaises(TypeError, d.get)
289289
self.assertRaises(TypeError, d.get, None, None, None)
290290

291-
@unittest.expectedFailure
292291
def test_setdefault(self):
293292
# dict.setdefault()
294293
d = {}
@@ -316,7 +315,6 @@ def __hash__(self):
316315
x.fail = True
317316
self.assertRaises(Exc, d.setdefault, x, [])
318317

319-
@unittest.expectedFailure
320318
def test_setdefault_atomic(self):
321319
# Issue #13521: setdefault() calls __hash__ and __eq__ only once.
322320
class Hashed(object):

0 commit comments

Comments
 (0)