Skip to content

Commit 936b4ea

Browse files
authored
Support class methods, properties, and static methods on deftype* (#378)
* Support class methods, properties, and static methods on `deftype*` * Remove references to record and type * Add lots of tests * Testssss * Static method tests * Class method tests * Test for disallowing a method to be multiple types * Disallow recur forms in class and static methods * Do not consider classmethod cls argument as LocalType.THIS * Add symbol table for property * Correct recur assertion * Remove unused _assert_recur_is_tail branch
1 parent 3d82670 commit 936b4ea

File tree

6 files changed

+1303
-357
lines changed

6 files changed

+1303
-357
lines changed

src/basilisp/lang/compiler/constants.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,121 @@ class SpecialForm:
2828

2929
DEFAULT_COMPILER_FILE_PATH = "NO_SOURCE_PATH"
3030

31+
# Trimmed list of __dunder__ methods generated by using this command:
32+
#
33+
# find "$(pipenv --venv)/lib/python3.6" \
34+
# -name '*.py' \
35+
# -exec egrep \
36+
# -oh '__[A-Za-z_][A-Za-z_0-9]*__' '{}' \; \
37+
# | sort | uniq
38+
OBJECT_DUNDER_METHODS = frozenset(
39+
{
40+
"__abs__",
41+
"__add__",
42+
"__aenter__",
43+
"__aexit__",
44+
"__aiter__",
45+
"__await__",
46+
"__bytes__",
47+
"__call__",
48+
"__complex__",
49+
"__contains__",
50+
"__del__",
51+
"__delattr__",
52+
"__delitem__",
53+
"__delslice__",
54+
"__dict__",
55+
"__dir__",
56+
"__div__",
57+
"__divmod__",
58+
"__enter__",
59+
"__eq__",
60+
"__exit__",
61+
"__float__",
62+
"__floordiv__",
63+
"__ge__",
64+
"__getattr__",
65+
"__getattribute__",
66+
"__getitem__",
67+
"__getslice__",
68+
"__getstate__",
69+
"__gt__",
70+
"__hash__",
71+
"__iadd__",
72+
"__iand__",
73+
"__idiv__",
74+
"__ifloordiv__",
75+
"__ilshift__",
76+
"__imatmul__",
77+
"__imod__",
78+
"__imul__",
79+
"__init__",
80+
"__instancecheck__",
81+
"__int__",
82+
"__invert__",
83+
"__ior__",
84+
"__ipow__",
85+
"__isub__",
86+
"__iter__",
87+
"__itruediv__",
88+
"__ixor__",
89+
"__le__",
90+
"__len__",
91+
"__lshift__",
92+
"__matmul__",
93+
"__mod__",
94+
"__mul__",
95+
"__ne__",
96+
"__neg__",
97+
"__new__",
98+
"__not__",
99+
"__pos__",
100+
"__pow__",
101+
"__radd__",
102+
"__rand__",
103+
"__rcmp__",
104+
"__rdiv__",
105+
"__rdivmod__",
106+
"__reduce__",
107+
"__reduce_ex__",
108+
"__repr__",
109+
"__rfloordiv__",
110+
"__rlshift__",
111+
"__rmatmul__",
112+
"__rmod__",
113+
"__rmul__",
114+
"__rne__",
115+
"__ror__",
116+
"__round__",
117+
"__rpow__",
118+
"__rrshift__",
119+
"__rshift__",
120+
"__rsub__",
121+
"__rtruediv__",
122+
"__rxor__",
123+
"__setattr__",
124+
"__setitem__",
125+
"__setslice__",
126+
"__setstate__",
127+
"__str__",
128+
"__sub__",
129+
"__subclasscheck__",
130+
"__subclasshook__",
131+
"__truediv__",
132+
"__xor__",
133+
}
134+
)
135+
31136
SYM_ASYNC_META_KEY = kw.keyword("async")
137+
SYM_CLASSMETHOD_META_KEY = kw.keyword("classmethod")
32138
SYM_DYNAMIC_META_KEY = kw.keyword("dynamic")
139+
SYM_PROPERTY_META_KEY = kw.keyword("property")
33140
SYM_MACRO_META_KEY = kw.keyword("macro")
34141
SYM_MUTABLE_META_KEY = kw.keyword("mutable")
35142
SYM_NO_WARN_ON_REDEF_META_KEY = kw.keyword("no-warn-on-redef")
36143
SYM_NO_WARN_WHEN_UNUSED_META_KEY = kw.keyword("no-warn-when-unused")
37144
SYM_REDEF_META_KEY = kw.keyword("redef")
145+
SYM_STATICMETHOD_META_KEY = kw.keyword("staticmethod")
38146

39147
ARGLISTS_KW = kw.keyword("arglists")
40148
COL_KW = kw.keyword("col")

src/basilisp/lang/compiler/generator.py

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@
4949
Await,
5050
Binding,
5151
Catch,
52+
ClassMethod,
5253
Const,
5354
ConstType,
5455
Def,
5556
DefType,
57+
DefTypeMember,
5658
Do,
5759
Fn,
5860
FnMethod,
@@ -72,6 +74,7 @@
7274
Node,
7375
NodeEnv,
7476
NodeOp,
77+
PropertyMethod,
7578
PyDict,
7679
PyList,
7780
PySet,
@@ -81,6 +84,7 @@
8184
Recur,
8285
Set as SetNode,
8386
SetBang,
87+
StaticMethod,
8488
Throw,
8589
Try,
8690
VarRef,
@@ -484,6 +488,9 @@ def _is_redefable(v: Var) -> bool:
484488
_COERCE_SEQ_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}.to_seq")
485489
_BASILISP_FN_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_fn")
486490
_FN_WITH_ATTRS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._with_attrs")
491+
_PY_CLASSMETHOD_FN_NAME = _load_attr("classmethod")
492+
_PY_PROPERTY_FN_NAME = _load_attr("property")
493+
_PY_STATICMETHOD_FN_NAME = _load_attr("staticmethod")
487494
_TRAMPOLINE_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._trampoline")
488495
_TRAMPOLINE_ARGS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._TrampolineArgs")
489496

