Skip to content

Commit e79db6b

Browse files
authored
Fully namespace-qualified symbols should resolve regardless of current Namespace aliases (#479)
* Fully namespace-qualified symbols should resolve regardless of current Namespace aliases * Change the log * Allow private Var references during macroexpansion * Redo the undo * Don't include the redef changes
1 parent 18a696c commit e79db6b

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515
* Change the default user namespace to `basilisp.user` (#466)
16-
* Changed multi-methods to use a `threading.Lock` internally rather than an Atom (#477)
16+
* Changed multi-methods to use a `threading.Lock` internally rather than an Atom (#478)
1717

1818
### Fixed
1919
* Fixed a reader bug where no exception was being thrown splicing reader conditional forms appeared outside of valid splicing contexts (#470)
20+
* Fixed a bug where fully Namespace-qualified symbols would not resolve if the current Namespace did not alias the referenced Namespace (#479)
2021

2122
## [v0.1.dev12] - 2020-01-26
2223
### Added

src/basilisp/lang/compiler/analyzer.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
SYM_MUTABLE_META_KEY,
6060
SYM_NO_WARN_ON_SHADOW_META_KEY,
6161
SYM_NO_WARN_WHEN_UNUSED_META_KEY,
62+
SYM_PRIVATE_META_KEY,
6263
SYM_PROPERTY_META_KEY,
6364
SYM_STATICMETHOD_META_KEY,
6465
SpecialForm,
@@ -2517,6 +2518,24 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches
25172518
env=ctx.get_node_env(pos=ctx.syntax_position),
25182519
)
25192520

2521+
v = Var.find(form)
2522+
if v is not None:
2523+
# Disallow global references to Vars defined with :private metadata
2524+
#
2525+
# Global references to private Vars are allowed in macroexpanded code
2526+
# as long as the macro referencing the private Var is in the same
2527+
# Namespace as the private Var (via `ctx.current_macro_ns`)
2528+
if (
2529+
v.ns != ctx.current_macro_ns
2530+
and v.meta is not None
2531+
and v.meta.val_at(SYM_PRIVATE_META_KEY, False)
2532+
):
2533+
raise AnalyzerException(
2534+
f"cannot resolve private Var {form.name} from namespace {form.ns}",
2535+
form=form,
2536+
)
2537+
return VarRef(form=form, var=v, env=ctx.get_node_env(pos=ctx.syntax_position))
2538+
25202539
if "." in form.name and form.name != _DOUBLE_DOT_MACRO_NAME:
25212540
raise AnalyzerException(
25222541
"symbol names may not contain the '.' operator", form=form

src/basilisp/lang/compiler/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ class SpecialForm:
135135
)
136136

137137
SYM_ASYNC_META_KEY = kw.keyword("async")
138+
SYM_PRIVATE_META_KEY = kw.keyword("private")
138139
SYM_CLASSMETHOD_META_KEY = kw.keyword("classmethod")
139140
SYM_DEFAULT_META_KEY = kw.keyword("default")
140141
SYM_DYNAMIC_META_KEY = kw.keyword("dynamic")

tests/basilisp/compiler_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import basilisp.lang.set as lset
2121
import basilisp.lang.symbol as sym
2222
import basilisp.lang.vector as vec
23+
from basilisp.lang.compiler.constants import SYM_PRIVATE_META_KEY
2324
from basilisp.lang.interfaces import IType
2425
from basilisp.lang.runtime import Var
2526
from basilisp.main import init
@@ -3077,6 +3078,48 @@ def test_aliased_macro_symbol_resolution(self, ns: runtime.Namespace):
30773078
finally:
30783079
runtime.Namespace.remove(other_ns_name)
30793080

3081+
def test_fully_namespaced_sym_resolves(self, ns: runtime.Namespace):
3082+
"""Ensure that references to Vars in other Namespaces by a fully Namespace
3083+
qualified symbol always resolve, regardless of whether the Namespace has
3084+
been aliased within the current Namespace."""
3085+
other_ns_name = sym.symbol("other.ns")
3086+
public_var_sym = sym.symbol("public-var")
3087+
private_var_sym = sym.symbol("private-var")
3088+
third_ns_name = sym.symbol("third.ns")
3089+
try:
3090+
other_ns = runtime.Namespace.get_or_create(other_ns_name)
3091+
runtime.Namespace.get_or_create(third_ns_name)
3092+
3093+
# Intern a public symbol in `other.ns`
3094+
public_var = Var(other_ns, public_var_sym)
3095+
public_var.value = kw.keyword("public-var")
3096+
other_ns.intern(public_var_sym, public_var)
3097+
3098+
# Intern a private symbol in `other.ns`
3099+
private_var = Var(
3100+
other_ns, private_var_sym, meta=lmap.map({SYM_PRIVATE_META_KEY: True})
3101+
)
3102+
private_var.value = kw.keyword("private-var")
3103+
other_ns.intern(private_var_sym, private_var)
3104+
3105+
with runtime.ns_bindings(third_ns_name.name):
3106+
# Verify that we can refer to `other.ns/public-var` with a fully
3107+
# namespace qualified symbol
3108+
assert kw.keyword("public-var") == lcompile(
3109+
"other.ns/public-var", resolver=runtime.resolve_alias,
3110+
)
3111+
3112+
# Verify we cannot refer to `other.ns/private-var` because it is
3113+
# marked private
3114+
with pytest.raises(compiler.CompilerException):
3115+
lcompile(
3116+
"other.ns/private-var", resolver=runtime.resolve_alias,
3117+
)
3118+
3119+
finally:
3120+
runtime.Namespace.remove(other_ns_name)
3121+
runtime.Namespace.remove(third_ns_name)
3122+
30803123
def test_cross_ns_macro_symbol_resolution(self, ns: runtime.Namespace):
30813124
"""Ensure that a macro symbol, `a`, delegating to another macro, named
30823125
by the symbol `b`, in a namespace directly required by `a`'s namespace

0 commit comments

Comments
 (0)