Skip to content

Commit d784db2

Browse files
committed
Add support for variadic arguments
1 parent 427f4d7 commit d784db2

File tree

3 files changed

+165
-165
lines changed

3 files changed

+165
-165
lines changed

checker/checker.go

Lines changed: 60 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -314,56 +314,7 @@ func (v *visitor) SliceNode(node *ast.SliceNode) reflect.Type {
314314
func (v *visitor) FunctionNode(node *ast.FunctionNode) reflect.Type {
315315
if f, ok := v.types[node.Name]; ok {
316316
if fn, ok := isFuncType(f.Type); ok {
317-
if isInterface(fn) {
318-
return interfaceType
319-
}
320-
321-
if fn.NumOut() == 0 {
322-
panic(v.error(node, "func %v doesn't return value", node.Name))
323-
}
324-
if fn.NumOut() != 1 {
325-
panic(v.error(node, "func %v returns more then one value", node.Name))
326-
}
327-
328-
numIn := fn.NumIn()
329-
330-
// If func is method on an env, first argument should be a receiver,
331-
// and actual arguments less then numIn by one.
332-
if f.Method {
333-
numIn--
334-
}
335-
336-
if len(node.Arguments) > numIn {
337-
panic(v.error(node, "too many arguments to call %v", node.Name))
338-
}
339-
if len(node.Arguments) < numIn {
340-
panic(v.error(node, "not enough arguments to call %v", node.Name))
341-
}
342-
343-
n := 0
344-
345-
// Skip first argument in case of the receiver.
346-
if f.Method {
347-
n = 1
348-
}
349-
350-
for _, arg := range node.Arguments {
351-
t := v.visit(arg)
352-
in := fn.In(n)
353-
354-
if isIntegerOrArithmeticOperation(arg) {
355-
t = in
356-
setTypeForIntegers(arg, t)
357-
}
358-
359-
if !t.AssignableTo(in) {
360-
panic(v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, node.Name))
361-
}
362-
n++
363-
}
364-
365-
return fn.Out(0)
366-
317+
return v.checkFunc(fn, f.Method, node, node.Name, node.Arguments)
367318
}
368319
}
369320
panic(v.error(node, "unknown func %v", node.Name))
@@ -373,59 +324,78 @@ func (v *visitor) MethodNode(node *ast.MethodNode) reflect.Type {
373324
t := v.visit(node.Node)
374325
if f, method, ok := methodType(t, node.Method); ok {
375326
if fn, ok := isFuncType(f); ok {
376-
if isInterface(fn) {
377-
return interfaceType
378-
}
327+
return v.checkFunc(fn, method, node, node.Method, node.Arguments)
328+
}
329+
}
330+
panic(v.error(node, "type %v has no method %v", t, node.Method))
331+
}
379332

380-
if fn.NumOut() == 0 {
381-
panic(v.error(node, "method %v doesn't return value", node.Method))
382-
}
383-
if fn.NumOut() != 1 {
384-
panic(v.error(node, "method %v returns more then one value", node.Method))
385-
}
333+
// checkFunc checks func arguments and returns "return type" of func or method.
334+
func (v *visitor) checkFunc(fn reflect.Type, method bool, node ast.Node, name string, arguments []ast.Node) reflect.Type {
335+
if isInterface(fn) {
336+
return interfaceType
337+
}
386338

387-
numIn := fn.NumIn()
339+
if fn.NumOut() == 0 {
340+
panic(v.error(node, "func %v doesn't return value", name))
341+
}
342+
if fn.NumOut() != 1 {
343+
panic(v.error(node, "func %v returns more then one value", name))
344+
}
388345

389-
// If func is method, first argument should be a receiver,
390-
// and actual arguments less then numIn by one.
391-
if method {
392-
numIn--
393-
}
346+
numIn := fn.NumIn()
394347

395-
if len(node.Arguments) > numIn {
396-
panic(v.error(node, "too many arguments to call %v", node.Method))
397-
}
398-
if len(node.Arguments) < numIn {
399-
panic(v.error(node, "not enough arguments to call %v", node.Method))
400-
}
348+
// If func is method on an env, first argument should be a receiver,
349+
// and actual arguments less then numIn by one.
350+
if method {
351+
numIn--
352+
}
401353

402-
n := 0
354+
if fn.IsVariadic() {
355+
if len(arguments) < numIn-1 {
356+
panic(v.error(node, "not enough arguments to call %v", name))
357+
}
358+
} else {
359+
if len(arguments) > numIn {
360+
panic(v.error(node, "too many arguments to call %v", name))
361+
}
362+
if len(arguments) < numIn {
363+
panic(v.error(node, "not enough arguments to call %v", name))
364+
}
365+
}
403366

404-
// Skip first argument in case of the receiver.
405-
if method {
406-
n = 1
407-
}
367+
n := 0
408368

409-
for _, arg := range node.Arguments {
410-
t := v.visit(arg)
411-
in := fn.In(n)
369+
// Skip first argument in case of the receiver.
370+
if method {
371+
n = 1
372+
}
412373

413-
if isIntegerOrArithmeticOperation(arg) {
414-
t = in
415-
setTypeForIntegers(arg, t)
416-
}
374+
for _, arg := range arguments {
375+
t := v.visit(arg)
417376

418-
if !t.AssignableTo(in) {
419-
panic(v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, node.Method))
420-
}
421-
n++
422-
}
377+
var in reflect.Type
378+
if fn.IsVariadic() && n >= numIn {
379+
// For variadic arguments fn(xs ...int), go replaces type of xs (int) with ([]int).
380+
// As we compare arguments one by one, we need underling type.
381+
in, _ = indexType(fn.In(numIn))
382+
} else {
383+
in = fn.In(n)
384+
}
423385

424-
return fn.Out(0)
386+
if isIntegerOrArithmeticOperation(arg) {
387+
t = in
388+
setTypeForIntegers(arg, t)
389+
}
425390

391+
if !t.AssignableTo(in) {
392+
panic(v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, name))
426393
}
394+
395+
n++
427396
}
428-
panic(v.error(node, "type %v has no method %v", t, node.Method))
397+
398+
return fn.Out(0)
429399
}
430400

