Skip to content

Commit a1fa690

Browse files
authored
Support more data types in keys and vals (#1017)
Fixes #1018 Fixes #1020
1 parent f3b136f commit a1fa690

File tree

6 files changed

+187
-31
lines changed

6 files changed

+187
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Fixed
1212
* Fix the behaviour of `nil` with several collection functions (#1011)
13+
* Fix a bug where `keys` and `vals` did not yield keys and values from sequences of map entries (#1018)
14+
* Fix a bug where `set` and `vec` do not produce collections of map entries when called on map arguments (#1020)
1315

1416
## [v0.2.0]
1517
### Added

src/basilisp/core.lpy

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@
246246
set
247247
(fn set
248248
[coll]
249-
(^:allow-builtins basilisp.lang.set/set coll)))
249+
(^:allow-builtins basilisp.lang.runtime/to-set coll)))
250250

251251
(def
252252
^{:doc "Create a vector from the input arguments."
@@ -261,7 +261,7 @@
261261
vec
262262
(fn vec
263263
[coll]
264-
(basilisp.lang.vector/vector coll)))
264+
(basilisp.lang.runtime/vector coll)))
265265

266266
;;;;;;;;;;;; full support for syntax quote begins here ;;;;;;;;;;;;
267267

@@ -332,8 +332,8 @@
332332
(.alter-meta #'vector? assoc :inline (fn [o] `(instance? basilisp.lang.interfaces/IPersistentVector ~o)))
333333
(.alter-meta #'seq? assoc :inline (fn [o] `(instance? basilisp.lang.interfaces/ISeq ~o)))
334334
(.alter-meta #'seq assoc :inline (fn [o] `(basilisp.lang.runtime/to-seq ~o)))
335-
(.alter-meta #'set assoc :inline (fn [coll] `(basilisp.lang.set/set ~coll)))
336-
(.alter-meta #'vec assoc :inline (fn [coll] `(basilisp.lang.vector/vector ~coll)))
335+
(.alter-meta #'set assoc :inline (fn [coll] `(basilisp.lang.runtime/to-set ~coll)))
336+
(.alter-meta #'vec assoc :inline (fn [coll] `(basilisp.lang.runtime/vector ~coll)))
337337

338338
(def
339339
^{:macro true
@@ -2322,14 +2322,32 @@
23222322
(.-value entry))
23232323

23242324
(defn ^:inline keys
2325-
"Return a seq of the keys from a map."
2325+
"Return a seq of the keys from a map-like object.
2326+
2327+
If ``m`` is ``nil``, return ``nil``.
2328+
2329+
If ``m`` is castable to a seq, cast it to a seq and call ``keys`` on that.
2330+
2331+
If ``m`` is a seq, yield the keys from successive entries (which must be map
2332+
entries).
2333+
2334+
If ``m`` is any type of mapping, return a seq of that mapping's keys."
23262335
[m]
2327-
(when m (seq (.keys m))))
2336+
(basilisp.lang.runtime/keys m))
23282337

23292338
(defn ^:inline vals
2330-
"Return a seq of the values from a map."
2339+
"Return a seq of the values from a map-like object
2340+
2341+
If ``m`` is ``nil``, return ``nil``.
2342+
2343+
If ``m`` is castable to a seq, cast it to a seq and call ``vals`` on that.
2344+
2345+
If ``m`` is a seq, yield the valuess from successive entries (which must be map
2346+
entries).
2347+
2348+
If ``m`` is any type of mapping, return a seq of that mapping's values."
23312349
[m]
2332-
(when m (seq (.values m))))
2350+
(basilisp.lang.runtime/vals m))
23332351

23342352
;;;;;;;;;;;;;;;;;;;;;;;;;;;
23352353
;; Transient Collections ;;

src/basilisp/lang/runtime.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
IBlockingDeref,
5252
IDeref,
5353
ILookup,
54+
IMapEntry,
5455
IPersistentCollection,
5556
IPersistentList,
5657
IPersistentMap,
@@ -59,6 +60,7 @@
5960
IPersistentVector,
6061
IReduce,
6162
ISeq,
63+
ISeqable,
6264
ITransientAssociative,
6365
ITransientSet,
6466
ReduceFunction,
@@ -980,6 +982,40 @@ def pop_thread_bindings() -> None:
980982
T = TypeVar("T")
981983

982984

985+
@functools.singledispatch
986+
def to_set(s):
987+
return lset.set(s)
988+
989+
990+
@to_set.register(type(None))
991+
def _to_set_none(_: None) -> lset.PersistentSet:
992+
return lset.EMPTY
993+
994+
995+
@to_set.register(IPersistentMap)
996+
def _to_set_map(m: IPersistentMap) -> lset.PersistentSet:
997+
if (s := m.seq()) is None:
998+
return lset.EMPTY
999+
return to_set(s)
1000+
1001+
1002+
@functools.singledispatch
1003+
def vector(v):
1004+
return vec.vector(v)
1005+
1006+
1007+
@vector.register(type(None))
1008+
def _vector_none(_: None) -> vec.PersistentVector:
1009+
return vec.EMPTY
1010+
1011+
1012+
@vector.register(IPersistentMap)
1013+
def _vector_map(m: IPersistentMap) -> vec.PersistentVector:
1014+
if (s := m.seq()) is None:
1015+
return vec.EMPTY
1016+
return vector(s)
1017+
1018+
9831019
def keyword(name: Any, ns: Any = None) -> kw.Keyword:
9841020
"""Return a new keyword with runtime type checks for name and namespace."""
9851021
if not isinstance(name, str):
@@ -1391,6 +1427,78 @@ def _update_iassociative(m: IAssociative, k, f, *args):
13911427
return m.assoc(k, new_v)
13921428

13931429

1430+
@functools.singledispatch
1431+
def keys(o):
1432+
raise TypeError(f"Object of type {type(o)} cannot be coerced to a key sequence")
1433+
1434+
1435+
@keys.register(type(None))
1436+
def _keys_none(_: None) -> None:
1437+
return None
1438+
1439+
1440+
@keys.register(collections.abc.Iterable)
1441+
@keys.register(ISeqable)
1442+
def _keys_iterable(o: Union[ISeqable, Iterable]) -> Optional[ISeq]:
1443+
return keys(to_seq(o))
1444+
1445+
1446+
@keys.register(ISeq)
1447+
def _keys_iseq(o: ISeq) -> Optional[ISeq]:
1448+
def _key_seq(s: ISeq) -> Optional[ISeq]:
1449+
if to_seq(s) is not None:
1450+
e = s.first
1451+
if not isinstance(e, IMapEntry):
1452+
raise TypeError(
1453+
f"Object of type {type(e)} cannot be coerced to a map entry"
1454+
)
1455+
return lseq.Cons(e.key, lseq.LazySeq(lambda: _key_seq(s.rest)))
1456+
return None
1457+
1458+
return lseq.LazySeq(lambda: _key_seq(o))
1459+
1460+
1461+
@keys.register(collections.abc.Mapping)
1462+
def _keys_mapping(o: Mapping) -> Optional[ISeq]:
1463+
return to_seq(o.keys())
1464+
1465+
1466+
@functools.singledispatch
1467+
def vals(o):
1468+
raise TypeError(f"Object of type {type(o)} cannot be coerced to a value sequence")
1469+
1470+
1471+
@vals.register(type(None))
1472+
def _vals_none(_: None) -> None:
1473+
return None
1474+
1475+
1476+
@keys.register(collections.abc.Iterable)
1477+
@vals.register(ISeqable)
1478+
def _vals_iterable(o: Union[ISeqable, Iterable]) -> Optional[ISeq]:
1479+
return vals(to_seq(o))
1480+
1481+
1482+
@vals.register(ISeq)
1483+
def _vals_iseq(o: ISeq) -> Optional[ISeq]:
1484+
def _val_seq(s: ISeq) -> Optional[ISeq]:
1485+
if to_seq(s) is not None:
1486+
e = s.first
1487+
if not isinstance(e, IMapEntry):
1488+
raise TypeError(
1489+
f"Object of type {type(e)} cannot be coerced to a map entry"
1490+
)
1491+
return lseq.Cons(e.value, lseq.LazySeq(lambda: _val_seq(s.rest)))
1492+
return None
1493+
1494+
return lseq.LazySeq(lambda: _val_seq(o))
1495+
1496+
1497+
@vals.register(collections.abc.Mapping)
1498+
def _vals_mapping(o: Mapping) -> Optional[ISeq]:
1499+
return to_seq(o.values())
1500+
1501+
13941502
@functools.singledispatch
13951503
def conj(coll, *xs):
13961504
"""Conjoin xs to collection. New elements may be added in different positions

src/basilisp/lang/vector.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,9 @@ def from_vec(v: Sequence[Union[K, V]]) -> "MapEntry[K, V]":
300300

301301

302302
def vector(
303-
members: Optional[Iterable[T]], meta: Optional[IPersistentMap] = None
303+
members: Iterable[T], meta: Optional[IPersistentMap] = None
304304
) -> PersistentVector[T]:
305305
"""Creates a new vector."""
306-
if members is None:
307-
members = []
308306
return PersistentVector(pvector(members), meta=meta)
309307

310308

tests/basilisp/core_test.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,16 +1342,6 @@ def test_update_in(self):
13421342
"u",
13431343
)
13441344

1345-
def test_keys(self):
1346-
assert None is core.keys(lmap.map({}))
1347-
assert llist.l("a") == core.keys(lmap.map({"a": 1}))
1348-
assert lset.s("a", "b") == lset.set(core.keys(lmap.map({"a": 1, "b": 2})))
1349-
1350-
def test_vals(self):
1351-
assert None is core.vals(lmap.map({}))
1352-
assert llist.l(1) == core.vals(lmap.map({"a": 1}))
1353-
assert lset.s(1, 2) == lset.set(core.vals(lmap.map({"a": 1, "b": 2})))
1354-
13551345
def test_select_keys(self):
13561346
assert lmap.PersistentMap.empty() == core.select_keys(
13571347
lmap.PersistentMap.empty(), vec.PersistentVector.empty()

tests/basilisp/test_core_fns.lpy

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,35 @@
88
[basilisp.set :as set]
99
[basilisp.test :refer [deftest are is testing]]))
1010

11+
(deftest vec-test
12+
(is (= #{[:a 1] [:b 2]} (set (vec {:a 1 :b 2}))))
13+
(is (= #{:a :b :c} (set (vec #{:a :b :c}))))
14+
15+
(are [res v] (= res (vec v))
16+
[] nil
17+
[] '()
18+
[] []
19+
[] #{}
20+
[] {}
21+
[:a :b :c] [:a :b :c]
22+
[:a :b :c] '(:a :b :c)
23+
["a" "b" "c"] "abc"
24+
[[:a 1]] {:a 1}))
25+
26+
(deftest set-test
27+
(are [res v] (= res (set v))
28+
#{} nil
29+
#{} '()
30+
#{} []
31+
#{} #{}
32+
#{} {}
33+
#{:a :b :c} [:a :b :c]
34+
#{:a :b :c} '(:a :b :c)
35+
#{:a :b :c} #{:a :b :c}
36+
#{"a" "b" "c"} "abc"
37+
#{[:a 1]} {:a 1}
38+
#{[:a 1] [:b 2]} {:a 1 :b 2}))
39+
1140
(deftest keyword-test
1241
(testing "name only"
1342
(are [res name] (= res (keyword name))
@@ -431,16 +460,7 @@
431460
(is (= 1 (rand-nth [1])))
432461
(is (#{1 2} (rand-nth [1 2]))))
433462

434-
(deftest vec-test
435-
(are [expected x] (= expected (vec x))
436-
[] []
437-
[] ()
438-
[] #{}
439-
[] {}
440-
[] nil
441-
[1] (list 1)
442-
[1] #{1}
443-
[1 2] (list 1 2)))
463+
444464

445465
(deftest subvec-test
446466
(is (= [] (subvec [] 0)))
@@ -1408,23 +1428,43 @@
14081428
^{:good :yes :great :also-yes} {:a "A" 'b 2} ^{:good :yes :great :also-yes} {:a "a" 'b 2}))
14091429

14101430
(deftest keys-test
1431+
(is (thrown? python/TypeError (keys :abc)))
1432+
;; use vec to actually realize the key sequence and trigger the error
1433+
(is (thrown? python/TypeError (vec (keys [:a :b :c]))))
1434+
(is (thrown? python/TypeError (vec (keys [[:a 1] [:b 2]]))))
1435+
14111436
(are [expected m] (= expected (set (keys m)))
14121437
#{} {}
14131438
#{} nil
1439+
#{} '()
1440+
#{} []
1441+
#{} #{}
14141442
#{:a} {:a 1}
14151443
#{:a} {:a nil}
14161444
#{:a :b} {:a 1 :b 2}
1445+
#{:a :b} (seq {:a 1 :b 2})
1446+
#{:a :b} (vec {:a 1 :b 2})
14171447
#{"a"} {"a" 1}
14181448
#{} (python/dict {})
14191449
#{:a} (python/dict {:a 1})))
14201450

14211451
(deftest vals-test
1452+
(is (thrown? python/TypeError (vals :abc)))
1453+
;; use vec to actually realize the val sequence and trigger the error
1454+
(is (thrown? python/TypeError (vec (vals [:a :b :c]))))
1455+
(is (thrown? python/TypeError (vec (vals [[:a 1] [:b 2]]))))
1456+
14221457
(are [expected m] (= expected (set (vals m)))
14231458
#{} {}
14241459
#{} nil
1460+
#{} '()
1461+
#{} []
1462+
#{} #{}
14251463
#{1} {:a 1}
14261464
#{nil} {:a nil}
14271465
#{1 2} {:a 1 :b 2}
1466+
#{1 2} (seq {:a 1 :b 2})
1467+
#{1 2} (vec {:a 1 :b 2})
14281468
#{1} {"a" 1}
14291469
#{} (python/dict {})
14301470
#{1} (python/dict {:a 1})))

0 commit comments

Comments
 (0)