From 90e9187cefe233b263405dccd014a7b736352901 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Mon, 16 Sep 2024 20:27:05 -0400 Subject: [PATCH 1/2] Emit warnings when requiring or importing with an already used alias --- CHANGELOG.md | 5 ++ docs/runtime.rst | 25 ++++++++++ src/basilisp/cli.py | 14 ++++++ src/basilisp/core.lpy | 6 --- src/basilisp/lang/compiler/__init__.py | 2 +- src/basilisp/lang/compiler/constants.py | 3 ++ src/basilisp/lang/compiler/generator.py | 33 ++++++++++--- src/basilisp/lang/compiler/optimizer.py | 3 +- src/basilisp/lang/runtime.py | 66 ++++++++++++++++++------- 9 files changed, 123 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97222856a..2517e56be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + * Added the `--emit-generated-python` CLI argument to control whether generated Python code strings are stored by the runtime for each compiled namespace (#1045) + +### Changed + * The compiler will issue a warning when adding any alias that might conflict with any other alias (#1045) ## [v0.2.2] ### Added diff --git a/docs/runtime.rst b/docs/runtime.rst index e640ff336..ffa739791 100644 --- a/docs/runtime.rst +++ b/docs/runtime.rst @@ -111,6 +111,31 @@ This is roughly analogous to the Java classpath in Clojure. These values may be set manually, but are more often configured by some project management tool such as Poetry or defined in your Python virtualenv. These values may also be set via :ref:`cli` arguments. +.. _namespace_imports: + +Namespace Imports +^^^^^^^^^^^^^^^^^ + +Basilisp compiles Lisp code into Python code in Python modules exactly the same way the Python compiler does. +The Python code compiled by the Basilisp compiler expects certain features to be available at runtime beyond the standard Python builtins. +To support this, the Python modules compiled by Basilisp automatically import a number of modules both from the Basilisp runtime, the Python standard library, and Basilisp's Python dependencies. +In Basilisp modules (particularly :lpy:ns:`basilisp.core`) you may find references to such modules without any corresponding :lpy:form:`import`. + +The modules imported by default are given below: + +- ``attr`` (from the `attrs `_ project) +- :external:py:mod:`builtins` (Basilisp users should prefer the ``python`` namespace for calling :ref:`python_builtins`) +- :external:py:mod:`functools` +- :external:py:mod:`io` +- :external:py:mod:`importlib` +- :external:py:mod:`operator` +- :external:py:mod:`sys` +- The majority of the modules in ``basilisp.lang.*`` + +.. warning:: + + Using any of these names (particularly the Python standard library module names) as an alias for a required namespace or imported Python module will trigger a warning. + .. _vars: Vars diff --git a/src/basilisp/cli.py b/src/basilisp/cli.py index b2ee352de..9543ced3d 100644 --- a/src/basilisp/cli.py +++ b/src/basilisp/cli.py @@ -307,6 +307,20 @@ def _add_debug_arg_group(parser: argparse.ArgumentParser) -> None: "(env: BASILISP_LOGGING_LEVEL; default: WARNING)" ), ) + group.add_argument( + "--emit-generated-python", + action=_set_envvar_action( + "BASILISP_EMIT_GENERATED_PYTHON", parent=argparse._StoreAction + ), + nargs="?", + const=True, + type=_to_bool, + help=( + "if true, store generated Python code in `*generated-python*` dynamic " + "Vars within each namespace (env: BASILISP_EMIT_GENERATED_PYTHON; " + "default: true)" + ), + ) def _add_import_arg_group(parser: argparse.ArgumentParser) -> None: diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index 092cbe9b8..fe8951f2a 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -6,8 +6,6 @@ datetime decimal fractions - functools - importlib importlib.util math multiprocessing @@ -20,10 +18,6 @@ [time :as py-time] uuid) -(import* attr) - -(import* basilisp.lang.multifn) - (def ^{:doc "Create a list from the arguments." :arglists '([& args])} list diff --git a/src/basilisp/lang/compiler/__init__.py b/src/basilisp/lang/compiler/__init__.py index 8f9e9c7e2..f09e76470 100644 --- a/src/basilisp/lang/compiler/__init__.py +++ b/src/basilisp/lang/compiler/__init__.py @@ -139,7 +139,7 @@ def _emit_ast_string( # TODO: eventually, this default should become "false" but during this # period of heavy development, having it set to "true" by default # is tremendously useful - if os.getenv("BASILISP_EMIT_GENERATED_PYTHON", "true") != "true": + if os.getenv("BASILISP_EMIT_GENERATED_PYTHON", "true").lower() != "true": return if runtime.print_generated_python(): diff --git a/src/basilisp/lang/compiler/constants.py b/src/basilisp/lang/compiler/constants.py index 13e207fe1..f4a7acfae 100644 --- a/src/basilisp/lang/compiler/constants.py +++ b/src/basilisp/lang/compiler/constants.py @@ -1,5 +1,6 @@ from basilisp.lang import keyword as kw from basilisp.lang import symbol as sym +from basilisp.lang.util import genname class SpecialForm: @@ -32,6 +33,8 @@ class SpecialForm: DEFAULT_COMPILER_FILE_PATH = "NO_SOURCE_PATH" +OPERATOR_ALIAS = genname("operator") + SYM_ABSTRACT_META_KEY = kw.keyword("abstract") SYM_ABSTRACT_MEMBERS_META_KEY = kw.keyword("abstract-members") SYM_ASYNC_META_KEY = kw.keyword("async") diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index e2d09e49c..65221e522 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -46,6 +46,7 @@ from basilisp.lang.compiler.constants import ( DEFAULT_COMPILER_FILE_PATH, INTERFACE_KW, + OPERATOR_ALIAS, REST_KW, SYM_DYNAMIC_META_KEY, SYM_REDEF_META_KEY, @@ -684,6 +685,13 @@ def _var_ns_as_python_sym(name: str) -> str: ####################### +_ATTR_ALIAS = genname("attr") +_BUILTINS_ALIAS = genname("builtins") +_FUNCTOOLS_ALIAS = genname("functools") +_IMPORTLIB_ALIAS = genname("importlib") +_IO_ALIAS = genname("io") +_SYS_ALIAS = genname("sys") + _ATOM_ALIAS = genname("atom") _COMPILER_ALIAS = genname("compiler") _CORE_ALIAS = genname("core") @@ -706,9 +714,17 @@ def _var_ns_as_python_sym(name: str) -> str: _VEC_ALIAS = genname("vec") _VOLATILE_ALIAS = genname("volatile") _VAR_ALIAS = genname("Var") +_UNION_ALIAS = genname("Union") _UTIL_ALIAS = genname("langutil") _MODULE_ALIASES = { + "attr": _ATTR_ALIAS, + "builtins": _BUILTINS_ALIAS, + "functools": _FUNCTOOLS_ALIAS, + "importlib": _IMPORTLIB_ALIAS, + "io": _IO_ALIAS, + "operator": OPERATOR_ALIAS, + "sys": _SYS_ALIAS, "basilisp.lang.atom": _ATOM_ALIAS, "basilisp.lang.compiler": _COMPILER_ALIAS, "basilisp.core": _CORE_ALIAS, @@ -732,6 +748,9 @@ def _var_ns_as_python_sym(name: str) -> str: "basilisp.lang.volatile": _VOLATILE_ALIAS, "basilisp.lang.util": _UTIL_ALIAS, } +assert set(_MODULE_ALIASES.keys()).issuperset( + map(lambda s: s.name, runtime.Namespace.DEFAULT_IMPORTS) +), "All default Namespace imports should have generator aliases" _NS_VAR_VALUE = f"{_NS_VAR}.value" @@ -753,16 +772,16 @@ def _var_ns_as_python_sym(name: str) -> str: _INTERN_VAR_FN_NAME = _load_attr(f"{_VAR_ALIAS}.intern") _INTERN_UNBOUND_VAR_FN_NAME = _load_attr(f"{_VAR_ALIAS}.intern_unbound") _FIND_VAR_FN_NAME = _load_attr(f"{_VAR_ALIAS}.find_safe") -_ATTR_CLASS_DECORATOR_NAME = _load_attr("attr.define") -_ATTR_FROZEN_DECORATOR_NAME = _load_attr("attr.frozen") -_ATTRIB_FIELD_FN_NAME = _load_attr("attr.field") +_ATTR_CLASS_DECORATOR_NAME = _load_attr(f"{_ATTR_ALIAS}.define") +_ATTR_FROZEN_DECORATOR_NAME = _load_attr(f"{_ATTR_ALIAS}.frozen") +_ATTRIB_FIELD_FN_NAME = _load_attr(f"{_ATTR_ALIAS}.field") _COERCE_SEQ_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}.to_seq") _BASILISP_FN_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_fn") _FN_WITH_ATTRS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._with_attrs") _BASILISP_TYPE_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_type") _BASILISP_WITH_META_INTERFACE_NAME = _load_attr(f"{_INTERFACES_ALIAS}.IWithMeta") -_BUILTINS_IMPORT_FN_NAME = _load_attr("builtins.__import__") -_IMPORTLIB_IMPORT_MODULE_FN_NAME = _load_attr("importlib.import_module") +_BUILTINS_IMPORT_FN_NAME = _load_attr(f"{_BUILTINS_ALIAS}.__import__") +_IMPORTLIB_IMPORT_MODULE_FN_NAME = _load_attr(f"{_IMPORTLIB_ALIAS}.import_module") _LISP_FN_APPLY_KWARGS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._lisp_fn_apply_kwargs") _LISP_FN_COLLECT_KWARGS_FN_NAME = _load_attr( f"{_RUNTIME_ALIAS}._lisp_fn_collect_kwargs" @@ -1964,7 +1983,7 @@ def fn(*args): ret_ann_deps.extend(ret_ann.dependencies) ret_ann_ast = ( ast.Subscript( - value=ast.Name(id="Union", ctx=ast.Load()), + value=ast.Name(id=_UNION_ALIAS, ctx=ast.Load()), slice=ast_index(ast.Tuple(elts=ret_ann_asts, ctx=ast.Load())), ctx=ast.Load(), ) @@ -3922,7 +3941,7 @@ def _from_module_imports() -> Iterable[ast.ImportFrom]: ast.ImportFrom( module="typing", names=[ - ast.alias(name="Union", asname=None), + ast.alias(name="Union", asname=_UNION_ALIAS), ], level=0, ), diff --git a/src/basilisp/lang/compiler/optimizer.py b/src/basilisp/lang/compiler/optimizer.py index 71459e7cd..1479d0208 100644 --- a/src/basilisp/lang/compiler/optimizer.py +++ b/src/basilisp/lang/compiler/optimizer.py @@ -4,6 +4,7 @@ from contextlib import contextmanager from typing import Deque, Iterable, List, Optional, Set +from basilisp.lang.compiler.constants import OPERATOR_ALIAS from basilisp.lang.compiler.utils import ast_FunctionDef, ast_index @@ -43,7 +44,7 @@ def _optimize_operator_call_attr( # pylint: disable=too-many-return-statements Using Python operators directly will allow for more direct bytecode to be emitted by the Python compiler and take advantage of any additional performance improvements in future versions of Python.""" - if isinstance(fn.value, ast.Name) and fn.value.id == "operator": + if isinstance(fn.value, ast.Name) and fn.value.id == OPERATOR_ALIAS: binop = { "add": ast.Add, "and_": ast.BitAnd, diff --git a/src/basilisp/lang/runtime.py b/src/basilisp/lang/runtime.py index b24532f19..bcea70fe4 100644 --- a/src/basilisp/lang/runtime.py +++ b/src/basilisp/lang/runtime.py @@ -503,31 +503,42 @@ def pop_bindings(self) -> Frame: class Namespace(ReferenceBase): - """Namespaces serve as organizational units in Basilisp code, just as - they do in Clojure code. Vars are mutable containers for functions and - data which may be interned in a namespace and referred to by a Symbol. - Namespaces additionally may have aliases to other namespaces, so code - organized in one namespace may conveniently refer to code or data in - other namespaces using that alias as the Symbol's namespace. - Namespaces are constructed def-by-def as Basilisp reads in each form - in a file (which will typically declare a namespace at the top). + """Namespaces serve as organizational units in Basilisp code, just as they do in + Clojure code. + + Vars are mutable containers for functions and data which may be interned in a + namespace and referred to by a Symbol. + + Namespaces additionally may have aliases to other namespaces, so code organized in + one namespace may conveniently refer to code or data in other namespaces using that + alias as the Symbol's namespace. + + Namespaces are constructed def-by-def as Basilisp reads in each form in a file + (which will typically declare a namespace at the top). + Namespaces have the following fields of interest: - - `aliases` is a mapping between a symbolic alias and another - Namespace. The fully qualified name of a namespace is also - an alias for itself. - - `imports` is a mapping of names to Python modules imported - into the current namespace. + - `aliases` is a mapping between a symbolic alias and another Namespace. The fully + qualified name of a namespace is also an alias for itself. - - `interns` is a mapping between a symbolic name and a Var. The - Var may point to code, data, or nothing, if it is unbound. Vars - in `interns` are interned in _this_ namespace. + - `imports` is a mapping of names to Python modules imported into the current + namespace. - - `refers` is a mapping between a symbolic name and a Var. Vars in - `refers` are interned in another namespace and are only referred - to without an alias in this namespace. + - `import_aliases` is a mapping of aliases for Python modules to the true module + name. + + - `interns` is a mapping between a symbolic name and a Var. The Var may point to + code, data, or nothing, if it is unbound. Vars in `interns` are interned in + _this_ namespace. + + - `refers` is a mapping between a symbolic name and a Var. Vars in `refers` are + interned in another namespace and are only referred to without an alias in + this namespace. """ + # If this set is updated, be sure to update the following two locations: + # - basilisp.lang.compiler.generator._MODULE_ALIASES + # - the `namespace_imports` section in the documentation DEFAULT_IMPORTS = lset.set( map( sym.symbol, @@ -659,6 +670,20 @@ def __repr__(self): def __hash__(self): return hash(self._name) + def _check_potential_name_conflicts(self, name: sym.Symbol) -> None: + if name in self._aliases: + logger.warning( + f"name '{name}' may be shadowed by existing alias in '{self}'" + ) + if name in self._import_aliases: + logger.warning( + f"name '{name}' may be shadowed by existing import alias in '{self}'" + ) + if name in self._imports: + logger.warning( + f"name '{name}' may be shadowed by existing import in '{self}'" + ) + def require(self, ns_name: str, *aliases: sym.Symbol) -> BasilispModule: """Require the Basilisp Namespace named by `ns_name` and add any aliases given to this Namespace. @@ -684,6 +709,7 @@ def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None: with self._lock: new_m = self._aliases for alias in aliases: + self._check_potential_name_conflicts(alias) new_m = new_m.assoc(alias, namespace) self._aliases = new_m @@ -725,10 +751,12 @@ def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> N """Add the Symbol as an imported Symbol in this Namespace. If aliases are given, the aliases will be applied to the""" with self._lock: + self._check_potential_name_conflicts(sym) self._imports = self._imports.assoc(sym, module) if aliases: m = self._import_aliases for alias in aliases: + self._check_potential_name_conflicts(alias) m = m.assoc(alias, sym) self._import_aliases = m From 5de65adc59ab8744f41a2d06ccd0f593e66ba62d Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Mon, 16 Sep 2024 20:28:30 -0400 Subject: [PATCH 2/2] Generate non-conflicting qualified Python names --- src/basilisp/lang/compiler/analyzer.py | 3 ++ src/basilisp/lang/compiler/generator.py | 55 ++++++++++++++++++++++--- src/basilisp/lang/compiler/nodes.py | 1 + src/basilisp/lang/runtime.py | 28 +++++++++++-- tests/basilisp/compiler_test.py | 6 +-- tests/basilisp/namespace_test.py | 10 ++++- 6 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index e02b83862..fe83c871d 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -3470,6 +3470,7 @@ def __resolve_namespaced_symbol_in_ns( if safe_name in vars(ns_module): return MaybeHostForm( form=form, + class_original=ns_sym.name, class_=munge(ns_sym.name), field=safe_name, target=vars(ns_module)[safe_name], @@ -3485,6 +3486,7 @@ def __resolve_namespaced_symbol_in_ns( # don't need to care if this is an import or an alias. return MaybeHostForm( form=form, + class_original=ns_sym.name, class_=munge(ns_sym.name), field=safe_name, target=vars(ns_module)[safe_name], @@ -3584,6 +3586,7 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M ctx.symbol_table.mark_used(maybe_import_or_require_sym) return MaybeHostForm( form=form, + class_original=form.ns, class_=munge(form.ns), field=munge(form.name), target=None, diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index 65221e522..baa76ed81 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -1,9 +1,11 @@ # pylint: disable=too-many-lines import ast +import base64 import collections import contextlib import functools +import hashlib import logging import re import uuid @@ -2275,6 +2277,34 @@ def _if_to_py_ast(ctx: GeneratorContext, node: If) -> GeneratedPyAST[ast.expr]: ) +_IMPORT_HASH_TRANSLATE_TABLE = str.maketrans({"=": "", "+": "", "/": ""}) + + +@functools.lru_cache +def _import_hash(s: str) -> str: + """Generate a short, consistent, hash which can be appended to imported module + names to effectively separate them from objects of the same name defined in the + module. + + Aliases in Clojure exist in a separate "namespace" from interned values, but + Basilisp generates Python modules (which are essentially just a single shared + namespace), so it is possible that imported module names could clash with `def`'ed + names. + + Below, we generate a truncated URL-safe Base64 representation of the MD5 hash of + the input string (typically the first '.' delimited component of the potentially + qualified module name), removing any '-' characters since those are not safe for + Python identifiers. + + The hash doesn't need to be cryptographically secure, but it does need to be + consistent across sessions such that when cached namespace modules are reloaded, + the new session can find objects generated by the session which generated the + cache file. Since we are not concerned with being able to round-trip this data, + destructive modifications are not an issue.""" + digest = hashlib.md5(s.encode()).digest() + return base64.b64encode(digest).decode().translate(_IMPORT_HASH_TRANSLATE_TABLE)[:6] + + @_with_ast_loc_deps def _import_to_py_ast(ctx: GeneratorContext, node: Import) -> GeneratedPyAST[ast.expr]: """Return a Python AST node for a Basilisp `import*` expression.""" @@ -2291,9 +2321,13 @@ def _import_to_py_ast(ctx: GeneratorContext, node: Import) -> GeneratedPyAST[ast # (import* collections collections.abc) if alias.alias is not None: py_import_alias = munge(alias.alias) + py_import_alias = f"{py_import_alias}_{_import_hash(py_import_alias)}" + full_import_name = py_import_alias import_func = _IMPORTLIB_IMPORT_MODULE_FN_NAME else: - py_import_alias = safe_name.split(".", maxsplit=1)[0] + py_import_alias, *submodules = safe_name.split(".", maxsplit=1) + py_import_alias = f"{py_import_alias}_{_import_hash(py_import_alias)}" + full_import_name = ".".join([py_import_alias, *submodules]) import_func = _BUILTINS_IMPORT_FN_NAME ctx.symbol_table.context_boundary.new_symbol( @@ -2326,6 +2360,11 @@ def _import_to_py_ast(ctx: GeneratorContext, node: Import) -> GeneratedPyAST[ast keywords=[], ), last, + ast.Call( + func=_NEW_SYM_FN_NAME, + args=[ast.Constant(full_import_name)], + keywords=[], + ), ], ( [ @@ -3280,14 +3319,20 @@ def _maybe_class_to_py_ast( @_with_ast_loc def _maybe_host_form_to_py_ast( - _: GeneratorContext, node: MaybeHostForm + ctx: GeneratorContext, node: MaybeHostForm ) -> GeneratedPyAST[ast.expr]: """Generate a Python AST node for accessing a potential Python module variable name with a namespace.""" assert node.op == NodeOp.MAYBE_HOST_FORM - return GeneratedPyAST( - node=_load_attr(f"{_MODULE_ALIASES.get(node.class_, node.class_)}.{node.field}") - ) + if (mod_name := _MODULE_ALIASES.get(node.class_)) is None: + current_ns = ctx.current_ns + mod_or_class = sym.symbol(node.class_original) + alias = current_ns.import_aliases.val_at(mod_or_class) + if (mod := current_ns.import_names.val_at(alias or mod_or_class)) is not None: + mod_name = mod.name + if mod_name is None: + mod_name = f"{node.class_}_{_import_hash(node.class_)}" + return GeneratedPyAST(node=_load_attr(f"{mod_name}.{node.field}")) ######################### diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 9aaf7beee..f4051eb72 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -727,6 +727,7 @@ class MaybeClass(Node[sym.Symbol]): @attr.frozen class MaybeHostForm(Node[sym.Symbol]): form: sym.Symbol + class_original: str class_: str field: str target: Any diff --git a/src/basilisp/lang/runtime.py b/src/basilisp/lang/runtime.py index bcea70fe4..274557e45 100644 --- a/src/basilisp/lang/runtime.py +++ b/src/basilisp/lang/runtime.py @@ -587,6 +587,7 @@ class Namespace(ReferenceBase): "_aliases", "_imports", "_import_aliases", + "_import_names", ) def __init__( @@ -599,6 +600,7 @@ def __init__( self._lock = threading.RLock() self._aliases: NamespaceMap = lmap.PersistentMap.empty() + self._import_names: AliasMap = lmap.PersistentMap.empty() self._imports: ModuleMap = lmap.map( dict( map( @@ -648,6 +650,16 @@ def import_aliases(self) -> AliasMap: with self._lock: return self._import_aliases + @property + def import_names(self) -> AliasMap: + """A mapping of the true name of imported Python modules to the aliased name + used in generated code. + + Default imports are not included in this mapping because the generator uses + constant aliases for all default imports.""" + with self._lock: + return self._import_names + @property def interns(self) -> VarMap: """A mapping between a symbolic name and a Var. The Var may point to @@ -747,12 +759,22 @@ def find(self, sym: sym.Symbol) -> Optional[Var]: return self._refers.val_at(sym, None) return v - def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None: - """Add the Symbol as an imported Symbol in this Namespace. If aliases are given, - the aliases will be applied to the""" + def add_import( + self, + sym: sym.Symbol, + module: Module, + imported_name: sym.Symbol, + *aliases: sym.Symbol, + ) -> None: + """Add the Symbol as an imported Symbol in this Namespace with the underlying + imported name. + + If aliases are given, the aliases will be added to the import aliases for the + imported module.""" with self._lock: self._check_potential_name_conflicts(sym) self._imports = self._imports.assoc(sym, module) + self._import_names = self._import_names.assoc(sym, imported_name) if aliases: m = self._import_aliases for alias in aliases: diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index b73a1f9ec..5beea7eea 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -3486,7 +3486,7 @@ def test_interop_new(self, lcompile: CompileFn): def test_interop_new_with_import(self, lcompile: CompileFn, ns: runtime.Namespace): import builtins - ns.add_import(sym.symbol("builtins"), builtins) + ns.add_import(sym.symbol("builtins"), builtins, sym.symbol("builtins")) assert "hi" == lcompile('(builtins.str. "hi")') assert "1" == lcompile("(builtins.str. 1)") @@ -6516,7 +6516,7 @@ def test_warning_on_imported_name( ): """Basilisp should be able to directly resolve a link to cross-namespace imports, so no warning should be raised.""" - ns.add_import(sym.symbol("string"), __import__("string")) + ns.add_import(sym.symbol("string"), __import__("string"), sym.symbol("string")) with runtime.ns_bindings(ns.name): lcompile( @@ -6533,7 +6533,7 @@ def test_exception_raised_for_nonexistent_imported_name( self, lcompile: CompileFn, ns: runtime.Namespace, caplog ): """If a name does not exist, then a CompilerException will be raised.""" - ns.add_import(sym.symbol("string"), __import__("string")) + ns.add_import(sym.symbol("string"), __import__("string"), sym.symbol("string")) with runtime.ns_bindings(ns.name), pytest.raises(compiler.CompilerException): lcompile( diff --git a/tests/basilisp/namespace_test.py b/tests/basilisp/namespace_test.py index ec487b78d..0abcd7271 100644 --- a/tests/basilisp/namespace_test.py +++ b/tests/basilisp/namespace_test.py @@ -125,7 +125,13 @@ def test_cannot_remove_core(ns_cache: atom.Atom[NamespaceMap]): def test_imports(ns_cache: atom.Atom[NamespaceMap]): ns = get_or_create_ns(sym.symbol("ns1")) time = __import__("time") - ns.add_import(sym.symbol("time"), time, sym.symbol("py-time"), sym.symbol("py-tm")) + ns.add_import( + sym.symbol("time"), + time, + sym.symbol("time"), + sym.symbol("py-time"), + sym.symbol("py-tm"), + ) assert time == ns.get_import(sym.symbol("time")) assert time == ns.get_import(sym.symbol("py-time")) assert time == ns.get_import(sym.symbol("py-tm")) @@ -327,7 +333,7 @@ def ns(self) -> Namespace: time_sym = sym.symbol("time") time_alias = sym.symbol("py-time") - ns.add_import(time_sym, __import__("time"), time_alias) + ns.add_import(time_sym, __import__("time"), time_sym, time_alias) core_ns = Namespace(sym.symbol("basilisp.core")) map_alias = sym.symbol("map")