431401
func (v *visitor) BuiltinNode(node *ast.BuiltinNode) reflect.Type {

checker/checker_test.go

Lines changed: 87 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -86,70 +86,6 @@ func TestVisitor_BuiltinNode(t *testing.T) {
8686
}
8787
}
8888

89-
func TestCheck_AsBool(t *testing.T) {
90-
input := `1+2`
91-
92-
tree, err := parser.Parse(input)
93-
assert.NoError(t, err)
94-
95-
config := &conf.Config{}
96-
expr.AsBool()(config)
97-
98-
_, err = checker.Check(tree, config)
99-
assert.Error(t, err)
100-
assert.Equal(t, "expected bool, but got int", err.Error())
101-
}
102-
103-
// Helper types and declarations.
104-
105-
type mockEnv struct {
106-
*mockEmbed
107-
Add func(int64) int64
108-
Any interface{}
109-
Var *mockVar
110-
Tickets []mockTicket
111-
Duration time.Duration
112-
Interface mockInterface
113-
}
114-
115-
func (f *mockEnv) Set(v int64, any interface{}) int64 {
116-
return v
117-
}
118-
119-
type mockEmbed struct {
120-
EmbedVar int64
121-
Sub func(int64) int64
122-
}
123-
124-
func (f *mockEmbed) Get() int64 {
125-
return 0
126-
}
127-
128-
type mockVar struct {
129-
*mockEmbed
130-
Add func(int64) int64
131-
Any interface{}
132-
}
133-
134-
func (*mockVar) Set(v int64, f float64) int64 {
135-
return 0
136-
}
137-
138-
type mockInterface interface {
139-
Method(int) int
140-
}
141-
142-
type mockTicket struct {
143-
Price int
144-
Origin string
145-
}
146-
147-
func (t mockTicket) Method(int) int {
148-
return 0
149-
}
150-
151-
// Other tests.
152-
15389
func TestCheck(t *testing.T) {
15490
var typeTests = []string{
15591
"!Bool",
@@ -220,6 +156,8 @@ func TestCheck(t *testing.T) {
220156
"true ? Any : Any",
221157
"{id: Foo.Bar.Baz, 'str': Bool}",
222158
`"a" < "b"`,
159+
"Variadic('', 1, 2) + Variadic('')",
160+
"Foo.Variadic('', 1, 2) + Foo.Variadic('')",
223161
}
224162
for _, test := range typeTests {
225163
var err error
@@ -442,6 +380,22 @@ func TestCheck_error(t *testing.T) {
442380
`map(Any, {0})[0] + "str"`,
443381
`invalid operation: + (mismatched types int and string)`,
444382
},
383+
{
384+
`Variadic()`,
385+
`not enough arguments to call Variadic`,
386+
},
387+
{
388+
`Variadic('', '')`,
389+
`cannot use string as argument (type int) to call Variadic`,
390+
},
391+
{
392+
`Foo.Variadic()`,
393+
`not enough arguments to call Variadic`,
394+
},
395+
{
396+
`Foo.Variadic('', '')`,
397+
`cannot use string as argument (type int) to call Variadic`,
398+
},
445399
}
446400

447401
re, _ := regexp.Compile(`\s*\(\d+:\d+\)\s*`)
@@ -464,7 +418,69 @@ func TestCheck_error(t *testing.T) {
464418
}
465419
}
466420

467-
// Other helper types.
421+
func TestCheck_AsBool(t *testing.T) {
422+
input := `1+2`
423+
424+
tree, err := parser.Parse(input)
425+
assert.NoError(t, err)
426+
427+
config := &conf.Config{}
428+
expr.AsBool()(config)
429+
430+
_, err = checker.Check(tree, config)
431+
assert.Error(t, err)
432+
assert.Equal(t, "expected bool, but got int", err.Error())
433+
}
434+
435+
//
436+
// Mock types
437+
//
438+
439+
type mockEnv struct {
440+
*mockEmbed
441+
Add func(int64) int64
442+
Any interface{}
443+
Var *mockVar
444+
Tickets []mockTicket
445+
Duration time.Duration
446+
Interface mockInterface
447+
}
448+
449+
func (f *mockEnv) Set(v int64, any interface{}) int64 {
450+
return v
451+
}
452+
453+
type mockEmbed struct {
454+
EmbedVar int64
455+
Sub func(int64) int64
456+
}
457+
458+
func (f *mockEmbed) Get() int64 {
459+
return 0
460+
}
461+
462+
type mockVar struct {
463+
*mockEmbed
464+
Add func(int64) int64
465+
Any interface{}
466+
}
467+
468+
func (*mockVar) Set(v int64, f float64) int64 {
469+
return 0
470+
}
471+
472+
type mockInterface interface {
473+
Method(int) int
474+
}
475+
476+
type mockTicket struct {
477+
Price int
478+
Origin string
479+
}
480+
481+
func (t mockTicket) Method(int) int {
482+
return 0
483+
}
468484

469485
type abc interface {
470486
Abc()
@@ -475,10 +491,11 @@ type bar struct {
475491
}
476492

477493
type foo struct {
478-
Int64 int64
479-
Bar bar
480-
Fn func() bool
481-
Abc abc
494+
Int64 int64
495+
Bar bar
496+
Fn func() bool
497+
Abc abc
498+
Variadic func(head string, xs ...int) int
482499
}
483500

484501
type SubSub struct {
@@ -521,6 +538,7 @@ type mockEnv2 struct {
521538
Foo2p **foo
522539
BoolFn func() bool
523540
NilFn func()
541+
Variadic func(head string, xs ...int) int
524542
}
525543

526544
func (p mockEnv2) Method(_ bar) int {

0 commit comments

Comments
 (0)