@@ -705,9 +712,76 @@ def _def_to_py_ast( # pylint: disable=too-many-branches
705712

706713

707714
@_with_ast_loc
708-
def __deftype_method_to_py_ast( # pylint: disable=too-many-branches
709-
ctx: GeneratorContext, node: Method
715+
def __deftype_classmethod_to_py_ast(
716+
ctx: GeneratorContext, node: ClassMethod
710717
) -> GeneratedPyAST:
718+
assert node.op == NodeOp.CLASS_METHOD
719+
method_name = munge(node.name)
720+
721+
with ctx.new_symbol_table(node.name):
722+
class_name = genname(munge(node.class_local.name))
723+
class_sym = sym.symbol(node.class_local.name)
724+
ctx.symbol_table.new_symbol(class_sym, class_name, LocalType.ARG)
725+
726+
fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, node.params, node.body)
727+
return GeneratedPyAST(
728+
node=ast.FunctionDef(
729+
name=method_name,
730+
args=ast.arguments(
731+
args=list(
732+
chain([ast.arg(arg=class_name, annotation=None)], fn_args)
733+
),
734+
kwarg=None,
735+
vararg=varg,
736+
kwonlyargs=[],
737+
defaults=[],
738+
kw_defaults=[],
739+
),
740+
body=fn_body_ast,
741+
decorator_list=[_PY_CLASSMETHOD_FN_NAME],
742+
returns=None,
743+
)
744+
)
745+
746+
747+
@_with_ast_loc
748+
def __deftype_property_to_py_ast(
749+
ctx: GeneratorContext, node: PropertyMethod
750+
) -> GeneratedPyAST:
751+
assert node.op == NodeOp.PROPERTY_METHOD
752+
method_name = munge(node.name)
753+
754+
with ctx.new_symbol_table(node.name):
755+
this_name = genname(munge(node.this_local.name))
756+
this_sym = sym.symbol(node.this_local.name)
757+
ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS)
758+
759+
with ctx.new_this(this_sym):
760+
fn_args, varg, fn_body_ast = __fn_args_to_py_ast(
761+
ctx, node.params, node.body
762+
)
763+
return GeneratedPyAST(
764+
node=ast.FunctionDef(
765+
name=method_name,
766+
args=ast.arguments(
767+
args=list(
768+
chain([ast.arg(arg=this_name, annotation=None)], fn_args)
769+
),
770+
kwarg=None,
771+
vararg=varg,
772+
kwonlyargs=[],
773+
defaults=[],
774+
kw_defaults=[],
775+
),
776+
body=fn_body_ast,
777+
decorator_list=[_PY_PROPERTY_FN_NAME],
778+
returns=None,
779+
)
780+
)
781+
782+
783+
@_with_ast_loc
784+
def __deftype_method_to_py_ast(ctx: GeneratorContext, node: Method) -> GeneratedPyAST:
711785
assert node.op == NodeOp.METHOD
712786
method_name = munge(node.name)
713787

@@ -744,6 +818,52 @@ def __deftype_method_to_py_ast( # pylint: disable=too-many-branches
744818
)
745819

746820

