Skip to content

Commit cd62689

Browse files
chrisrink10Christopher Rink
andauthored
Add INamed interface for Keywords and Symbols (#885)
This PR introduces the `INamed` interface (similar to Clojure's `Named`) which is applied to the Keyword and Symbol classes. Having an interface for this allows us to perform type checking for relevant features (in particular, the existence of a name or namespace) without needing the import either class directly (thus avoiding circular dependencies). I also added type annotations for the Lisp representation keyword arguments (which is a useful addition ahead of #882 merging). Fixes #884 Co-authored-by: Christopher Rink <[email protected]>
1 parent 3ce080d commit cd62689

File tree

11 files changed

+80
-27
lines changed

11 files changed

+80
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
* Added custom exception formatting for `basilisp.lang.compiler.exception.CompilerException` and `basilisp.lang.reader.SyntaxError` to show more useful details to users on errors (#870)
1717
* Added `merge-with` core function (#860)
1818
* Added `fnext` core function (#879)
19+
* Added `INamed` interface for Keywords and Symbols (#884)
1920

2021
### Changed
2122
* Cause exceptions arising from compilation issues during macroexpansion will no longer be nested for each level of macroexpansion (#852)

src/basilisp/lang/interfaces.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
Union,
1616
)
1717

18+
from typing_extensions import Unpack
19+
1820
from basilisp.lang.obj import LispObject as _LispObject
19-
from basilisp.lang.obj import seq_lrepr
21+
from basilisp.lang.obj import PrintSettings, seq_lrepr
2022

2123
T = TypeVar("T")
2224

@@ -107,6 +109,20 @@ def with_meta(self: T_with_meta, meta: "Optional[IPersistentMap]") -> T_with_met
107109
raise NotImplementedError()
108110

109111

112+
class INamed(ABC):
113+
__slots__ = ()
114+
115+
@property
116+
@abstractmethod
117+
def name(self) -> str:
118+
raise NotImplementedError()
119+
120+
@property
121+
@abstractmethod
122+
def ns(self) -> Optional[str]:
123+
raise NotImplementedError()
124+
125+
110126
ILispObject = _LispObject
111127

112128

@@ -407,11 +423,11 @@ def create(cls, m: IPersistentMap) -> "IRecord":
407423
"""Class method constructor from an IPersistentMap instance."""
408424
raise NotImplementedError()
409425

410-
def _lrepr(self, **kwargs) -> str:
426+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
411427
return self._record_lrepr(kwargs)
412428

413429
@abstractmethod
414-
def _record_lrepr(self, kwargs: Mapping) -> str:
430+
def _record_lrepr(self, kwargs: PrintSettings) -> str:
415431
"""Translation method converting Python keyword arguments into a
416432
Python dict.
417433
@@ -487,7 +503,7 @@ def cons(self, elem: T) -> "ISeq[T]":
487503
def seq(self) -> "Optional[ISeq[T]]":
488504
return self
489505

490-
def _lrepr(self, **kwargs):
506+
def _lrepr(self, **kwargs: Unpack[PrintSettings]):
491507
return seq_lrepr(iter(self), "(", ")", **kwargs)
492508

493509
def __eq__(self, other):

src/basilisp/lang/keyword.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22
from functools import total_ordering
33
from typing import Iterable, Optional, Union
44

5+
from typing_extensions import Unpack
6+
57
from basilisp.lang import map as lmap
68
from basilisp.lang.interfaces import (
79
IAssociative,
810
ILispObject,
11+
INamed,
912
IPersistentMap,
1013
IPersistentSet,
1114
)
15+
from basilisp.lang.obj import PrintSettings
1216

1317
_LOCK = threading.Lock()
1418
_INTERN: IPersistentMap[int, "Keyword"] = lmap.PersistentMap.empty()
1519

1620

1721
@total_ordering
18-
class Keyword(ILispObject):
22+
class Keyword(ILispObject, INamed):
1923
__slots__ = ("_name", "_ns", "_hash")
2024

2125
def __init__(self, name: str, ns: Optional[str] = None) -> None:
@@ -31,7 +35,7 @@ def name(self) -> str:
3135
def ns(self) -> Optional[str]:
3236
return self._ns
3337

34-
def _lrepr(self, **kwargs) -> str:
38+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
3539
if self._ns is not None:
3640
return f":{self._ns}/{self._name}"
3741
return f":{self._name}"

src/basilisp/lang/list.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from pyrsistent import PList, plist # noqa # pylint: disable=unused-import
44
from pyrsistent._plist import _EMPTY_PLIST # pylint: disable=import-private-name
5+
from typing_extensions import Unpack
56

67
from basilisp.lang.interfaces import IPersistentList, IPersistentMap, ISeq, IWithMeta
8+
from basilisp.lang.obj import PrintSettings
79
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
810
from basilisp.lang.seq import EMPTY as _EMPTY_SEQ
911

@@ -36,7 +38,7 @@ def __hash__(self):
3638
def __len__(self):
3739
return len(self._inner)
3840

39-
def _lrepr(self, **kwargs) -> str:
41+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
4042
return _seq_lrepr(self._inner, "(", ")", meta=self._meta, **kwargs)
4143

4244
@property

src/basilisp/lang/map.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from immutables import Map as _Map
55
from immutables import MapMutation
6+
from typing_extensions import Unpack
67

78
from basilisp.lang.interfaces import (
89
IEvolveableCollection,
@@ -14,6 +15,7 @@
1415
ITransientMap,
1516
IWithMeta,
1617
)
18+
from basilisp.lang.obj import PrintSettings
1719
from basilisp.lang.obj import map_lrepr as _map_lrepr
1820
from basilisp.lang.seq import sequence
1921
from basilisp.lang.vector import MapEntry
@@ -160,7 +162,7 @@ def __iter__(self):
160162
def __len__(self):
161163
return len(self._inner)
162164

163-
def _lrepr(self, **kwargs):
165+
def _lrepr(self, **kwargs: Unpack[PrintSettings]):
164166
return _map_lrepr(
165167
self._inner.items, start="{", end="}", meta=self._meta, **kwargs
166168
)

src/basilisp/lang/obj.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
from functools import singledispatch
99
from itertools import islice
1010
from pathlib import Path
11-
from typing import Any, Callable, Iterable, Pattern, Tuple, Union
11+
from typing import Any, Callable, Iterable, Pattern, Tuple, Union, cast
12+
13+
from typing_extensions import TypedDict, Unpack
1214

1315
PrintCountSetting = Union[bool, int, None]
1416

@@ -23,17 +25,28 @@
2325
PRINT_SEPARATOR = " "
2426

2527

26-
def _dec_print_level(lvl: PrintCountSetting):
28+
class PrintSettings(TypedDict, total=False):
29+
human_readable: bool
30+
print_dup: bool
31+
print_length: PrintCountSetting
32+
print_level: PrintCountSetting
33+
print_meta: bool
34+
print_readably: bool
35+
36+
37+
def _dec_print_level(lvl: PrintCountSetting) -> PrintCountSetting:
2738
"""Decrement the print level if it is numeric."""
2839
if isinstance(lvl, int):
2940
return lvl - 1
3041
return lvl
3142

3243

33-
def _process_kwargs(**kwargs):
44+
def _process_kwargs(**kwargs: Unpack[PrintSettings]) -> PrintSettings:
3445
"""Process keyword arguments, decreasing the print-level. Should be called
3546
after examining the print level for the current level."""
36-
return dict(kwargs, print_level=_dec_print_level(kwargs["print_level"]))
47+
return cast(
48+
PrintSettings, dict(kwargs, print_level=_dec_print_level(kwargs["print_level"]))
49+
)
3750

3851

3952
class LispObject(ABC):
@@ -53,13 +66,13 @@ def __str__(self):
5366
return self.lrepr(human_readable=True)
5467

5568
@abstractmethod
56-
def _lrepr(self, **kwargs) -> str:
69+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
5770
"""Private Lisp representation method. Callers (including object
5871
internal callers) should not call this method directly, but instead
5972
should use the module function .lrepr()."""
6073
raise NotImplementedError()
6174

62-
def lrepr(self, **kwargs) -> str:
75+
def lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
6376
"""Return a string representation of this Lisp object which can be
6477
read by the reader."""
6578
return lrepr(self, **kwargs)
@@ -70,7 +83,7 @@ def map_lrepr(
7083
start: str,
7184
end: str,
7285
meta=None,
73-
**kwargs,
86+
**kwargs: Unpack[PrintSettings],
7487
) -> str:
7588
"""Produce a Lisp representation of an associative collection, bookended
7689
with the start and end string supplied. The entries argument must be a
@@ -109,7 +122,11 @@ def entry_reprs():
109122

110123

111124
def seq_lrepr(
112-
iterable: Iterable[Any], start: str, end: str, meta=None, **kwargs
125+
iterable: Iterable[Any],
126+
start: str,
127+
end: str,
128+
meta=None,
129+
**kwargs: Unpack[PrintSettings],
113130
) -> str:
114131
"""Produce a Lisp representation of a sequential collection, bookended
115132
with the start and end string supplied. The keyword arguments will be
@@ -224,22 +241,22 @@ def _lrepr_str(
224241

225242

226243
@lrepr.register(list)
227-
def _lrepr_py_list(o: list, **kwargs) -> str:
244+
def _lrepr_py_list(o: list, **kwargs: Unpack[PrintSettings]) -> str:
228245
return f"#py {seq_lrepr(o, '[', ']', **kwargs)}"
229246

230247

231248
@lrepr.register(dict)
232-
def _lrepr_py_dict(o: dict, **kwargs) -> str:
249+
def _lrepr_py_dict(o: dict, **kwargs: Unpack[PrintSettings]) -> str:
233250
return f"#py {map_lrepr(o.items, '{', '}', **kwargs)}"
234251

235252

236253
@lrepr.register(set)
237-
def _lrepr_py_set(o: set, **kwargs) -> str:
254+
def _lrepr_py_set(o: set, **kwargs: Unpack[PrintSettings]) -> str:
238255
return f"#py {seq_lrepr(o, '#{', '}', **kwargs)}"
239256

240257

241258
@lrepr.register(tuple)
242-
def _lrepr_py_tuple(o: tuple, **kwargs) -> str:
259+
def _lrepr_py_tuple(o: tuple, **kwargs: Unpack[PrintSettings]) -> str:
243260
return f"#py {seq_lrepr(o, '(', ')', **kwargs)}"
244261

245262

src/basilisp/lang/queue.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional, TypeVar
22

33
from pyrsistent import PDeque, pdeque # noqa # pylint: disable=unused-import
4+
from typing_extensions import Unpack
45

56
from basilisp.lang.interfaces import (
67
ILispObject,
@@ -10,6 +11,7 @@
1011
IWithMeta,
1112
seq_equals,
1213
)
14+
from basilisp.lang.obj import PrintSettings
1315
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
1416
from basilisp.lang.seq import sequence
1517

@@ -47,7 +49,7 @@ def __iter__(self):
4749
def __len__(self):
4850
return len(self._inner)
4951

50-
def _lrepr(self, **kwargs) -> str:
52+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
5153
return _seq_lrepr(self._inner, "#queue (", ")", meta=self._meta, **kwargs)
5254

5355
@property

src/basilisp/lang/reader.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434

3535
import attr
36+
from typing_extensions import Unpack
3637

3738
from basilisp.lang import keyword as kw
3839
from basilisp.lang import list as llist
@@ -56,6 +57,7 @@
5657
IType,
5758
IWithMeta,
5859
)
60+
from basilisp.lang.obj import PrintSettings
5961
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
6062
from basilisp.lang.runtime import (
6163
READER_COND_DEFAULT_FEATURE_SET,
@@ -520,7 +522,7 @@ def select_feature(
520522
return form
521523
return self.FEATURE_NOT_PRESENT
522524

523-
def _lrepr(self, **kwargs) -> str:
525+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
524526
return _seq_lrepr(
525527
chain.from_iterable(self._feature_vec),
526528
"#?@(" if self.is_splicing else "#?(",

src/basilisp/lang/set.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from immutables import Map as _Map
55
from immutables import MapMutation
6+
from typing_extensions import Unpack
67

78
from basilisp.lang.interfaces import (
89
IEvolveableCollection,
@@ -13,6 +14,7 @@
1314
ITransientSet,
1415
IWithMeta,
1516
)
17+
from basilisp.lang.obj import PrintSettings
1618
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
1719
from basilisp.lang.seq import sequence
1820

@@ -111,7 +113,7 @@ def __iter__(self):
111113
def __len__(self):
112114
return len(self._inner)
113115

114-
def _lrepr(self, **kwargs):
116+
def _lrepr(self, **kwargs: Unpack[PrintSettings]):
115117
return _seq_lrepr(self._inner, "#{", "}", meta=self._meta, **kwargs)
116118

117119
issubset = _PySet.__le__

src/basilisp/lang/symbol.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
from functools import total_ordering
22
from typing import Optional, Union
33

4+
from typing_extensions import Unpack
5+
46
from basilisp.lang.interfaces import (
57
IAssociative,
68
ILispObject,
9+
INamed,
710
IPersistentMap,
811
IPersistentSet,
912
IWithMeta,
1013
)
11-
from basilisp.lang.obj import lrepr
14+
from basilisp.lang.obj import PrintSettings, lrepr
1215
from basilisp.lang.util import munge
1316

1417

1518
@total_ordering
16-
class Symbol(ILispObject, IWithMeta):
19+
class Symbol(ILispObject, INamed, IWithMeta):
1720
__slots__ = ("_name", "_ns", "_meta", "_hash")
1821

1922
def __init__(
@@ -24,7 +27,7 @@ def __init__(
2427
self._meta = meta
2528
self._hash = hash((ns, name))
2629

27-
def _lrepr(self, **kwargs) -> str:
30+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
2831
print_meta = kwargs["print_meta"]
2932

3033
if self._ns is not None:

0 commit comments

Comments
 (0)