Skip to content

Commit c2db50d

Browse files
authored
Attach metadata via defn attribute map (#350)
* Attach metadata via defn attribute map * Lint
1 parent 1a51e17 commit c2db50d

File tree

4 files changed

+249
-22
lines changed

4 files changed

+249
-22
lines changed

src/basilisp/core/__init__.lpy

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
:arglists '([o meta])}
5959
with-meta
6060
(fn* with-meta [o meta]
61-
(.with-meta o meta)))
61+
(if meta
62+
(.with-meta o meta)
63+
o)))
6264

6365
(def ^:macro ^:redef let
6466
(fn* let [&env &form & decl]
@@ -262,7 +264,7 @@
262264
(def
263265
^{:macro true
264266
:doc "Define a new function with an optional docstring."
265-
:arglists '([name & body] [name doc & body])}
267+
:arglists '([name & body] [name doc? & body] [name doc? attr-map? & body])}
266268
defn
267269
(fn defn [&env &form name & body]
268270
(if (symbol? name)
@@ -273,22 +275,28 @@
273275
doc (if (string? (first body))
274276
(first body)
275277
nil)
276-
fname (if doc
277-
(with-meta name {:doc doc})
278-
name)
279278
body (if doc
280279
(rest body)
281280
body)
281+
fmeta (if (map? (first body))
282+
(first body)
283+
nil)
284+
fname (if doc
285+
(with-meta name (assoc fmeta :doc doc))
286+
(with-meta name fmeta))
287+
body (if fmeta
288+
(rest body)
289+
body)
282290
multi? (list? (first body))
283291
fsigs (if multi?
284292
(loop [arities body
285293
sigs []]
286294
(if (seq arities)
287295
(recur (rest arities)
288296
(conj sigs (ffirst arities)))
289-
`(quote ~(apply list sigs))))
290-
`(quote ~(list (first body))))
291-
fname (with-meta fname {:arglists fsigs})
297+
(apply list sigs)))
298+
(list (first body)))
299+
fname (with-meta fname {:arglists (list 'quote fsigs)})
292300
body (if multi?
293301
body
294302
(cons
@@ -378,22 +386,28 @@
378386
(.-ns v))
379387

380388
(def
381-
^{:macro true
382-
:doc "Define a new macro like defn. Macro functions are available to the
383-
compiler during macroexpansion."}
389+
^{:macro true
390+
:doc "Define a new macro like defn. Macro functions are available to the
391+
compiler during macroexpansion."
392+
:arglists '([name & body] [name doc? & body] [name doc? attr-map? & body])}
384393
defmacro
385394
(fn defmacro [&env &form name & body]
386395
(let [body (concat body)
387396
doc (if (string? (first body))
388397
(first body)
389398
nil)
390-
fname (with-meta (if doc
391-
(with-meta name {:doc doc})
392-
name)
393-
{:macro true})
394399
body (if doc
395400
(rest body)
396401
body)
402+
fmeta (if (map? (first body))
403+
(first body)
404+
nil)
405+
body (if fmeta
406+
(rest body)
407+
body)
408+
fname (if doc
409+
(with-meta name (assoc fmeta :doc doc :macro true))
410+
(with-meta name (assoc fmeta :macro true)))
397411
multi? (list? (first body))
398412
fsigs (if multi?
399413
(loop [arities body
@@ -429,6 +443,30 @@
429443
`(defn ~fname
430444
~@body))))
431445

446+
(defmacro defasync
447+
"Define a new asynchronous function with an optional docstring."
448+
[name & body]
449+
(let [body (concat body)
450+
doc (if (string? (first body))
451+
(first body)
452+
nil)
453+
body (if doc
454+
(rest body)
455+
body)
456+
fmeta (if (map? (first body))
457+
(assoc (first body))
458+
nil)
459+
body (if fmeta
460+
(rest body)
461+
body)
462+
fmeta (apply assoc fmeta (concat [:async true]
463+
(if doc
464+
[:doc doc]
465+
nil)))]
466+
`(defn ~name
467+
~fmeta
468+
~@body)))
469+
432470
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
433471
;; Logical Comparisons & Macros ;;
434472
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1546,12 +1584,11 @@
15461584
(defn eval
15471585
"Evaluate a form (not a string) and return its result."
15481586
[form]
1549-
(let [ctx (basilisp.lang.compiler.CompilerContext.)
1587+
(let [ctx (basilisp.lang.compiler.CompilerContext. "<Eval Input>")
15501588
module (.-module *ns*)]
15511589
(basilisp.lang.compiler/compile-and-exec-form form
15521590
ctx
1553-
module
1554-
"<Eval Input>")))
1591+
module)))
15551592

15561593
;;;;;;;;;;;;;;;;;;;;;;;;;
15571594
;; Namespace Utilities ;;

src/basilisp/lang/compiler/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SpecialForm:
3434
SYM_NO_WARN_WHEN_UNUSED_META_KEY = kw.keyword("no-warn-when-unused")
3535
SYM_REDEF_META_KEY = kw.keyword("redef")
3636

37+
ARGLISTS_KW = kw.keyword("arglists")
3738
COL_KW = kw.keyword("col")
3839
DOC_KW = kw.keyword("doc")
3940
FILE_KW = kw.keyword("file")

src/basilisp/lang/compiler/parser.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import basilisp.lang.vector as vec
3838
from basilisp.lang.compiler.constants import (
3939
AMPERSAND,
40+
ARGLISTS_KW,
4041
COL_KW,
4142
DEFAULT_COMPILER_FILE_PATH,
4243
DOC_KW,
@@ -499,7 +500,7 @@ def _await_ast(ctx: ParserContext, form: lseq.Seq) -> Await:
499500
return Await(form=form, expr=expr, env=ctx.get_node_env())
500501

501502

502-
def _def_ast( # pylint: disable=too-many-locals
503+
def _def_ast( # pylint: disable=too-many-branches,too-many-locals
503504
ctx: ParserContext, form: lseq.Seq
504505
) -> Def:
505506
assert form.first == SpecialForm.DEF
@@ -550,6 +551,20 @@ def _def_ast( # pylint: disable=too-many-locals
550551
if doc is not None:
551552
def_meta = def_meta.assoc(DOC_KW, doc)
552553

554+
# Var metadata is set both for the running Basilisp instance
555+
# and cached as Python bytecode to be reread again. Argument lists
556+
# are quoted so as not to resolve argument symbols. For the compiled
557+
# and cached bytecode, this causes no trouble. However, for the case
558+
# where we directly set the Var meta for the running Basilisp instance
559+
# this causes problems since we'll end up getting something like
560+
# `(quote ([] [v]))` rather than simply `([] [v])`.
561+
arglists_meta = def_meta.entry(ARGLISTS_KW)
562+
if isinstance(arglists_meta, llist.List):
563+
assert arglists_meta.first == SpecialForm.QUOTE
564+
var_meta = def_meta.update({ARGLISTS_KW: runtime.nth(arglists_meta, 1)})
565+
else:
566+
var_meta = def_meta
567+
553568
# Generation fails later if we use the same symbol we received, since
554569
# its meta may contain values which fail to compile.
555570
bare_name = sym.symbol(name.name)
@@ -559,7 +574,7 @@ def _def_ast( # pylint: disable=too-many-locals
559574
ns_sym,
560575
bare_name,
561576
dynamic=def_meta.entry(SYM_DYNAMIC_META_KEY, False),
562-
meta=def_meta,
577+
meta=var_meta,
563578
)
564579
descriptor = Def(
565580
form=form,
@@ -1756,7 +1771,10 @@ def _parse_ast( # pylint: disable=too-many-branches
17561771
if form == llist.List.empty():
17571772
with ctx.quoted():
17581773
return _const_node(ctx, form)
1759-
return _list_node(ctx, form)
1774+
elif ctx.is_quoted:
1775+
return _const_node(ctx, form)
1776+
else:
1777+
return _list_node(ctx, form)
17601778
elif isinstance(form, vec.Vector):
17611779
if ctx.is_quoted:
17621780
return _const_node(ctx, form)

tests/basilisp/core_macros_test.lpy

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,179 @@
11
(ns basilisp.core-macros-test
2+
(:import inspect)
23
(:require
34
[basilisp.test :refer [deftest is testing]]))
45

5-
(deftest fn-meta
6+
(deftest defn-test
7+
(testing "single arity defn"
8+
(testing "simple"
9+
(let [fvar (defn f1 [] :kw)
10+
vmeta (meta fvar)]
11+
(is (= 'f1 (:name vmeta)))
12+
(is (= '([]) (:arglists vmeta)))
13+
(is (not (contains? vmeta :doc)))))
14+
15+
(testing "with docstring"
16+
(let [fvar (defn f2 "a docstring" [] :kw)
17+
vmeta (meta fvar)]
18+
(is (= 'f2 (:name vmeta)))
19+
(is (= '([]) (:arglists vmeta)))
20+
(is (= "a docstring" (:doc vmeta)))))
21+
22+
(testing "with attr-map"
23+
(let [fvar (defn f3 {:added "0.1"} [] :kw)
24+
vmeta (meta fvar)]
25+
(is (= 'f3 (:name vmeta)))
26+
(is (= '([]) (:arglists vmeta)))
27+
(is (= "0.1" (:added vmeta)))
28+
(is (not (contains? vmeta :doc)))))
29+
30+
(testing "attr-map and docstring"
31+
(let [fvar (defn f4
32+
"another docstring"
33+
{:added "0.1"}
34+
[]
35+
:kw)
36+
vmeta (meta fvar)]
37+
(is (= 'f4 (:name vmeta)))
38+
(is (= '([]) (:arglists vmeta)))
39+
(is (= "0.1" (:added vmeta)))
40+
(is (= "another docstring" (:doc vmeta))))))
41+
42+
(testing "multi arity defn"
43+
(testing "simple"
44+
(let [fvar (defn f5 ([] :kw) ([a] a))
45+
vmeta (meta fvar)]
46+
(is (= 'f5 (:name vmeta)))
47+
(is (= '([] [a]) (:arglists vmeta)))
48+
(is (not (contains? vmeta :doc)))))
49+
50+
(testing "with docstring"
51+
(let [fvar (defn f6
52+
"multi-arity docstring"
53+
([] :kw)
54+
([a] a))
55+
vmeta (meta fvar)]
56+
(is (= 'f6 (:name vmeta)))
57+
(is (= '([] [a]) (:arglists vmeta)))
58+
(is (= "multi-arity docstring" (:doc vmeta)))))
59+
60+
(testing "with attr-map"
61+
(let [fvar (defn f7
62+
{:added "0.1"}
63+
([] :kw)
64+
([a] a))
65+
vmeta (meta fvar)]
66+
(is (= 'f7 (:name vmeta)))
67+
(is (= '([] [a]) (:arglists vmeta)))
68+
(is (= "0.1" (:added vmeta)))
69+
(is (not (contains? vmeta :doc)))))
70+
71+
(testing "attr-map and docstring"
72+
(let [fvar (defn f8
73+
"another multi-arity docstring"
74+
{:added "0.1"}
75+
([] :kw)
76+
([a] a))
77+
vmeta (meta fvar)]
78+
(is (= 'f8 (:name vmeta)))
79+
(is (= '([] [a]) (:arglists vmeta)))
80+
(is (= "0.1" (:added vmeta)))
81+
(is (= "another multi-arity docstring" (:doc vmeta)))))))
82+
83+
(deftest defasync-test
84+
(testing "single arity defasync"
85+
(testing "simple"
86+
(let [fvar (defasync af1 [] :kw)
87+
vmeta (meta fvar)]
88+
(is (= 'af1 (:name vmeta)))
89+
(is (= '([]) (:arglists vmeta)))
90+
(is (:async vmeta))
91+
(is (inspect/iscoroutinefunction af1))
92+
(is (not (contains? vmeta :doc)))))
93+
94+
(testing "with docstring"
95+
(let [fvar (defasync af2 "a docstring" [] :kw)
96+
vmeta (meta fvar)]
97+
(is (= 'af2 (:name vmeta)))
98+
(is (= '([]) (:arglists vmeta)))
99+
(is (:async vmeta))
100+
(is (inspect/iscoroutinefunction af2))
101+
(is (= "a docstring" (:doc vmeta)))))
102+
103+
(testing "with attr-map"
104+
(let [fvar (defasync af3 {:added "0.1"} [] :kw)
105+
vmeta (meta fvar)]
106+
(is (= 'af3 (:name vmeta)))
107+
(is (= '([]) (:arglists vmeta)))
108+
(is (:async vmeta))
109+
(is (inspect/iscoroutinefunction af3))
110+
(is (= "0.1" (:added vmeta)))
111+
(is (not (contains? vmeta :doc)))))
112+
113+
(testing "attr-map and docstring"
114+
(let [fvar (defasync af4
115+
"another docstring"
116+
{:added "0.1"}
117+
[]
118+
:kw)
119+
vmeta (meta fvar)]
120+
(is (= 'af4 (:name vmeta)))
121+
(is (= '([]) (:arglists vmeta)))
122+
(is (:async vmeta))
123+
(is (inspect/iscoroutinefunction af4))
124+
(is (= "0.1" (:added vmeta)))
125+
(is (= "another docstring" (:doc vmeta))))))
126+
127+
(testing "multi arity defasync"
128+
(testing "simple"
129+
(let [fvar (defasync af5 ([] :kw) ([a] a))
130+
vmeta (meta fvar)]
131+
(is (= 'af5 (:name vmeta)))
132+
(is (= '([] [a]) (:arglists vmeta)))
133+
(is (:async vmeta))
134+
(is (inspect/iscoroutinefunction af5))
135+
(is (not (contains? vmeta :doc)))))
136+
137+
(testing "with docstring"
138+
(let [fvar (defasync af6
139+
"multi-arity docstring"
140+
([] :kw)
141+
([a] a))
142+
vmeta (meta fvar)]
143+
(is (= 'af6 (:name vmeta)))
144+
(is (= '([] [a]) (:arglists vmeta)))
145+
(is (:async vmeta))
146+
(is (inspect/iscoroutinefunction af6))
147+
(is (= "multi-arity docstring" (:doc vmeta)))))
148+
149+
(testing "with attr-map"
150+
(let [fvar (defasync af7
151+
{:added "0.1"}
152+
([] :kw)
153+
([a] a))
154+
vmeta (meta fvar)]
155+
(is (= 'af7 (:name vmeta)))
156+
(is (= '([] [a]) (:arglists vmeta)))
157+
(is (:async vmeta))
158+
(is (inspect/iscoroutinefunction af7))
159+
(is (= "0.1" (:added vmeta)))
160+
(is (not (contains? vmeta :doc)))))
161+
162+
(testing "attr-map and docstring"
163+
(let [fvar (defasync af8
164+
"another multi-arity docstring"
165+
{:added "0.1"}
166+
([] :kw)
167+
([a] a))
168+
vmeta (meta fvar)]
169+
(is (= 'af8 (:name vmeta)))
170+
(is (= '([] [a]) (:arglists vmeta)))
171+
(is (:async vmeta))
172+
(is (inspect/iscoroutinefunction af8))
173+
(is (= "0.1" (:added vmeta)))
174+
(is (= "another multi-arity docstring" (:doc vmeta)))))))
175+
176+
(deftest fn-meta-test
6177
(testing "fn has no meta to start"
7178
(is (nil? (meta (fn* []))))
8179
(is (nil? (meta (fn []))))

0 commit comments

Comments
 (0)