Skip to content

Commit affc3ad

Browse files
committed
Add CanSeq function and fix seqable?
Add CanSeq() function to pkg/lang/seq.go that checks whether a value can be converted to a sequence. This mirrors the Java implementation of RT.canSeq and returns true for types that implement ISeq, Seqable, as well as strings, slices, arrays, maps, and nil. Fix seqable? predicate in pkg/stdlib/clojure/core/loader.go to use the new CanSeq function instead of incorrectly calling Apply(nil, ...), which caused "cannot call nil" panics during AOT compilation. Add comprehensive tests in pkg/lang/seq_test.go covering all seqable and non-seqable types to prevent regression.
1 parent 3877664 commit affc3ad

3 files changed

Lines changed: 77 additions & 1 deletion

File tree

pkg/lang/seq.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ func IsSeq(x interface{}) bool {
4343
return ok
4444
}
4545

46+
func CanSeq(x interface{}) bool {
47+
switch x.(type) {
48+
case *EmptyList, *LazySeq, ISeq, Seqable, string, nil:
49+
return true
50+
}
51+
t := reflect.TypeOf(x)
52+
if t == nil {
53+
return true
54+
}
55+
switch t.Kind() {
56+
case reflect.Slice, reflect.Array, reflect.Map:
57+
return true
58+
}
59+
return false
60+
}
61+
4662
func Seq(x interface{}) ISeq {
4763
switch x := x.(type) {
4864
case *EmptyList:

pkg/lang/seq_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package lang
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestCanSeq(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
value interface{}
11+
expected bool
12+
}{
13+
// Should return true for seqable types
14+
{"nil", nil, true},
15+
{"string", "hello", true},
16+
{"empty string", "", true},
17+
{"slice", []int{1, 2, 3}, true},
18+
{"empty slice", []int{}, true},
19+
{"array", [3]int{1, 2, 3}, true},
20+
{"map", map[string]int{"a": 1}, true},
21+
{"empty map", map[string]int{}, true},
22+
{"empty list", emptyList, true},
23+
{"lazy seq", NewLazySeq(func() interface{} { return nil }), true},
24+
25+
// Should return false for non-seqable types
26+
{"int", 42, false},
27+
{"float", 3.14, false},
28+
{"bool", true, false},
29+
{"struct", struct{ X int }{X: 1}, false},
30+
{"pointer to int", new(int), false},
31+
}
32+
33+
for _, tt := range tests {
34+
t.Run(tt.name, func(t *testing.T) {
35+
result := CanSeq(tt.value)
36+
if result != tt.expected {
37+
t.Errorf("CanSeq(%v) = %v, expected %v", tt.value, result, tt.expected)
38+
}
39+
})
40+
}
41+
}
42+
43+
func TestCanSeqConsistentWithSeq(t *testing.T) {
44+
// CanSeq should return true for any value that Seq() doesn't panic on
45+
seqableValues := []interface{}{
46+
nil,
47+
"test",
48+
[]int{1, 2, 3},
49+
[2]string{"a", "b"},
50+
map[string]int{"x": 1},
51+
emptyList,
52+
NewLazySeq(func() interface{} { return nil }),
53+
}
54+
55+
for _, val := range seqableValues {
56+
if !CanSeq(val) {
57+
t.Errorf("CanSeq returned false for value that should be seqable: %v", val)
58+
}
59+
}
60+
}

pkg/stdlib/clojure/core/loader.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)