Skip to content

Commit 91fd511

Browse files
authored
Fix multiple issues with destructuring (#1091)
Fixes #1078 Fixes #1090
1 parent 8d34366 commit 91fd511

File tree

7 files changed

+181
-32
lines changed

7 files changed

+181
-32
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 an issue with `basilisp test` standard streams output that can lead to failures on MS-Windows (#1080)
13+
* Fix an issue where destructuring a vector would throw an exception rather than returning `nil` for invalid key types (#1090)
14+
* Fix an issue where destructuring default values would take precedence over falsey values in the source data structure (#1078)
1315

1416
### Removed
1517
* Removed support for Python 3.8 (#1083)

src/basilisp/core.lpy

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5806,45 +5806,47 @@
58065806
(:children ddef))
58075807

58085808
kw-binding (fn [arg]
5809-
(let [kw-ns (namespace arg)
5810-
kw-name (name arg)
5811-
sym (symbol kw-name)
5812-
kw (if kw-ns
5813-
(keyword kw-ns kw-name)
5814-
(keyword kw-name))
5815-
or-binding (get ors sym)]
5816-
(if or-binding
5817-
[sym `(or (get ~fn-arg ~kw) ~or-binding)]
5809+
(let [kw-ns (namespace arg)
5810+
kw-name (name arg)
5811+
sym (symbol kw-name)
5812+
kw (if kw-ns
5813+
(keyword kw-ns kw-name)
5814+
(keyword kw-name))]
5815+
(if (contains? ors sym)
5816+
[sym `(get ~fn-arg ~kw ~(get ors sym))]
58185817
[sym `(get ~fn-arg ~kw)])))
58195818

58205819
map-binding (fn [f arg]
5821-
(let [k (f arg)
5822-
or-binding (get ors arg)]
5823-
(if or-binding
5824-
[arg `(or (get ~fn-arg ~k) ~or-binding)]
5820+
(let [k (f arg)]
5821+
(if (contains? ors arg)
5822+
[arg `(get ~fn-arg ~k ~(get ors arg))]
58255823
[arg `(get ~fn-arg ~k)])))
58265824

58275825
sym-binding (fn [arg]
5828-
(let [sym-name (name arg)
5829-
sym (symbol sym-name)
5830-
or-binding (get ors sym)]
5831-
(if or-binding
5832-
[sym `(or (get ~fn-arg (quote ~arg)) ~or-binding)]
5826+
(let [sym-name (name arg)
5827+
sym (symbol sym-name)]
5828+
(if (contains? ors sym)
5829+
[sym `(get ~fn-arg (quote ~arg) ~(get ors sym))]
58335830
[sym `(get ~fn-arg (quote ~arg))])))
58345831

58355832
named-binding (fn [arg]
5836-
(let [binding (get-in arg [:binding :name])
5837-
key (:key arg)
5838-
or-binding (get ors binding)]
5839-
(if or-binding
5840-
[binding `(or (get ~fn-arg (quote ~key)) ~or-binding)]
5833+
(let [binding (get-in arg [:binding :name])
5834+
key (:key arg)]
5835+
(if (contains? ors binding)
5836+
[binding `(get ~fn-arg (quote ~key) ~(get ors binding))]
58415837
[binding `(get ~fn-arg ~key)])))
58425838

58435839
child-binding (fn [child]
58445840
(let [arg (get-in child [:binding :name])
58455841
k (:key child)]
58465842
[arg `(get ~fn-arg ~k)]))]
58475843
(concat
5844+
[fn-arg
5845+
`(if (seq? ~fn-arg)
5846+
(if (next ~fn-arg)
5847+
(apply hash-map ~fn-arg)
5848+
(first ~fn-arg))
5849+
~fn-arg)]
58485850
(mapcat kw-binding (:keys ddef))
58495851
(mapcat (partial map-binding name) (:strs ddef))
58505852
(mapcat sym-binding (:syms ddef))

src/basilisp/lang/runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,7 @@ def get(m, k, default=None): # pylint: disable=unused-argument
14051405
def _get_others(m, k, default=None):
14061406
try:
14071407
return m[k]
1408-
except (KeyError, IndexError):
1408+
except (KeyError, IndexError, TypeError):
14091409
return default
14101410

14111411

src/basilisp/lang/vector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def entry(self, k: int) -> Optional[IMapEntry[int, T]]:
191191
def val_at(self, k: int, default: Optional[T] = None) -> Optional[T]:
192192
try:
193193
return self._inner[k]
194-
except IndexError:
194+
except (IndexError, TypeError):
195195
return default
196196

197197
@staticmethod

tests/basilisp/runtime_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,9 @@ def test_get():
392392
assert "nada" == runtime.get(vec.v(1, 2, 3).to_transient(), 3, "nada")
393393
assert "l" == runtime.get("hello world", 2)
394394
assert None is runtime.get("hello world", 50)
395+
assert None is runtime.get("hello world", "key")
395396
assert "nada" == runtime.get("hello world", 50, "nada")
397+
assert "nada" == runtime.get("hello world", "key", "nada")
396398
assert "l" == runtime.get(["h", "e", "l", "l", "o"], 2)
397399
assert None is runtime.get(["h", "e", "l", "l", "o"], 50)
398400
assert "nada" == runtime.get(["h", "e", "l", "l", "o"], 50, "nada")

tests/basilisp/test_core_macros.lpy

Lines changed: 146 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
(:import contextlib inspect os socket tempfile)
33
(:require
44
[basilisp.string :as str]
5-
[basilisp.test :refer [deftest is testing]]))
5+
[basilisp.test :refer [deftest is are testing]]))
66

77
(deftest defn-test
88
(testing "single arity defn"
@@ -739,6 +739,37 @@
739739
(g v))))))
740740

741741
(deftest fn-associative-destructuring
742+
(testing "ignore non-associative types"
743+
(let [f (fn [{:keys [a]}]
744+
a)]
745+
(are [v] (nil? (f v))
746+
nil
747+
[]
748+
[:a 5]
749+
""
750+
"abc"
751+
5
752+
5.5))
753+
754+
(testing "with defaults"
755+
(let [f (fn [{:keys [a] :or {a :not-defined}}]
756+
a)]
757+
(are [v] (= :not-defined (f v))
758+
nil
759+
[]
760+
[:a 5]
761+
""
762+
"abc"
763+
5
764+
5.5))))
765+
766+
(testing "falsey values not overridden by default values"
767+
(let [f (fn [{:keys [a] :or {a :not-defined}}]
768+
a)]
769+
(are [v] (= v (f {:a v}))
770+
nil
771+
false)))
772+
742773
(testing "destructuring simple keys"
743774
(let [m {:a "a" :b "b"}
744775

@@ -754,11 +785,15 @@
754785

755786
(let [m {:a "a"}
756787
m1 {:a "a" :c "d"}
788+
m2 '(:a "a" :c "d")
789+
m3 (list m1)
757790

758791
f (fn [{:keys [a c] :as m :or {c "c"}}]
759792
[a c m])]
760793
(is (= ["a" "c" m] (f m)))
761-
(is (= ["a" "d" m1] (f m1)))))
794+
(is (= ["a" "d" m1] (f m1)))
795+
(is (= ["a" "d" (apply hash-map m2)] (f m2)))
796+
(is (= ["a" "d" m1] (f m3)))))
762797

763798
(testing "destructuring simple string keys"
764799
(let [m {:a 1 :b "b" "a" :a "b" :b}
@@ -1019,6 +1054,32 @@
10191054
(is (= [[:a :b] [:c]] all)))))
10201055

10211056
(deftest let-associative-destructuring
1057+
(testing "ignore non-associative types"
1058+
(are [v] (nil? (let [{:keys [a]} v] a))
1059+
nil
1060+
[]
1061+
[:a 5]
1062+
""
1063+
"abc"
1064+
5
1065+
5.5)
1066+
1067+
(testing "with defaults"
1068+
(are [v] (= :not-defined (let [{:keys [a] :or {a :not-defined}} v] a))
1069+
nil
1070+
[]
1071+
[:a 5]
1072+
""
1073+
"abc"
1074+
5
1075+
5.5)))
1076+
1077+
(testing "falsey values not overridden by default values"
1078+
(are [v] (= v (let [{:keys [a] :or {a :not-defined}} {:a v}]
1079+
a))
1080+
nil
1081+
false))
1082+
10221083
(testing "destructuring simple keys"
10231084
(let [{:keys [a b]} {:a "a" :b "b"}]
10241085
(is (= "a" a))
@@ -1032,7 +1093,17 @@
10321093
(let [{:keys [a b] :as m :or {b "c"}} {:a "a"}]
10331094
(is (= "a" a))
10341095
(is (= "c" b))
1035-
(is (= {:a "a"} m))))
1096+
(is (= {:a "a"} m)))
1097+
1098+
(let [{:keys [a b] :as m :or {b "c"}} '(:a "a")]
1099+
(is (= "a" a))
1100+
(is (= "c" b))
1101+
(is (= {:a "a"} m)))
1102+
1103+
(let [{:keys [a b] :as m :or {b "c"}} '({:a "a" :b "b"})]
1104+
(is (= "a" a))
1105+
(is (= "b" b))
1106+
(is (= {:a "a" :b "b"} m))))
10361107

10371108
(testing "destructuring simple string keys"
10381109
(let [{:strs [a b]} {:a 1 :b 2 "a" :a "b" :b}]
@@ -1253,6 +1324,37 @@
12531324
(g v)))))))
12541325

12551326
(deftest letfn-associative-destructuring
1327+
(testing "ignore non-associative types"
1328+
(letfn [(f [{:keys [a]}]
1329+
a)]
1330+
(are [v] (nil? (f v))
1331+
nil
1332+
[]
1333+
[:a 5]
1334+
""
1335+
"abc"
1336+
5
1337+
5.5))
1338+
1339+
(testing "with defaults"
1340+
(letfn [(f [{:keys [a] :or {a :not-defined}}]
1341+
a)]
1342+
(are [v] (= :not-defined (f v))
1343+
nil
1344+
[]
1345+
[:a 5]
1346+
""
1347+
"abc"
1348+
5
1349+
5.5))))
1350+
1351+
(testing "falsey values not overridden by default values"
1352+
(letfn [(f [{:keys [a] :or {a :not-defined}}]
1353+
a)]
1354+
(are [v] (= v (f {:a v}))
1355+
nil
1356+
false)))
1357+
12561358
(testing "destructuring simple keys"
12571359
(let [m {:a "a" :b "b"}]
12581360
(letfn [(f [{:keys [a]}]
@@ -1266,11 +1368,15 @@
12661368
(is (= ["a" "b" m] (h m)))))
12671369

12681370
(let [m {:a "a"}
1269-
m1 {:a "a" :c "d"}]
1371+
m1 {:a "a" :c "d"}
1372+
m2 '(:a "a" :c "d")
1373+
m3 (list m1)]
12701374
(letfn [(f [{:keys [a c] :as m :or {c "c"}}]
12711375
[a c m])]
12721376
(is (= ["a" "c" m] (f m)))
1273-
(is (= ["a" "d" m1] (f m1))))))
1377+
(is (= ["a" "d" m1] (f m1)))
1378+
(is (= ["a" "d" (apply hash-map m2)] (f m2)))
1379+
(is (= ["a" "d" m1] (f m3))))))
12741380

12751381
(testing "destructuring simple string keys"
12761382
(let [m {:a 1 :b "b" "a" :a "b" :b}]
@@ -1498,9 +1604,42 @@
14981604
(recur r orig-all (conj pairs pair) (+ accum v1 v2))))))))
14991605

