Skip to content

Commit 6a437f0

Browse files
authored
Merge pull request #17 from cpunion/extension
test struct field
2 parents cab098a + 54b0d71 commit 6a437f0

File tree

8 files changed

+433
-40
lines changed

8 files changed

+433
-40
lines changed

.github/workflows/go.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ jobs:
4646
os:
4747
- macos-latest
4848
- ubuntu-24.04
49-
go:
50-
- '1.20'
51-
- '1.21'
52-
- '1.22'
53-
- '1.23'
5449
runs-on: ${{matrix.os}}
5550
steps:
5651
- name: Install Python 3.12 (macOS only)
@@ -65,7 +60,7 @@ jobs:
6560
- name: Set up Go
6661
uses: actions/setup-go@v4
6762
with:
68-
go-version: ${{matrix.go}}
63+
go-version: 1.23
6964

7065
- name: Build
7166
run: go build -v ./...
@@ -74,7 +69,7 @@ jobs:
7469
run: go test -v -coverprofile=coverage.txt -covermode=atomic ./...
7570

7671
- name: Upload coverage to Codecov
77-
if: matrix.os == 'ubuntu-24.04' && matrix.go == '1.23'
72+
if: matrix.os == 'ubuntu-24.04'
7873
uses: codecov/codecov-action@v4
7974
with:
8075
token: ${{ secrets.CODECOV_TOKEN }}

