Skip to content

Commit 27a866b

Browse files
authored
Fix symbol warnings (#312)
* Fix symbol warnings * Test coverage for uncovered lines
1 parent 4cbf5b4 commit 27a866b

File tree

5 files changed

+133
-63
lines changed

5 files changed

+133
-63
lines changed

src/basilisp/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def bootstrap_repl(which_ns: str) -> types.ModuleType:
7474
)
7575
@click.option(
7676
"--warn-on-shadowed-var",
77-
default=True,
77+
default=False,
7878
is_flag=True,
7979
envvar="BASILISP_WARN_ON_SHADOWED_VAR",
8080
help="if provided, emit warnings if a Var name is shadowed by a local name",
@@ -162,7 +162,7 @@ def repl(
162162
)
163163
@click.option(
164164
"--warn-on-shadowed-var",
165-
default=True,
165+
default=False,
166166
is_flag=True,
167167
envvar="BASILISP_WARN_ON_SHADOWED_VAR",
168168
help="if provided, emit warnings if a Var name is shadowed by a local name",

src/basilisp/core/__init__.lpy

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -418,39 +418,6 @@
418418
{:first (first clauses)})))
419419
(cons 'basilisp.core/cond (nthrest clauses 2)))))
420420

421-
(defmacro condp
422-
"Take a predicate and an expression and a series of clauses, call
423-
(pred test expr) on the first expression for each clause. The result
424-
expression from first the set of clauses for which this expression
425-
returns a truthy value will be returned from the condp expression.
426-
427-
Clauses can take two forms:
428-
429-
- test-expr result-expr
430-
- test-expr :>> result-fn, where :>> is a keyword literal
431-
432-
For the ternary expression clause, the unary result-fn will be called
433-
with the result of the predicate."
434-
[pred expr & clauses]
435-
(when (seq clauses)
436-
(let [test-expr (first clauses)
437-
remaining (rest clauses)]
438-
(if (seq remaining)
439-
(let [result (first remaining)
440-
remaining (rest remaining)]
441-
(cond
442-
(= result :>>) `(let [res# ~(list pred test-expr expr)]
443-
(if res#
444-
(~(first remaining) res#)
445-
(condp ~pred ~expr ~@(rest remaining))))
446-
result `(if ~(list pred test-expr expr)
447-
~result
448-
(condp ~pred ~expr ~@remaining))
449-
:else (throw
450-
(ex-info "expected result expression"
451-
{:test test-expr}))))
452-
test-expr))))
453-
454421
(defn not
455422
"Return the logical negation of expr."
456423
[expr]
@@ -1089,6 +1056,39 @@
10891056
[& forms]
10901057
nil)
10911058

1059+
(defmacro condp
1060+
"Take a predicate and an expression and a series of clauses, call
1061+
(pred test expr) on the first expression for each clause. The result
1062+
expression from first the set of clauses for which this expression
1063+
returns a truthy value will be returned from the condp expression.
1064+
1065+
Clauses can take two forms:
1066+
1067+
- test-expr result-expr
1068+
- test-expr :>> result-fn, where :>> is a keyword literal
1069+
1070+
For the ternary expression clause, the unary result-fn will be called
1071+
with the result of the predicate."
1072+
[pred expr & clauses]
1073+
(when (seq clauses)
1074+
(let [test-expr (first clauses)
1075+
remaining (rest clauses)]
1076+
(if (seq remaining)
1077+
(let [result (first remaining)
1078+
remaining (rest remaining)]
1079+
(cond
1080+
(= result :>>) `(let [res# ~(list pred test-expr expr)]
1081+
(if res#
1082+
(~(first remaining) res#)
1083+
(condp ~pred ~expr ~@(rest remaining))))
1084+
result `(if ~(list pred test-expr expr)
1085+
~result
1086+
(condp ~pred ~expr ~@remaining))
1087+
:else (throw
1088+
(ex-info "expected result expression"
1089+
{:test test-expr}))))
1090+
test-expr))))
1091+
10921092
(defmacro new
10931093
"Create a new instance of class with args.
10941094

@@ -1621,7 +1621,7 @@
16211621
;; Add refers
16221622
(cond
16231623
(= :all (:refer opts))
1624-
(.refer-all which-ns new-ns)
1624+
(.refer-all current-ns new-ns)
16251625

16261626
(:refer opts)
16271627
(add-refers current-ns new-ns (:refer opts))

src/basilisp/lang/compiler.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def warn_on_shadowed_var(self) -> bool:
212212
Implied by warn_on_shadowed_name. The value of warn_on_shadowed_name
213213
supersedes the value of this flag."""
214214
return self.warn_on_shadowed_name or self._opts.entry(
215-
WARN_ON_SHADOWED_VAR, True
215+
WARN_ON_SHADOWED_VAR, False
216216
)
217217

218218
@property
@@ -1842,6 +1842,8 @@ def _resolve_sym_var(ctx: CompilerContext, v: Var) -> Optional[str]:
18421842
safe_ns = munge(v.ns.name)
18431843
return f"{safe_ns}.{safe_name}"
18441844

1845+
if ctx.warn_on_var_indirection:
1846+
logger.warning(f"could not resolve a direct link to Var '{v.name}'")
18451847
return None
18461848

18471849

@@ -1880,12 +1882,8 @@ def _resolve_sym(ctx: CompilerContext, form: sym.Symbol) -> Optional[str]: # no
18801882

18811883
# Otherwise, try to direct-link it like a Python variable
18821884
safe_ns = munge(form.ns)
1883-
try:
1884-
ns_module = sys.modules[safe_ns]
1885-
except KeyError:
1886-
# This should never happen. A module listed in the namespace
1887-
# imports should always be imported already.
1888-
raise CompilerException(f"Module '{safe_ns}' is not imported")
1885+
assert safe_ns in sys.modules, f"Module '{safe_ns}' is not imported"
1886+
ns_module = sys.modules[safe_ns]
18891887

18901888
# Try without allowing builtins first
18911889
safe_name = munge(form.name)
@@ -1898,12 +1896,18 @@ def _resolve_sym(ctx: CompilerContext, form: sym.Symbol) -> Optional[str]: # no
18981896
return f"{safe_ns}.{safe_name}"
18991897

19001898
# If neither resolve, then defer to a Var.find
1899+
if ctx.warn_on_var_indirection:
1900+
logger.warning(
1901+
f"could not resolve a direct link to Python variable '{form}'"
1902+
)
19011903
return None
19021904
elif ns_sym in ctx.current_ns.aliases:
19031905
aliased_ns: runtime.Namespace = ctx.current_ns.aliases[ns_sym]
19041906
v = Var.find(sym.symbol(form.name, ns=aliased_ns.name))
19051907
if v is not None:
19061908
return _resolve_sym_var(ctx, v)
1909+
if ctx.warn_on_var_indirection:
1910+
logger.warning(f"could not resolve a direct link to Var '{form}'")
19071911
return None
19081912

19091913
# Look up the symbol in the namespace mapping of the current namespace.
@@ -1914,6 +1918,8 @@ def _resolve_sym(ctx: CompilerContext, form: sym.Symbol) -> Optional[str]: # no
19141918
if v is not None:
19151919
return _resolve_sym_var(ctx, v)
19161920

1921+
if ctx.warn_on_var_indirection:
1922+
logger.warning(f"could not resolve a direct link to Var '{form}'")
19171923
return None
19181924

19191925

@@ -1974,9 +1980,6 @@ def _sym_ast(ctx: CompilerContext, form: sym.Symbol) -> ASTStream:
19741980
return
19751981

19761982
# If we couldn't find the symbol anywhere else, generate a Var.find call
1977-
# and issue a warning if warn_on_var_indirection is active.
1978-
if ctx.warn_on_var_indirection:
1979-
logger.warning(f"could not resolve a direct link to Var '{form}'")
19801983
yield _node(
19811984
ast.Attribute(
19821985
value=ast.Call(func=_FIND_VAR_FN_NAME, args=[base_sym], keywords=[]),

src/basilisp/lang/runtime.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
_GENERATED_PYTHON_VAR_NAME = "*generated-python*"
3535
_PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
3636

37+
_DYNAMIC_META_KEY = kw.keyword("dynamic")
38+
_PRIVATE_META_KEY = kw.keyword("private")
39+
_REDEF_META_KEY = kw.keyword("redef")
40+
3741
_CATCH = sym.symbol("catch")
3842
_DEF = sym.symbol("def")
3943
_DO = sym.symbol("do")
@@ -97,6 +101,15 @@ def __init__(
97101
self._tl = threading.local()
98102
self._meta = meta
99103

104+
if dynamic:
105+
# If this var was created with the dynamic keyword argument, then the
106+
# Var metadata should also specify that the Var is dynamic.
107+
if isinstance(self._meta, lmap.Map):
108+
if not self._meta.entry(_DYNAMIC_META_KEY):
109+
self._meta = self._meta.assoc(_DYNAMIC_META_KEY, True)
110+
else:
111+
self._meta = lmap.map({_DYNAMIC_META_KEY: True})
112+
100113
def __repr__(self):
101114
return f"#'{self.ns.name}/{self.name}"
102115

@@ -127,7 +140,7 @@ def dynamic(self, is_dynamic: bool):
127140
@property
128141
def is_private(self) -> Optional[bool]:
129142
try:
130-
return self.meta.entry(kw.keyword("private"))
143+
return self.meta.entry(_PRIVATE_META_KEY)
131144
except AttributeError:
132145
return False
133146

@@ -175,9 +188,8 @@ def intern(
175188
) -> "Var":
176189
"""Intern the value bound to the symbol `name` in namespace `ns`."""
177190
var_ns = Namespace.get_or_create(ns)
178-
var = var_ns.intern(name, Var(var_ns, name, dynamic=dynamic))
191+
var = var_ns.intern(name, Var(var_ns, name, dynamic=dynamic, meta=meta))
179192
var.root = val
180-
var.meta = meta
181193
return var
182194

183195
@staticmethod
@@ -186,9 +198,7 @@ def intern_unbound(
186198
) -> "Var":
187199
"""Create a new unbound `Var` instance to the symbol `name` in namespace `ns`."""
188200
var_ns = Namespace.get_or_create(ns)
189-
var = var_ns.intern(name, Var(var_ns, name, dynamic=dynamic))
190-
var.meta = meta
191-
return var
201+
return var_ns.intern(name, Var(var_ns, name, dynamic=dynamic, meta=meta))
192202

193203
@staticmethod
194204
def find_in_ns(ns_sym: sym.Symbol, name_sym: sym.Symbol) -> "Optional[Var]":
@@ -929,7 +939,7 @@ def add_generated_python(
929939
sym.symbol(var_name),
930940
"",
931941
dynamic=True,
932-
meta=lmap.map({kw.keyword("private"): True}),
942+
meta=lmap.map({_PRIVATE_META_KEY: True}),
933943
)
934944
)
935945
v.value = v.value + generated_python
@@ -973,19 +983,26 @@ def in_ns(s: sym.Symbol):
973983

974984
Var.intern_unbound(core_ns_sym, sym.symbol("unquote"))
975985
Var.intern_unbound(core_ns_sym, sym.symbol("unquote-splicing"))
976-
Var.intern(core_ns_sym, sym.symbol("set!"), set_BANG_)
977-
Var.intern(core_ns_sym, sym.symbol("in-ns"), in_ns)
986+
Var.intern(
987+
core_ns_sym,
988+
sym.symbol("set!"),
989+
set_BANG_,
990+
meta=lmap.map({_REDEF_META_KEY: True}),
991+
)
992+
Var.intern(
993+
core_ns_sym, sym.symbol("in-ns"), in_ns, meta=lmap.map({_REDEF_META_KEY: True})
994+
)
978995
Var.intern(
979996
core_ns_sym,
980997
sym.symbol(_PRINT_GENERATED_PY_VAR_NAME),
981998
False,
982999
dynamic=True,
983-
meta=lmap.map({kw.keyword("private"): True}),
1000+
meta=lmap.map({_PRIVATE_META_KEY: True}),
9841001
)
9851002
Var.intern(
9861003
core_ns_sym,
9871004
sym.symbol(_GENERATED_PYTHON_VAR_NAME),
9881005
"",
9891006
dynamic=True,
990-
meta=lmap.map({kw.keyword("private"): True}),
1007+
meta=lmap.map({_PRIVATE_META_KEY: True}),
9911008
)

tests/basilisp/compiler_test.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -329,25 +329,27 @@ def test_fn_warn_on_shadow_var(ns: runtime.Namespace):
329329
logger.warning.assert_not_called()
330330

331331
with mock.patch("basilisp.lang.compiler.logger") as logger:
332-
lcompile(
333-
"""
332+
code = """
334333
(def unique-kuieeid :a)
335334
(fn [unique-kuieeid] unique-kuieeid)
336335
"""
336+
lcompile(
337+
code, ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True})
337338
)
338339

339340
logger.warning.assert_called_once_with(
340341
"name 'unique-kuieeid' shadows def'ed Var from outer scope"
341342
)
342343

343344
with mock.patch("basilisp.lang.compiler.logger") as logger:
344-
lcompile(
345-
"""
345+
code = """
346346
(def unique-peuudcdf :a)
347347
(fn
348348
([] :b)
349349
([unique-peuudcdf] unique-peuudcdf))
350350
"""
351+
lcompile(
352+
code, ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True})
351353
)
352354

353355
logger.warning.assert_called_once_with(
@@ -654,11 +656,12 @@ def test_let_warn_on_shadow_var(ns: runtime.Namespace):
654656
logger.warning.assert_not_called()
655657

656658
with mock.patch("basilisp.lang.compiler.logger") as logger:
657-
lcompile(
658-
"""
659+
code = """
659660
(def unique-uoieyqq :a)
660661
(let [unique-uoieyqq 3] unique-uoieyqq)
661662
"""
663+
lcompile(
664+
code, ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True})
662665
)
663666
logger.warning.assert_called_once_with(
664667
"name 'unique-uoieyqq' shadows def'ed Var from outer scope"
@@ -991,6 +994,53 @@ def test_aliased_macro_symbol_resolution(ns: runtime.Namespace):
991994
runtime.Namespace.remove(other_ns_name)
992995

993996

997+
def test_warn_on_var_indirection_cross_ns(ns: runtime.Namespace):
998+
current_ns: runtime.Namespace = ns
999+
other_ns_name = sym.symbol("other.ns")
1000+
try:
1001+
other_ns = runtime.Namespace.get_or_create(other_ns_name)
1002+
current_ns.add_alias(other_ns_name, other_ns)
1003+
current_ns.add_alias(sym.symbol("other"), other_ns)
1004+
1005+
with runtime.ns_bindings(current_ns.name):
1006+
with mock.patch("basilisp.lang.compiler.logger") as logger:
1007+
lcompile(
1008+
"(fn [] (other.ns/m :z))",
1009+
ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True}),
1010+
)
1011+
1012+
logger.warning.assert_called_once_with(
1013+
"could not resolve a direct link to Var 'other.ns/m'"
1014+
)
1015+
1016+
with mock.patch("basilisp.lang.compiler.logger") as logger:
1017+
lcompile(
1018+
"(fn [] (other/m :z))",
1019+
ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True}),
1020+
)
1021+
1022+
logger.warning.assert_called_once_with(
1023+
"could not resolve a direct link to Var 'other/m'"
1024+
)
1025+
finally:
1026+
runtime.Namespace.remove(other_ns_name)
1027+
1028+
1029+
def test_warn_on_var_indirection_on_import(ns: runtime.Namespace):
1030+
ns.add_import(sym.symbol("string"), __import__("string"))
1031+
1032+
with runtime.ns_bindings(ns.name):
1033+
with mock.patch("basilisp.lang.compiler.logger") as logger:
1034+
lcompile(
1035+
"(fn [] (string/m :z))",
1036+
ctx=compiler.CompilerContext({compiler.WARN_ON_SHADOWED_VAR: True}),
1037+
)
1038+
1039+
logger.warning.assert_called_once_with(
1040+
"could not resolve a direct link to Python variable 'string/m'"
1041+
)
1042+
1043+
9941044
def test_var(ns: runtime.Namespace):
9951045
code = """
9961046
(def some-var "a value")

0 commit comments

Comments
 (0)