Skip to content

Commit f84204c

Browse files
authored
Fix a bug with unquoted objects from other namespaces (#1158)
Fixes #1153
1 parent 1efdd4a commit f84204c

File tree

5 files changed

+240
-182
lines changed

5 files changed

+240
-182
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
* Fix a bug where seqs were not considered valid input for matching clauses of the `case` macro (#1148)
1414
* Fix a bug where `py->lisp` did not keywordize string keys potentially containing namespaces (#1156)
1515
* Fix a bug where anonymous functions using the `#(...)` reader syntax were not properly expanded in a syntax quote (#1160)
16+
* Fix a bug where certain types of objects (such as objects created via `deftype`) could not be unquoted correctly in macros (#1153)
1617

1718
## [v0.3.3]
1819
### Added

src/basilisp/lang/compiler/generator.py

Lines changed: 21 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import functools
88
import hashlib
99
import logging
10+
import pickle # nosec B403
1011
import re
1112
import uuid
1213
from collections.abc import Collection, Iterable, Mapping, MutableMapping
@@ -95,7 +96,7 @@
9596
ast_ClassDef,
9697
ast_FunctionDef,
9798
)
98-
from basilisp.lang.interfaces import IMeta, IRecord, ISeq, ISeqable, IType
99+
from basilisp.lang.interfaces import IMeta, ISeq
99100
from basilisp.lang.runtime import CORE_NS
100101
from basilisp.lang.runtime import NS_VAR_NAME as LISP_NS_VAR
101102
from basilisp.lang.runtime import BasilispModule, Var
@@ -764,6 +765,7 @@ def _var_ns_as_python_sym(name: str) -> str:
764765
_ATTR_CLASS_DECORATOR_NAME = _load_attr(f"{_ATTR_ALIAS}.define")
765766
_ATTR_FROZEN_DECORATOR_NAME = _load_attr(f"{_ATTR_ALIAS}.frozen")
766767
_ATTRIB_FIELD_FN_NAME = _load_attr(f"{_ATTR_ALIAS}.field")
768+
_BASILISP_LOAD_CONSTANT_NAME = _load_attr(f"{_RUNTIME_ALIAS}._load_constant")
767769
_COERCE_SEQ_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}.to_seq")
768770
_BASILISP_FN_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_fn")
769771
_FN_WITH_ATTRS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._with_attrs")
@@ -3559,9 +3561,24 @@ def _const_val_to_py_ast(
35593561
structures need to call into this function to generate Python AST nodes for
35603562
nested elements. For top-level :const Lisp AST nodes, see
35613563
`_const_node_to_py_ast`."""
3562-
raise ctx.GeneratorException(
3563-
f"No constant handler is defined for type {type(form)}"
3564-
)
3564+
try:
3565+
serialized = pickle.dumps(form)
3566+
except (pickle.PicklingError, RecursionError) as e:
3567+
# For types without custom "constant" handling code, we defer to pickle
3568+
# to generate a representation that can be reloaded from the generated
3569+
# byte code. There are a few cases where that may not be possible for one
3570+
# reason or another, in which case we'll fail here.
3571+
raise ctx.GeneratorException(
3572+
f"Unable to emit bytecode for generating a constant {type(form)}"
3573+
) from e
3574+
else:
3575+
return GeneratedPyAST(
3576+
node=ast.Call(
3577+
func=_BASILISP_LOAD_CONSTANT_NAME,
3578+
args=[ast.Constant(value=serialized)],
3579+
keywords=[],
3580+
),
3581+
)
35653582

35663583

35673584
def _collection_literal_to_py_ast(
@@ -3777,54 +3794,6 @@ def _const_set_to_py_ast(
37773794
)
37783795

37793796

3780-
@_const_val_to_py_ast.register(IRecord)
3781-
def _const_record_to_py_ast(
3782-
form: IRecord, ctx: GeneratorContext
3783-
) -> GeneratedPyAST[ast.expr]:
3784-
assert isinstance(form, IRecord) and isinstance(
3785-
form, ISeqable
3786-
), "IRecord types should also be ISeq"
3787-
3788-
tp = type(form)
3789-
assert hasattr(tp, "create") and callable(
3790-
tp.create
3791-
), "IRecord and IType must declare a .create class method"
3792-
3793-
form_seq = runtime.to_seq(form)
3794-
assert form_seq is not None, "IRecord types must be iterable"
3795-
3796-
# pylint: disable=no-member
3797-
keys: list[Optional[ast.expr]] = []
3798-
vals: list[ast.expr] = []
3799-
vals_deps: list[PyASTNode] = []
3800-
for k, v in form_seq:
3801-
assert isinstance(k, kw.Keyword), "Record key in seq must be keyword"
3802-
key_nodes = _kw_to_py_ast(k, ctx)
3803-
keys.append(key_nodes.node)
3804-
assert (
3805-
not key_nodes.dependencies
3806-
), "Simple AST generators must emit no dependencies"
3807-
3808-
val_nodes = _const_val_to_py_ast(v, ctx)
3809-
vals.append(val_nodes.node)
3810-
vals_deps.extend(val_nodes.dependencies)
3811-
3812-
return GeneratedPyAST(
3813-
node=ast.Call(
3814-
func=_load_attr(f"{tp.__qualname__}.create"),
3815-
args=[
3816-
ast.Call(
3817-
func=_NEW_MAP_FN_NAME,
3818-
args=[ast.Dict(keys=keys, values=vals)],
3819-
keywords=[],
3820-
)
3821-
],
3822-
keywords=[],
3823-
),
3824-
dependencies=vals_deps,
3825-
)
3826-
3827-
38283797
@_const_val_to_py_ast.register(llist.PersistentList)
38293798
@_const_val_to_py_ast.register(ISeq)
38303799
def _const_seq_to_py_ast(
@@ -3849,25 +3818,6 @@ def _const_seq_to_py_ast(
38493818
)
38503819

38513820

3852-
@_const_val_to_py_ast.register(IType)
3853-
def _const_type_to_py_ast(
3854-
form: IType, ctx: GeneratorContext
3855-
) -> GeneratedPyAST[ast.expr]:
3856-
tp = type(form)
3857-
3858-
ctor_args = []
3859-
ctor_arg_deps: list[PyASTNode] = []
3860-
for field in attr.fields(tp): # type: ignore[arg-type, misc, unused-ignore]
3861-
field_nodes = _const_val_to_py_ast(getattr(form, field.name, None), ctx)
3862-
ctor_args.append(field_nodes.node)
3863-
ctor_args.extend(field_nodes.dependencies) # type: ignore[arg-type]
3864-
3865-
return GeneratedPyAST(
3866-
node=ast.Call(func=_load_attr(tp.__qualname__), args=ctor_args, keywords=[]),
3867-
dependencies=ctor_arg_deps,
3868-
)
3869-
3870-
38713821
@_const_val_to_py_ast.register(vec.PersistentVector)
38723822
def _const_vec_to_py_ast(
38733823
form: vec.PersistentVector, ctx: GeneratorContext

src/basilisp/lang/runtime.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import logging
1212
import math
1313
import numbers
14+
import pickle # nosec B403
1415
import platform
1516
import re
1617
import sys
@@ -2170,6 +2171,18 @@ def wrap_class(cls: type):
21702171
return wrap_class
21712172

21722173

2174+
def _load_constant(s: bytes) -> Any:
2175+
"""Load a compiler "constant" stored as a byte string as by Python's `pickle`
2176+
module.
2177+
2178+
Constant types without special handling are emitted to bytecode as a byte string
2179+
produced by `pickle.dumps`."""
2180+
try:
2181+
return pickle.loads(s) # nosec B301
2182+
except pickle.UnpicklingError as e:
2183+
raise RuntimeException("Unable to load constant value") from e
2184+
2185+
21732186
###############################
21742187
# Symbol and Alias Resolution #
21752188
###############################

0 commit comments

Comments
 (0)