Skip to content

Commit 23e77d0

Browse files
authored
Allow creating keywords and symbols from other keywords and symbols (#914)
Fixes #911
1 parent d67a0f7 commit 23e77d0

File tree

4 files changed

+131
-7
lines changed

4 files changed

+131
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Fixed
1515
* Fix a bug where `.` characters were not allowed in keyword names (#899)
1616
* Fix a bug where nested quotation marks were not escaped properly by various print functions and at the REPL (#894)
17-
* Fixed a bug that caused a syntax error when presenting any filepath that includes the MS-Windows `\` file separator to the cli run command (#912)
17+
* Fix a bug that caused a syntax error when presenting any filepath that includes the MS-Windows `\` file separator to the cli run command (#912)
18+
* Fix a bug where the core functions `symbol` and `keyword` would not accept non-string data types (#911)
1819

1920
### Other
2021
* Update Sphinx documentation theme (#909)

src/basilisp/core.lpy

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -508,19 +508,30 @@
508508
(basilisp.lang.queue/queue coll)))
509509

510510
(defn symbol
511-
"Create a new symbol with ``name`` and optional namespace ``ns``."
511+
"Create a new symbol with ``name`` and optional namespace ``ns``.
512+
513+
``name`` may be keyword, symbol, string, or Var. If ``name`` is a keyword or symbol with a
514+
namespace, the namespace will be included in the resulting value. If ``name`` is a Var,
515+
the Var's namespace will always be the namespace of the resulting value.
516+
517+
If ``ns`` is not ``nil``, then both ``name`` and ``ns`` must be strings."
512518
([name]
513-
(basilisp.lang.symbol/symbol name))
519+
(basilisp.lang.runtime/symbol-from-name name))
514520
([ns name]
515-
(basilisp.lang.symbol/symbol name ns)))
521+
(basilisp.lang.runtime/symbol name ns)))
516522

517523
(defn keyword
518524
"Create a new keyword with ``name`` and optional namespace ``ns``\\. Keywords will
519-
have the colon prefix added automatically, so it should not be provided."
525+
have the colon prefix added automatically, so it should not be provided.
526+
527+
``name`` may be keyword, symbol, or string. If ``name`` is a keyword or symbol with a
528+
namespace, the namespace will be included in the resulting value.
529+
530+
If ``ns`` is not ``nil``, then both ``name`` and ``ns`` must be strings."
520531
([name]
521-
(basilisp.lang.keyword/keyword name))
532+
(basilisp.lang.runtime/keyword-from-name name))
522533
([ns name]
523-
(basilisp.lang.keyword/keyword name ns)))
534+
(basilisp.lang.runtime/keyword name ns)))
524535

525536
(defn name
526537
"Return the name of a string, symbol, or keyword."

src/basilisp/lang/runtime.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
Iterator,
2727
List,
2828
Mapping,
29+
NoReturn,
2930
Optional,
3031
Set,
3132
Tuple,
@@ -976,6 +977,69 @@ def pop_thread_bindings() -> None:
976977
T = TypeVar("T")
977978

978979

980+
def keyword(name: Any, ns: Any = None) -> kw.Keyword:
981+
"""Return a new keyword with runtime type checks for name and namespace."""
982+
if not isinstance(name, str):
983+
raise TypeError(f"Keyword name must be a string, not '{type(name)}'")
984+
if not isinstance(ns, (type(None), str)):
985+
raise TypeError(f"Keyword namespace must be a string or nil, not '{type(ns)}")
986+
return kw.keyword(name, ns)
987+
988+
989+
@functools.singledispatch
990+
def keyword_from_name(o: Any) -> NoReturn:
991+
raise TypeError(f"Cannot create keyword from '{type(o)}'")
992+
993+
994+
@keyword_from_name.register(kw.Keyword)
995+
def _keyword_from_name_keyword(o: kw.Keyword) -> kw.Keyword:
996+
return o
997+
998+
999+
@keyword_from_name.register(sym.Symbol)
1000+
def _keyword_from_name_symbol(o: sym.Symbol) -> kw.Keyword:
1001+
return kw.keyword(o.name, ns=o.ns)
1002+
1003+
1004+
@keyword_from_name.register(str)
1005+
def _keyword_from_name_str(o: str) -> kw.Keyword:
1006+
return kw.keyword(o)
1007+
1008+
1009+
def symbol(name: Any, ns: Any = None) -> sym.Symbol:
1010+
"""Return a new symbol with runtime type checks for name and namespace."""
1011+
if not isinstance(name, str):
1012+
raise TypeError(f"Symbol name must be a string, not '{type(name)}'")
1013+
if not isinstance(ns, (type(None), str)):
1014+
raise TypeError(f"Symbol namespace must be a string or nil, not '{type(ns)}")
1015+
return sym.symbol(name, ns)
1016+
1017+
1018+
@functools.singledispatch
1019+
def symbol_from_name(o: Any) -> NoReturn:
1020+
raise TypeError(f"Cannot create symbol from '{type(o)}'")
1021+
1022+
1023+
@symbol_from_name.register(kw.Keyword)
1024+
def _symbol_from_name_keyword(o: kw.Keyword) -> sym.Symbol:
1025+
return sym.symbol(o.name, ns=o.ns)
1026+
1027+
1028+
@symbol_from_name.register(sym.Symbol)
1029+
def _symbol_from_name_symbol(o: sym.Symbol) -> sym.Symbol:
1030+
return o
1031+
1032+
1033+
@symbol_from_name.register(str)
1034+
def _symbol_from_name_str(o: str) -> sym.Symbol:
1035+
return sym.symbol(o)
1036+
1037+
1038+
@symbol_from_name.register(Var)
1039+
def _symbol_from_name_var(o: Var) -> sym.Symbol:
1040+
return sym.symbol(o.name.name, ns=o.ns.name)
1041+
1042+
9791043
@functools.singledispatch
9801044
def first(o):
9811045
"""If o is a ISeq, return the first element from o. If o is None, return

tests/basilisp/test_core_fns.lpy

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,54 @@
77
[basilisp.set :as set]
88
[basilisp.test :refer [deftest are is testing]]))
99

10+
(deftest keyword-test
11+
(testing "name only"
12+
(are [res name] (= res (keyword name))
13+
(keyword "") ""
14+
:name "name"
15+
:ns/name :ns/name
16+
:ns/name 'ns/name)
17+
18+
(are [v] (thrown? python/TypeError (keyword v))
19+
nil
20+
#'basilisp.core/map))
21+
22+
(testing "name and namespace"
23+
(are [res ns name] (= res (keyword ns name))
24+
(keyword "" "") "" ""
25+
:name nil "name"
26+
:ns/name "ns" "name"
27+
:dotted.ns/name "dotted.ns" "name")
28+
29+
(are [ns name] (thrown? python/TypeError (keyword ns name))
30+
nil nil
31+
:kw :kw
32+
'sym 'sym)))
33+
34+
(deftest symbol-test
35+
(testing "name only"
36+
(are [res name] (= res (symbol name))
37+
(symbol "") ""
38+
'name "name"
39+
'ns/name :ns/name
40+
'ns/name 'ns/name
41+
'basilisp.core/map #'basilisp.core/map)
42+
43+
(are [v] (thrown? python/TypeError (symbol v))
44+
nil))
45+
46+
(testing "name and namespace"
47+
(are [res ns name] (= res (symbol ns name))
48+
(symbol "" "") "" ""
49+
'name nil "name"
50+
'ns/name "ns" "name"
51+
'dotted.ns/name "dotted.ns" "name")
52+
53+
(are [ns name] (thrown? python/TypeError (symbol ns name))
54+
nil nil
55+
:kw :kw
56+
'sym 'sym)))
57+
1058
(deftest vary-meta-test
1159
(testing "with lists"
1260
(let [l '(:list)

0 commit comments

Comments
 (0)