Skip to content

Commit 3ef3252

Browse files
committed
py: improve ParseTuple{,AndKeywords} to handle 's{,*,#}'
Signed-off-by: Sebastien Binet <[email protected]>
1 parent 4679429 commit 3ef3252

File tree

2 files changed

+326
-6
lines changed

2 files changed

+326
-6
lines changed

py/args.go

+49-6
Original file line numberDiff line numberDiff line change
@@ -465,17 +465,60 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
465465
switch op.code {
466466
case 'O':
467467
*result = arg
468-
case 'Z', 'z':
469-
if _, ok := arg.(NoneType); ok {
470-
*result = arg
471-
break
468+
case 'Z':
469+
switch op.modifier {
470+
default:
471+
return ExceptionNewf(TypeError, "%s() argument %d must be str or None, not %s", name, i+1, arg.Type().Name)
472+
case '#', 0:
473+
switch arg := arg.(type) {
474+
case String, NoneType:
475+
default:
476+
return ExceptionNewf(TypeError, "%s() argument %d must be str or None, not %s", name, i+1, arg.Type().Name)
477+
}
478+
}
479+
*result = arg
480+
case 'z':
481+
switch op.modifier {
482+
default:
483+
switch arg := arg.(type) {
484+
case String, NoneType:
485+
// ok
486+
default:
487+
return ExceptionNewf(TypeError, "%s() argument %d must be str or None, not %s", name, i+1, arg.Type().Name)
488+
}
489+
case '#':
490+
fallthrough // FIXME(sbinet): check for read-only?
491+
case '*':
492+
switch arg := arg.(type) {
493+
case String, Bytes, NoneType:
494+
// ok.
495+
default:
496+
return ExceptionNewf(TypeError, "%s() argument %d must be str, bytes-like or None, not %s", name, i+1, arg.Type().Name)
497+
}
472498
}
473-
fallthrough
474-
case 'U', 's':
499+
*result = arg
500+
case 'U':
475501
if _, ok := arg.(String); !ok {
476502
return ExceptionNewf(TypeError, "%s() argument %d must be str, not %s", name, i+1, arg.Type().Name)
477503
}
478504
*result = arg
505+
case 's':
506+
switch op.modifier {
507+
default:
508+
if _, ok := arg.(String); !ok {
509+
return ExceptionNewf(TypeError, "%s() argument %d must be str, not %s", name, i+1, arg.Type().Name)
510+
}
511+
case '#':
512+
fallthrough // FIXME(sbinet): check for read-only?
513+
case '*':
514+
switch arg := arg.(type) {
515+
case String, Bytes:
516+
// ok.
517+
default:
518+
return ExceptionNewf(TypeError, "%s() argument %d must be str or bytes-like, not %s", name, i+1, arg.Type().Name)
519+
}
520+
}
521+
*result = arg
479522
case 'i', 'n':
480523
if _, ok := arg.(Int); !ok {
481524
return ExceptionNewf(TypeError, "%s() argument %d must be int, not %s", name, i+1, arg.Type().Name)

py/args_test.go

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Copyright 2022 The go-python 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+
5+
package py
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
)
11+
12+
func TestParseTupleAndKeywords(t *testing.T) {
13+
for _, tc := range []struct {
14+
args Tuple
15+
kwargs StringDict
16+
format string
17+
kwlist []string
18+
results []Object
19+
err error
20+
}{
21+
{
22+
args: Tuple{String("a")},
23+
format: "O:func",
24+
results: []Object{String("a")},
25+
},
26+
{
27+
args: Tuple{None},
28+
format: "Z:func",
29+
results: []Object{None},
30+
},
31+
{
32+
args: Tuple{String("a")},
33+
format: "Z:func",
34+
results: []Object{String("a")},
35+
},
36+
{
37+
args: Tuple{Int(42)},
38+
format: "Z:func",
39+
results: []Object{nil},
40+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or None, not int'"),
41+
},
42+
{
43+
args: Tuple{None},
44+
format: "Z*:func", // FIXME(sbinet): invalid format.
45+
results: []Object{nil},
46+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or None, not NoneType'"),
47+
},
48+
{
49+
args: Tuple{None},
50+
format: "Z#:func",
51+
results: []Object{None},
52+
},
53+
{
54+
args: Tuple{String("a")},
55+
format: "Z#:func",
56+
results: []Object{String("a")},
57+
},
58+
{
59+
args: Tuple{Int(42)},
60+
format: "Z#:func",
61+
results: []Object{nil},
62+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or None, not int'"),
63+
},
64+
{
65+
args: Tuple{None},
66+
format: "z:func",
67+
results: []Object{None},
68+
},
69+
{
70+
args: Tuple{String("a")},
71+
format: "z:func",
72+
results: []Object{String("a")},
73+
},
74+
{
75+
args: Tuple{Int(42)},
76+
format: "z:func",
77+
results: []Object{nil},
78+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or None, not int'"),
79+
},
80+
{
81+
args: Tuple{Bytes("a")},
82+
format: "z:func",
83+
results: []Object{nil},
84+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or None, not bytes'"),
85+
},
86+
{
87+
args: Tuple{None},
88+
format: "z*:func",
89+
results: []Object{None},
90+
},
91+
{
92+
args: Tuple{Bytes("a")},
93+
format: "z*:func",
94+
results: []Object{Bytes("a")},
95+
},
96+
{
97+
args: Tuple{String("a")},
98+
format: "z*:func",
99+
results: []Object{String("a")},
100+
},
101+
{
102+
args: Tuple{Int(42)},
103+
format: "z*:func",
104+
results: []Object{nil},
105+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, bytes-like or None, not int'"),
106+
},
107+
{
108+
args: Tuple{None},
109+
format: "z#:func",
110+
results: []Object{None},
111+
},
112+
{
113+
args: Tuple{Bytes("a")},
114+
format: "z#:func",
115+
results: []Object{Bytes("a")},
116+
},
117+
{
118+
args: Tuple{String("a")},
119+
format: "z#:func",
120+
results: []Object{String("a")},
121+
},
122+
{
123+
args: Tuple{Int(42)},
124+
format: "z#:func",
125+
results: []Object{nil},
126+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, bytes-like or None, not int'"),
127+
},
128+
{
129+
args: Tuple{String("a")},
130+
format: "s:func",
131+
results: []Object{String("a")},
132+
},
133+
{
134+
args: Tuple{None},
135+
format: "s:func",
136+
results: []Object{nil},
137+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not NoneType'"),
138+
},
139+
{
140+
args: Tuple{Bytes("a")},
141+
format: "s:func",
142+
results: []Object{nil},
143+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not bytes'"),
144+
},
145+
{
146+
args: Tuple{String("a")},
147+
format: "s#:func",
148+
results: []Object{String("a")},
149+
},
150+
{
151+
args: Tuple{Bytes("a")},
152+
format: "s#:func",
153+
results: []Object{Bytes("a")},
154+
},
155+
{
156+
args: Tuple{None},
157+
format: "s#:func",
158+
results: []Object{nil},
159+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or bytes-like, not NoneType'"),
160+
},
161+
{
162+
args: Tuple{String("a")},
163+
format: "s*:func",
164+
results: []Object{String("a")},
165+
},
166+
{
167+
args: Tuple{Bytes("a")},
168+
format: "s*:func",
169+
results: []Object{Bytes("a")},
170+
},
171+
{
172+
args: Tuple{None},
173+
format: "s*:func",
174+
results: []Object{nil},
175+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str or bytes-like, not NoneType'"),
176+
},
177+
{
178+
args: Tuple{String("a")},
179+
format: "U:func",
180+
results: []Object{String("a")},
181+
},
182+
{
183+
args: Tuple{None},
184+
format: "U:func",
185+
results: []Object{nil},
186+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not NoneType'"),
187+
},
188+
{
189+
args: Tuple{Bytes("a")},
190+
format: "U:func",
191+
results: []Object{nil},
192+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not bytes'"),
193+
},
194+
{
195+
args: Tuple{Bytes("a")},
196+
format: "U*:func", // FIXME(sbinet): invalid format
197+
results: []Object{nil},
198+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not bytes'"),
199+
},
200+
{
201+
args: Tuple{Bytes("a")},
202+
format: "U#:func", // FIXME(sbinet): invalid format
203+
results: []Object{nil},
204+
err: fmt.Errorf("TypeError: 'func() argument 1 must be str, not bytes'"),
205+
},
206+
{
207+
args: Tuple{Int(42)},
208+
format: "i:func",
209+
results: []Object{Int(42)},
210+
},
211+
{
212+
args: Tuple{None},
213+
format: "i:func",
214+
results: []Object{nil},
215+
err: fmt.Errorf("TypeError: 'func() argument 1 must be int, not NoneType'"),
216+
},
217+
{
218+
args: Tuple{Int(42)},
219+
format: "n:func",
220+
results: []Object{Int(42)},
221+
},
222+
{
223+
args: Tuple{None},
224+
format: "n:func",
225+
results: []Object{nil},
226+
err: fmt.Errorf("TypeError: 'func() argument 1 must be int, not NoneType'"),
227+
},
228+
{
229+
args: Tuple{Bool(true)},
230+
format: "p:func",
231+
results: []Object{Bool(true)},
232+
},
233+
{
234+
args: Tuple{None},
235+
format: "p:func",
236+
results: []Object{nil},
237+
err: fmt.Errorf("TypeError: 'func() argument 1 must be bool, not NoneType'"),
238+
},
239+
{
240+
args: Tuple{Float(42)},
241+
format: "d:func",
242+
results: []Object{Float(42)},
243+
},
244+
{
245+
args: Tuple{Int(42)},
246+
format: "d:func",
247+
results: []Object{Float(42)},
248+
},
249+
{
250+
args: Tuple{None},
251+
format: "p:func",
252+
results: []Object{nil},
253+
err: fmt.Errorf("TypeError: 'func() argument 1 must be bool, not NoneType'"),
254+
},
255+
} {
256+
t.Run(tc.format, func(t *testing.T) {
257+
results := make([]*Object, len(tc.results))
258+
for i := range tc.results {
259+
results[i] = &tc.results[i]
260+
}
261+
err := ParseTupleAndKeywords(tc.args, tc.kwargs, tc.format, tc.kwlist, results...)
262+
switch {
263+
case err != nil && tc.err != nil:
264+
if got, want := err.Error(), tc.err.Error(); got != want {
265+
t.Fatalf("invalid error:\ngot= %s\nwant=%s", got, want)
266+
}
267+
case err != nil && tc.err == nil:
268+
t.Fatalf("could not parse tuple+kwargs: %+v", err)
269+
case err == nil && tc.err != nil:
270+
t.Fatalf("expected an error (got=nil): %+v", tc.err)
271+
case err == nil && tc.err == nil:
272+
// ok.
273+
}
274+
// FIXME(sbinet): check results
275+
})
276+
}
277+
}

0 commit comments

Comments
 (0)