Skip to content

Commit

Permalink
feat: add support for nil in function calls (fixes #277)
Browse files Browse the repository at this point in the history
  • Loading branch information
flosch committed Jun 24, 2022
1 parent a73f037 commit aec36e7
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 8 deletions.
7 changes: 7 additions & 0 deletions lexer.go
Expand Up @@ -18,6 +18,7 @@ const (
TokenString
TokenNumber
TokenSymbol
TokenNil
)

var (
Expand Down Expand Up @@ -96,6 +97,8 @@ func (t *Token) String() string {
typ = "String"
case TokenSymbol:
typ = "Symbol"
case TokenNil:
typ = "Nil"
default:
typ = "Unknown"
}
Expand Down Expand Up @@ -371,6 +374,10 @@ func (l *lexer) stateIdentifier() lexerStateFn {
l.emit(TokenKeyword)
return l.stateCode
}
if kw == "nil" {
l.emit(TokenNil)
return l.stateCode
}
}
l.emit(TokenIdentifier)
return l.stateCode
Expand Down
22 changes: 22 additions & 0 deletions pongo2_template_test.go
Expand Up @@ -188,6 +188,17 @@ Yep!`,
}
return pongo2.AsValue(s)
},
"func_ensure_nil": func(x any) bool {
return x == nil
},
"func_ensure_nil_variadic": func(args ...any) bool {
for _, i := range args {
if i != nil {
return false
}
}
return true
},
},
"complex": map[string]any{
"is_admin": isAdmin,
Expand Down Expand Up @@ -316,6 +327,17 @@ func TestTemplate_Functions(t *testing.T) {
errorMessage: "[Error (where: execution) in <string> | Line 1 Col 4 near 'testFunc'] the second return value is not an error",
wantErr: true,
},
{
name: "NilToNonNilParameter",
template: "{{ testFunc(nil) }}",
context: pongo2.Context{
"testFunc": func(i int) int {
return 1
},
},
errorMessage: "[Error (where: execution) in <string> | Line 1 Col 4 near 'testFunc'] function input argument 0 of 'testFunc' must be of type int or *pongo2.Value (not <nil>)",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 1 addition & 3 deletions pongo2_test.go
Expand Up @@ -10,9 +10,7 @@ import (
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

type TestSuite struct {
tpl *pongo2.Template
}
type TestSuite struct{}

var (
_ = Suite(&TestSuite{})
Expand Down
7 changes: 6 additions & 1 deletion template_tests/function_calls_wrapper.tpl
Expand Up @@ -8,4 +8,9 @@
{{ simple.func_variadic_sum_int(1, 19, 185) }}
{{ simple.func_variadic_sum_int2() }}
{{ simple.func_variadic_sum_int2(2) }}
{{ simple.func_variadic_sum_int2(1, 7, 100) }}
{{ simple.func_variadic_sum_int2(1, 7, 100) }}
eqnil: {{ simple.func_ensure_nil(nil) }}
neqnil: {{ simple.func_ensure_nil(1) }}
v1: {{ simple.func_ensure_nil_variadic(nil) }}
v2: {{ simple.func_ensure_nil_variadic() }}
v3: {{ simple.func_ensure_nil_variadic(nil, 1, nil, "test") }}
7 changes: 6 additions & 1 deletion template_tests/function_calls_wrapper.tpl.out
Expand Up @@ -8,4 +8,9 @@ hello, john doe
205
0
2
108
108
eqnil: True
neqnil: False
v1: True
v2: True
v3: False
19 changes: 16 additions & 3 deletions variable.go
Expand Up @@ -11,6 +11,7 @@ const (
varTypeInt = iota
varTypeIdent
varTypeSubscript
varTypeNil
)

var (
Expand All @@ -23,6 +24,7 @@ type variablePart struct {
s string
i int
subscript IEvaluator
isNil bool

isFunctionCall bool
callingArgs []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls)
Expand Down Expand Up @@ -419,14 +421,18 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
return nil, fmt.Errorf("function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T)",
idx, vr.String(), fnArg.String(), pv.Interface())
}
// Function's argument has another type, using the interface-value
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
} else {
if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
return nil, fmt.Errorf("function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T)",
vr.String(), fnArg.String(), pv.Interface())
}
// Function's argument has another type, using the interface-value
}

if pv.IsNil() {
// Workaround to present an interface nil as reflect.Value
var empty any = nil
parameters = append(parameters, reflect.ValueOf(&empty).Elem())
} else {
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
}
} else {
Expand Down Expand Up @@ -623,6 +629,13 @@ variableLoop:
})
p.Consume() // consume: NUMBER
continue variableLoop
case TokenNil:
resolver.parts = append(resolver.parts, &variablePart{
typ: varTypeNil,
isNil: true,
})
p.Consume() // consume: NIL
continue variableLoop
default:
return nil, p.Error("This token is not allowed within a variable name.", t2)
}
Expand Down

0 comments on commit aec36e7

Please sign in to comment.