Skip to content

Commit 5fe98dc

Browse files
authored
Namespace.get_or_create should not auto-refer 'basilisp.core (#481)
* EDN Reader * Revert incorrect changes * No EDN * Types * But this * This is a nightmare * Remove unused runtime function * Changelog * Type fix * Remove unused PyTest fixture usages * Fix up the generator * Fix the CLI * Test running with subprocess as well * Ok fine this doesn't work * Emit directly to the Namespace `*generated-python*` * Order the args better
1 parent dc03efc commit 5fe98dc

File tree

14 files changed

+629
-526
lines changed

14 files changed

+629
-526
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
* Change the default user namespace to `basilisp.user` (#466)
1717
* Changed multi-methods to use a `threading.Lock` internally rather than an Atom (#478)
1818
* Changed the Basilisp module type from `types.ModuleType` to a custom subtype with support for custom attributes (#482)
19+
* Basilisp's runtime function `Namespace.get_or_create` no longer refers `basilisp.core` by default, which allows callers to exclude `basilisp.core` names in the `ns` macro (#481)
1920

2021
### Fixed
2122
* Fixed a reader bug where no exception was being thrown splicing reader conditional forms appeared outside of valid splicing contexts (#470)

src/basilisp/cli.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
import basilisp.lang.runtime as runtime
1212
import basilisp.lang.symbol as sym
1313
import basilisp.main as basilisp
14-
from basilisp.lang.typing import BasilispModule
1514
from basilisp.prompt import get_prompter
1615

1716
CLI_INPUT_FILE_PATH = "<CLI Input>"
1817
REPL_INPUT_FILE_PATH = "<REPL Input>"
18+
REPL_NS = "basilisp.repl"
1919
STDIN_INPUT_FILE_PATH = "<stdin>"
2020
STDIN_FILE_NAME = "-"
2121

@@ -25,41 +25,43 @@ def cli():
2525
"""Basilisp is a Lisp dialect inspired by Clojure targeting Python 3."""
2626

2727

28-
def eval_file(filename: str, ctx: compiler.CompilerContext, module: BasilispModule):
28+
def eval_file(filename: str, ctx: compiler.CompilerContext, ns: runtime.Namespace):
2929
"""Evaluate a file with the given name into a Python module AST node."""
3030
last = None
3131
for form in reader.read_file(filename, resolver=runtime.resolve_alias):
3232
assert not isinstance(form, reader.ReaderConditional)
33-
last = compiler.compile_and_exec_form(form, ctx, module)
33+
last = compiler.compile_and_exec_form(form, ctx, ns)
3434
return last
3535

3636

37-
def eval_stream(stream, ctx: compiler.CompilerContext, module: BasilispModule):
37+
def eval_stream(stream, ctx: compiler.CompilerContext, ns: runtime.Namespace):
3838
"""Evaluate the forms in stdin into a Python module AST node."""
3939
last = None
4040
for form in reader.read(stream, resolver=runtime.resolve_alias):
4141
assert not isinstance(form, reader.ReaderConditional)
42-
last = compiler.compile_and_exec_form(form, ctx, module)
42+
last = compiler.compile_and_exec_form(form, ctx, ns)
4343
return last
4444

4545

46-
def eval_str(s: str, ctx: compiler.CompilerContext, module: BasilispModule, eof: Any):
46+
def eval_str(s: str, ctx: compiler.CompilerContext, ns: runtime.Namespace, eof: Any):
4747
"""Evaluate the forms in a string into a Python module AST node."""
4848
last = eof
4949
for form in reader.read_str(s, resolver=runtime.resolve_alias, eof=eof):
5050
assert not isinstance(form, reader.ReaderConditional)
51-
last = compiler.compile_and_exec_form(form, ctx, module)
51+
last = compiler.compile_and_exec_form(form, ctx, ns)
5252
return last
5353

5454

5555
def bootstrap_repl(which_ns: str) -> types.ModuleType:
56-
"""Bootstrap the REPL with a few useful vars and returned the
57-
bootstrapped module so it's functions can be used by the REPL
58-
command."""
59-
repl_ns = runtime.Namespace.get_or_create(sym.symbol("basilisp.repl"))
56+
"""Bootstrap the REPL with a few useful vars and returned the bootstrapped
57+
module so it's functions can be used by the REPL command."""
58+
repl_ns = runtime.Namespace.get_or_create(sym.symbol(REPL_NS))
6059
ns = runtime.Namespace.get_or_create(sym.symbol(which_ns))
61-
repl_module = importlib.import_module("basilisp.repl")
62-
ns.add_alias(sym.symbol("basilisp.repl"), repl_ns)
60+
core_ns = runtime.Namespace.get(sym.symbol(runtime.CORE_NS))
61+
assert core_ns is not None
62+
ns.refer_all(core_ns)
63+
repl_module = importlib.import_module(REPL_NS)
64+
ns.add_alias(sym.symbol(REPL_NS), repl_ns)
6365
ns.refer_all(repl_ns)
6466
return repl_module
6567

@@ -133,7 +135,7 @@ def repl(
133135
continue
134136

135137
try:
136-
result = eval_str(lsrc, ctx, ns.module, eof)
138+
result = eval_str(lsrc, ctx, ns, eof)
137139
if result is eof: # pragma: no cover
138140
continue
139141
prompter.print(runtime.lrepr(result))
@@ -214,17 +216,18 @@ def run( # pylint: disable=too-many-arguments
214216
)
215217
eof = object()
216218

219+
core_ns = runtime.Namespace.get(sym.symbol(runtime.CORE_NS))
220+
assert core_ns is not None
221+
217222
with runtime.ns_bindings(in_ns) as ns:
223+
ns.refer_all(core_ns)
224+
218225
if code:
219-
print(runtime.lrepr(eval_str(file_or_code, ctx, ns.module, eof)))
226+
print(runtime.lrepr(eval_str(file_or_code, ctx, ns, eof)))
220227
elif file_or_code == STDIN_FILE_NAME:
221-
print(
222-
runtime.lrepr(
223-
eval_stream(click.get_text_stream("stdin"), ctx, ns.module)
224-
)
225-
)
228+
print(runtime.lrepr(eval_stream(click.get_text_stream("stdin"), ctx, ns)))
226229
else:
227-
print(runtime.lrepr(eval_file(file_or_code, ctx, ns.module)))
230+
print(runtime.lrepr(eval_file(file_or_code, ctx, ns)))
228231

229232

230233
@cli.command(short_help="run tests in a Basilisp project")

src/basilisp/core.lpy

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3191,11 +3191,10 @@
31913191
(defn eval
31923192
"Evaluate a form (not a string) and return its result."
31933193
[form]
3194-
(let [ctx (basilisp.lang.compiler.CompilerContext. "<Eval Input>")
3195-
module (.-module *ns*)]
3194+
(let [ctx (basilisp.lang.compiler.CompilerContext. "<Eval Input>")]
31963195
(basilisp.lang.compiler/compile-and-exec-form form
31973196
ctx
3198-
module)))
3197+
*ns*)))
31993198

32003199
;;;;;;;;;;;;;;;;;;;
32013200
;; Ref Utilities ;;
@@ -3527,6 +3526,7 @@
35273526
Example:
35283527
(ns my.namespace
35293528
\"My namespace with code\"
3529+
(:refer-basilisp :exclude [get])
35303530
(:require
35313531
[basilisp.string :as str])
35323532
(:import inspect))"
@@ -3548,14 +3548,16 @@
35483548
{}
35493549
opts))
35503550

3551-
requires (when (:require opts)
3552-
`(require ~@(map #(list 'quote %) (:require opts))))
3553-
imports (when (:import opts)
3554-
(map (fn [v]
3555-
`(import ~v))
3556-
(:import opts)))]
3551+
requires (when (:require opts)
3552+
`(require ~@(map #(list 'quote %) (:require opts))))
3553+
imports (when (:import opts)
3554+
(map (fn [v]
3555+
`(import ~v))
3556+
(:import opts)))]
35573557
`(do
35583558
(in-ns (quote ~name))
3559+
(refer-basilisp ~@(or (:refer-basilisp opts)
3560+
(:refer-clojure opts)))
35593561
~requires
35603562
~@imports)))
35613563

src/basilisp/importer.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import basilisp.lang.compiler as compiler
1414
import basilisp.lang.reader as reader
1515
import basilisp.lang.runtime as runtime
16+
import basilisp.lang.symbol as sym
1617
from basilisp.lang.typing import BasilispModule, ReaderForm
1718
from basilisp.lang.util import demunge
1819
from basilisp.util import timed
@@ -232,7 +233,7 @@ def _exec_cached_module(
232233
fullname: str,
233234
loader_state: Mapping[str, str],
234235
path_stats: Mapping[str, int],
235-
module: BasilispModule,
236+
ns: runtime.Namespace,
236237
):
237238
"""Load and execute a cached Basilisp module."""
238239
filename = loader_state["filename"]
@@ -252,15 +253,15 @@ def _exec_cached_module(
252253
cached_code,
253254
compiler.GeneratorContext(filename=filename),
254255
compiler.PythonASTOptimizer(),
255-
module,
256+
ns,
256257
)
257258

258259
def _exec_module(
259260
self,
260261
fullname: str,
261262
loader_state: Mapping[str, str],
262263
path_stats: Mapping[str, int],
263-
module: BasilispModule,
264+
ns: runtime.Namespace,
264265
):
265266
"""Load and execute a non-cached Basilisp module."""
266267
filename = loader_state["filename"]
@@ -290,7 +291,7 @@ def add_bytecode(bytecode: types.CodeType):
290291
compiler.compile_module( # pylint: disable=unexpected-keyword-arg
291292
forms,
292293
compiler.CompilerContext(filename=filename),
293-
module,
294+
ns,
294295
collect_bytecode=add_bytecode,
295296
)
296297

@@ -321,21 +322,19 @@ def exec_module(self, module):
321322
# generating, then we will not be able to use advanced compilation features such
322323
# as direct Python variable access to functions and other def'ed values.
323324
ns_name = demunge(fullname)
324-
ns: runtime.Namespace = runtime.set_current_ns(ns_name).value
325+
ns: runtime.Namespace = runtime.Namespace.get_or_create(sym.symbol(ns_name))
325326
ns.module = module
326327

327328
# Check if a valid, cached version of this Basilisp namespace exists and, if so,
328329
# load it and bypass the expensive compilation process below.
329330
if os.getenv(_NO_CACHE_ENVVAR, None) == "true":
330-
self._exec_module(fullname, spec.loader_state, path_stats, module)
331+
self._exec_module(fullname, spec.loader_state, path_stats, ns)
331332
else:
332333
try:
333-
self._exec_cached_module(
334-
fullname, spec.loader_state, path_stats, module
335-
)
334+
self._exec_cached_module(fullname, spec.loader_state, path_stats, ns)
336335
except (EOFError, ImportError, IOError, OSError) as e:
337336
logger.debug(f"Failed to load cached Basilisp module: {e}")
338-
self._exec_module(fullname, spec.loader_state, path_stats, module)
337+
self._exec_module(fullname, spec.loader_state, path_stats, ns)
339338

340339
# Because we want to (by default) add 'basilisp.core into every namespace by default,
341340
# we want to make sure we don't try to add 'basilisp.core into itself, causing a

src/basilisp/lang/compiler/__init__.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
statementize as _statementize,
2929
)
3030
from basilisp.lang.compiler.optimizer import PythonASTOptimizer
31-
from basilisp.lang.typing import BasilispModule, ReaderForm
31+
from basilisp.lang.typing import ReaderForm
3232
from basilisp.lang.util import genname
3333

3434
_DEFAULT_FN = "__lisp_expr__"
@@ -69,7 +69,9 @@ def py_ast_optimizer(self) -> PythonASTOptimizer:
6969
return self._optimizer
7070

7171

72-
def _emit_ast_string(module: ast.AST) -> None: # pragma: no cover
72+
def _emit_ast_string(
73+
ns: runtime.Namespace, module: ast.AST,
74+
) -> None: # pragma: no cover
7375
"""Emit the generated Python AST string either to standard out or to the
7476
*generated-python* dynamic Var for the current namespace. If the
7577
BASILISP_EMIT_GENERATED_PYTHON env var is not set True, this method is a
@@ -83,13 +85,13 @@ def _emit_ast_string(module: ast.AST) -> None: # pragma: no cover
8385
if runtime.print_generated_python():
8486
print(to_py_str(module))
8587
else:
86-
runtime.add_generated_python(to_py_str(module))
88+
runtime.add_generated_python(to_py_str(module), which_ns=ns)
8789

8890

8991
def compile_and_exec_form( # pylint: disable= too-many-arguments
9092
form: ReaderForm,
9193
ctx: CompilerContext,
92-
module: BasilispModule,
94+
ns: runtime.Namespace,
9395
wrapped_fn_name: str = _DEFAULT_FN,
9496
collect_bytecode: Optional[BytecodeCollector] = None,
9597
) -> Any:
@@ -101,8 +103,8 @@ def compile_and_exec_form( # pylint: disable= too-many-arguments
101103
if form is None:
102104
return None
103105

104-
if not module.__basilisp_bootstrapped__:
105-
_bootstrap_module(ctx.generator_context, ctx.py_ast_optimizer, module)
106+
if not ns.module.__basilisp_bootstrapped__:
107+
_bootstrap_module(ctx.generator_context, ctx.py_ast_optimizer, ns)
106108

107109
final_wrapped_name = genname(wrapped_fn_name)
108110

@@ -122,19 +124,19 @@ def compile_and_exec_form( # pylint: disable= too-many-arguments
122124
ast_module = ctx.py_ast_optimizer.visit(ast_module)
123125
ast.fix_missing_locations(ast_module)
124126

125-
_emit_ast_string(ast_module)
127+
_emit_ast_string(ns, ast_module)
126128

127129
bytecode = compile(ast_module, ctx.filename, "exec")
128130
if collect_bytecode:
129131
collect_bytecode(bytecode)
130-
exec(bytecode, module.__dict__)
131-
return getattr(module, final_wrapped_name)()
132+
exec(bytecode, ns.module.__dict__)
133+
return getattr(ns.module, final_wrapped_name)()
132134

133135

134136
def _incremental_compile_module(
135137
optimizer: PythonASTOptimizer,
136138
py_ast: GeneratedPyAST,
137-
mod: BasilispModule,
139+
ns: runtime.Namespace,
138140
source_filename: str,
139141
collect_bytecode: Optional[BytecodeCollector] = None,
140142
) -> None:
@@ -152,35 +154,35 @@ def _incremental_compile_module(
152154
module = optimizer.visit(module)
153155
ast.fix_missing_locations(module)
154156

155-
_emit_ast_string(module)
157+
_emit_ast_string(ns, module)
156158

157159
bytecode = compile(module, source_filename, "exec")
158160
if collect_bytecode:
159161
collect_bytecode(bytecode)
160-
exec(bytecode, mod.__dict__)
162+
exec(bytecode, ns.module.__dict__)
161163

162164

163165
def _bootstrap_module(
164166
gctx: GeneratorContext,
165167
optimizer: PythonASTOptimizer,
166-
mod: BasilispModule,
168+
ns: runtime.Namespace,
167169
collect_bytecode: Optional[BytecodeCollector] = None,
168170
) -> None:
169171
"""Bootstrap a new module with imports and other boilerplate."""
170172
_incremental_compile_module(
171173
optimizer,
172-
py_module_preamble(gctx),
173-
mod,
174+
py_module_preamble(ns),
175+
ns,
174176
source_filename=gctx.filename,
175177
collect_bytecode=collect_bytecode,
176178
)
177-
mod.__basilisp_bootstrapped__ = True
179+
ns.module.__basilisp_bootstrapped__ = True
178180

179181

180182
def compile_module(
181183
forms: Iterable[ReaderForm],
182184
ctx: CompilerContext,
183-
module: BasilispModule,
185+
ns: runtime.Namespace,
184186
collect_bytecode: Optional[BytecodeCollector] = None,
185187
) -> None:
186188
"""Compile an entire Basilisp module into Python bytecode which can be
@@ -190,7 +192,7 @@ def compile_module(
190192
Basilisp import machinery, to allow callers to import Basilisp modules from
191193
Python code.
192194
"""
193-
_bootstrap_module(ctx.generator_context, ctx.py_ast_optimizer, module)
195+
_bootstrap_module(ctx.generator_context, ctx.py_ast_optimizer, ns)
194196

195197
for form in forms:
196198
nodes = gen_py_ast(
@@ -199,7 +201,7 @@ def compile_module(
199201
_incremental_compile_module(
200202
ctx.py_ast_optimizer,
201203
nodes,
202-
module,
204+
ns,
203205
source_filename=ctx.filename,
204206
collect_bytecode=collect_bytecode,
205207
)
@@ -209,14 +211,14 @@ def compile_bytecode(
209211
code: List[types.CodeType],
210212
gctx: GeneratorContext,
211213
optimizer: PythonASTOptimizer,
212-
module: BasilispModule,
214+
ns: runtime.Namespace,
213215
) -> None:
214216
"""Compile cached bytecode into the given module.
215217
216218
The Basilisp import hook attempts to cache bytecode while compiling Basilisp
217219
namespaces. When the cached bytecode is reloaded from disk, it needs to be
218220
compiled within a bootstrapped module. This function bootstraps the module
219221
and then proceeds to compile a collection of bytecodes into the module."""
220-
_bootstrap_module(gctx, optimizer, module)
222+
_bootstrap_module(gctx, optimizer, ns)
221223
for bytecode in code:
222-
exec(bytecode, module.__dict__)
224+
exec(bytecode, ns.module.__dict__)

0 commit comments

Comments
 (0)