Skip to content

Commit 8a5d76c

Browse files
authored
Intern Deftypes as Vars and resolve static and class methods (#539)
* Intern Deftypes as Vars and resolve static and class methods * d o a t e s t * Dewit * Changelog
1 parent abd449a commit 8a5d76c

File tree

4 files changed

+132
-79
lines changed

4 files changed

+132
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
* Fixed a bug where `defonce` would throw a Python SyntaxError due to a superfluous `global` statement in the generated Python (#525)
1717
* Fixed a bug where Basilisp would throw an exception when comparing seqs by `=` to non-seqable values (#530)
1818
* Fixed a bug where aliased Python submodule imports referred to the top-level module rather than the submodule (#533)
19+
* Fixed a bug where static methods and class methods on types created by `deftype` could not be referred to directly (defeating the purpose of the static or class method) (#537)
1920

2021
## [v0.1.dev13] - 2020-03-16
2122
### Added

src/basilisp/lang/compiler/analyzer.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,7 +2731,7 @@ def _resolve_nested_symbol(ctx: AnalyzerContext, form: sym.Symbol) -> HostField:
27312731
parent_node = __resolve_namespaced_symbol(ctx, parent)
27322732

27332733
return HostField(
2734-
form,
2734+
form=form,
27352735
field=form.name,
27362736
target=parent_node,
27372737
is_assignable=True,
@@ -2810,7 +2810,7 @@ def __resolve_namespaced_symbol_in_ns( # pylint: disable=too-many-branches
28102810
return None
28112811

28122812

2813-
def __resolve_namespaced_symbol( # pylint: disable=too-many-branches
2813+
def __resolve_namespaced_symbol( # pylint: disable=too-many-branches # noqa: MC0001
28142814
ctx: AnalyzerContext, form: sym.Symbol
28152815
) -> Union[Const, HostField, MaybeClass, MaybeHostForm, VarRef]:
28162816
"""Resolve a namespaced symbol into a Python name or Basilisp Var."""
@@ -2873,11 +2873,37 @@ def __resolve_namespaced_symbol( # pylint: disable=too-many-branches
28732873
) from None
28742874
elif ctx.should_allow_unresolved_symbols:
28752875
return _const_node(ctx, form)
2876-
else:
2877-
raise AnalyzerException(
2878-
f"unable to resolve symbol '{form}' in this context", form=form
2876+
2877+
# Static and class methods on types in the current namespace can be referred
2878+
# to as `Type/static-method`. In these casess, we will try to resolve the
2879+
# namespace portion of the symbol as a Var within the current namespace.
2880+
maybe_type_or_class = current_ns.find(sym.symbol(form.ns))
2881+
if maybe_type_or_class is not None:
2882+
safe_name = munge(form.name)
2883+
member = getattr(maybe_type_or_class.value, safe_name, None)
2884+
2885+
if member is None:
2886+
raise AnalyzerException(
2887+
f"unable to resolve static or class member '{form}' in this context",
2888+
form=form,
2889+
)
2890+
2891+
return HostField(
2892+
form=form,
2893+
field=safe_name,
2894+
target=VarRef(
2895+
form=form,
2896+
var=maybe_type_or_class,
2897+
env=ctx.get_node_env(pos=ctx.syntax_position),
2898+
),
2899+
is_assignable=False,
2900+
env=ctx.get_node_env(pos=ctx.syntax_position),
28792901
)
28802902

2903+
raise AnalyzerException(
2904+
f"unable to resolve symbol '{form}' in this context", form=form
2905+
)
2906+
28812907

28822908
def __resolve_bare_symbol(
28832909
ctx: AnalyzerContext, form: sym.Symbol

src/basilisp/lang/compiler/generator.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,20 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches
13361336
keywords=[],
13371337
body=type_nodes,
13381338
decorator_list=[decorator],
1339-
)
1339+
),
1340+
ast.Call(
1341+
func=_INTERN_VAR_FN_NAME,
1342+
args=[
1343+
_load_attr(_NS_VAR_VALUE),
1344+
ast.Call(
1345+
func=_NEW_SYM_FN_NAME,
1346+
args=[ast.Constant(type_name)],
1347+
keywords=[],
1348+
),
1349+
ast.Name(id=type_name, ctx=ast.Load()),
1350+
],
1351+
keywords=[],
1352+
),
13401353
],
13411354
)
13421355
),

tests/basilisp/compiler_test.py

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3618,82 +3618,48 @@ def test_multi_arity_recur(self, lcompile: CompileFn):
36183618
assert 10 == lcompile("(+++ 1 2 3 4)")
36193619
assert 15 == lcompile("(+++ 1 2 3 4 5)")
36203620

3621-
def test_disallow_recur_in_special_forms(self, lcompile: CompileFn):
3622-
with pytest.raises(compiler.CompilerException):
3623-
lcompile('(fn [a] (def b (recur "a")))')
3624-
3625-
with pytest.raises(compiler.CompilerException):
3626-
lcompile('(fn [a] (import* (recur "a")))')
3627-
3628-
with pytest.raises(compiler.CompilerException):
3629-
lcompile('(fn [a] (.join "" (recur "a")))')
3630-
3631-
with pytest.raises(compiler.CompilerException):
3632-
lcompile('(fn [a] (.-p (recur "a")))')
3633-
3634-
with pytest.raises(compiler.CompilerException):
3635-
lcompile('(fn [a] (throw (recur "a"))))')
3636-
3637-
with pytest.raises(compiler.CompilerException):
3638-
lcompile('(fn [a] (var (recur "a"))))')
3639-
3640-
def test_disallow_recur_outside_tail(self, lcompile: CompileFn):
3641-
with pytest.raises(compiler.CompilerException):
3642-
lcompile("(recur)")
3643-
3644-
with pytest.raises(compiler.CompilerException):
3645-
lcompile("(do (recur))")
3646-
3647-
with pytest.raises(compiler.CompilerException):
3648-
lcompile("(if true (recur) :b)")
3649-
3650-
with pytest.raises(compiler.CompilerException):
3651-
lcompile('(fn [a] (do (recur "a") :b))')
3652-
3653-
with pytest.raises(compiler.CompilerException):
3654-
lcompile('(fn [a] (if (recur "a") :a :b))')
3655-
3656-
with pytest.raises(compiler.CompilerException):
3657-
lcompile('(fn [a] (if (recur "a") :a))')
3658-
3659-
with pytest.raises(compiler.CompilerException):
3660-
lcompile('(fn [a] (let [a (recur "a")] a))')
3661-
3662-
with pytest.raises(compiler.CompilerException):
3663-
lcompile('(fn [a] (let [a (do (recur "a"))] a))')
3664-
3665-
with pytest.raises(compiler.CompilerException):
3666-
lcompile('(fn [a] (let [a (do :b (recur "a"))] a))')
3667-
3668-
with pytest.raises(compiler.CompilerException):
3669-
lcompile('(fn [a] (let [a (do (recur "a") :c)] a))')
3670-
3671-
with pytest.raises(compiler.CompilerException):
3672-
lcompile('(fn [a] (let [a "a"] (recur a) a))')
3673-
3674-
with pytest.raises(compiler.CompilerException):
3675-
lcompile('(fn [a] (loop* [a (recur "a")] a))')
3676-
3677-
with pytest.raises(compiler.CompilerException):
3678-
lcompile('(fn [a] (loop* [a (do (recur "a"))] a))')
3679-
3680-
with pytest.raises(compiler.CompilerException):
3681-
lcompile('(fn [a] (loop* [a (do :b (recur "a"))] a))')
3682-
3683-
with pytest.raises(compiler.CompilerException):
3684-
lcompile('(fn [a] (loop* [a (do (recur "a") :c)] a))')
3685-
3686-
with pytest.raises(compiler.CompilerException):
3687-
lcompile('(fn [a] (loop* [a "a"] (recur a) a))')
3688-
3689-
with pytest.raises(compiler.CompilerException):
3690-
lcompile("(fn [a] (try (do (recur a) :b) (catch AttributeError _ nil)))")
3691-
3621+
@pytest.mark.parametrize(
3622+
"code",
3623+
[
3624+
'(fn [a] (def b (recur "a")))',
3625+
'(fn [a] (import* (recur "a")))',
3626+
'(fn [a] (.join "" (recur "a")))',
3627+
'(fn [a] (.-p (recur "a")))',
3628+
'(fn [a] (throw (recur "a"))))',
3629+
'(fn [a] (var (recur "a"))))',
3630+
],
3631+
)
3632+
def test_disallow_recur_in_special_forms(self, lcompile: CompileFn, code: str):
36923633
with pytest.raises(compiler.CompilerException):
3693-
lcompile("(fn [a] (try :b (catch AttributeError _ (do (recur :a) :c))))")
3634+
lcompile(code)
36943635

3636+
@pytest.mark.parametrize(
3637+
"code",
3638+
[
3639+
"(recur)",
3640+
"(do (recur))",
3641+
"(if true (recur) :b)",
3642+
'(fn [a] (do (recur "a") :b))',
3643+
'(fn [a] (if (recur "a") :a :b))',
3644+
'(fn [a] (if (recur "a") :a))',
3645+
'(fn [a] (let [a (recur "a")] a))',
3646+
'(fn [a] (let [a (do (recur "a"))] a))',
3647+
'(fn [a] (let [a (do :b (recur "a"))] a))',
3648+
'(fn [a] (let [a (do (recur "a") :c)] a))',
3649+
'(fn [a] (let [a "a"] (recur a) a))',
3650+
'(fn [a] (loop* [a (recur "a")] a))',
3651+
'(fn [a] (loop* [a (do (recur "a"))] a))',
3652+
'(fn [a] (loop* [a (do :b (recur "a"))] a))',
3653+
'(fn [a] (loop* [a (do (recur "a") :c)] a))',
3654+
'(fn [a] (loop* [a "a"] (recur a) a))',
3655+
"(fn [a] (try (do (recur a) :b) (catch AttributeError _ nil)))",
3656+
"(fn [a] (try :b (catch AttributeError _ (do (recur :a) :c))))",
3657+
"(fn [a] (try :b (finally (do (recur :a) :c))))",
3658+
],
3659+
)
3660+
def test_disallow_recur_outside_tail(self, lcompile: CompileFn, code: str):
36953661
with pytest.raises(compiler.CompilerException):
3696-
lcompile("(fn [a] (try :b (finally (do (recur :a) :c))))")
3662+
lcompile(code)
36973663

36983664
def test_single_arity_named_anonymous_fn_recursion(self, lcompile: CompileFn):
36993665
code = """
@@ -4106,6 +4072,53 @@ def test_nested_bare_sym_will_not_resolve(self, lcompile: CompileFn):
41064072
with pytest.raises(compiler.CompilerException):
41074073
lcompile("basilisp.lang.map.MapEntry.of")
41084074

4075+
def test_local_deftype_classmethod_resolves(self, lcompile: CompileFn):
4076+
Point = lcompile(
4077+
"""
4078+
(import* abc)
4079+
(def WithCls
4080+
(python/type "WithCls"
4081+
#py (abc/ABC)
4082+
#py {"create"
4083+
(python/classmethod
4084+
(abc/abstractmethod
4085+
(fn [cls])))}))
4086+
(deftype* Point [x y z]
4087+
:implements [WithCls]
4088+
(^:classmethod create [cls x y z]
4089+
[cls x y z]))
4090+
"""
4091+
)
4092+
4093+
assert vec.v(Point, 1, 2, 3) == lcompile("(Point/create 1 2 3)")
4094+
4095+
with pytest.raises(compiler.CompilerException):
4096+
lcompile("(Point/make 1 2 3)")
4097+
4098+
def test_local_deftype_staticmethod_resolves(self, lcompile: CompileFn):
4099+
Point = lcompile(
4100+
"""
4101+
(import* abc)
4102+
(def WithStatic
4103+
(python/type "WithStatic"
4104+
#py (abc/ABC)
4105+
#py {"dostatic"
4106+
(python/staticmethod
4107+
(abc/abstractmethod
4108+
(fn [])))}))
4109+
(deftype* Point [x y z]
4110+
:implements [WithStatic]
4111+
(^:staticmethod dostatic [arg1 arg2]
4112+
[arg1 arg2]))
4113+
"""
4114+
)
4115+
4116+
assert Point.dostatic is lcompile("Point/dostatic")
4117+
assert vec.v(kw.keyword("a"), 2) == lcompile("(Point/dostatic :a 2)")
4118+
4119+
with pytest.raises(compiler.CompilerException):
4120+
lcompile("(Point/do-non-static 1 2)")
4121+
41094122
def test_aliased_namespace_not_hidden_by_python_module(
41104123
self, lcompile: CompileFn, monkeypatch: MonkeyPatch
41114124
):

0 commit comments

Comments
 (0)