Skip to content

Commit c46d26d

Browse files
authored
Disallow private Vars to be resolved within macros during macroexpansion (#648)
* Disallow private Vars to be resolved within macros during macroexpansion * Spit fire
1 parent ec2c6a0 commit c46d26d

File tree

4 files changed

+14
-64
lines changed

4 files changed

+14
-64
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
* Fixed a bug where it was impossible to specify negative CLI options for the compiler flags (#638)
2727
* Fixed a bug where it was impossible to use more than a single body expression in a `try` special form (#640)
2828
* Fixed a bug where re-`def`ing a Var (regardless of `^:redef` metadata) would not update metadata or dynamic flag (#642)
29+
* Fixed a bug where private Vars could be resolved from the source namespace of a public macro during macroexpansion (#648)
2930

3031
### Removed
3132
* Removed Click as a dependency in favor of builtin `argparse` (#622, #624, #636)

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test:
2929
lispcore.py:
3030
@BASILISP_DO_NOT_CACHE_NAMESPACES=true \
3131
poetry run basilisp run -c \
32-
'(with [f (python/open "lispcore.py" "w")] (.write f basilisp.core/*generated-python*))'
32+
'(spit "lispcore.py" @#'"'"'basilisp.core/*generated-python*)'
3333
@poetry run black lispcore.py
3434

3535

@@ -44,4 +44,4 @@ pypy-shell:
4444
--mount src=`pwd`,target=/usr/src/app,type=bind \
4545
--workdir /usr/src/app \
4646
pypy:3.6-7.3-slim-buster \
47-
/bin/sh -c 'pip install -e . && basilisp repl'
47+
/bin/sh -c 'pip install -e . && basilisp repl'

src/basilisp/core.lpy

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4720,7 +4720,7 @@
47204720
(refer-lib current-ns libspec))
47214721
nil))
47224722

4723-
(defonce ^:dynamic ^:private *loaded-libs*
4723+
(defonce ^:dynamic *loaded-libs*
47244724
(atom #{}))
47254725

47264726
(defn loaded-libs
@@ -5794,7 +5794,7 @@
57945794
(alter-var-root (var ~protocol-name) merge ~protocol-name)))
57955795
~protocol-name)))
57965796

5797-
(defn ^:private protocol?
5797+
(defn protocol?
57985798
"Return true if x is a Protocol."
57995799
[x]
58005800
(boolean (and (map? x) (:interface x))))
@@ -6174,21 +6174,21 @@
61746174
[v]
61756175
(instance? basilisp.lang.interfaces/IRecord v))
61766176

6177-
(defmulti ^:private evolve
6178-
(fn [v & _]
6179-
(cond
6180-
(record? v) :record
6181-
(map? v) :map)))
6177+
(defmulti evolve
6178+
"Implementation detail of `defrecord`."
6179+
(fn [v & _] (type v)))
61826180

6183-
(defmethod evolve :map
6181+
(defmethod evolve basilisp.lang.interfaces/IPersistentMap
61846182
[m & kwargs]
61856183
(apply assoc m kwargs))
61866184

6187-
(defmethod evolve :record
6185+
(defmethod evolve basilisp.lang.interfaces/IRecord
61886186
[rec & kwargs]
61896187
(->> (apply hash-map kwargs)
61906188
(apply-kw attr/evolve rec)))
61916189

6190+
(prefer-method evolve basilisp.lang.interfaces/IRecord basilisp.lang.interfaces/IPersistentMap)
6191+
61926192
(defn ^:private validate-record-fields
61936193
"Validate that record fields do not contain any reserved entries,
61946194
are not declared as mutable, and do not declare any default values."

src/basilisp/lang/compiler/analyzer.py

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ class AnalyzerContext:
307307
"_filename",
308308
"_func_ctx",
309309
"_is_quoted",
310-
"_macro_ns",
311310
"_opts",
312311
"_recur_points",
313312
"_should_macroexpand",
@@ -326,7 +325,6 @@ def __init__(
326325
self._filename = Maybe(filename).or_else_get(DEFAULT_COMPILER_FILE_PATH)
327326
self._func_ctx: Deque[FunctionContext] = collections.deque([])
328327
self._is_quoted: Deque[bool] = collections.deque([])
329-
self._macro_ns: Deque[Optional[runtime.Namespace]] = collections.deque([])
330328
self._opts = (
331329
Maybe(opts).map(lmap.map).or_else_get(lmap.PersistentMap.empty()) # type: ignore
332330
)
@@ -383,35 +381,6 @@ def quoted(self):
383381
yield
384382
self._is_quoted.pop()
385383

386-
@property
387-
def current_macro_ns(self) -> Optional[runtime.Namespace]:
388-
"""Return the current transient namespace available during macroexpansion.
389-
390-
If None, the analyzer should only use the current namespace for symbol
391-
resolution."""
392-
try:
393-
return self._macro_ns[-1]
394-
except IndexError:
395-
return None
396-
397-
@contextlib.contextmanager
398-
def macro_ns(self, ns: Optional[runtime.Namespace]):
399-
"""Set the transient namespace which is available to the analyzer during a
400-
macroexpansion phase.
401-
402-
If set to None, prohibit the analyzer from using another namespace for symbol
403-
resolution.
404-
405-
During macroexpansion, new forms referenced from the macro namespace would
406-
be unavailable to the namespace containing the original macro invocation.
407-
The macro namespace is a temporary override pointing to the namespace of the
408-
macro definition which can be used to resolve these transient references."""
409-
self._macro_ns.append(ns)
410-
try:
411-
yield
412-
finally:
413-
self._macro_ns.pop()
414-
415384
@property
416385
def should_allow_unresolved_symbols(self) -> bool:
417386
"""If True, the analyzer will allow unresolved symbols. This is primarily
@@ -2285,9 +2254,7 @@ def _invoke_ast(form: Union[llist.PersistentList, ISeq], ctx: AnalyzerContext) -
22852254
expanded = expanded.with_meta(
22862255
old_meta.cons(form.meta) if old_meta else form.meta
22872256
)
2288-
with ctx.macro_ns(
2289-
fn.var.ns if fn.var.ns is not ctx.current_ns else None
2290-
), ctx.expr_pos():
2257+
with ctx.expr_pos():
22912258
expanded_ast = _analyze_form(expanded, ctx)
22922259

22932260
# Verify that macroexpanded code also does not have any
@@ -3117,15 +3084,7 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: M
31173084
v = Var.find(form)
31183085
if v is not None:
31193086
# Disallow global references to Vars defined with :private metadata
3120-
#
3121-
# Global references to private Vars are allowed in macroexpanded code
3122-
# as long as the macro referencing the private Var is in the same
3123-
# Namespace as the private Var (via `ctx.current_macro_ns`)
3124-
if (
3125-
v.ns != ctx.current_macro_ns
3126-
and v.meta is not None
3127-
and v.meta.val_at(SYM_PRIVATE_META_KEY, False)
3128-
):
3087+
if v.meta is not None and v.meta.val_at(SYM_PRIVATE_META_KEY, False):
31293088
raise AnalyzerException(
31303089
f"cannot resolve private Var {form.name} from namespace {form.ns}",
31313090
form=form,
@@ -3219,16 +3178,6 @@ def __resolve_bare_symbol(
32193178
env=ctx.get_node_env(pos=ctx.syntax_position),
32203179
)
32213180

3222-
# Look up the symbol in the current macro namespace, if one
3223-
if ctx.current_macro_ns is not None:
3224-
v = ctx.current_macro_ns.find(form)
3225-
if v is not None:
3226-
return VarRef(
3227-
form=form,
3228-
var=v,
3229-
env=ctx.get_node_env(pos=ctx.syntax_position),
3230-
)
3231-
32323181
if "." in form.name:
32333182
raise AnalyzerException(
32343183
"symbol names may not contain the '.' operator", form=form

0 commit comments

Comments
 (0)