convert.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,15 @@ func ToValue(obj Object, v reflect.Value) bool {
149149
t := v.Type()
150150
v.Set(reflect.MakeMap(t))
151151
dict := Cast[Dict](obj)
152-
dict.ForEach(func(key, value Object) {
152+
for key, value := range dict.Items() {
153153
vk := reflect.New(t.Key()).Elem()
154154
vv := reflect.New(t.Elem()).Elem()
155155
if !ToValue(key, vk) || !ToValue(value, vv) {
156-
panic(fmt.Errorf("failed to convert key or value to %v", t.Key()))
156+
return false
157157
}
158158
v.SetMapIndex(vk, vv)
159-
})
159+
}
160+
return true
160161
} else {
161162
return false
162163
}
@@ -167,9 +168,13 @@ func ToValue(obj Object, v reflect.Value) bool {
167168
for i := 0; i < t.NumField(); i++ {
168169
field := t.Field(i)
169170
key := goNameToPythonName(field.Name)
171+
if !dict.HasKey(MakeStr(key)) {
172+
continue
173+
}
170174
value := dict.Get(MakeStr(key))
171175
if !ToValue(value, v.Field(i)) {
172-
panic(fmt.Errorf("failed to convert value to %v", field.Name))
176+
SetTypeError(fmt.Errorf("failed to convert value to %v", field.Name))
177+
return false
173178
}
174179
}
175180
} else {

dict.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func MakeDict(m map[any]any) Dict {
3838
return dict
3939
}
4040

41-
func (d Dict) Has(key any) bool {
41+
func (d Dict) HasKey(key any) bool {
4242
keyObj := From(key)
4343
return C.PyDict_Contains(d.obj, keyObj.obj) != 0
4444
}
@@ -75,22 +75,26 @@ func (d Dict) Del(key Objecter) {
7575
C.PyDict_DelItem(d.obj, key.Obj())
7676
}
7777

78-
func (d Dict) ForEach(fn func(key, value Object)) {
79-
items := C.PyDict_Items(d.obj)
80-
check(items != nil, "failed to get items of dict")
81-
defer C.Py_DecRef(items)
82-
iter := C.PyObject_GetIter(items)
83-
for {
84-
item := C.PyIter_Next(iter)
85-
if item == nil {
86-
break
78+
func (d Dict) Items() func(fn func(key, value Object) bool) {
79+
return func(fn func(key, value Object) bool) {
80+
items := C.PyDict_Items(d.obj)
81+
check(items != nil, "failed to get items of dict")
82+
defer C.Py_DecRef(items)
83+
iter := C.PyObject_GetIter(items)
84+
for {
85+
item := C.PyIter_Next(iter)
86+
if item == nil {
87+
break
88+
}
89+
C.Py_IncRef(item)
90+
key := C.PyTuple_GetItem(item, 0)
91+
value := C.PyTuple_GetItem(item, 1)
92+
C.Py_IncRef(key)
93+
C.Py_IncRef(value)
94+
C.Py_DecRef(item)
95+
if !fn(newObject(key), newObject(value)) {
96+
break
97+
}
8798
}
88-
C.Py_IncRef(item)
89-
key := C.PyTuple_GetItem(item, 0)
90-
value := C.PyTuple_GetItem(item, 1)
91-
C.Py_IncRef(key)
92-
C.Py_IncRef(value)
93-
C.Py_DecRef(item)
94-
fn(newObject(key), newObject(value))
9599
}
96100
}

dict_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func TestDictDel(t *testing.T) {
135135
dict.Del(key)
136136

137137
// After deletion, the key should not exist
138-
if dict.Has(key) {
138+
if dict.HasKey(key) {
139139
t.Errorf("After deletion, key %v should not exist", key)
140140
}
141141
}
@@ -155,14 +155,14 @@ func TestDictForEach(t *testing.T) {
155155
"key3": "value3",
156156
}
157157

158-
dict.ForEach(func(key, value Object) {
158+
for key, value := range dict.Items() {
159159
count++
160160
k := key.String()
161161
v := value.String()
162162
if expectedVal, ok := expectedPairs[k]; !ok || expectedVal != v {
163163
t.Errorf("ForEach() unexpected pair: %v: %v", k, v)
164164
}
165-
})
165+
}
166166

167167
if count != len(expectedPairs) {
168168
t.Errorf("ForEach() visited %d pairs, want %d", count, len(expectedPairs))

extension.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.
131131

132132
fieldType := field.Type()
133133
if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct {
134+
if field.IsNil() {
135+
return C.Py_None
136+
}
134137
if pyType, ok := maps.pyTypes[fieldType.Elem()]; ok {
135138
newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), field.Interface())
136139
if newWrapper == nil {
@@ -187,15 +190,24 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i
187190

188191
fieldType := field.Type()
189192
if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct {
193+
if C.Py_Is(value, C.Py_None) != 0 {
194+
field.Set(reflect.Zero(fieldType))
195+
return 0
196+
}
190197
if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 {
191198
if field.IsNil() {
192199
field.Set(reflect.New(fieldType.Elem()))
193200
}
194201
if !ToValue(FromPy(value), field.Elem()) {
195-
SetError(fmt.Errorf("failed to convert dict to %s", fieldType.Elem()))
202+
SetTypeError(fmt.Errorf("failed to convert dict to %s", fieldType.Elem()))
196203
return -1
197204
}
198205
} else {
206+
pyType := C.Py_TYPE(value)
207+
if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok {
208+
SetTypeError(fmt.Errorf("invalid value of type %v for struct pointer field", FromPy((*C.PyObject)(unsafe.Pointer(pyType)))))
209+
return -1
210+
}
199211
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
200212
if valueWrapper == nil {
201213
SetError(fmt.Errorf("invalid value for struct pointer field"))
@@ -207,25 +219,30 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i
207219
} else if field.Kind() == reflect.Struct {
208220
if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 {
209221
if !ToValue(FromPy(value), field) {
210-
SetError(fmt.Errorf("failed to convert dict to %s", field.Type()))
222+
SetTypeError(fmt.Errorf("failed to convert dict to %s", field.Type()))
211223
return -1
212224
}
213225
} else {
226+
pyType := (*C.PyTypeObject)(unsafe.Pointer(value.ob_type))
227+
if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok {
228+
SetTypeError(fmt.Errorf("invalid value of type %v for struct field", FromPy((*C.PyObject)(unsafe.Pointer(pyType)))))
229+
return -1
230+
}
214231
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
215232
if valueWrapper == nil {
216233
SetError(fmt.Errorf("invalid value for struct field"))
217234
return -1
218235
}
219236
baseAddr := goPtr.UnsafePointer()
220237
fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset)
221-
fieldPtr := reflect.NewAt(fieldType, fieldAddr).Interface()
222-
reflect.ValueOf(fieldPtr).Set(reflect.ValueOf(valueWrapper.goObj))
238+
fieldPtr := reflect.NewAt(fieldType, fieldAddr)
239+
fieldPtr.Elem().Set(reflect.ValueOf(valueWrapper.goObj).Elem())
223240
}
224241
return 0
225242
}
226243

227244
if !ToValue(FromPy(value), field) {
228-
SetError(fmt.Errorf("failed to convert value to %s", methodMeta.typ))
245+
SetTypeError(fmt.Errorf("failed to convert value to %s", methodMeta.typ))
229246
return -1
230247
}
231248
return 0

0 commit comments

Comments
 (0)