Skip to content

Commit 898ae20

Browse files
authored
Meta utility functions (#449)
* IReference interface * Implementa IReference and shuffle some interfaces * E d i t * Slot change * More interface shakeup * Change a lot of stuff * Break out ReferenceBase and add tests * Don't forget symbols * Minor stuff * Fix lint and format * Use Python stuff instead of my dumb thing * Some minor stuff * vary-meta tests
1 parent 449417f commit 898ae20

24 files changed

+574
-300
lines changed

src/basilisp/core.lpy

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454
(.-meta o)
5555
nil)))
5656

57-
(def ^{:doc "Return o with the supplied metadata map assoc'ed into its existing metadata."
57+
(def ^{:doc "Return an object of the same type and representing the same value as
58+
o with meta as its metadata. Of Basilisp's builtin types, only those
59+
implementing the interface IWithMeta support `with-meta`."
5860
:arglists '([o meta])}
5961
with-meta
6062
(fn* with-meta [o meta]
@@ -180,6 +182,14 @@
180182
(fn apply [f & args]
181183
(basilisp.lang.runtime/apply f args)))
182184

185+
(def ^{:doc "Return an object of the same type and representing the same value as o
186+
(as by `with-meta`) with the new object's metadata set to the value of
187+
`(apply f (meta o) & args)`."
188+
:arglists '([o f & args])}
189+
vary-meta
190+
(fn* vary-meta [o f & args]
191+
(with-meta o (apply f (meta o) args))))
192+
183193
(def ^{:doc "Concatenate the sequences given by seqs into a single Seq."
184194
:arglists '([& seqs])}
185195
concat
@@ -281,9 +291,12 @@
281291
fmeta (if (map? (first body))
282292
(first body)
283293
nil)
294+
fname (if fmeta
295+
(vary-meta name conj fmeta)
296+
name)
284297
fname (if doc
285-
(with-meta name (assoc fmeta :doc doc))
286-
(with-meta name fmeta))
298+
(vary-meta fname assoc :doc doc)
299+
(vary-meta fname conj fmeta))
287300
body (if fmeta
288301
(rest body)
289302
body)
@@ -296,7 +309,7 @@
296309
(conj sigs (ffirst arities)))
297310
(apply list sigs)))
298311
(list (first body)))
299-
fname (with-meta fname {:arglists (list 'quote fsigs)})
312+
fname (vary-meta fname assoc :arglists (list 'quote fsigs))
300313
body (if multi?
301314
body
302315
(cons
@@ -416,8 +429,8 @@
416429
(rest body)
417430
body)
418431
fname (if doc
419-
(with-meta name (assoc fmeta :doc doc :macro true))
420-
(with-meta name (assoc fmeta :macro true)))
432+
(vary-meta name assoc :doc doc :macro true)
433+
(vary-meta name assoc :macro true))
421434
multi? (list? (first body))
422435
fsigs (if multi?
423436
(loop [arities body
@@ -427,7 +440,7 @@
427440
(conj sigs (ffirst arities)))
428441
`(quote ~(apply list sigs))))
429442
`(quote ~(list (first body))))
430-
fname (with-meta fname {:arglists fsigs})
443+
fname (vary-meta fname assoc :arglists fsigs)
431444

432445
add-implicit-args (fn [body]
433446
(cons
@@ -2953,6 +2966,23 @@
29532966
ctx
29542967
module)))
29552968

2969+
;;;;;;;;;;;;;;;;;;;
2970+
;; Ref Utilities ;;
2971+
;;;;;;;;;;;;;;;;;;;
2972+
2973+
(defn alter-meta!
2974+
"Atomically swap the metadata on reference o to the result of (apply f m args) where
2975+
m is the current metadata of o. f should be free of side effects. References include
2976+
atoms, namespaces, and vars."
2977+
[o f & args]
2978+
(apply-method o alter-meta f args))
2979+
2980+
(defn reset-meta!
2981+
"Atomically swap the metadata on reference o to meta. References include
2982+
atoms, namespaces, and vars."
2983+
[o meta]
2984+
(.reset-meta o meta))
2985+
29562986
;;;;;;;;;;;;;;;;;;;
29572987
;; Var Utilities ;;
29582988
;;;;;;;;;;;;;;;;;;;
@@ -4025,7 +4055,7 @@
40254055

40264056
interfaces (concat interfaces
40274057
'[basilisp.lang.interfaces/IPersistentMap
4028-
basilisp.lang.interfaces/IMeta
4058+
basilisp.lang.interfaces/IWithMeta
40294059
basilisp.lang.interfaces/IRecord
40304060
python/object])
40314061

@@ -4149,10 +4179,9 @@
41494179
(+ ~(count field-kw-set)
41504180
(count ~'_recmap)))
41514181

4152-
;; IMeta
4182+
;; IWithMeta
41534183
(~'with-meta [~this-gs new-meta#]
4154-
(->> (merge (or ~'meta {}) new-meta#)
4155-
(evolve ~this-gs "meta")))
4184+
(evolve ~this-gs "meta" new-meta#))
41564185

41574186
;; IRecord
41584187
(~' ^:classmethod create [cls# ~map-gs]

src/basilisp/lang/atom.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
from typing import Callable, Generic, TypeVar
1+
import threading
2+
from typing import Callable, Generic, Optional, TypeVar
23

34
import atomos.atom
45

5-
from basilisp.lang.interfaces import IDeref
6+
from basilisp.lang.interfaces import IDeref, IPersistentMap
7+
from basilisp.lang.reference import ReferenceBase
68

79
T = TypeVar("T")
810

911

10-
class Atom(IDeref[T], Generic[T]):
11-
__slots__ = ("_atom",)
12+
class Atom(IDeref[T], ReferenceBase, Generic[T]):
13+
__slots__ = ("_atom", "_meta", "_lock")
1214

15+
# pylint: disable=assigning-non-slot
1316
def __init__(self, state: T) -> None:
14-
self._atom = atomos.atom.Atom(state) # pylint: disable=assigning-non-slot
17+
self._atom = atomos.atom.Atom(state)
18+
self._meta: Optional[IPersistentMap] = None
19+
self._lock = threading.Lock()
1520

1621
def compare_and_set(self, old, new):
1722
return self._atom.compare_and_set(old, new)

src/basilisp/lang/compiler/analyzer.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
Vector as VectorNode,
114114
WithMeta,
115115
)
116-
from basilisp.lang.interfaces import IMeta, IRecord, ISeq, IType
116+
from basilisp.lang.interfaces import IMeta, IRecord, ISeq, IType, IWithMeta
117117
from basilisp.lang.runtime import Var
118118
from basilisp.lang.typing import LispForm, ReaderForm
119119
from basilisp.lang.util import count, genname, munge
@@ -236,9 +236,7 @@ def _warn_unused_names(self):
236236
if entry.warn_if_unused and not entry.used:
237237
code_loc = (
238238
Maybe(entry.symbol.meta)
239-
.map(
240-
lambda m: f": {m.val_at(reader.READER_LINE_KW)}" # type: ignore
241-
)
239+
.map(lambda m: f": {m.val_at(reader.READER_LINE_KW)}")
242240
.or_else_get("")
243241
)
244242
logger.warning(
@@ -514,15 +512,16 @@ def has_meta_prop(o: Union[IMeta, Var]) -> bool:
514512
def _loc(form: Union[LispForm, ISeq]) -> Optional[Tuple[int, int]]:
515513
"""Fetch the location of the form in the original filename from the
516514
input form, if it has metadata."""
517-
try:
518-
meta = form.meta # type: ignore
519-
line = meta.get(reader.READER_LINE_KW) # type: ignore
520-
col = meta.get(reader.READER_COL_KW) # type: ignore
521-
except AttributeError:
522-
return None
523-
else:
524-
assert isinstance(line, int) and isinstance(col, int)
525-
return line, col
515+
# Technically, IMeta is sufficient for fetching `form.meta` but the
516+
# reader only applies line and column metadata to IWithMeta instances
517+
if isinstance(form, IWithMeta):
518+
meta = form.meta
519+
if meta is not None:
520+
line = meta.get(reader.READER_LINE_KW)
521+
col = meta.get(reader.READER_COL_KW)
522+
if isinstance(line, int) and isinstance(col, int):
523+
return line, col
524+
return None
526525

527526

528527
def _with_loc(f: AnalyzeFunction):
@@ -655,7 +654,7 @@ def _def_ast( # pylint: disable=too-many-branches,too-many-locals
655654
def_loc = _loc(form) or (None, None)
656655
def_node_env = ctx.get_node_env()
657656
def_meta = _clean_meta(
658-
name.meta.update(
657+
name.meta.update( # type: ignore [union-attr]
659658
lmap.map(
660659
{
661660
COL_KW: def_loc[1],
@@ -1222,9 +1221,7 @@ def _deftype_ast( # pylint: disable=too-many-branches
12221221
field_default = (
12231222
Maybe(field.meta)
12241223
.map(
1225-
lambda m: m.val_at( # type: ignore
1226-
SYM_DEFAULT_META_KEY, __DEFTYPE_DEFAULT_SENTINEL
1227-
)
1224+
lambda m: m.val_at(SYM_DEFAULT_META_KEY, __DEFTYPE_DEFAULT_SENTINEL)
12281225
)
12291226
.value
12301227
)

src/basilisp/lang/compiler/generator.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,17 +405,15 @@ def _is_dynamic(v: Var) -> bool:
405405
Var access."""
406406
return (
407407
Maybe(v.meta)
408-
.map(lambda m: m.get(SYM_DYNAMIC_META_KEY, None)) # type: ignore
408+
.map(lambda m: m.get(SYM_DYNAMIC_META_KEY, None))
409409
.or_else_get(False)
410410
)
411411

412412

413413
def _is_redefable(v: Var) -> bool:
414414
"""Return True if the Var can be redefined."""
415415
return (
416-
Maybe(v.meta)
417-
.map(lambda m: m.get(SYM_REDEF_META_KEY, None)) # type: ignore
418-
.or_else_get(False)
416+
Maybe(v.meta).map(lambda m: m.get(SYM_REDEF_META_KEY, None)).or_else_get(False)
419417
)
420418

421419

src/basilisp/lang/interfaces.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from abc import ABC, abstractmethod
33
from typing import (
44
AbstractSet,
5+
Callable,
56
Generic,
67
Iterable,
78
Iterator,
89
Mapping,
910
Optional,
1011
Sequence,
1112
TypeVar,
13+
Union,
1214
)
1315

1416
from basilisp.lang.obj import LispObject as _LispObject, seq_lrepr
@@ -77,14 +79,33 @@ class IMeta(ABC):
7779
def meta(self) -> Optional["IPersistentMap"]:
7880
raise NotImplementedError()
7981

82+
83+
T_with_meta = TypeVar("T_with_meta", bound="IWithMeta")
84+
85+
86+
class IWithMeta(IMeta):
87+
__slots__ = ()
88+
8089
@abstractmethod
81-
def with_meta(self, meta: "IPersistentMap") -> "IMeta":
90+
def with_meta(self: T_with_meta, meta: "Optional[IPersistentMap]") -> T_with_meta:
8291
raise NotImplementedError()
8392

8493

8594
ILispObject = _LispObject
8695

8796

97+
class IReference(IMeta):
98+
__slots__ = ()
99+
100+
@abstractmethod
101+
def alter_meta(self, f: Callable[..., "IPersistentMap"], *args) -> "IPersistentMap":
102+
raise NotImplementedError()
103+
104+
@abstractmethod
105+
def reset_meta(self, meta: "IPersistentMap") -> "IPersistentMap":
106+
raise NotImplementedError()
107+
108+
88109
class IReversible(Generic[T]):
89110
__slots__ = ()
90111

@@ -113,11 +134,14 @@ def val_at(self, k: K, default: Optional[V] = None) -> Optional[V]:
113134
raise NotImplementedError()
114135

115136

137+
T_pcoll = TypeVar("T_pcoll", bound="IPersistentCollection", covariant=True)
138+
139+
116140
class IPersistentCollection(ISeqable[T]):
117141
__slots__ = ()
118142

119143
@abstractmethod
120-
def cons(self, *elems: T) -> "IPersistentCollection[T]":
144+
def cons(self: T_pcoll, *elems: T) -> "T_pcoll":
121145
raise NotImplementedError()
122146

123147
@staticmethod
@@ -126,13 +150,16 @@ def empty() -> "IPersistentCollection[T]":
126150
raise NotImplementedError()
127151

128152

153+
T_assoc = TypeVar("T_assoc", bound="IAssociative")
154+
155+
129156
class IAssociative(
130157
ILookup[K, V], Mapping[K, V], IPersistentCollection[IMapEntry[K, V]]
131158
):
132159
__slots__ = ()
133160

134161
@abstractmethod
135-
def assoc(self, *kvs) -> "IAssociative[K, V]":
162+
def assoc(self: T_assoc, *kvs) -> T_assoc:
136163
raise NotImplementedError()
137164

138165
@abstractmethod
@@ -144,6 +171,9 @@ def entry(self, k: K) -> Optional[IMapEntry[K, V]]:
144171
raise NotImplementedError()
145172

146173

174+
T_stack = TypeVar("T_stack", bound="IPersistentStack")
175+
176+
147177
class IPersistentStack(IPersistentCollection[T]):
148178
__slots__ = ()
149179

@@ -152,30 +182,45 @@ def peek(self) -> Optional[T]:
152182
raise NotImplementedError()
153183

154184
@abstractmethod
155-
def pop(self) -> "IPersistentStack[T]":
185+
def pop(self: T_stack) -> T_stack:
156186
raise NotImplementedError()
157187

158188

159189
class IPersistentList(ISequential, IPersistentStack[T]):
160190
__slots__ = ()
161191

162192

193+
T_map = TypeVar("T_map", bound="IPersistentMap")
194+
195+
163196
class IPersistentMap(ICounted, IAssociative[K, V]):
164197
__slots__ = ()
165198

166199
@abstractmethod
167-
def dissoc(self, *ks: K) -> "IPersistentMap[K, V]":
200+
def cons( # type: ignore[override]
201+
self: T_map, *elems: Union[IMapEntry[K, V], "IPersistentMap[K, V]"]
202+
) -> T_map:
203+
raise NotImplementedError()
204+
205+
@abstractmethod
206+
def dissoc(self: T_map, *ks: K) -> T_map:
168207
raise NotImplementedError()
169208

170209

210+
T_set = TypeVar("T_set", bound="IPersistentSet")
211+
212+
171213
class IPersistentSet(AbstractSet[T], ICounted, IPersistentCollection[T]):
172214
__slots__ = ()
173215

174216
@abstractmethod
175-
def disj(self, *elems: T) -> "IPersistentSet[T]":
217+
def disj(self: T_set, *elems: T) -> T_set:
176218
raise NotImplementedError()
177219

178220

221+
T_vec = TypeVar("T_vec", bound="IPersistentVector")
222+
223+
179224
class IPersistentVector(
180225
Sequence[T],
181226
IAssociative[int, T],
@@ -187,11 +232,15 @@ class IPersistentVector(
187232
__slots__ = ()
188233

189234
@abstractmethod
190-
def cons(self, *elems: T) -> "IPersistentVector[T]": # type: ignore
235+
def assoc(self: T_vec, *kvs) -> T_vec: # type: ignore[override]
236+
raise NotImplementedError()
237+
238+
@abstractmethod
239+
def cons(self: T_vec, *elems: T) -> T_vec: # type: ignore[override]
191240
raise NotImplementedError()
192241

193242
@abstractmethod
194-
def seq(self) -> "ISeq[T]": # type: ignore
243+
def seq(self) -> "ISeq[T]": # type: ignore[override]
195244
raise NotImplementedError()
196245

197246

0 commit comments

Comments
 (0)