Skip to content

Commit dc03efc

Browse files
authored
Typed Basilisp module (#482)
* Typed Basilisp module * Change the log
1 parent 69365c2 commit dc03efc

File tree

7 files changed

+43
-36
lines changed

7 files changed

+43
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Changed
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)
18+
* Changed the Basilisp module type from `types.ModuleType` to a custom subtype with support for custom attributes (#482)
1819

1920
### Fixed
2021
* 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: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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
1415
from basilisp.prompt import get_prompter
1516

1617
CLI_INPUT_FILE_PATH = "<CLI Input>"
@@ -24,7 +25,7 @@ def cli():
2425
"""Basilisp is a Lisp dialect inspired by Clojure targeting Python 3."""
2526

2627

27-
def eval_file(filename: str, ctx: compiler.CompilerContext, module: types.ModuleType):
28+
def eval_file(filename: str, ctx: compiler.CompilerContext, module: BasilispModule):
2829
"""Evaluate a file with the given name into a Python module AST node."""
2930
last = None
3031
for form in reader.read_file(filename, resolver=runtime.resolve_alias):
@@ -33,7 +34,7 @@ def eval_file(filename: str, ctx: compiler.CompilerContext, module: types.Module
3334
return last
3435

3536

36-
def eval_stream(stream, ctx: compiler.CompilerContext, module: types.ModuleType):
37+
def eval_stream(stream, ctx: compiler.CompilerContext, module: BasilispModule):
3738
"""Evaluate the forms in stdin into a Python module AST node."""
3839
last = None
3940
for form in reader.read(stream, resolver=runtime.resolve_alias):
@@ -42,7 +43,7 @@ def eval_stream(stream, ctx: compiler.CompilerContext, module: types.ModuleType)
4243
return last
4344

4445

45-
def eval_str(s: str, ctx: compiler.CompilerContext, module: types.ModuleType, eof: Any):
46+
def eval_str(s: str, ctx: compiler.CompilerContext, module: BasilispModule, eof: Any):
4647
"""Evaluate the forms in a string into a Python module AST node."""
4748
last = eof
4849
for form in reader.read_str(s, resolver=runtime.resolve_alias, eof=eof):

src/basilisp/importer.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import basilisp.lang.compiler as compiler
1414
import basilisp.lang.reader as reader
1515
import basilisp.lang.runtime as runtime
16-
from basilisp.lang.typing import ReaderForm
16+
from basilisp.lang.typing import BasilispModule, ReaderForm
1717
from basilisp.lang.util import demunge
1818
from basilisp.util import timed
1919

@@ -219,7 +219,7 @@ def get_filename(self, fullname: str) -> str: # pragma: no cover
219219

220220
def create_module(self, spec: ModuleSpec):
221221
logger.debug(f"Creating Basilisp module '{spec.name}''")
222-
mod = types.ModuleType(spec.name)
222+
mod = BasilispModule(spec.name)
223223
mod.__file__ = spec.loader_state["filename"]
224224
mod.__loader__ = spec.loader
225225
mod.__package__ = spec.parent
@@ -232,7 +232,7 @@ def _exec_cached_module(
232232
fullname: str,
233233
loader_state: Mapping[str, str],
234234
path_stats: Mapping[str, int],
235-
module: types.ModuleType,
235+
module: BasilispModule,
236236
):
237237
"""Load and execute a cached Basilisp module."""
238238
filename = loader_state["filename"]
@@ -260,7 +260,7 @@ def _exec_module(
260260
fullname: str,
261261
loader_state: Mapping[str, str],
262262
path_stats: Mapping[str, int],
263-
module: types.ModuleType,
263+
module: BasilispModule,
264264
):
265265
"""Load and execute a non-cached Basilisp module."""
266266
filename = loader_state["filename"]
@@ -307,6 +307,8 @@ def exec_module(self, module):
307307
each form in a module may require code compiled from an earlier form, so
308308
we incrementally compile a Python module by evaluating a single top-level
309309
form at a time and inserting the resulting AST nodes into the Pyton module."""
310+
assert isinstance(module, BasilispModule)
311+
310312
fullname = module.__name__
311313
cached = self._cache[fullname]
312314
cached["module"] = module

src/basilisp/lang/compiler/__init__.py

Lines changed: 8 additions & 8 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 ReaderForm
31+
from basilisp.lang.typing import BasilispModule, ReaderForm
3232
from basilisp.lang.util import genname
3333

3434
_DEFAULT_FN = "__lisp_expr__"
@@ -89,7 +89,7 @@ def _emit_ast_string(module: ast.AST) -> None: # pragma: no cover
8989
def compile_and_exec_form( # pylint: disable= too-many-arguments
9090
form: ReaderForm,
9191
ctx: CompilerContext,
92-
module: types.ModuleType,
92+
module: BasilispModule,
9393
wrapped_fn_name: str = _DEFAULT_FN,
9494
collect_bytecode: Optional[BytecodeCollector] = None,
9595
) -> Any:
@@ -101,7 +101,7 @@ def compile_and_exec_form( # pylint: disable= too-many-arguments
101101
if form is None:
102102
return None
103103

104-
if not module.__basilisp_bootstrapped__: # type: ignore
104+
if not module.__basilisp_bootstrapped__:
105105
_bootstrap_module(ctx.generator_context, ctx.py_ast_optimizer, module)
106106

107107
final_wrapped_name = genname(wrapped_fn_name)
@@ -134,7 +134,7 @@ def compile_and_exec_form( # pylint: disable= too-many-arguments
134134
def _incremental_compile_module(
135135
optimizer: PythonASTOptimizer,
136136
py_ast: GeneratedPyAST,
137-
mod: types.ModuleType,
137+
mod: BasilispModule,
138138
source_filename: str,
139139
collect_bytecode: Optional[BytecodeCollector] = None,
140140
) -> None:
@@ -163,7 +163,7 @@ def _incremental_compile_module(
163163
def _bootstrap_module(
164164
gctx: GeneratorContext,
165165
optimizer: PythonASTOptimizer,
166-
mod: types.ModuleType,
166+
mod: BasilispModule,
167167
collect_bytecode: Optional[BytecodeCollector] = None,
168168
) -> None:
169169
"""Bootstrap a new module with imports and other boilerplate."""
@@ -174,13 +174,13 @@ def _bootstrap_module(
174174
source_filename=gctx.filename,
175175
collect_bytecode=collect_bytecode,
176176
)
177-
mod.__basilisp_bootstrapped__ = True # type: ignore
177+
mod.__basilisp_bootstrapped__ = True
178178

179179

180180
def compile_module(
181181
forms: Iterable[ReaderForm],
182182
ctx: CompilerContext,
183-
module: types.ModuleType,
183+
module: BasilispModule,
184184
collect_bytecode: Optional[BytecodeCollector] = None,
185185
) -> None:
186186
"""Compile an entire Basilisp module into Python bytecode which can be
@@ -209,7 +209,7 @@ def compile_bytecode(
209209
code: List[types.CodeType],
210210
gctx: GeneratorContext,
211211
optimizer: PythonASTOptimizer,
212-
module: types.ModuleType,
212+
module: BasilispModule,
213213
) -> None:
214214
"""Compile cached bytecode into the given module.
215215

src/basilisp/lang/compiler/generator.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import importlib
44
import logging
55
import re
6-
import types
76
import uuid
87
from datetime import datetime
98
from decimal import Decimal
@@ -257,11 +256,11 @@ def new_symbol_table(self, name):
257256
yield st
258257
self._st.pop()
259258

260-
def add_import(self, imp: sym.Symbol, mod: types.ModuleType, *aliases: sym.Symbol):
259+
def add_import(self, imp: sym.Symbol, mod: runtime.Module, *aliases: sym.Symbol):
261260
self.current_ns.add_import(imp, mod, *aliases)
262261

263262
@property
264-
def imports(self) -> lmap.Map[sym.Symbol, types.ModuleType]:
263+
def imports(self) -> runtime.ModuleMap:
265264
return self.current_ns.imports
266265

267266
@property
@@ -2790,8 +2789,8 @@ def _module_imports(ctx: GeneratorContext) -> Iterable[ast.Import]:
27902789
# Yield `import basilisp` so code attempting to call fully qualified
27912790
# `basilisp.lang...` modules don't result in compiler errors
27922791
yield ast.Import(names=[ast.alias(name="basilisp", asname=None)])
2793-
for imp in ctx.imports:
2794-
name = imp.key.name
2792+
for s in sorted(ctx.imports.keys(), key=lambda s: s.name):
2793+
name = s.name
27952794
alias = _MODULE_ALIASES.get(name, None)
27962795
yield ast.Import(names=[ast.alias(name=name, asname=alias)])
27972796

src/basilisp/lang/runtime.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
ISeqable,
4747
)
4848
from basilisp.lang.reference import ReferenceBase
49-
from basilisp.lang.typing import LispNumber
49+
from basilisp.lang.typing import BasilispModule, LispNumber
5050
from basilisp.lang.util import munge
5151
from basilisp.logconfig import TRACE
5252
from basilisp.util import Maybe
@@ -121,14 +121,14 @@
121121
CompletionTrimmer = Callable[[Tuple[sym.Symbol, Any]], str]
122122

123123

124-
def _new_module(name: str, doc=None) -> types.ModuleType:
124+
def _new_module(name: str, doc=None) -> BasilispModule:
125125
"""Create a new empty Basilisp Python module.
126126
Modules are created for each Namespace when it is created."""
127-
mod = types.ModuleType(name, doc=doc)
127+
mod = BasilispModule(name, doc=doc)
128128
mod.__loader__ = None
129129
mod.__package__ = None
130130
mod.__spec__ = None
131-
mod.__basilisp_bootstrapped__ = False # type: ignore
131+
mod.__basilisp_bootstrapped__ = False
132132
return mod
133133

134134

@@ -338,7 +338,8 @@ def pop_bindings(self):
338338

339339

340340
AliasMap = lmap.Map[sym.Symbol, sym.Symbol]
341-
ModuleMap = lmap.Map[sym.Symbol, types.ModuleType]
341+
Module = Union[BasilispModule, types.ModuleType]
342+
ModuleMap = lmap.Map[sym.Symbol, Module]
342343
NamespaceMap = lmap.Map[sym.Symbol, "Namespace"]
343344
VarMap = lmap.Map[sym.Symbol, Var]
344345

@@ -416,7 +417,7 @@ class Namespace(ReferenceBase):
416417
"_import_aliases",
417418
)
418419

419-
def __init__(self, name: sym.Symbol, module: types.ModuleType = None) -> None:
420+
def __init__(self, name: sym.Symbol, module: BasilispModule = None) -> None:
420421
self._name = name
421422
self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
422423

@@ -452,11 +453,11 @@ def name(self) -> str:
452453
return self._name.name
453454

454455
@property
455-
def module(self) -> types.ModuleType:
456+
def module(self) -> BasilispModule:
456457
return self._module
457458

458459
@module.setter
459-
def module(self, m: types.ModuleType):
460+
def module(self, m: BasilispModule):
460461
"""Override the Python module for this Namespace.
461462
462463
***WARNING**
@@ -543,9 +544,7 @@ def find(self, sym: sym.Symbol) -> Optional[Var]:
543544
return self.refers.val_at(sym, None)
544545
return v
545546

546-
def add_import(
547-
self, sym: sym.Symbol, module: types.ModuleType, *aliases: sym.Symbol
548-
) -> None:
547+
def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None:
549548
"""Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
550549
the aliases will be applied to the """
551550
self._imports.swap(lambda m: m.assoc(sym, module))
@@ -556,7 +555,7 @@ def add_import(
556555
)
557556
)
558557

559-
def get_import(self, sym: sym.Symbol) -> Optional[types.ModuleType]:
558+
def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
560559
"""Return the module if a moduled named by sym has been imported into
561560
this Namespace, None otherwise.
562561
@@ -603,7 +602,7 @@ def ns_cache(cls) -> lmap.Map:
603602
def __get_or_create(
604603
ns_cache: NamespaceMap,
605604
name: sym.Symbol,
606-
module: types.ModuleType = None,
605+
module: BasilispModule = None,
607606
core_ns_name=CORE_NS,
608607
) -> lmap.Map:
609608
"""Private swap function used by `get_or_create` to atomically swap
@@ -620,7 +619,7 @@ def __get_or_create(
620619

621620
@classmethod
622621
def get_or_create(
623-
cls, name: sym.Symbol, module: types.ModuleType = None
622+
cls, name: sym.Symbol, module: BasilispModule = None
624623
) -> "Namespace":
625624
"""Get the namespace bound to the symbol `name` in the global namespace
626625
cache, creating it if it does not exist.
@@ -1396,7 +1395,7 @@ def init_ns_var(which_ns: str = CORE_NS, ns_var_name: str = NS_VAR_NAME) -> Var:
13961395

13971396
def set_current_ns(
13981397
ns_name: str,
1399-
module: types.ModuleType = None,
1398+
module: BasilispModule = None,
14001399
ns_var_name: str = NS_VAR_NAME,
14011400
ns_var_ns: str = NS_VAR_NS,
14021401
) -> Var:
@@ -1417,7 +1416,7 @@ def set_current_ns(
14171416
@contextlib.contextmanager
14181417
def ns_bindings(
14191418
ns_name: str,
1420-
module: types.ModuleType = None,
1419+
module: BasilispModule = None,
14211420
ns_var_name: str = NS_VAR_NAME,
14221421
ns_var_ns: str = NS_VAR_NS,
14231422
):

src/basilisp/lang/typing.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import datetime
33
from decimal import Decimal
44
from fractions import Fraction
5+
from types import ModuleType
56
from typing import Pattern, Union
67

78
import basilisp.lang.keyword as kw
@@ -36,3 +37,7 @@
3637
PyCollectionForm = Union[dict, list, set, tuple]
3738
ReaderForm = Union[LispForm, IRecord, ISeq, IType, PyCollectionForm]
3839
SpecialForm = Union[llist.List, ISeq]
40+
41+
42+
class BasilispModule(ModuleType):
43+
__basilisp_bootstrapped__: bool = False

0 commit comments

Comments
 (0)