Skip to content

Commit e05e06b

Browse files
authored
Emit warnings when requiring or importing with an already used alias (#1050)
Partially addresses #1045
1 parent 702c86d commit e05e06b

File tree

14 files changed

+282
-36
lines changed

14 files changed

+282
-36
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ docs/_build/
5151

5252
# Environments
5353
.env
54+
.graalvenv
5455
.venv
5556
env/
5657
venv/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
* Added a compiler metadata flag for suppressing warnings when Var indirection is unavoidable (#1052)
10+
* Added the `--emit-generated-python` CLI argument to control whether generated Python code strings are stored by the runtime for each compiled namespace (#1045)
11+
12+
### Changed
13+
* The compiler will issue a warning when adding any alias that might conflict with any other alias (#1045)
1014

1115
### Fixed
1216
* Basilisp now respects the value of Python's `sys.dont_write_bytecode` flag when generating bytecode (#1054)

docs/runtime.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,31 @@ This is roughly analogous to the Java classpath in Clojure.
111111
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.
112112
These values may also be set via :ref:`cli` arguments.
113113

114+
.. _namespace_imports:
115+
116+
Namespace Imports
117+
^^^^^^^^^^^^^^^^^
118+
119+
Basilisp compiles Lisp code into Python code in Python modules exactly the same way the Python compiler does.
120+
The Python code compiled by the Basilisp compiler expects certain features to be available at runtime beyond the standard Python builtins.
121+
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.
122+
In Basilisp modules (particularly :lpy:ns:`basilisp.core`) you may find references to such modules without any corresponding :lpy:form:`import`.
123+
124+
The modules imported by default are given below:
125+
126+
- ``attr`` (from the `attrs <https://www.attrs.org/en/stable/>`_ project)
127+
- :external:py:mod:`builtins` (Basilisp users should prefer the ``python`` namespace for calling :ref:`python_builtins`)
128+
- :external:py:mod:`functools`
129+
- :external:py:mod:`io`
130+
- :external:py:mod:`importlib`
131+
- :external:py:mod:`operator`
132+
- :external:py:mod:`sys`
133+
- The majority of the modules in ``basilisp.lang.*``
134+
135+
.. warning::
136+
137+
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.
138+
114139
.. _vars:
115140

116141
Vars

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ exclude = '''
8585
\.git
8686
| \.hg
8787
| \.cache
88+
| \.graalvenv
8889
| \.mypy_cache
8990
| \.pytest_cache
9091
| \.tox
@@ -133,6 +134,7 @@ skip = [
133134
".env",
134135
".hg",
135136
".git",
137+
".graalvenv",
136138
".mypy_cache",
137139
".pytest_cache",
138140
".tox",

src/basilisp/cli.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,20 @@ def _add_debug_arg_group(parser: argparse.ArgumentParser) -> None:
307307
"(env: BASILISP_LOGGING_LEVEL; default: WARNING)"
308308
),
309309
)
310+
group.add_argument(
311+
"--emit-generated-python",
312+
action=_set_envvar_action(
313+
"BASILISP_EMIT_GENERATED_PYTHON", parent=argparse._StoreAction
314+
),
315+
nargs="?",
316+
const=True,
317+
type=_to_bool,
318+
help=(
319+
"if true, store generated Python code in `*generated-python*` dynamic "
320+
"Vars within each namespace (env: BASILISP_EMIT_GENERATED_PYTHON; "
321+
"default: true)"
322+
),
323+
)
310324

311325

312326
def _add_import_arg_group(parser: argparse.ArgumentParser) -> None:

src/basilisp/core.lpy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
datetime
77
decimal
88
fractions
9-
functools
10-
importlib
119
importlib.util
1210
math
1311
multiprocessing
@@ -20,10 +18,6 @@
2018
[time :as py-time]
2119
uuid)
2220

23-
(import* attr)
24-
25-
(import* basilisp.lang.multifn)
26-
2721
(def ^{:doc "Create a list from the arguments."
2822
:arglists '([& args])}
2923
list

src/basilisp/io.lpy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
streams from a wide variety of different input types as well as utility functions for
66
interacting with the filesystem."
77
(:import
8-
io
98
os.path
109
pathlib
1110
shutil

src/basilisp/lang/compiler/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def _emit_ast_string(
139139
# TODO: eventually, this default should become "false" but during this
140140
# period of heavy development, having it set to "true" by default
141141
# is tremendously useful
142-
if os.getenv("BASILISP_EMIT_GENERATED_PYTHON", "true") != "true":
142+
if os.getenv("BASILISP_EMIT_GENERATED_PYTHON", "true").lower() != "true":
143143
return
144144

145145
if runtime.print_generated_python():

src/basilisp/lang/compiler/analyzer.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import re
1010
import sys
1111
import uuid
12+
from collections import defaultdict
1213
from datetime import datetime
1314
from decimal import Decimal
1415
from fractions import Fraction
@@ -35,6 +36,7 @@
3536
)
3637

3738
import attr
39+
from typing_extensions import Literal
3840

3941
from basilisp.lang import keyword as kw
4042
from basilisp.lang import list as llist
@@ -611,7 +613,7 @@ def syntax_position(self) -> NodeSyntacticPosition:
611613
def get_node_env(self, pos: Optional[NodeSyntacticPosition] = None) -> NodeEnv:
612614
"""Return the current Node environment.
613615
614-
If a synax position is given, it will be included in the environment.
616+
If a syntax position is given, it will be included in the environment.
615617
Otherwise, the position will be set to None."""
616618
return NodeEnv(
617619
ns=self.current_ns, file=self.filename, pos=pos, func_ctx=self.func_ctx
@@ -2505,6 +2507,55 @@ def _if_ast(form: ISeq, ctx: AnalyzerContext) -> If:
25052507
)
25062508

25072509

2510+
T_alias_node = TypeVar("T_alias_node", ImportAlias, RequireAlias)
2511+
2512+
2513+
def _do_warn_on_import_or_require_name_clash(
2514+
ctx: AnalyzerContext,
2515+
alias_nodes: List[T_alias_node],
2516+
action: Literal["import", "require"],
2517+
) -> None:
2518+
assert alias_nodes, "Must have at least one alias"
2519+
2520+
# Fetch these locally to avoid triggering more locks than we need to
2521+
current_ns = ctx.current_ns
2522+
aliases, import_aliases, imports = (
2523+
current_ns.aliases,
2524+
current_ns.import_aliases,
2525+
current_ns.imports,
2526+
)
2527+
2528+
# Identify duplicates in the import list first
2529+
name_to_nodes = defaultdict(list)
2530+
for node in alias_nodes:
2531+
name_to_nodes[(node.alias or node.name)].append(node)
2532+
2533+
for name, nodes in name_to_nodes.items():
2534+
if len(nodes) < 2:
2535+
continue
2536+
2537+
logger.warning(f"duplicate name or alias '{name}' in {action}")
2538+
2539+
# Now check against names in the namespace
2540+
for name, nodes in name_to_nodes.items():
2541+
name_sym = sym.symbol(name)
2542+
node, *_ = nodes
2543+
2544+
if name_sym in aliases:
2545+
logger.warning(
2546+
f"name '{name}' may shadow an existing alias in '{current_ns}'"
2547+
)
2548+
if name_sym in import_aliases:
2549+
logger.warning(
2550+
f"name '{name}' may be shadowed by an existing import alias in "
2551+
f"'{current_ns}'"
2552+
)
2553+
if name_sym in imports:
2554+
logger.warning(
2555+
f"name '{name}' may be shadowed by an existing import in '{current_ns}'"
2556+
)
2557+
2558+
25082559
def _import_ast(form: ISeq, ctx: AnalyzerContext) -> Import:
25092560
assert form.first == SpecialForm.IMPORT
25102561

@@ -2567,6 +2618,12 @@ def _import_ast(form: ISeq, ctx: AnalyzerContext) -> Import:
25672618
)
25682619
)
25692620

2621+
if not aliases:
2622+
raise ctx.AnalyzerException(
2623+
"import forms must name at least one module", form=form
2624+
)
2625+
2626+
_do_warn_on_import_or_require_name_clash(ctx, aliases, "import")
25702627
return Import(
25712628
form=form,
25722629
aliases=aliases,
@@ -3103,6 +3160,12 @@ def _require_ast(form: ISeq, ctx: AnalyzerContext) -> Require:
31033160
)
31043161
)
31053162

3163+
if not aliases:
3164+
raise ctx.AnalyzerException(
3165+
"require forms must name at least one namespace", form=form
3166+
)
3167+
3168+
_do_warn_on_import_or_require_name_clash(ctx, aliases, "require")
31063169
return Require(
31073170
form=form,
31083171
aliases=aliases,

src/basilisp/lang/compiler/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from basilisp.lang import keyword as kw
22
from basilisp.lang import symbol as sym
3+
from basilisp.lang.util import genname
34

45

56
class SpecialForm:
@@ -32,6 +33,8 @@ class SpecialForm:
3233

3334
DEFAULT_COMPILER_FILE_PATH = "NO_SOURCE_PATH"
3435

36+
OPERATOR_ALIAS = genname("operator")
37+
3538
SYM_ABSTRACT_META_KEY = kw.keyword("abstract")
3639
SYM_ABSTRACT_MEMBERS_META_KEY = kw.keyword("abstract-members")
3740
SYM_ASYNC_META_KEY = kw.keyword("async")

0 commit comments

Comments
 (0)