Skip to content

Commit ab7b96d

Browse files
authored
Add support for pre- and post-conditions on fn forms (#1168)
Fixes #1167
1 parent f84204c commit ab7b96d

File tree

3 files changed

+119
-0
lines changed

3 files changed

+119
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
* Added support for the optional `attr-map?` on the `ns` macro (#1159)
10+
* Added support for the optional pre- and post-conditions in `fn` forms (#1167)
1011

1112
### Fixed
1213
* Fix a bug where `#` characters were not legal in keywords and symbols (#1149)

src/basilisp/core.lpy

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5940,6 +5940,25 @@
59405940
(let [args (first body)
59415941
arg-vec-meta (meta args)
59425942
body (rest body)
5943+
conditions (when (and (next body) (map? (first body)))
5944+
(first body))
5945+
body (if conditions
5946+
(rest body)
5947+
body)
5948+
5949+
conditions (or conditions arg-vec-meta)
5950+
pre-conds (:pre conditions)
5951+
post-conds (:post conditions)
5952+
body (if post-conds
5953+
[`(let [~'% (do ~@body)]
5954+
~@(map (fn* [c] `(assert ~c)) post-conds)
5955+
~'%)]
5956+
body)
5957+
body (if pre-conds
5958+
(concat
5959+
(map (fn* [c] `(assert ~c)) pre-conds)
5960+
body)
5961+
body)
59435962

59445963
arg-groups (split-with (partial not= '&) args)
59455964
args (first arg-groups)
@@ -5964,6 +5983,7 @@
59645983
(filter #(not= :symbol (:type %)))
59655984
(mapcat destructure-binding)
59665985
(concat rest-binding))
5986+
59675987
new-body (if (seq bindings)
59685988
[`(let* [~@bindings]
59695989
~@body)]

tests/basilisp/test_core_macros.lpy

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,104 @@
17951795
(conj accum [a b c d e f v]))
17961796
[coll accum])))))))
17971797

1798+
(deftest fn-pre-and-post-condition-test
1799+
(testing "fn with map as body member does not count as condition map"
1800+
(let [f (fn [v]
1801+
{:pre [v]
1802+
:post [:nothin]})]
1803+
(is (= {:pre [1] :post [:nothin]} (f 1)))))
1804+
1805+
(testing "pre-condition only"
1806+
(testing "empty pre-condition vec"
1807+
(let [f (fn [x]
1808+
{:pre []}
1809+
(* x x))]
1810+
(is (= 1 (f 1)))
1811+
(is (= 100 (f 10)))
1812+
(is (= 0 (f 0)))
1813+
(is (= 25 (f -5)))))
1814+
1815+
(testing "single pre-condition"
1816+
(let [f (fn [x]
1817+
{:pre [(pos? x)]}
1818+
(* x x))]
1819+
(is (= 1 (f 1)))
1820+
(is (= 100 (f 10)))
1821+
(is (thrown? python/AssertionError
1822+
(f 0)))
1823+
(is (thrown? python/AssertionError
1824+
(f -5)))))
1825+
1826+
(testing "multiple pre-condition"
1827+
(let [f (fn [x]
1828+
{:pre [(pos? x) (even? x)]}
1829+
(* x x))]
1830+
(is (= 4 (f 2)))
1831+
(is (= 100 (f 10)))
1832+
(is (thrown? python/AssertionError
1833+
(f 1)))
1834+
(is (thrown? python/AssertionError
1835+
(f 0)))
1836+
(is (thrown? python/AssertionError
1837+
(f -5)))
1838+
(is (thrown? python/AssertionError
1839+
(f -6))))))
1840+
1841+
(testing "post-condition only"
1842+
(testing "empty post-condition vec"
1843+
(let [f (fn [x]
1844+
{:post []}
1845+
(* x x))]
1846+
(is (= 1 (f 1)))
1847+
(is (= 100 (f 10)))
1848+
(is (= 0 (f 0)))
1849+
(is (= 25 (f -5)))))
1850+
1851+
(testing "single post-condition"
1852+
(let [f (fn [x]
1853+
{:post [(> % 16)]}
1854+
(* x x))]
1855+
(is (= 25 (f 5)))
1856+
(is (= 100 (f 10)))
1857+
(is (thrown? python/AssertionError
1858+
(f 1)))
1859+
(is (thrown? python/AssertionError
1860+
(f -3)))))
1861+
1862+
(testing "multiple post-condition"
1863+
(let [f (fn [x]
1864+
{:post [(> % 16) (< % 225)]}
1865+
(* x x))]
1866+
(is (= 25 (f 5)))
1867+
(is (= 25 (f -5)))
1868+
(is (= 196 (f 14)))
1869+
(is (thrown? python/AssertionError
1870+
(f 1)))
1871+
(is (thrown? python/AssertionError
1872+
(f 0)))
1873+
(is (thrown? python/AssertionError
1874+
(f 15)))
1875+
(is (thrown? python/AssertionError
1876+
(f 100))))))
1877+
1878+
(testing "pre- and post-conditions"
1879+
(let [f (fn [x]
1880+
{:pre [(pos? x)]
1881+
:post [(> % 16) (< % 225)]}
1882+
(* x x))]
1883+
(is (= 25 (f 5)))
1884+
(is (= 196 (f 14)))
1885+
(is (thrown? python/AssertionError
1886+
(f 1)))
1887+
(is (thrown? python/AssertionError
1888+
(f 0)))
1889+
(is (thrown? python/AssertionError
1890+
(f 15)))
1891+
(is (thrown? python/AssertionError
1892+
(f 100)))
1893+
(is (thrown? python/AssertionError
1894+
(f -5))))))
1895+
17981896
(defmacro ^:private variadic-fn []
17991897
`(fn [& r#] r#))
18001898

0 commit comments

Comments
 (0)