821+
@_with_ast_loc
822+
def __deftype_staticmethod_to_py_ast(
823+
ctx: GeneratorContext, node: StaticMethod
824+
) -> GeneratedPyAST:
825+
assert node.op == NodeOp.STATIC_METHOD
826+
method_name = munge(node.name)
827+
828+
with ctx.new_symbol_table(node.name):
829+
fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, node.params, node.body)
830+
return GeneratedPyAST(
831+
node=ast.FunctionDef(
832+
name=method_name,
833+
args=ast.arguments(
834+
args=list(fn_args),
835+
kwarg=None,
836+
vararg=varg,
837+
kwonlyargs=[],
838+
defaults=[],
839+
kw_defaults=[],
840+
),
841+
body=fn_body_ast,
842+
decorator_list=[_PY_STATICMETHOD_FN_NAME],
843+
returns=None,
844+
)
845+
)
846+
847+
848+
_DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, PyASTGenerator] = {
849+
NodeOp.CLASS_METHOD: __deftype_classmethod_to_py_ast,
850+
NodeOp.METHOD: __deftype_method_to_py_ast,
851+
NodeOp.PROPERTY_METHOD: __deftype_property_to_py_ast,
852+
NodeOp.STATIC_METHOD: __deftype_staticmethod_to_py_ast,
853+
}
854+
855+
856+
def __deftype_member_to_py_ast(
857+
ctx: GeneratorContext, node: DefTypeMember
858+
) -> GeneratedPyAST:
859+
member_type = node.op
860+
handle_deftype_member = _DEFTYPE_MEMBER_HANDLER.get(member_type)
861+
assert (
862+
handle_deftype_member is not None
863+
), f"Invalid :const AST type handler for {member_type}"
864+
return handle_deftype_member(ctx, node)
865+
866+
747867
@_with_ast_loc
748868
def _deftype_to_py_ast( # pylint: disable=too-many-branches
749869
ctx: GeneratorContext, node: DefType
@@ -784,9 +904,9 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches
784904
ctx.symbol_table.new_symbol(sym.symbol(field.name), safe_field, field.local)
785905

786906
type_deps: List[ast.AST] = []
787-
for method in node.methods:
788-
type_ast = __deftype_method_to_py_ast(ctx, method)
789-
type_nodes.append(type_ast.node)
907+
for member in node.members:
908+
type_ast = __deftype_member_to_py_ast(ctx, member)
909+
type_nodes.append(type_ast.node) # type: ignore
790910
type_deps.extend(type_ast.dependencies)
791911

792912
return GeneratedPyAST(
@@ -1870,7 +1990,11 @@ def _var_sym_to_py_ast(
18701990
if safe_name in ns_module.__dict__:
18711991
if ns is ctx.current_ns:
18721992
return GeneratedPyAST(node=ast.Name(id=safe_name, ctx=py_var_ctx))
1873-
return GeneratedPyAST(node=_load_attr(f"{safe_ns}.{safe_name}", ctx=py_var_ctx))
1993+
return GeneratedPyAST(
1994+
node=_load_attr(
1995+
f"{_MODULE_ALIASES.get(ns_name, safe_ns)}.{safe_name}", ctx=py_var_ctx
1996+
)
1997+
)
18741998

18751999
if ctx.warn_on_var_indirection:
18762000
logger.warning(f"could not resolve a direct link to Var '{var_name}'")
@@ -1930,10 +2054,7 @@ def _maybe_class_to_py_ast(_: GeneratorContext, node: MaybeClass) -> GeneratedPy
19302054
variable name."""
19312055
assert node.op == NodeOp.MAYBE_CLASS
19322056
return GeneratedPyAST(
1933-
node=ast.Name(
1934-
id=Maybe(_MODULE_ALIASES.get(node.class_)).or_else_get(node.class_),
1935-
ctx=ast.Load(),
1936-
)
2057+
node=ast.Name(id=_MODULE_ALIASES.get(node.class_, node.class_), ctx=ast.Load())
19372058
)
19382059

19392060

@@ -1945,9 +2066,7 @@ def _maybe_host_form_to_py_ast(
19452066
variable name with a namespace."""
19462067
assert node.op == NodeOp.MAYBE_HOST_FORM
19472068
return GeneratedPyAST(
1948-
node=_load_attr(
1949-
f"{Maybe(_MODULE_ALIASES.get(node.class_)).or_else_get(node.class_)}.{node.field}"
1950-
)
2069+
node=_load_attr(f"{_MODULE_ALIASES.get(node.class_, node.class_)}.{node.field}")
19512070
)
19522071

19532072

@@ -2373,7 +2492,9 @@ def _collection_literal_to_py_ast(
23732492
ConstType.KEYWORD: _kw_to_py_ast,
23742493
ConstType.MAP: _const_map_to_py_ast,
23752494
ConstType.SET: _const_set_to_py_ast,
2495+
ConstType.RECORD: None,
23762496
ConstType.SEQ: _const_seq_to_py_ast,
2497+
ConstType.TYPE: None,
23772498
ConstType.REGEX: _regex_to_py_ast,
23782499
ConstType.SYMBOL: _const_sym_to_py_ast,
23792500
ConstType.STRING: _str_to_py_ast,

0 commit comments

Comments
 (0)