15001606
(deftest loop-associative-destructuring
1607+
(testing "ignore non-associative types"
1608+
(is (= []
1609+
(loop [items [nil [] [:a 5] "" "abc" 5 5.5]
1610+
{:keys [a]} (first items)
1611+
accum []]
1612+
(if (seq items)
1613+
(recur (rest items)
1614+
(first (rest items))
1615+
(cond-> accum a (conj a)))
1616+
accum))))
1617+
1618+
(testing "with defaults"
1619+
(is (= (vec (repeat 7 :not-defined))
1620+
(loop [items [nil [] [:a 5] "" "abc" 5 5.5]
1621+
{:keys [a] :or {a :not-defined}} (first items)
1622+
accum []]
1623+
(if (seq items)
1624+
(recur (rest items)
1625+
(first (rest items))
1626+
(cond-> accum a (conj a)))
1627+
accum))))))
1628+
1629+
(testing "falsey values not overridden by default values"
1630+
(is (= [nil false]
1631+
(loop [items (map #(hash-map :a %) [nil false])
1632+
{:keys [a] :as m :or {a :not-defined}} (first items)
1633+
accum []]
1634+
(if (seq items)
1635+
(recur (rest items)
1636+
(first (rest items))
1637+
(conj accum a))
1638+
accum)))))
1639+
15011640
(testing "destructuring simple keys"
1502-
(is (= [[{:a "a" :b "b"} {:a "c" :b "d"} {:a "e"}] "abcdee"]
1503-
(loop [items [{:a "a" :b "b"} {:a "c" :b "d"} {:a "e"}]
1641+
(is (= [[{:a "a" :b "b"} {:a "c" :b "d"} {:a "e"} {:a "f"} {:a "g" :b "h"}] "abcdeefegh"]
1642+
(loop [items [{:a "a" :b "b"} {:a "c" :b "d"} {:a "e"} '(:a "f") '({:a "g" :b "h"})]
15041643
{:keys [a b] :as m :or {b "e"}} (first items)
15051644
coll []
15061645
accum []]

tests/basilisp/vector_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ def test_val_at():
124124
assert None is vec.PersistentVector.empty().val_at(0)
125125
assert None is vec.PersistentVector.empty().val_at(1)
126126
assert None is vec.PersistentVector.empty().val_at(-1)
127+
assert None is vec.PersistentVector.empty().val_at(keyword("blah"))
128+
assert "default" == vec.PersistentVector.empty().val_at(keyword("blah"), "default")
129+
assert None is vec.PersistentVector.empty().val_at("key")
130+
assert "default" == vec.PersistentVector.empty().val_at("key", "default")
127131

128132

129133
def test_peek():

0 commit comments

Comments
 (0)