From c48c49ba6a8a1d13aa34c45ee17ae29459fce925 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Thu, 2 Apr 2020 06:53:04 -0400 Subject: [PATCH 01/20] Reorganize --- docs/reference.rst | 1 + docs/typesandrecords.rst | 16 ++++++++++++++++ src/basilisp/lang/runtime.py | 34 +++++++++++++++++----------------- 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 docs/typesandrecords.rst diff --git a/docs/reference.rst b/docs/reference.rst index 510235600..a7170d4b0 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -10,4 +10,5 @@ Reference specialforms compiler pyinterop + typesandrecords cli \ No newline at end of file diff --git a/docs/typesandrecords.rst b/docs/typesandrecords.rst new file mode 100644 index 000000000..9a514244b --- /dev/null +++ b/docs/typesandrecords.rst @@ -0,0 +1,16 @@ +.. _types_and_records: + +Data Types and Records +====================== + + +.. contents:: + :depth: 2 + +Data Types +---------- + + +Records +------- + diff --git a/src/basilisp/lang/runtime.py b/src/basilisp/lang/runtime.py index d04aa1881..9750a250c 100644 --- a/src/basilisp/lang/runtime.py +++ b/src/basilisp/lang/runtime.py @@ -1353,6 +1353,23 @@ def kwargs(self) -> Dict: return self._kwargs +def _trampoline(f): + """Trampoline a function repeatedly until it is finished recurring to help + avoid stack growth.""" + + @functools.wraps(f) + def trampoline(*args, **kwargs): + while True: + ret = f(*args, **kwargs) + if isinstance(ret, _TrampolineArgs): + args = ret.args + kwargs = ret.kwargs + continue + return ret + + return trampoline + + def _lisp_fn_apply_kwargs(f): """Convert a Python function into a Lisp function. @@ -1392,23 +1409,6 @@ def wrapped_f(*args, **kwargs): return wrapped_f -def _trampoline(f): - """Trampoline a function repeatedly until it is finished recurring to help - avoid stack growth.""" - - @functools.wraps(f) - def trampoline(*args, **kwargs): - while True: - ret = f(*args, **kwargs) - if isinstance(ret, _TrampolineArgs): - args = ret.args - kwargs = ret.kwargs - continue - return ret - - return trampoline - - def _with_attrs(**kwargs): """Decorator to set attributes on a function. Returns the original function after setting the attributes named by the keyword arguments.""" From b82dff79e282525a0f66db7e22742ab50c222f0b Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Sat, 4 Apr 2020 16:03:33 -0400 Subject: [PATCH 02/20] Change FnMethod to FnArity to better reflect its purpose --- src/basilisp/lang/compiler/analyzer.py | 8 ++++---- src/basilisp/lang/compiler/generator.py | 12 ++++++------ src/basilisp/lang/compiler/nodes.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 99135bd1b..0bc084d97 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -80,7 +80,7 @@ DefTypeMember, Do, Fn, - FnMethod, + FnArity, HostCall, HostField, If, @@ -1492,7 +1492,7 @@ def _do_ast(ctx: AnalyzerContext, form: ISeq) -> Do: def __fn_method_ast( # pylint: disable=too-many-branches,too-many-locals ctx: AnalyzerContext, form: ISeq, fnname: Optional[sym.Symbol] = None -) -> FnMethod: +) -> FnArity: with ctx.new_symbol_table("fn-method"): params = form.first if not isinstance(params, vec.Vector): @@ -1552,7 +1552,7 @@ def __fn_method_ast( # pylint: disable=too-many-branches,too-many-locals with ctx.new_recur_point(fn_loop_id, param_nodes): with ctx.expr_pos(): stmts, ret = _body_ast(ctx, form.rest) - method = FnMethod( + method = FnArity( form=form, loop_id=fn_loop_id, params=vec.vector(param_nodes), @@ -2236,7 +2236,7 @@ def _assert_recur_is_tail(node: Node) -> None: # pylint: disable=too-many-branc _assert_no_recur(child) _assert_recur_is_tail(node.ret) elif node.op in {NodeOp.FN, NodeOp.FN_METHOD, NodeOp.METHOD}: - assert isinstance(node, (Fn, FnMethod, Method)) + assert isinstance(node, (Fn, FnArity, Method)) node.visit(_assert_recur_is_tail) elif node.op == NodeOp.IF: assert isinstance(node, If) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index f0a067a30..f3a62ebdc 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -55,7 +55,7 @@ DefTypeMember, Do, Fn, - FnMethod, + FnArity, HostCall, HostField, If, @@ -1193,7 +1193,7 @@ def __kwargs_support_decorator( def __single_arity_fn_to_py_ast( ctx: GeneratorContext, node: Fn, - method: FnMethod, + method: FnArity, def_name: Optional[str] = None, meta_node: Optional[MetaNode] = None, ) -> GeneratedPyAST: @@ -1415,7 +1415,7 @@ def fn(*args): def __multi_arity_fn_to_py_ast( # pylint: disable=too-many-locals ctx: GeneratorContext, node: Fn, - methods: Collection[FnMethod], + methods: Collection[FnArity], def_name: Optional[str] = None, meta_node: Optional[MetaNode] = None, ) -> GeneratedPyAST: @@ -1496,13 +1496,13 @@ def _fn_to_py_ast( ) -> GeneratedPyAST: """Return a Python AST Node for a `fn` expression.""" assert node.op == NodeOp.FN - if len(node.methods) == 1: + if len(node.arities) == 1: return __single_arity_fn_to_py_ast( - ctx, node, next(iter(node.methods)), def_name=def_name, meta_node=meta_node + ctx, node, next(iter(node.arities)), def_name=def_name, meta_node=meta_node ) else: return __multi_arity_fn_to_py_ast( - ctx, node, node.methods, def_name=def_name, meta_node=meta_node + ctx, node, node.arities, def_name=def_name, meta_node=meta_node ) diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 446885ff2..f055822fc 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -411,7 +411,7 @@ class Do(Node[SpecialForm]): class Fn(Node[SpecialForm]): form: SpecialForm max_fixed_arity: int - methods: IPersistentVector["FnMethod"] + arities: IPersistentVector["FnArity"] env: NodeEnv local: Optional[Binding] = None is_variadic: bool = False @@ -424,7 +424,7 @@ class Fn(Node[SpecialForm]): @attr.s(auto_attribs=True, frozen=True, slots=True) -class FnMethod(Node[SpecialForm]): +class FnArity(Node[SpecialForm]): form: SpecialForm loop_id: LoopID params: Iterable[Binding] @@ -873,7 +873,7 @@ class WithMeta(Node[LispForm]): Binding, ClassMethod, Catch, - FnMethod, + FnArity, ImportAlias, Local, Method, From 46449806dffb0d3d182861b5e9437a746cc33f2a Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Mon, 6 Apr 2020 09:09:15 -0400 Subject: [PATCH 03/20] Multi-arity methods for deftype --- src/basilisp/lang/compiler/analyzer.py | 237 ++++++++++--- src/basilisp/lang/compiler/generator.py | 428 ++++++++++++++++++------ src/basilisp/lang/compiler/nodes.py | 216 ++++++------ 3 files changed, 631 insertions(+), 250 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 0bc084d97..038af027b 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -71,13 +71,21 @@ Await, Binding, Catch, - ClassMethod, Const, ConstType, Def, DefType, DefTypeBase, + DefTypeClassMethod, + DefTypeClassMethodArity, DefTypeMember, + DefTypeMethod, + DefTypeMethodArity, + DefTypeMethodArityBase, + DefTypeMethodBase, + DefTypeProperty, + DefTypeStaticMethod, + DefTypeStaticMethodArity, Do, Fn, FnArity, @@ -97,12 +105,10 @@ Map as MapNode, MaybeClass, MaybeHostForm, - Method, Node, NodeEnv, NodeOp, NodeSyntacticPosition, - PropertyMethod, PyDict, PyList, PySet, @@ -114,7 +120,6 @@ Set as SetNode, SetBang, SpecialFormNode, - StaticMethod, Throw, Try, VarRef, @@ -937,10 +942,14 @@ def _def_ast( # pylint: disable=too-many-branches,too-many-locals def __deftype_method_param_bindings( ctx: AnalyzerContext, params: vec.Vector -) -> Tuple[bool, List[Binding]]: +) -> Tuple[bool, int, List[Binding]]: """Generate parameter bindings for deftype* methods. - Special cases for class and static methods must be handled by their + Return a tuple containing a boolean, indicating if the parameter bindings + contain a variadic binding, an integer indicating the fixed arity of the + parameter bindings, and the list of parameter bindings. + + Special cases for individual method types must be handled by their respective handlers. This method will only produce vanilla ARG type bindings.""" has_vargs, vargs_idx = False, 0 @@ -967,6 +976,8 @@ def __deftype_method_param_bindings( param_nodes.append(binding) ctx.put_new_symbol(s, binding) + fixed_arity = len(param_nodes) + if has_vargs: try: vargs_sym = params[vargs_idx + 1] @@ -992,7 +1003,7 @@ def __deftype_method_param_bindings( "Expected variadic argument name after '&'", form=params ) from None - return has_vargs, param_nodes + return has_vargs, fixed_arity, param_nodes def __deftype_classmethod( @@ -1001,7 +1012,7 @@ def __deftype_classmethod( method_name: str, args: vec.Vector, kwarg_support: Optional[KeywordArgSupport] = None, -) -> ClassMethod: +) -> DefTypeClassMethodArity: """Emit a node for a :classmethod member of a deftype* form.""" with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(method_name): try: @@ -1024,13 +1035,18 @@ def __deftype_classmethod( ctx.put_new_symbol(cls_arg, cls_binding) params = args[1:] - has_vargs, param_nodes = __deftype_method_param_bindings(ctx, params) + has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings( + ctx, params + ) with ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) - method = ClassMethod( + method = DefTypeClassMethodArity( form=form, name=method_name, params=vec.vector(param_nodes), + fixed_arity=fixed_arity, + is_variadic=has_vargs, + kwarg_support=kwarg_support, body=Do( form=form.rest, statements=vec.vector(stmts), @@ -1040,10 +1056,8 @@ def __deftype_classmethod( # exists, for metadata. env=ctx.get_node_env(), ), - env=ctx.get_node_env(), class_local=cls_binding, - is_variadic=has_vargs, - kwarg_support=kwarg_support, + env=ctx.get_node_env(), ) method.visit(_assert_no_recur) return method @@ -1055,7 +1069,7 @@ def __deftype_method( method_name: str, args: vec.Vector, kwarg_support: Optional[KeywordArgSupport] = None, -) -> Method: +) -> DefTypeMethodArity: """Emit a node for a method member of a deftype* form.""" with ctx.new_symbol_table(method_name): try: @@ -1078,17 +1092,20 @@ def __deftype_method( ctx.put_new_symbol(this_arg, this_binding, warn_if_unused=False) params = args[1:] - has_vargs, param_nodes = __deftype_method_param_bindings(ctx, params) + has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings( + ctx, params + ) loop_id = genname(method_name) with ctx.new_recur_point(loop_id, param_nodes): with ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) - method = Method( + method = DefTypeMethodArity( form=form, name=method_name, this_local=this_binding, params=vec.vector(param_nodes), + fixed_arity=fixed_arity, is_variadic=has_vargs, kwarg_support=kwarg_support, body=Do( @@ -1112,7 +1129,7 @@ def __deftype_property( form: Union[llist.List, ISeq], method_name: str, args: vec.Vector, -) -> PropertyMethod: +) -> DefTypeProperty: """Emit a node for a :property member of a deftype* form.""" with ctx.new_symbol_table(method_name): try: @@ -1135,7 +1152,7 @@ def __deftype_property( ctx.put_new_symbol(this_arg, this_binding, warn_if_unused=False) params = args[1:] - has_vargs, param_nodes = __deftype_method_param_bindings(ctx, params) + has_vargs, _, param_nodes = __deftype_method_param_bindings(ctx, params) if len(param_nodes) > 0: raise AnalyzerException( @@ -1146,7 +1163,7 @@ def __deftype_property( with ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) - prop = PropertyMethod( + prop = DefTypeProperty( form=form, name=method_name, this_local=this_binding, @@ -1172,16 +1189,19 @@ def __deftype_staticmethod( method_name: str, args: vec.Vector, kwarg_support: Optional[KeywordArgSupport] = None, -) -> StaticMethod: +) -> DefTypeStaticMethodArity: """Emit a node for a :staticmethod member of a deftype* form.""" with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(method_name): - has_vargs, param_nodes = __deftype_method_param_bindings(ctx, args) + has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(ctx, args) with ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) - method = StaticMethod( + method = DefTypeStaticMethodArity( form=form, name=method_name, params=vec.vector(param_nodes), + fixed_arity=fixed_arity, + is_variadic=has_vargs, + kwarg_support=kwarg_support, body=Do( form=form.rest, statements=vec.vector(stmts), @@ -1192,21 +1212,24 @@ def __deftype_staticmethod( env=ctx.get_node_env(), ), env=ctx.get_node_env(), - is_variadic=has_vargs, - kwarg_support=kwarg_support, ) method.visit(_assert_no_recur) return method -def __deftype_member( # pylint: disable=too-many-branches +def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches ctx: AnalyzerContext, form: Union[llist.List, ISeq] -) -> DefTypeMember: - """Emit a member node for a deftype* form. +) -> Union[DefTypeMethodArityBase, DefTypeProperty]: + """Emit either a `deftype*` property node or an arity of a `deftype*` method. + + Unlike standard `fn*` definitions, multiple arities for a single method are + not defined within some containing node. As such, we can only emit either a + full property node (since properties may not be multi-arity) or the single + arity of a method, classmethod, or staticmethod. - Member nodes are determined by the presence or absence of certain - metadata elements on the input form (or the form's first member, - typically a symbol naming that member).""" + The type of the member node is determined by the presence or absence of certain + metadata elements on the input form (or the form's first member, typically a + symbol naming that member).""" if not isinstance(form.first, sym.Symbol): raise AnalyzerException( "deftype* method must be named by symbol: (name [& args] & body)", @@ -1263,14 +1286,95 @@ def __deftype_member( # pylint: disable=too-many-branches ) +def __deftype_method_node_from_arities( + ctx: AnalyzerContext, + form: Union[llist.List, ISeq], + arities: List[DefTypeMethodArityBase], +) -> DefTypeMethodBase: + """Roll all of the collected arities up into a single method node.""" + fixed_arities: MutableSet[int] = set() + fixed_arity_for_variadic: Optional[int] = None + num_variadic = 0 + for arity in arities: + if fixed_arity_for_variadic is not None: + if arity.fixed_arity >= fixed_arity_for_variadic: + raise AnalyzerException( + "deftype method may not have a method with fixed arity greater " + "than fixed arity of variadic function", + form=arity.form, + ) + if arity.is_variadic: + if num_variadic > 0: + raise AnalyzerException( + "deftype method may have at most 1 variadic arity", form=arity.form + ) + fixed_arity_for_variadic = arity.fixed_arity + num_variadic += 1 + else: + if arity.fixed_arity in fixed_arities: + raise AnalyzerException( + "deftype may not have multiple methods with the same fixed arity", + form=arity.form, + ) + fixed_arities.add(arity.fixed_arity) + + if fixed_arity_for_variadic is not None and any( + fixed_arity_for_variadic < arity for arity in fixed_arities + ): + raise AnalyzerException( + "variadic arity may not have fewer fixed arity arguments than any other arities", + form=form, + ) + + assert ( + len(set(arity.name for arity in arities)) <= 1 + ), "arities must have the same name defined" + + if len(arities) > 1 and any(arity.kwarg_support is not None for arity in arities): + raise AnalyzerException( + "multi-arity deftype* methods may not declare support for keyword arguments", + form=form, + ) + + if all(isinstance(e, DefTypeMethodArity) for e in arities): + return DefTypeMethod( + form=form, + name=arities[0].name, + max_fixed_arity=max(fixed_arities), + arities=vec.vector(arities), # type: ignore[arg-type] + is_variadic=num_variadic == 1, + env=ctx.get_node_env(), + ) + elif all(isinstance(e, DefTypeClassMethodArity) for e in arities): + return DefTypeClassMethod( + form=form, + name=arities[0].name, + max_fixed_arity=max(fixed_arities), + arities=vec.vector(arities), # type: ignore[arg-type] + is_variadic=num_variadic == 1, + env=ctx.get_node_env(), + ) + elif all(isinstance(e, DefTypeStaticMethodArity) for e in arities): + return DefTypeStaticMethod( + form=form, + name=arities[0].name, + max_fixed_arity=max(fixed_arities), + arities=vec.vector(arities), # type: ignore[arg-type] + is_variadic=num_variadic == 1, + env=ctx.get_node_env(), + ) + else: + raise AnalyzerException( + "deftype* method arities must all be declared one of :classmethod, " + ":property, :staticmethod, or none (for a standard method)", + form=form, + ) + + def __deftype_impls( # pylint: disable=too-many-branches ctx: AnalyzerContext, form: ISeq ) -> Tuple[List[DefTypeBase], List[DefTypeMember]]: """Roll up deftype* declared bases and method implementations.""" - interface_names: MutableSet[sym.Symbol] = set() - interfaces = [] - methods: List[DefTypeMember] = [] - if runtime.to_seq(form) is None: return [], [] @@ -1286,6 +1390,8 @@ def __deftype_impls( # pylint: disable=too-many-branches form=implements, ) + interface_names: MutableSet[sym.Symbol] = set() + interfaces = [] for iface in implements: if not isinstance(iface, sym.Symbol): raise AnalyzerException("deftype* interfaces must be symbols", form=iface) @@ -1305,16 +1411,34 @@ def __deftype_impls( # pylint: disable=too-many-branches ) interfaces.append(current_interface) + methods: MutableMapping[ + str, List[DefTypeMethodArityBase] + ] = collections.defaultdict(list) + props: MutableMapping[str, DefTypeProperty] = {} for elem in runtime.nthrest(form, 2): - if isinstance(elem, ISeq): - methods.append(__deftype_member(ctx, elem)) - else: + if not isinstance(elem, ISeq): raise AnalyzerException( f"deftype* must consist of interface or protocol names and methods", form=elem, ) - return interfaces, list(methods) + member = __deftype_prop_or_method_arity(ctx, elem) + if isinstance(member, DefTypeProperty): + if member.name in props: + raise AnalyzerException( + f"deftype* property may only have one arity defined", + form=elem, + lisp_ast=member, + ) + props[member.name] = member + else: + methods[member.name].append(member) + + members: List[DefTypeMember] = [] + for method_name, arities in methods.items(): + members.append(__deftype_method_node_from_arities(ctx, form, arities)) + + return interfaces, list(members) def __is_abstract(tp: Type) -> bool: @@ -1643,14 +1767,14 @@ def _fn_ast( # pylint: disable=too-many-branches with ctx.new_func_ctx(is_async=is_async): if isinstance(arity_or_args, llist.List): - methods = vec.vector( + arities = vec.vector( map( partial(__fn_method_ast, ctx, fnname=name), runtime.nthrest(form, idx), ) ) elif isinstance(arity_or_args, vec.Vector): - methods = vec.v( + arities = vec.v( __fn_method_ast(ctx, runtime.nthrest(form, idx), fnname=name) ) else: @@ -1659,7 +1783,7 @@ def _fn_ast( # pylint: disable=too-many-branches form=form, ) - nmethods = count(methods) + nmethods = count(arities) assert nmethods > 0, "fn must have at least one arity" if kwarg_support is not None and nmethods > 1: @@ -1671,28 +1795,28 @@ def _fn_ast( # pylint: disable=too-many-branches fixed_arities: MutableSet[int] = set() fixed_arity_for_variadic: Optional[int] = None num_variadic = 0 - for method in methods: + for arity in arities: if fixed_arity_for_variadic is not None: - if method.fixed_arity >= fixed_arity_for_variadic: + if arity.fixed_arity >= fixed_arity_for_variadic: raise AnalyzerException( "fn may not have a method with fixed arity greater than " "fixed arity of variadic function", - form=method.form, + form=arity.form, ) - if method.is_variadic: + if arity.is_variadic: if num_variadic > 0: raise AnalyzerException( - "fn may have at most 1 variadic arity", form=method.form + "fn may have at most 1 variadic arity", form=arity.form ) - fixed_arity_for_variadic = method.fixed_arity + fixed_arity_for_variadic = arity.fixed_arity num_variadic += 1 else: - if method.fixed_arity in fixed_arities: + if arity.fixed_arity in fixed_arities: raise AnalyzerException( "fn may not have multiple methods with the same fixed arity", - form=method.form, + form=arity.form, ) - fixed_arities.add(method.fixed_arity) + fixed_arities.add(arity.fixed_arity) if fixed_arity_for_variadic is not None and any( fixed_arity_for_variadic < arity for arity in fixed_arities @@ -1705,8 +1829,8 @@ def _fn_ast( # pylint: disable=too-many-branches return Fn( form=form, is_variadic=num_variadic == 1, - max_fixed_arity=max([node.fixed_arity for node in methods]), - methods=methods, + max_fixed_arity=max(node.fixed_arity for node in arities), + arities=arities, local=name_node, env=ctx.get_node_env(pos=ctx.syntax_position), is_async=is_async, @@ -2235,8 +2359,13 @@ def _assert_recur_is_tail(node: Node) -> None: # pylint: disable=too-many-branc for child in node.statements: _assert_no_recur(child) _assert_recur_is_tail(node.ret) - elif node.op in {NodeOp.FN, NodeOp.FN_METHOD, NodeOp.METHOD}: - assert isinstance(node, (Fn, FnArity, Method)) + elif node.op in { + NodeOp.FN, + NodeOp.FN_ARITY, + NodeOp.DEFTYPE_METHOD, + NodeOp.DEFTYPE_METHOD_ARITY, + }: + assert isinstance(node, (Fn, FnArity, DefTypeMethod, DefTypeMethodArity)) node.visit(_assert_recur_is_tail) elif node.op == NodeOp.IF: assert isinstance(node, If) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index f3a62ebdc..016bbbdf9 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -47,12 +47,17 @@ Await, Binding, Catch, - ClassMethod, Const, ConstType, Def, DefType, + DefTypeClassMethod, DefTypeMember, + DefTypeMethod, + DefTypeMethodArityBase, + DefTypeMethodBase, + DefTypeProperty, + DefTypeStaticMethod, Do, Fn, FnArity, @@ -71,12 +76,10 @@ Map as MapNode, MaybeClass, MaybeHostForm, - Method, Node, NodeEnv, NodeOp, NodeSyntacticPosition, - PropertyMethod, PyDict, PyList, PySet, @@ -87,7 +90,6 @@ Require, Set as SetNode, SetBang, - StaticMethod, Throw, Try, VarRef, @@ -792,64 +794,187 @@ def _def_to_py_ast( # pylint: disable=too-many-branches ) -@_with_ast_loc -def __deftype_classmethod_to_py_ast( - ctx: GeneratorContext, node: ClassMethod +def __multi_arity_deftype_dispatch_method( # pylint: disable=too-many-arguments,too-many-locals + name: str, + arity_map: Mapping[int, str], + default_name: Optional[str] = None, + max_fixed_arity: Optional[int] = None, ) -> GeneratedPyAST: - assert node.op == NodeOp.CLASS_METHOD - method_name = munge(node.name) + """Return the Python AST nodes for an argument-length dispatch method + for multi-arity deftype* methods. + + class DefType: + def method(self, *args): + nargs = len(args) + method = __fn_dispatch_map.get(nargs) + if method: + return method(*args) + # Only if default + if nargs > max_fixed_arity: + return default(*args) + raise RuntimeError + """ + dispatch_map_name = f"{name}_dispatch_map" - with ctx.new_symbol_table(node.name): - class_name = genname(munge(node.class_local.name)) - class_sym = sym.symbol(node.class_local.name) - ctx.symbol_table.new_symbol(class_sym, class_name, LocalType.ARG) + dispatch_keys, dispatch_vals = [], [] + for k, v in arity_map.items(): + dispatch_keys.append(ast.Constant(k)) + dispatch_vals.append(ast.Name(id=v, ctx=ast.Load())) - fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, node.params, node.body) - return GeneratedPyAST( - node=ast.FunctionDef( - name=method_name, - args=ast.arguments( - args=list( - chain([ast.arg(arg=class_name, annotation=None)], fn_args) - ), - kwarg=None, - vararg=varg, - kwonlyargs=[], - defaults=[], - kw_defaults=[], - ), - body=fn_body_ast, - decorator_list=list( - chain([_PY_CLASSMETHOD_FN_NAME], __kwargs_support_decorator(node)) + nargs_name = genname("nargs") + method_name = genname("method") + body = [ + ast.Assign( + targets=[ast.Name(id=nargs_name, ctx=ast.Store())], + value=ast.Call( + func=ast.Name(id="len", ctx=ast.Load()), + args=[ast.Name(id=_MULTI_ARITY_ARG_NAME, ctx=ast.Load())], + keywords=[], + ), + ), + ast.Assign( + targets=[ast.Name(id=method_name, ctx=ast.Store())], + value=ast.Call( + func=ast.Attribute( + value=ast.Name(id=dispatch_map_name, ctx=ast.Load()), + attr="get", + ctx=ast.Load(), ), - returns=None, - ) - ) + args=[ast.Name(id=nargs_name, ctx=ast.Load())], + keywords=[], + ), + ), + ast.If( + test=ast.Compare( + left=ast.Constant(None), + ops=[ast.IsNot()], + comparators=[ast.Name(id=method_name, ctx=ast.Load())], + ), + body=[ + ast.Return( + value=ast.Call( + func=ast.Name(id=method_name, ctx=ast.Load()), + args=[ + ast.Starred( + value=ast.Name( + id=_MULTI_ARITY_ARG_NAME, ctx=ast.Load() + ), + ctx=ast.Load(), + ) + ], + keywords=[], + ) + ) + ], + orelse=[] + if default_name is None + else [ + ast.If( + test=ast.Compare( + left=ast.Name(id=nargs_name, ctx=ast.Load()), + ops=[ast.GtE()], + comparators=[ast.Constant(max_fixed_arity)], + ), + body=[ + ast.Return( + value=ast.Call( + func=ast.Name(id=default_name, ctx=ast.Load()), + args=[ + ast.Starred( + value=ast.Name( + id=_MULTI_ARITY_ARG_NAME, ctx=ast.Load() + ), + ctx=ast.Load(), + ) + ], + keywords=[], + ) + ) + ], + orelse=[], + ) + ], + ), + ast.Raise( + exc=ast.Call( + func=_load_attr("basilisp.lang.runtime.RuntimeException"), + args=[ + ast.Constant(f"Wrong number of args passed to method: {name}"), + ast.Name(id=nargs_name, ctx=ast.Load()), + ], + keywords=[], + ), + cause=None, + ), + ] + return GeneratedPyAST( + node=ast.Name(id=name, ctx=ast.Load()), + dependencies=chain( + [ + ast.Assign( + targets=[ast.Name(id=dispatch_map_name, ctx=ast.Store())], + value=ast.Dict(keys=dispatch_keys, values=dispatch_vals), + ) + ], + [ + ast.FunctionDef( + name=name, + args=ast.arguments( + args=[], + kwarg=None, + vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=body, + decorator_list=[_BASILISP_FN_FN_NAME], + returns=None, + ) + ], + ), + ) -@_with_ast_loc -def __deftype_property_to_py_ast( - ctx: GeneratorContext, node: PropertyMethod + +@_with_ast_loc_deps +def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals + ctx: GeneratorContext, + node: DefTypeMethodBase, + methods: Collection[DefTypeMethodArityBase], ) -> GeneratedPyAST: - assert node.op == NodeOp.PROPERTY_METHOD - method_name = munge(node.name) + """Return a Python AST node for a function with multiple arities.""" + assert node.op in { + NodeOp.DEFTYPE_METHOD, + NodeOp.DEFTYPE_CLASSMETHOD, + NodeOp.DEFTYPE_STATICMETHOD, + } + assert all(method.op == NodeOp.FN_ARITY for method in methods) + assert node.kwarg_support is None, "multi-arity methods do not support kwargs" - with ctx.new_symbol_table(node.name): - this_name = genname(munge(node.this_local.name)) - this_sym = sym.symbol(node.this_local.name) - ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) + py_method_name = genname(f"__{node.name}") - with ctx.new_this(this_sym): + arity_to_name = {} + rest_arity_name: Optional[str] = None + fn_defs = [] + for method in methods: + arity_name = f"{py_method_name}__arity{'_rest' if method.is_variadic else method.fixed_arity}" + if method.is_variadic: + rest_arity_name = arity_name + else: + arity_to_name[method.fixed_arity] = arity_name + + with ctx.new_symbol_table(arity_name), ctx.new_recur_point( + method.loop_id, RecurType.FN, is_variadic=node.is_variadic + ): fn_args, varg, fn_body_ast = __fn_args_to_py_ast( - ctx, node.params, node.body + ctx, method.params, method.body ) - return GeneratedPyAST( - node=ast.FunctionDef( - name=method_name, + fn_defs.append( + ast.FunctionDef( + name=arity_name, args=ast.arguments( - args=list( - chain([ast.arg(arg=this_name, annotation=None)], fn_args) - ), + args=fn_args, kwarg=None, vararg=varg, kwonlyargs=[], @@ -857,25 +982,95 @@ def __deftype_property_to_py_ast( kw_defaults=[], ), body=fn_body_ast, - decorator_list=[_PY_PROPERTY_FN_NAME], + decorator_list=[_TRAMPOLINE_FN_NAME] + if ctx.recur_point.has_recur + else [], returns=None, ) ) + dispatch_fn_ast = __multi_arity_deftype_dispatch_method( + py_method_name, + arity_to_name, + default_name=rest_arity_name, + max_fixed_arity=node.max_fixed_arity, + ) + + return GeneratedPyAST( + node=dispatch_fn_ast.node, + dependencies=list(chain(fn_defs, dispatch_fn_ast.dependencies)), + ) + + +def __single_arity_deftype_method_to_py_ast( + ctx: GeneratorContext, + arity: DefTypeMethodArityBase, + prefix_args: Iterable[ast.arg] = (), + decorators: Iterable[ast.AST] = (), +) -> GeneratedPyAST: + """Generate a single arity deftype* method body.""" + fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, arity.params, arity.body) + return GeneratedPyAST( + node=ast.FunctionDef( + name=munge(arity.name), + args=ast.arguments( + args=list(chain(prefix_args, fn_args)), + kwarg=None, + vararg=varg, + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=fn_body_ast, + decorator_list=list(decorators), + returns=None, + ) + ) + @_with_ast_loc -def __deftype_method_to_py_ast(ctx: GeneratorContext, node: Method) -> GeneratedPyAST: - assert node.op == NodeOp.METHOD +def __deftype_classmethod_to_py_ast( + ctx: GeneratorContext, node: DefTypeClassMethod +) -> GeneratedPyAST: + """Return a Python AST Node for a `deftype*` classmethod.""" + assert node.op == NodeOp.DEFTYPE_CLASSMETHOD + if len(node.arities) == 1: + arity = next(iter(node.arities)) + + assert arity.op == NodeOp.DEFTYPE_CLASSMETHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name): + class_name = genname(munge(arity.class_local.name)) + class_sym = sym.symbol(arity.class_local.name) + ctx.symbol_table.new_symbol(class_sym, class_name, LocalType.ARG) + + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + prefix_args=(ast.arg(arg=class_name, annotation=None),), + decorators=list( + chain(_PY_CLASSMETHOD_FN_NAME, __kwargs_support_decorator(node),) + ), + ) + else: + return __multi_arity_fn_to_py_ast(ctx, node, node.arities,) + + +@_with_ast_loc +def __deftype_property_to_py_ast( + ctx: GeneratorContext, node: DefTypeProperty +) -> GeneratedPyAST: + assert node.op == NodeOp.DEFTYPE_PROPERTY method_name = munge(node.name) - with ctx.new_symbol_table(node.name), ctx.new_recur_point( - node.loop_id, RecurType.METHOD, is_variadic=node.is_variadic - ): + with ctx.new_symbol_table(node.name): this_name = genname(munge(node.this_local.name)) this_sym = sym.symbol(node.this_local.name) ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) with ctx.new_this(this_sym): + fn_args, varg, fn_body_ast = __fn_args_to_py_ast( ctx, node.params, node.body ) @@ -893,51 +1088,78 @@ def __deftype_method_to_py_ast(ctx: GeneratorContext, node: Method) -> Generated kw_defaults=[], ), body=fn_body_ast, - decorator_list=list( + decorator_list=[_PY_PROPERTY_FN_NAME], + returns=None, + ) + ) + + +@_with_ast_loc +def __deftype_method_to_py_ast( + ctx: GeneratorContext, node: DefTypeMethod +) -> GeneratedPyAST: + """Return a Python AST Node for a `deftype*` method.""" + assert node.op == NodeOp.DEFTYPE_METHOD + + if len(node.arities) == 1: + arity = next(iter(node.arities)) + + assert arity.op == NodeOp.DEFTYPE_METHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name), ctx.new_recur_point( + arity.loop_id, RecurType.METHOD, is_variadic=node.is_variadic + ): + this_name = genname(munge(arity.this_local.name)) + this_sym = sym.symbol(arity.this_local.name) + ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) + + with ctx.new_this(this_sym): + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + prefix_args=(ast.arg(arg=this_name, annotation=None),), + decorators=list( chain( [_TRAMPOLINE_FN_NAME] if ctx.recur_point.has_recur else [], __kwargs_support_decorator(node), ) ), - returns=None, ) - ) + else: + return __multi_arity_fn_to_py_ast(ctx, node, node.arities,) @_with_ast_loc def __deftype_staticmethod_to_py_ast( - ctx: GeneratorContext, node: StaticMethod + ctx: GeneratorContext, node: DefTypeStaticMethod ) -> GeneratedPyAST: - assert node.op == NodeOp.STATIC_METHOD - method_name = munge(node.name) + """Return a Python AST Node for a `deftype*` staticmethod.""" + assert node.op == NodeOp.DEFTYPE_METHOD - with ctx.new_symbol_table(node.name): - fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, node.params, node.body) - return GeneratedPyAST( - node=ast.FunctionDef( - name=method_name, - args=ast.arguments( - args=list(fn_args), - kwarg=None, - vararg=varg, - kwonlyargs=[], - defaults=[], - kw_defaults=[], - ), - body=fn_body_ast, - decorator_list=list( - chain([_PY_STATICMETHOD_FN_NAME], __kwargs_support_decorator(node)) + if len(node.arities) == 1: + arity = next(iter(node.arities)) + + assert arity.op == NodeOp.DEFTYPE_STATICMETHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name): + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + decorators=list( + chain(_PY_STATICMETHOD_FN_NAME, __kwargs_support_decorator(node)) ), - returns=None, ) - ) + else: + return __multi_arity_fn_to_py_ast(ctx, node, node.arities) _DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, PyASTGenerator] = { - NodeOp.CLASS_METHOD: __deftype_classmethod_to_py_ast, - NodeOp.METHOD: __deftype_method_to_py_ast, - NodeOp.PROPERTY_METHOD: __deftype_property_to_py_ast, - NodeOp.STATIC_METHOD: __deftype_staticmethod_to_py_ast, + NodeOp.DEFTYPE_CLASSMETHOD: __deftype_classmethod_to_py_ast, + NodeOp.DEFTYPE_METHOD: __deftype_method_to_py_ast, + NodeOp.DEFTYPE_PROPERTY: __deftype_property_to_py_ast, + NodeOp.DEFTYPE_STATICMETHOD: __deftype_staticmethod_to_py_ast, } @@ -1178,7 +1400,7 @@ def __fn_meta( def __kwargs_support_decorator( - node: Union[Fn, ClassMethod, Method, StaticMethod] + node: Union[Fn, DefTypeClassMethod, DefTypeMethod, DefTypeStaticMethod] ) -> Iterable[ast.AST]: if node.kwarg_support is None: return @@ -1199,7 +1421,7 @@ def __single_arity_fn_to_py_ast( ) -> GeneratedPyAST: """Return a Python AST node for a function with a single arity.""" assert node.op == NodeOp.FN - assert method.op == NodeOp.FN_METHOD + assert method.op == NodeOp.FN_ARITY lisp_fn_name = node.local.name if node.local is not None else None py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name) @@ -1274,9 +1496,9 @@ def __multi_arity_dispatch_fn( # pylint: disable=too-many-arguments,too-many-lo def fn(*args): nargs = len(args) - method = __fn_dispatch_map.get(nargs) - if method: - return method(*args) + arity = __fn_dispatch_map.get(nargs) + if arity: + return arity(*args) # Only if default if nargs > max_fixed_arity: return default(*args) @@ -1293,7 +1515,7 @@ def fn(*args): handle_return = __handle_async_return if is_async else __handle_return nargs_name = genname("nargs") - method_name = genname("method") + arity_name = genname("arity") body = [ ast.Assign( targets=[ast.Name(id=nargs_name, ctx=ast.Store())], @@ -1304,7 +1526,7 @@ def fn(*args): ), ), ast.Assign( - targets=[ast.Name(id=method_name, ctx=ast.Store())], + targets=[ast.Name(id=arity_name, ctx=ast.Store())], value=ast.Call( func=ast.Attribute( value=ast.Name(id=dispatch_map_name, ctx=ast.Load()), @@ -1319,12 +1541,12 @@ def fn(*args): test=ast.Compare( left=ast.Constant(None), ops=[ast.IsNot()], - comparators=[ast.Name(id=method_name, ctx=ast.Load())], + comparators=[ast.Name(id=arity_name, ctx=ast.Load())], ), body=[ handle_return( ast.Call( - func=ast.Name(id=method_name, ctx=ast.Load()), + func=ast.Name(id=arity_name, ctx=ast.Load()), args=[ ast.Starred( value=ast.Name( @@ -1415,14 +1637,14 @@ def fn(*args): def __multi_arity_fn_to_py_ast( # pylint: disable=too-many-locals ctx: GeneratorContext, node: Fn, - methods: Collection[FnArity], + arities: Collection[FnArity], def_name: Optional[str] = None, meta_node: Optional[MetaNode] = None, ) -> GeneratedPyAST: """Return a Python AST node for a function with multiple arities.""" assert node.op == NodeOp.FN - assert all([method.op == NodeOp.FN_METHOD for method in methods]) - assert node.kwarg_support is None, "multi-arity functions may not support kwargs" + assert all(arity.op == NodeOp.FN_ARITY for arity in arities) + assert node.kwarg_support is None, "multi-arity functions do not support kwargs" lisp_fn_name = node.local.name if node.local is not None else None py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name) @@ -1432,15 +1654,17 @@ def __multi_arity_fn_to_py_ast( # pylint: disable=too-many-locals arity_to_name = {} rest_arity_name: Optional[str] = None fn_defs = [] - for method in methods: - arity_name = f"{py_fn_name}__arity{'_rest' if method.is_variadic else method.fixed_arity}" - if method.is_variadic: + for arity in arities: + arity_name = ( + f"{py_fn_name}__arity{'_rest' if arity.is_variadic else arity.fixed_arity}" + ) + if arity.is_variadic: rest_arity_name = arity_name else: - arity_to_name[method.fixed_arity] = arity_name + arity_to_name[arity.fixed_arity] = arity_name with ctx.new_symbol_table(arity_name), ctx.new_recur_point( - method.loop_id, RecurType.FN, is_variadic=node.is_variadic + arity.loop_id, RecurType.FN, is_variadic=node.is_variadic ): # Allow named anonymous functions to recursively call themselves if lisp_fn_name is not None: @@ -1449,7 +1673,7 @@ def __multi_arity_fn_to_py_ast( # pylint: disable=too-many-locals ) fn_args, varg, fn_body_ast = __fn_args_to_py_ast( - ctx, method.params, method.body + ctx, arity.params, arity.body ) fn_defs.append( py_fn_node( diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index f055822fc..105feddf9 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -27,6 +27,7 @@ _CMP_OFF = getattr(attr, "__version_info__", (0,)) >= (19, 2) +ARITIES = kw.keyword("arities") BODY = kw.keyword("body") CLASS = kw.keyword("class") LOCAL = kw.keyword("local") @@ -35,7 +36,6 @@ CLASS_LOCAL = kw.keyword("class-local") THIS_LOCAL = kw.keyword("this-local") FIELDS = kw.keyword("fields") -METHODS = kw.keyword("methods") MEMBERS = kw.keyword("members") PARAMS = kw.keyword("params") TARGET = kw.keyword("target") @@ -59,13 +59,19 @@ class NodeOp(Enum): AWAIT = kw.keyword("await") BINDING = kw.keyword("binding") CATCH = kw.keyword("catch") - CLASS_METHOD = kw.keyword("class-method") CONST = kw.keyword("const") DEF = kw.keyword("def") DEFTYPE = kw.keyword("deftype") + DEFTYPE_PROPERTY = kw.keyword("deftype-property") + DEFTYPE_METHOD = kw.keyword("deftype-method") + DEFTYPE_METHOD_ARITY = kw.keyword("deftype-method-arity") + DEFTYPE_CLASSMETHOD = kw.keyword("deftype-classmethod") + DEFTYPE_CLASSMETHOD_ARITY = kw.keyword("deftype-classmethod-arity") + DEFTYPE_STATICMETHOD = kw.keyword("deftype-staticmethod") + DEFTYPE_STATICMETHOD_ARITY = kw.keyword("deftype-staticmethod-arity") DO = kw.keyword("do") FN = kw.keyword("fn") - FN_METHOD = kw.keyword("fn-method") + FN_ARITY = kw.keyword("fn-arity") HOST_CALL = kw.keyword("host-call") HOST_FIELD = kw.keyword("host-field") HOST_INTEROP = kw.keyword("host-interop") @@ -80,8 +86,6 @@ class NodeOp(Enum): MAP = kw.keyword("map") MAYBE_CLASS = kw.keyword("maybe-class") MAYBE_HOST_FORM = kw.keyword("maybe-host-form") - METHOD = kw.keyword("method") - PROPERTY_METHOD = kw.keyword("property") PY_DICT = kw.keyword("py-dict") PY_LIST = kw.keyword("py-list") PY_SET = kw.keyword("py-set") @@ -92,7 +96,6 @@ class NodeOp(Enum): REQUIRE_ALIAS = kw.keyword("require-alias") SET = kw.keyword("set") SET_BANG = kw.keyword("set!") - STATIC_METHOD = kw.keyword("static-method") THROW = kw.keyword("throw") TRY = kw.keyword("try") VAR = kw.keyword("var") @@ -389,10 +392,121 @@ class DefType(Node[SpecialForm]): class DefTypeMember(Node[SpecialForm]): form: SpecialForm name: str + env: NodeEnv + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeProperty(DefTypeMember): + this_local: Binding params: Iterable[Binding] body: "Do" + children: Sequence[kw.Keyword] = vec.v(THIS_LOCAL, PARAMS, BODY) + op: NodeOp = NodeOp.DEFTYPE_PROPERTY + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +T_DefTypeMethodArity = TypeVar("T_DefTypeMethodArity", bound="DefTypeMethodArityBase") + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeMethodBase(DefTypeMember, Generic[T_DefTypeMethodArity]): + max_fixed_arity: int + arities: IPersistentVector[T_DefTypeMethodArity] + + @property + @abstractmethod + def is_variadic(self) -> bool: + raise NotImplementedError + + @property + @abstractmethod + def kwarg_support(self) -> Optional[KeywordArgSupport]: + raise NotImplementedError + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeMethod(DefTypeMethodBase["DefTypeMethodArity"]): + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(ARITIES) + op: NodeOp = NodeOp.DEFTYPE_METHOD + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeClassMethod(DefTypeMethodBase["DefTypeClassMethodArity"]): + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(ARITIES) + op: NodeOp = NodeOp.DEFTYPE_CLASSMETHOD + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeStaticMethod(DefTypeMethodBase["DefTypeStaticMethodArity"]): + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(ARITIES) + op: NodeOp = NodeOp.DEFTYPE_STATICMETHOD + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeMethodArityBase(Node[SpecialForm]): + form: SpecialForm + name: str + params: Iterable[Binding] + fixed_arity: int + body: "Do" env: NodeEnv + @property + @abstractmethod + def is_variadic(self) -> bool: + raise NotImplementedError + + @property + @abstractmethod + def kwarg_support(self) -> Optional[KeywordArgSupport]: + raise NotImplementedError + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeMethodArity(DefTypeMethodArityBase): + this_local: Binding + loop_id: LoopID + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(THIS_LOCAL, PARAMS, BODY) + op: NodeOp = NodeOp.DEFTYPE_METHOD_ARITY + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeClassMethodArity(DefTypeMethodArityBase): + class_local: Binding + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(CLASS_LOCAL, PARAMS, BODY) + op: NodeOp = NodeOp.DEFTYPE_CLASSMETHOD_ARITY + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class DefTypeStaticMethodArity(DefTypeMethodArityBase): + is_variadic: bool = False + kwarg_support: Optional[KeywordArgSupport] = None + children: Sequence[kw.Keyword] = vec.v(PARAMS, BODY) + op: NodeOp = NodeOp.DEFTYPE_STATICMETHOD_ARITY + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + @attr.s(auto_attribs=True, frozen=True, slots=True) class Do(Node[SpecialForm]): @@ -417,7 +531,7 @@ class Fn(Node[SpecialForm]): is_variadic: bool = False is_async: bool = False kwarg_support: Optional[KeywordArgSupport] = None - children: Sequence[kw.Keyword] = vec.v(METHODS) + children: Sequence[kw.Keyword] = vec.v(ARITIES) op: NodeOp = NodeOp.FN top_level: bool = False raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() @@ -433,7 +547,7 @@ class FnArity(Node[SpecialForm]): env: NodeEnv is_variadic: bool = False children: Sequence[kw.Keyword] = vec.v(PARAMS, BODY) - op: NodeOp = NodeOp.FN_METHOD + op: NodeOp = NodeOp.FN_ARITY top_level: bool = False raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() @@ -603,48 +717,6 @@ class MaybeHostForm(Node[sym.Symbol]): raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() -@attr.s(auto_attribs=True, frozen=True, slots=True) -class Method(DefTypeMember): - this_local: Binding - loop_id: LoopID - is_variadic: bool - kwarg_support: Optional[KeywordArgSupport] = None - children: Sequence[kw.Keyword] = vec.v(THIS_LOCAL, PARAMS, BODY) - op: NodeOp = NodeOp.METHOD - top_level: bool = False - raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() - - -@attr.s(auto_attribs=True, frozen=True, slots=True) -class ClassMethod(DefTypeMember): - class_local: Binding - is_variadic: bool - kwarg_support: Optional[KeywordArgSupport] = None - children: Sequence[kw.Keyword] = vec.v(CLASS_LOCAL, PARAMS, BODY) - op: NodeOp = NodeOp.CLASS_METHOD - top_level: bool = False - raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() - - -@attr.s(auto_attribs=True, frozen=True, slots=True) -class PropertyMethod(DefTypeMember): - this_local: Binding - children: Sequence[kw.Keyword] = vec.v(THIS_LOCAL, PARAMS, BODY) - op: NodeOp = NodeOp.PROPERTY_METHOD - top_level: bool = False - raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() - - -@attr.s(auto_attribs=True, frozen=True, slots=True) -class StaticMethod(DefTypeMember): - is_variadic: bool - kwarg_support: Optional[KeywordArgSupport] = None - children: Sequence[kw.Keyword] = vec.v(PARAMS, BODY) - op: NodeOp = NodeOp.STATIC_METHOD - top_level: bool = False - raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() - - @attr.s( # type: ignore auto_attribs=True, **({"eq": True} if _CMP_OFF else {"cmp": False}), @@ -838,50 +910,6 @@ class WithMeta(Node[LispForm]): raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() -ParentNode = Union[ - Await, - Const, - Def, - Do, - Fn, - HostCall, - HostField, - If, - Import, - Invoke, - Let, - LetFn, - Loop, - Map, - MaybeClass, - MaybeHostForm, - PyDict, - PyList, - PySet, - PyTuple, - Quote, - Require, - Set, - SetBang, - Throw, - Try, - VarRef, - Vector, - WithMeta, -] -ChildOnlyNode = Union[ - Binding, - ClassMethod, - Catch, - FnArity, - ImportAlias, - Local, - Method, - PropertyMethod, - Recur, - StaticMethod, -] -AnyNode = Union[ParentNode, ChildOnlyNode] SpecialFormNode = Union[ Await, Def, From 11b4f47428113dddf1dc706a1f4f1b9c09e2474c Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 8 Apr 2020 09:02:33 -0400 Subject: [PATCH 04/20] Appease our linting overlord --- src/basilisp/lang/compiler/analyzer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 038af027b..a410c70fb 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1286,7 +1286,7 @@ def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches ) -def __deftype_method_node_from_arities( +def __deftype_method_node_from_arities( # pylint: disable=too-many-branches ctx: AnalyzerContext, form: Union[llist.List, ISeq], arities: List[DefTypeMethodArityBase], @@ -1435,7 +1435,7 @@ def __deftype_impls( # pylint: disable=too-many-branches methods[member.name].append(member) members: List[DefTypeMember] = [] - for method_name, arities in methods.items(): + for _, arities in methods.items(): members.append(__deftype_method_node_from_arities(ctx, form, arities)) return interfaces, list(members) From f9b5c3325d5427d9efc159d88a94c2c33f369fab Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 8 Apr 2020 09:07:25 -0400 Subject: [PATCH 05/20] Fix node defs for 3.6 --- src/basilisp/lang/compiler/nodes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 105feddf9..5cd231776 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -409,7 +409,13 @@ class DefTypeProperty(DefTypeMember): T_DefTypeMethodArity = TypeVar("T_DefTypeMethodArity", bound="DefTypeMethodArityBase") -@attr.s(auto_attribs=True, frozen=True, slots=True) +# Use attrs `these` for now as there is an open bug around slotted +# generic classes: https://github.com/python-attrs/attrs/issues/313 +@attr.s( + auto_attribs=True, + frozen=True, + these={"max_fixed_arity": attr.ib(), "arities": attr.ib()}, +) class DefTypeMethodBase(DefTypeMember, Generic[T_DefTypeMethodArity]): max_fixed_arity: int arities: IPersistentVector[T_DefTypeMethodArity] From f6f5975c1afa0b2f7bfdf252aacdd30f70b32573 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 8 Apr 2020 19:32:12 -0400 Subject: [PATCH 06/20] Do a thing --- src/basilisp/lang/compiler/analyzer.py | 8 +- src/basilisp/lang/compiler/generator.py | 346 ++++++++++++++---------- 2 files changed, 205 insertions(+), 149 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index a410c70fb..142c7f234 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1336,11 +1336,13 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches form=form, ) + max_fixed_arity = max(arity.fixed_arity for arity in arities) + if all(isinstance(e, DefTypeMethodArity) for e in arities): return DefTypeMethod( form=form, name=arities[0].name, - max_fixed_arity=max(fixed_arities), + max_fixed_arity=max_fixed_arity, arities=vec.vector(arities), # type: ignore[arg-type] is_variadic=num_variadic == 1, env=ctx.get_node_env(), @@ -1349,7 +1351,7 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches return DefTypeClassMethod( form=form, name=arities[0].name, - max_fixed_arity=max(fixed_arities), + max_fixed_arity=max_fixed_arity, arities=vec.vector(arities), # type: ignore[arg-type] is_variadic=num_variadic == 1, env=ctx.get_node_env(), @@ -1358,7 +1360,7 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches return DefTypeStaticMethod( form=form, name=arities[0].name, - max_fixed_arity=max(fixed_arities), + max_fixed_arity=max_fixed_arity, arities=vec.vector(arities), # type: ignore[arg-type] is_variadic=num_variadic == 1, env=ctx.get_node_env(), diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index 016bbbdf9..afa805870 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -52,12 +52,15 @@ Def, DefType, DefTypeClassMethod, + DefTypeClassMethodArity, DefTypeMember, DefTypeMethod, + DefTypeMethodArity, DefTypeMethodArityBase, DefTypeMethodBase, DefTypeProperty, DefTypeStaticMethod, + DefTypeStaticMethodArity, Do, Fn, FnArity, @@ -794,19 +797,55 @@ def _def_to_py_ast( # pylint: disable=too-many-branches ) +def __single_arity_deftype_method_to_py_ast( + ctx: GeneratorContext, + arity: DefTypeMethodArityBase, + method_name: Optional[str] = None, + prefix_args: Iterable[ast.arg] = (), + decorators: Iterable[ast.AST] = (), +) -> GeneratedPyAST: + """Generate a single arity deftype* method body.""" + fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, arity.params, arity.body) + return GeneratedPyAST( + node=ast.FunctionDef( + name=method_name if method_name is not None else munge(arity.name), + args=ast.arguments( + args=list(chain(prefix_args, fn_args)), + kwarg=None, + vararg=varg, + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=fn_body_ast, + decorator_list=list(decorators), + returns=None, + ) + ) + + def __multi_arity_deftype_dispatch_method( # pylint: disable=too-many-arguments,too-many-locals name: str, arity_map: Mapping[int, str], default_name: Optional[str] = None, max_fixed_arity: Optional[int] = None, + instance_or_class_name: Optional[str] = None, + decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: - """Return the Python AST nodes for an argument-length dispatch method - for multi-arity deftype* methods. + """Return the Python AST nodes for an argument-length dispatch method for + multi-arity deftype* methods. class DefType: + def __method_arity_1(self, arg): ... + + def __method_arity_2(self, arg1, arg2): ... + def method(self, *args): nargs = len(args) - method = __fn_dispatch_map.get(nargs) + method = { + 1: self.__method_arity_1, + 2: self.__method_arity_2 + }.get(nargs) if method: return method(*args) # Only if default @@ -814,12 +853,14 @@ def method(self, *args): return default(*args) raise RuntimeError """ - dispatch_map_name = f"{name}_dispatch_map" - dispatch_keys, dispatch_vals = [], [] for k, v in arity_map.items(): dispatch_keys.append(ast.Constant(k)) - dispatch_vals.append(ast.Name(id=v, ctx=ast.Load())) + dispatch_vals.append( + _load_attr( + v if instance_or_class_name is None else f"{instance_or_class_name}.{v}" + ) + ) nargs_name = genname("nargs") method_name = genname("method") @@ -836,7 +877,7 @@ def method(self, *args): targets=[ast.Name(id=method_name, ctx=ast.Store())], value=ast.Call( func=ast.Attribute( - value=ast.Name(id=dispatch_map_name, ctx=ast.Load()), + value=ast.Dict(keys=dispatch_keys, values=dispatch_vals), attr="get", ctx=ast.Load(), ), @@ -910,90 +951,85 @@ def method(self, *args): return GeneratedPyAST( node=ast.Name(id=name, ctx=ast.Load()), - dependencies=chain( - [ - ast.Assign( - targets=[ast.Name(id=dispatch_map_name, ctx=ast.Store())], - value=ast.Dict(keys=dispatch_keys, values=dispatch_vals), - ) - ], - [ - ast.FunctionDef( - name=name, - args=ast.arguments( - args=[], - kwarg=None, - vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), - kwonlyargs=[], - defaults=[], - kw_defaults=[], - ), - body=body, - decorator_list=[_BASILISP_FN_FN_NAME], - returns=None, - ) - ], - ), + dependencies=[ + ast.FunctionDef( + name=name, + args=ast.arguments( + args=[] + if instance_or_class_name is None + else [ast.arg(arg=instance_or_class_name, annotation=None)], + kwarg=None, + vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=body, + decorator_list=list(decorators), + returns=None, + ) + ], ) +_CreateMethodASTFunction = Callable[ + [GeneratorContext, DefTypeMethodBase, DefTypeMethodArityBase, Optional[str]], + GeneratedPyAST, +] + + @_with_ast_loc_deps def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals ctx: GeneratorContext, node: DefTypeMethodBase, - methods: Collection[DefTypeMethodArityBase], + create_method_ast: _CreateMethodASTFunction, + instance_or_class_name: Optional[str] = None, + decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: """Return a Python AST node for a function with multiple arities.""" - assert node.op in { - NodeOp.DEFTYPE_METHOD, - NodeOp.DEFTYPE_CLASSMETHOD, - NodeOp.DEFTYPE_STATICMETHOD, - } - assert all(method.op == NodeOp.FN_ARITY for method in methods) + arities = node.arities + + assert ( + ( + node.op == NodeOp.DEFTYPE_METHOD + and all(arity.op == NodeOp.DEFTYPE_METHOD_ARITY for arity in arities) + ) + or ( + node.op == NodeOp.DEFTYPE_CLASSMETHOD + and all(arity.op == NodeOp.DEFTYPE_CLASSMETHOD_ARITY for arity in arities) + ) + or ( + node.op == NodeOp.DEFTYPE_STATICMETHOD + and all(arity.op == NodeOp.DEFTYPE_STATICMETHOD_ARITY for arity in arities) + ) + ) assert node.kwarg_support is None, "multi-arity methods do not support kwargs" - py_method_name = genname(f"__{node.name}") + py_method_arity_name = genname(f"__{node.name}") arity_to_name = {} rest_arity_name: Optional[str] = None fn_defs = [] - for method in methods: - arity_name = f"{py_method_name}__arity{'_rest' if method.is_variadic else method.fixed_arity}" - if method.is_variadic: + for arity in arities: + arity_name = f"{py_method_arity_name}__arity{'_rest' if arity.is_variadic else arity.fixed_arity}" + if arity.is_variadic: rest_arity_name = arity_name else: - arity_to_name[method.fixed_arity] = arity_name + arity_to_name[arity.fixed_arity] = arity_name - with ctx.new_symbol_table(arity_name), ctx.new_recur_point( - method.loop_id, RecurType.FN, is_variadic=node.is_variadic - ): - fn_args, varg, fn_body_ast = __fn_args_to_py_ast( - ctx, method.params, method.body - ) - fn_defs.append( - ast.FunctionDef( - name=arity_name, - args=ast.arguments( - args=fn_args, - kwarg=None, - vararg=varg, - kwonlyargs=[], - defaults=[], - kw_defaults=[], - ), - body=fn_body_ast, - decorator_list=[_TRAMPOLINE_FN_NAME] - if ctx.recur_point.has_recur - else [], - returns=None, - ) - ) + fn_def = create_method_ast(ctx, node, arity, arity_name) + assert ( + not fn_def.dependencies + ), "deftype* method arities may not have dependency nodes" + fn_defs.append(fn_def.node) dispatch_fn_ast = __multi_arity_deftype_dispatch_method( - py_method_name, + py_method_arity_name, arity_to_name, default_name=rest_arity_name, max_fixed_arity=node.max_fixed_arity, + instance_or_class_name=instance_or_class_name, + decorators=decorators, ) return GeneratedPyAST( @@ -1002,30 +1038,29 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals ) -def __single_arity_deftype_method_to_py_ast( +def __deftype_classmethod_arity_to_py_ast( ctx: GeneratorContext, - arity: DefTypeMethodArityBase, - prefix_args: Iterable[ast.arg] = (), - decorators: Iterable[ast.AST] = (), + node: DefTypeClassMethod, + arity: DefTypeClassMethodArity, + method_name: Optional[str] = None, ) -> GeneratedPyAST: - """Generate a single arity deftype* method body.""" - fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, arity.params, arity.body) - return GeneratedPyAST( - node=ast.FunctionDef( - name=munge(arity.name), - args=ast.arguments( - args=list(chain(prefix_args, fn_args)), - kwarg=None, - vararg=varg, - kwonlyargs=[], - defaults=[], - kw_defaults=[], + assert arity.op == NodeOp.DEFTYPE_CLASSMETHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name): + class_name = genname(munge(arity.class_local.name)) + class_sym = sym.symbol(arity.class_local.name) + ctx.symbol_table.new_symbol(class_sym, class_name, LocalType.ARG) + + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + method_name=method_name, + prefix_args=(ast.arg(arg=class_name, annotation=None),), + decorators=list( + chain([_PY_CLASSMETHOD_FN_NAME], __kwargs_support_decorator(node),) ), - body=fn_body_ast, - decorator_list=list(decorators), - returns=None, ) - ) @_with_ast_loc @@ -1034,27 +1069,19 @@ def __deftype_classmethod_to_py_ast( ) -> GeneratedPyAST: """Return a Python AST Node for a `deftype*` classmethod.""" assert node.op == NodeOp.DEFTYPE_CLASSMETHOD - if len(node.arities) == 1: - arity = next(iter(node.arities)) - assert arity.op == NodeOp.DEFTYPE_CLASSMETHOD_ARITY - assert node.name == arity.name - - with ctx.new_symbol_table(node.name): - class_name = genname(munge(arity.class_local.name)) - class_sym = sym.symbol(arity.class_local.name) - ctx.symbol_table.new_symbol(class_sym, class_name, LocalType.ARG) - - return __single_arity_deftype_method_to_py_ast( - ctx, - arity, - prefix_args=(ast.arg(arg=class_name, annotation=None),), - decorators=list( - chain(_PY_CLASSMETHOD_FN_NAME, __kwargs_support_decorator(node),) - ), - ) + if len(node.arities) == 1: + return __deftype_classmethod_arity_to_py_ast( + ctx, node, next(iter(node.arities)) # type: ignore[arg-type] + ) else: - return __multi_arity_fn_to_py_ast(ctx, node, node.arities,) + return __multi_arity_deftype_method_to_py_ast( + ctx, + node, + cast(_CreateMethodASTFunction, __deftype_classmethod_arity_to_py_ast), + instance_or_class_name=genname("cls"), + decorators=(_PY_CLASSMETHOD_FN_NAME,), + ) @_with_ast_loc @@ -1070,7 +1097,6 @@ def __deftype_property_to_py_ast( ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) with ctx.new_this(this_sym): - fn_args, varg, fn_body_ast = __fn_args_to_py_ast( ctx, node.params, node.body ) @@ -1094,6 +1120,37 @@ def __deftype_property_to_py_ast( ) +def __deftype_method_arity_to_py_ast( + ctx: GeneratorContext, + node: DefTypeMethod, + arity: DefTypeMethodArity, + method_name: Optional[str] = None, +) -> GeneratedPyAST: + assert arity.op == NodeOp.DEFTYPE_METHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name), ctx.new_recur_point( + arity.loop_id, RecurType.METHOD, is_variadic=node.is_variadic + ): + this_name = genname(munge(arity.this_local.name)) + this_sym = sym.symbol(arity.this_local.name) + ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) + + with ctx.new_this(this_sym): + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + method_name=method_name, + prefix_args=(ast.arg(arg=this_name, annotation=None),), + decorators=list( + chain( + [_TRAMPOLINE_FN_NAME] if ctx.recur_point.has_recur else [], + __kwargs_support_decorator(node), + ) + ), + ) + + @_with_ast_loc def __deftype_method_to_py_ast( ctx: GeneratorContext, node: DefTypeMethod @@ -1102,32 +1159,34 @@ def __deftype_method_to_py_ast( assert node.op == NodeOp.DEFTYPE_METHOD if len(node.arities) == 1: - arity = next(iter(node.arities)) + return __deftype_method_arity_to_py_ast(ctx, node, next(iter(node.arities))) # type: ignore[arg-type] + else: + return __multi_arity_deftype_method_to_py_ast( + ctx, + node, + cast(_CreateMethodASTFunction, __deftype_method_arity_to_py_ast), + instance_or_class_name="self", + ) - assert arity.op == NodeOp.DEFTYPE_METHOD_ARITY - assert node.name == arity.name - with ctx.new_symbol_table(node.name), ctx.new_recur_point( - arity.loop_id, RecurType.METHOD, is_variadic=node.is_variadic - ): - this_name = genname(munge(arity.this_local.name)) - this_sym = sym.symbol(arity.this_local.name) - ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) - - with ctx.new_this(this_sym): - return __single_arity_deftype_method_to_py_ast( - ctx, - arity, - prefix_args=(ast.arg(arg=this_name, annotation=None),), - decorators=list( - chain( - [_TRAMPOLINE_FN_NAME] if ctx.recur_point.has_recur else [], - __kwargs_support_decorator(node), - ) - ), - ) - else: - return __multi_arity_fn_to_py_ast(ctx, node, node.arities,) +def __deftype_staticmethod_arity_to_py_ast( + ctx: GeneratorContext, + node: DefTypeStaticMethod, + arity: DefTypeStaticMethodArity, + method_name: Optional[str] = None, +) -> GeneratedPyAST: + assert arity.op == NodeOp.DEFTYPE_STATICMETHOD_ARITY + assert node.name == arity.name + + with ctx.new_symbol_table(node.name): + return __single_arity_deftype_method_to_py_ast( + ctx, + arity, + method_name=method_name, + decorators=list( + chain([_PY_STATICMETHOD_FN_NAME], __kwargs_support_decorator(node)) + ), + ) @_with_ast_loc @@ -1138,21 +1197,16 @@ def __deftype_staticmethod_to_py_ast( assert node.op == NodeOp.DEFTYPE_METHOD if len(node.arities) == 1: - arity = next(iter(node.arities)) - - assert arity.op == NodeOp.DEFTYPE_STATICMETHOD_ARITY - assert node.name == arity.name - - with ctx.new_symbol_table(node.name): - return __single_arity_deftype_method_to_py_ast( - ctx, - arity, - decorators=list( - chain(_PY_STATICMETHOD_FN_NAME, __kwargs_support_decorator(node)) - ), - ) + return __deftype_staticmethod_arity_to_py_ast( + ctx, node, next(iter(node.arities)) # type: ignore[arg-type] + ) else: - return __multi_arity_fn_to_py_ast(ctx, node, node.arities) + return __multi_arity_deftype_method_to_py_ast( + ctx, + node, + cast(_CreateMethodASTFunction, __deftype_staticmethod_arity_to_py_ast), + decorators=(_PY_STATICMETHOD_FN_NAME,), + ) _DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, PyASTGenerator] = { From d54ed4973b58bc06c2cfd30f321e556395ab24b8 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Mon, 11 May 2020 19:07:30 -0400 Subject: [PATCH 07/20] How 'bout that? --- src/basilisp/lang/compiler/generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index afa805870..ba0d54b7d 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -1194,7 +1194,7 @@ def __deftype_staticmethod_to_py_ast( ctx: GeneratorContext, node: DefTypeStaticMethod ) -> GeneratedPyAST: """Return a Python AST Node for a `deftype*` staticmethod.""" - assert node.op == NodeOp.DEFTYPE_METHOD + assert node.op == NodeOp.DEFTYPE_STATICMETHOD if len(node.arities) == 1: return __deftype_staticmethod_arity_to_py_ast( From bf874a73e2a48b6f7419916796b95558d55cb0d6 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Mon, 11 May 2020 21:31:55 -0400 Subject: [PATCH 08/20] Fix some tests --- src/basilisp/lang/compiler/analyzer.py | 20 ++++++++++++-------- src/basilisp/lang/compiler/generator.py | 11 ++++++----- src/basilisp/lang/compiler/nodes.py | 8 -------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 142c7f234..6b12ea340 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1382,7 +1382,7 @@ def __deftype_impls( # pylint: disable=too-many-branches if not isinstance(form.first, kw.Keyword) or form.first != IMPLEMENTS: raise AnalyzerException( - f"deftype* forms must declare which interfaces they implement", form=form + "deftype* forms must declare which interfaces they implement", form=form ) implements = runtime.nth(form, 1) @@ -1408,7 +1408,7 @@ def __deftype_impls( # pylint: disable=too-many-branches current_interface = _analyze_form(ctx, iface) if not isinstance(current_interface, (MaybeClass, MaybeHostForm, VarRef)): raise AnalyzerException( - f"deftype* interface implementation must be an existing interface", + "deftype* interface implementation must be an existing interface", form=iface, ) interfaces.append(current_interface) @@ -1420,7 +1420,7 @@ def __deftype_impls( # pylint: disable=too-many-branches for elem in runtime.nthrest(form, 2): if not isinstance(elem, ISeq): raise AnalyzerException( - f"deftype* must consist of interface or protocol names and methods", + "deftype* must consist of interface or protocol names and methods", form=elem, ) @@ -1428,7 +1428,7 @@ def __deftype_impls( # pylint: disable=too-many-branches if isinstance(member, DefTypeProperty): if member.name in props: raise AnalyzerException( - f"deftype* property may only have one arity defined", + "deftype* property may only have one arity defined", form=elem, lisp_ast=member, ) @@ -1495,13 +1495,17 @@ def __assert_deftype_impls_are_abstract( # pylint: disable=too-many-branches,to missing_methods = ", ".join(interface_method_names - member_names) raise AnalyzerException( "deftype* definition missing interface members for interface " - f"{interface.form}: {missing_methods}" + f"{interface.form}: {missing_methods}", + form=interface.form, + lisp_ast=interface, ) elif not interface_property_names.issubset(all_member_names): missing_fields = ", ".join(interface_property_names - field_names) raise AnalyzerException( "deftype* definition missing interface properties for interface " - f"{interface.form}: {missing_fields}" + f"{interface.form}: {missing_fields}", + form=interface.form, + lisp_ast=interface, ) all_interface_methods.update(interface_names) @@ -1526,7 +1530,7 @@ def _deftype_ast( # pylint: disable=too-many-branches nelems = count(form) if nelems < 3: raise AnalyzerException( - f"deftype forms must have 3 or more elements, as in: (deftype* name fields [bases+impls])", + "deftype forms must have 3 or more elements, as in: (deftype* name fields [bases+impls])", form=form, ) @@ -1555,7 +1559,7 @@ def _deftype_ast( # pylint: disable=too-many-branches param_nodes = [] for field in fields: if not isinstance(field, sym.Symbol): - raise AnalyzerException(f"deftype* fields must be symbols", form=field) + raise AnalyzerException("deftype* fields must be symbols", form=field) field_default = ( Maybe(field.meta) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index ba0d54b7d..d71458fec 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -1003,7 +1003,6 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals and all(arity.op == NodeOp.DEFTYPE_STATICMETHOD_ARITY for arity in arities) ) ) - assert node.kwarg_support is None, "multi-arity methods do not support kwargs" py_method_arity_name = genname(f"__{node.name}") @@ -1058,7 +1057,7 @@ def __deftype_classmethod_arity_to_py_ast( method_name=method_name, prefix_args=(ast.arg(arg=class_name, annotation=None),), decorators=list( - chain([_PY_CLASSMETHOD_FN_NAME], __kwargs_support_decorator(node),) + chain([_PY_CLASSMETHOD_FN_NAME], __kwargs_support_decorator(arity),) ), ) @@ -1145,7 +1144,7 @@ def __deftype_method_arity_to_py_ast( decorators=list( chain( [_TRAMPOLINE_FN_NAME] if ctx.recur_point.has_recur else [], - __kwargs_support_decorator(node), + __kwargs_support_decorator(arity), ) ), ) @@ -1184,7 +1183,7 @@ def __deftype_staticmethod_arity_to_py_ast( arity, method_name=method_name, decorators=list( - chain([_PY_STATICMETHOD_FN_NAME], __kwargs_support_decorator(node)) + chain([_PY_STATICMETHOD_FN_NAME], __kwargs_support_decorator(arity)) ), ) @@ -1454,7 +1453,9 @@ def __fn_meta( def __kwargs_support_decorator( - node: Union[Fn, DefTypeClassMethod, DefTypeMethod, DefTypeStaticMethod] + node: Union[ + Fn, DefTypeClassMethodArity, DefTypeMethodArity, DefTypeStaticMethodArity + ] ) -> Iterable[ast.AST]: if node.kwarg_support is None: return diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 5cd231776..5072463dc 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -425,16 +425,10 @@ class DefTypeMethodBase(DefTypeMember, Generic[T_DefTypeMethodArity]): def is_variadic(self) -> bool: raise NotImplementedError - @property - @abstractmethod - def kwarg_support(self) -> Optional[KeywordArgSupport]: - raise NotImplementedError - @attr.s(auto_attribs=True, frozen=True, slots=True) class DefTypeMethod(DefTypeMethodBase["DefTypeMethodArity"]): is_variadic: bool = False - kwarg_support: Optional[KeywordArgSupport] = None children: Sequence[kw.Keyword] = vec.v(ARITIES) op: NodeOp = NodeOp.DEFTYPE_METHOD top_level: bool = False @@ -444,7 +438,6 @@ class DefTypeMethod(DefTypeMethodBase["DefTypeMethodArity"]): @attr.s(auto_attribs=True, frozen=True, slots=True) class DefTypeClassMethod(DefTypeMethodBase["DefTypeClassMethodArity"]): is_variadic: bool = False - kwarg_support: Optional[KeywordArgSupport] = None children: Sequence[kw.Keyword] = vec.v(ARITIES) op: NodeOp = NodeOp.DEFTYPE_CLASSMETHOD top_level: bool = False @@ -454,7 +447,6 @@ class DefTypeClassMethod(DefTypeMethodBase["DefTypeClassMethodArity"]): @attr.s(auto_attribs=True, frozen=True, slots=True) class DefTypeStaticMethod(DefTypeMethodBase["DefTypeStaticMethodArity"]): is_variadic: bool = False - kwarg_support: Optional[KeywordArgSupport] = None children: Sequence[kw.Keyword] = vec.v(ARITIES) op: NodeOp = NodeOp.DEFTYPE_STATICMETHOD top_level: bool = False From 3c71745765d955d1e3a234d11ea8960c669eda1d Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Mon, 11 May 2020 22:01:21 -0400 Subject: [PATCH 09/20] Fix properties --- src/basilisp/lang/compiler/analyzer.py | 32 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 6b12ea340..8f3edee32 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1373,7 +1373,7 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches ) -def __deftype_impls( # pylint: disable=too-many-branches +def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noqa: MC0001 ctx: AnalyzerContext, form: ISeq ) -> Tuple[List[DefTypeBase], List[DefTypeMember]]: """Roll up deftype* declared bases and method implementations.""" @@ -1413,6 +1413,7 @@ def __deftype_impls( # pylint: disable=too-many-branches ) interfaces.append(current_interface) + member_order = [] methods: MutableMapping[ str, List[DefTypeMethodArityBase] ] = collections.defaultdict(list) @@ -1425,6 +1426,7 @@ def __deftype_impls( # pylint: disable=too-many-branches ) member = __deftype_prop_or_method_arity(ctx, elem) + member_order.append(member.name) if isinstance(member, DefTypeProperty): if member.name in props: raise AnalyzerException( @@ -1432,15 +1434,37 @@ def __deftype_impls( # pylint: disable=too-many-branches form=elem, lisp_ast=member, ) + elif member.name in methods: + raise AnalyzerException( + "deftype* property name already defined as a method", + form=elem, + lisp=member, + ) props[member.name] = member else: + if member.name in props: + raise AnalyzerException( + "deftype* method name already defined as a property", + form=elem, + lisp_ast=member, + ) methods[member.name].append(member) members: List[DefTypeMember] = [] - for _, arities in methods.items(): - members.append(__deftype_method_node_from_arities(ctx, form, arities)) + for member_name in member_order: + arities = methods.get(member_name) + if arities is not None: + members.append(__deftype_method_node_from_arities(ctx, form, arities)) + continue + + prop = props.get(member_name) + if prop is not None: + members.append(prop) + continue + + assert False, "Member must be a method or property" - return interfaces, list(members) + return interfaces, members def __is_abstract(tp: Type) -> bool: From f67d1ce6f9aeece0aad6076a4db42fae20cb3da4 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Mon, 11 May 2020 22:29:11 -0400 Subject: [PATCH 10/20] This is very bad --- src/basilisp/lang/compiler/generator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index d71458fec..6709e08d6 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -1136,16 +1136,20 @@ def __deftype_method_arity_to_py_ast( ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) with ctx.new_this(this_sym): + # This is a very bad hack to allow us to evaluate the context + # recur point after the body has been read and the recur point + # has been set + def _should_trampoline() -> Iterable[ast.AST]: + if ctx.recur_point.has_recur: + yield _TRAMPOLINE_FN_NAME + return __single_arity_deftype_method_to_py_ast( ctx, arity, method_name=method_name, prefix_args=(ast.arg(arg=this_name, annotation=None),), - decorators=list( - chain( - [_TRAMPOLINE_FN_NAME] if ctx.recur_point.has_recur else [], - __kwargs_support_decorator(arity), - ) + decorators=chain( + _should_trampoline(), __kwargs_support_decorator(arity), ), ) From 07bc5f30436c5ff4016e0e1f2432acbd785c942e Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Tue, 12 May 2020 09:25:23 -0400 Subject: [PATCH 11/20] I do not know what is going on --- src/basilisp/lang/compiler/generator.py | 56 +++--- tests/basilisp/compiler_test.py | 243 ++++++++++++++++++------ 2 files changed, 213 insertions(+), 86 deletions(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index 6709e08d6..d0ff36ced 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -950,25 +950,26 @@ def method(self, *args): ] return GeneratedPyAST( - node=ast.Name(id=name, ctx=ast.Load()), - dependencies=[ - ast.FunctionDef( - name=name, - args=ast.arguments( - args=[] - if instance_or_class_name is None - else [ast.arg(arg=instance_or_class_name, annotation=None)], - kwarg=None, - vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), - kwonlyargs=[], - defaults=[], - kw_defaults=[], - ), - body=body, - decorator_list=list(decorators), - returns=None, - ) - ], + # This is a pretty unusual case where we actually want to return the statement + # node itself, rather than a name or expression. We're injecting all of these + # nodes directly into the generated class body and Python does not like the + # empty ast.Name in the class body definition (and also its unnecessary). + node=ast.FunctionDef( + name=name, + args=ast.arguments( + args=[] + if instance_or_class_name is None + else [ast.arg(arg=instance_or_class_name, annotation=None)], + kwarg=None, + vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=body, + decorator_list=list(decorators), + returns=None, + ), ) @@ -1004,7 +1005,8 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals ) ) - py_method_arity_name = genname(f"__{node.name}") + safe_name = munge(node.name) + py_method_arity_name = genname(f"__{safe_name}") arity_to_name = {} rest_arity_name: Optional[str] = None @@ -1023,18 +1025,18 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals fn_defs.append(fn_def.node) dispatch_fn_ast = __multi_arity_deftype_dispatch_method( - py_method_arity_name, + safe_name, arity_to_name, default_name=rest_arity_name, max_fixed_arity=node.max_fixed_arity, instance_or_class_name=instance_or_class_name, decorators=decorators, ) + assert ( + not dispatch_fn_ast.dependencies + ), "dispatch function should have no dependencies" - return GeneratedPyAST( - node=dispatch_fn_ast.node, - dependencies=list(chain(fn_defs, dispatch_fn_ast.dependencies)), - ) + return GeneratedPyAST(node=dispatch_fn_ast.node, dependencies=fn_defs,) def __deftype_classmethod_arity_to_py_ast( @@ -1136,7 +1138,7 @@ def __deftype_method_arity_to_py_ast( ctx.symbol_table.new_symbol(this_sym, this_name, LocalType.THIS) with ctx.new_this(this_sym): - # This is a very bad hack to allow us to evaluate the context + # This is a Very Bad Hack (tm) to allow us to evaluate the context # recur point after the body has been read and the recur point # has been set def _should_trampoline() -> Iterable[ast.AST]: @@ -1297,7 +1299,7 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches for member in node.members: type_ast = __deftype_member_to_py_ast(ctx, member) type_nodes.append(type_ast.node) # type: ignore - type_deps.extend(type_ast.dependencies) + type_nodes.extend(type_ast.dependencies) return GeneratedPyAST( node=ast.Name(id=type_name, ctx=ast.Load()), diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index 97b8519fa..bc8de4a98 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -927,6 +927,133 @@ def test_deftype_classmethod_collect_kwargs( pt = Point.create(1, 2, z=3) assert (1, 2, 3) == (pt.x, pt.y, pt.z) + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] + :no-args) + (^:classmethod create [cls] + :also-no-args)) + """, + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls s] + :one-arg) + (^:classmethod create [cls s] + :also-one-arg)) + """, + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] + :no-args) + (^:classmethod create [cls s] + :one-arg) + (^:classmethod create [cls a b] + [a b]) + (^:classmethod create [cls s3] + :also-one-arg)) + """, + ], + ) + def test_no_deftype_classmethod_arity_has_same_fixed_arity( + self, lcompile: CompileFn, class_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls & args] + (concat [:no-starter] args)) + (^:classmethod create [cls s & args] + (concat [s] args))) + """, + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls s & args] + (concat [s] args)) + (^:classmethod create [cls & args] + (concat [:no-starter] args))) + """, + ], + ) + def test_deftype_classmethod_cannot_have_two_variadic_arities( + self, lcompile: CompileFn, class_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_deftype_classmethod_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( + self, lcompile: CompileFn + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls s & args] + (concat [s] args)) + (^:classmethod create [cls & args] + (concat [:no-starter] args))) + """ + ) + + def test_multi_arity_deftype_classmethod_dispatches_properly( + self, lcompile: CompileFn, ns: runtime.Namespace, class_interface: Var + ): + code = """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] cls) + (^:classmethod create [cls s] cls)) + """ + Point = lcompile(code) + assert callable(Point.create) + assert Point is Point.create() + assert Point is Point.create("STRING") + + code = """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] :no-args) + (^:classmethod create [cls s] s) + (^:classmethod create [cls s & args] s + (concat [s] args))) + """ + Point = lcompile(code) + assert callable(Point.create) + assert Point.create() == kw.keyword("no-args") + assert Point.create("STRING") == "STRING" + assert Point.create(kw.keyword("first-arg"), "second-arg", 3) == llist.l( + kw.keyword("first-arg"), "second-arg", 3 + ) + + def test_multi_arity_fn_call_fails_if_no_valid_arity( + self, lcompile: CompileFn, class_interface: Var + ): + Point = lcompile( + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] :send-me-an-arg!) + (^:classmethod create [cls i] i) + (^:classmethod create [cls i j] (concat [i] [j]))) + """ + ) + + with pytest.raises(runtime.RuntimeException): + Point.create(1, 2, 3) + class TestDefTypeMethod: def test_deftype_fields_and_methods(self, lcompile: CompileFn): Point = lcompile( @@ -1796,59 +1923,57 @@ def test_multi_arity_fns_do_not_support_kwargs( f"^{{:kwargs {kwarg_support}}} (fn* ([arg] arg) ([arg kwargs] [arg kwargs]))" ) - def test_no_fn_method_has_same_fixed_arity(self, lcompile: CompileFn): - with pytest.raises(compiler.CompilerException): - lcompile( - """ - (def f - (fn* f - ([] :no-args) - ([] :also-no-args))) - """ - ) - - with pytest.raises(compiler.CompilerException): - lcompile( - """ - (def f - (fn* f - ([s] :one-arg) - ([s] :also-one-arg))) - """ - ) - - with pytest.raises(compiler.CompilerException): - lcompile( - """ - (def f - (fn* f - ([] :no-args) - ([s] :one-arg) - ([a b] [a b]) - ([s3] :also-one-arg))) - """ - ) - - def test_multi_arity_fn_cannot_have_two_variadic_methods(self, lcompile: CompileFn): + @pytest.mark.parametrize( + "code", + [ + """ + (def f + (fn* f + ([] :no-args) + ([] :also-no-args))) + """, + """ + (def f + (fn* f + ([s] :one-arg) + ([s] :also-one-arg))) + """, + """ + (def f + (fn* f + ([] :no-args) + ([s] :one-arg) + ([a b] [a b]) + ([s3] :also-one-arg))) + """, + ], + ) + def test_no_fn_method_has_same_fixed_arity(self, lcompile: CompileFn, code: str): with pytest.raises(compiler.CompilerException): - lcompile( - """ - (def f - (fn* f - ([& args] (concat [:no-starter] args)) - ([s & args] (concat [s] args)))) - """ - ) + lcompile(code) + @pytest.mark.parametrize( + "code", + [ + """ + (def f + (fn* f + ([& args] (concat [:no-starter] args)) + ([s & args] (concat [s] args)))) + """, + """ + (def f + (fn* f + ([s & args] (concat [s] args)) + ([& args] (concat [:no-starter] args)))) + """, + ], + ) + def test_multi_arity_fn_cannot_have_two_variadic_methods( + self, lcompile: CompileFn, code: str + ): with pytest.raises(compiler.CompilerException): - lcompile( - """ - (def f - (fn* f - ([s & args] (concat [s] args)) - ([& args] (concat [:no-starter] args)))) - """ - ) + lcompile(code) def test_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( self, lcompile: CompileFn @@ -1897,16 +2022,16 @@ def test_multi_arity_fn_dispatches_properly( ) def test_multi_arity_fn_call_fails_if_no_valid_arity(self, lcompile: CompileFn): + fvar = lcompile( + """ + (def angry-multi-fn + (fn* angry-multi-fn + ([] :send-me-an-arg!) + ([i] i) + ([i j] (concat [i] [j])))) + """ + ) with pytest.raises(runtime.RuntimeException): - fvar = lcompile( - """ - (def angry-multi-fn - (fn* angry-multi-fn - ([] :send-me-an-arg!) - ([i] i) - ([i j] (concat [i] [j])))) - """ - ) fvar.value(1, 2, 3) def test_async_single_arity(self, lcompile: CompileFn): From bceab3c296ec74845e879fd0a2861b8f30af9e76 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Tue, 12 May 2020 09:35:52 -0400 Subject: [PATCH 12/20] ooooooh k --- src/basilisp/lang/compiler/analyzer.py | 9 ++++++--- src/basilisp/lang/compiler/generator.py | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 8f3edee32..030c85a37 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1413,7 +1413,10 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq ) interfaces.append(current_interface) - member_order = [] + # Use the insertion-order preserving capabilities of a dictionary with 'True' + # keys to act as an ordered set of members we've seen. We don't want to register + # duplicates. + member_order = {} methods: MutableMapping[ str, List[DefTypeMethodArityBase] ] = collections.defaultdict(list) @@ -1426,7 +1429,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq ) member = __deftype_prop_or_method_arity(ctx, elem) - member_order.append(member.name) + member_order[member.name] = True if isinstance(member, DefTypeProperty): if member.name in props: raise AnalyzerException( @@ -1451,7 +1454,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq methods[member.name].append(member) members: List[DefTypeMember] = [] - for member_name in member_order: + for member_name in member_order.keys(): arities = methods.get(member_name) if arities is not None: members.append(__deftype_method_node_from_arities(ctx, form, arities)) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index d0ff36ced..7b2efcff4 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -919,7 +919,11 @@ def method(self, *args): body=[ ast.Return( value=ast.Call( - func=ast.Name(id=default_name, ctx=ast.Load()), + func=_load_attr( + default_name + if instance_or_class_name is None + else f"{instance_or_class_name}.{default_name}" + ), args=[ ast.Starred( value=ast.Name( @@ -1272,7 +1276,7 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches ) with ctx.new_symbol_table(node.name): - type_nodes = [] + type_nodes: List[ast.AST] = [] type_deps: List[ast.AST] = [] for field in node.fields: safe_field = munge(field.name) @@ -1298,7 +1302,7 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches for member in node.members: type_ast = __deftype_member_to_py_ast(ctx, member) - type_nodes.append(type_ast.node) # type: ignore + type_nodes.append(type_ast.node) type_nodes.extend(type_ast.dependencies) return GeneratedPyAST( From ff68054a3559464ccfba39fa68cf1e2c3526d502 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Tue, 12 May 2020 19:13:49 -0400 Subject: [PATCH 13/20] Fix it --- src/basilisp/lang/compiler/analyzer.py | 2 +- tests/basilisp/compiler_test.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 030c85a37..eb1c8be0b 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1454,7 +1454,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq methods[member.name].append(member) members: List[DefTypeMember] = [] - for member_name in member_order.keys(): + for member_name in member_order: arities = methods.get(member_name) if arities is not None: members.append(__deftype_method_node_from_arities(ctx, form, arities)) diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index bc8de4a98..0e2ea1650 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -994,15 +994,15 @@ def test_deftype_classmethod_cannot_have_two_variadic_arities( lcompile(code) def test_deftype_classmethod_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( - self, lcompile: CompileFn + self, lcompile: CompileFn, class_interface: Var, ): with pytest.raises(compiler.CompilerException): lcompile( """ (deftype* Point [x y z] :implements [WithCls] - (^:classmethod create [cls s & args] - (concat [s] args)) + (^:classmethod create [cls a b] + [a b]) (^:classmethod create [cls & args] (concat [:no-starter] args))) """ From 66484ff889a0496030730fd47362d8ff52f2ea35 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 08:26:54 -0400 Subject: [PATCH 14/20] More test cases --- src/basilisp/lang/compiler/analyzer.py | 7 ++-- tests/basilisp/compiler_test.py | 50 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index eb1c8be0b..68676c507 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1461,11 +1461,8 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq continue prop = props.get(member_name) - if prop is not None: - members.append(prop) - continue - - assert False, "Member must be a method or property" + assert prop is not None, "Member must be a method or property" + members.append(prop) return interfaces, members diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index 0e2ea1650..f58b6e28f 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -1008,6 +1008,56 @@ def test_deftype_classmethod_variadic_method_cannot_have_lower_fixed_arity_than_ """ ) + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls s] + s) + (^:classmethod ^{:kwargs :collect} create [cls s kwargs] + (concat [s] kwargs))) + """, + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod ^{:kwargs :collect} create [cls kwargs] + kwargs) + (^:classmethod ^{:kwargs :apply} create [cls head & kwargs] + (apply hash-map :first head kwargs))) + """, + ], + ) + def test_deftype_classmethod_does_not_support_kwargs( + self, lcompile: CompileFn, class_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithCls] + (create [cls] cls) + (^:classmethod create [cls s] cls)) + """, + """ + (deftype* Point [x y z] + :implements [WithCls] + (^:classmethod create [cls] cls) + (^:staticmethod create [s] s)) + """, + ], + ) + def test_deftype_classmethod_arities_must_all_be_annotated_with_classmethod( + self, lcompile: CompileFn, class_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + def test_multi_arity_deftype_classmethod_dispatches_properly( self, lcompile: CompileFn, ns: runtime.Namespace, class_interface: Var ): From 4dfca0726a60bedd8729d9f8d858f9f9179f609c Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 09:22:34 -0400 Subject: [PATCH 15/20] Static method tests --- src/basilisp/lang/compiler/generator.py | 69 +++++---- tests/basilisp/compiler_test.py | 181 +++++++++++++++++++++++- 2 files changed, 224 insertions(+), 26 deletions(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index 7b2efcff4..97b68c314 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -829,12 +829,27 @@ def __multi_arity_deftype_dispatch_method( # pylint: disable=too-many-arguments arity_map: Mapping[int, str], default_name: Optional[str] = None, max_fixed_arity: Optional[int] = None, - instance_or_class_name: Optional[str] = None, + instance_or_class_var_name: Optional[str] = None, + class_name: Optional[str] = None, decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: """Return the Python AST nodes for an argument-length dispatch method for multi-arity deftype* methods. + The `arity_map` names the mapping of number of arguments to the munged name of the + method arity handling that method. `default_name` is the name of the default + handler method if no method is found in the `arity_map` and the number of arguments + exceeds `max_fixed_arity`. `decorators` are applied to the generated function. + + `instance_or_class_var_name` is used to generate the first argument name and + prefixes for class methods and standard methods; this name is not used outside the + generated method and no user code exists here, so it can be a unique name that does + not match the user's selected 'this' or 'self' name. If no + `instance_or_class_var_name` is given, then a `class_name` must be given. + `class_name` is used for static methods to ensure we can dispatch to static method + arities without a class or self reference. You may *only* provide either a + `class_name` or `instance_or_class_var_name`. Providing both is an error. + class DefType: def __method_arity_1(self, arg): ... @@ -853,14 +868,16 @@ def method(self, *args): return default(*args) raise RuntimeError """ + assert instance_or_class_var_name is None or class_name is None + method_prefix = instance_or_class_var_name or class_name + assert ( + method_prefix is not None + ), "Fully qualified default name must've been specified" + dispatch_keys, dispatch_vals = [], [] for k, v in arity_map.items(): dispatch_keys.append(ast.Constant(k)) - dispatch_vals.append( - _load_attr( - v if instance_or_class_name is None else f"{instance_or_class_name}.{v}" - ) - ) + dispatch_vals.append(_load_attr(f"{method_prefix}.{v}")) nargs_name = genname("nargs") method_name = genname("method") @@ -919,11 +936,7 @@ def method(self, *args): body=[ ast.Return( value=ast.Call( - func=_load_attr( - default_name - if instance_or_class_name is None - else f"{instance_or_class_name}.{default_name}" - ), + func=_load_attr(f"{method_prefix}.{default_name}"), args=[ ast.Starred( value=ast.Name( @@ -962,8 +975,8 @@ def method(self, *args): name=name, args=ast.arguments( args=[] - if instance_or_class_name is None - else [ast.arg(arg=instance_or_class_name, annotation=None)], + if instance_or_class_var_name is None + else [ast.arg(arg=instance_or_class_var_name, annotation=None)], kwarg=None, vararg=ast.arg(arg=_MULTI_ARITY_ARG_NAME, annotation=None), kwonlyargs=[], @@ -988,7 +1001,8 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals ctx: GeneratorContext, node: DefTypeMethodBase, create_method_ast: _CreateMethodASTFunction, - instance_or_class_name: Optional[str] = None, + instance_or_class_var_name: Optional[str] = None, + class_name: Optional[str] = None, decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: """Return a Python AST node for a function with multiple arities.""" @@ -1033,7 +1047,8 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals arity_to_name, default_name=rest_arity_name, max_fixed_arity=node.max_fixed_arity, - instance_or_class_name=instance_or_class_name, + instance_or_class_var_name=instance_or_class_var_name, + class_name=class_name, decorators=decorators, ) assert ( @@ -1070,7 +1085,7 @@ def __deftype_classmethod_arity_to_py_ast( @_with_ast_loc def __deftype_classmethod_to_py_ast( - ctx: GeneratorContext, node: DefTypeClassMethod + ctx: GeneratorContext, node: DefTypeClassMethod, _: DefType, ) -> GeneratedPyAST: """Return a Python AST Node for a `deftype*` classmethod.""" assert node.op == NodeOp.DEFTYPE_CLASSMETHOD @@ -1084,14 +1099,14 @@ def __deftype_classmethod_to_py_ast( ctx, node, cast(_CreateMethodASTFunction, __deftype_classmethod_arity_to_py_ast), - instance_or_class_name=genname("cls"), + instance_or_class_var_name=genname("cls"), decorators=(_PY_CLASSMETHOD_FN_NAME,), ) @_with_ast_loc def __deftype_property_to_py_ast( - ctx: GeneratorContext, node: DefTypeProperty + ctx: GeneratorContext, node: DefTypeProperty, _: DefType, ) -> GeneratedPyAST: assert node.op == NodeOp.DEFTYPE_PROPERTY method_name = munge(node.name) @@ -1162,7 +1177,7 @@ def _should_trampoline() -> Iterable[ast.AST]: @_with_ast_loc def __deftype_method_to_py_ast( - ctx: GeneratorContext, node: DefTypeMethod + ctx: GeneratorContext, node: DefTypeMethod, _: DefType, ) -> GeneratedPyAST: """Return a Python AST Node for a `deftype*` method.""" assert node.op == NodeOp.DEFTYPE_METHOD @@ -1174,7 +1189,7 @@ def __deftype_method_to_py_ast( ctx, node, cast(_CreateMethodASTFunction, __deftype_method_arity_to_py_ast), - instance_or_class_name="self", + instance_or_class_var_name=genname("self"), ) @@ -1200,7 +1215,7 @@ def __deftype_staticmethod_arity_to_py_ast( @_with_ast_loc def __deftype_staticmethod_to_py_ast( - ctx: GeneratorContext, node: DefTypeStaticMethod + ctx: GeneratorContext, node: DefTypeStaticMethod, parent: DefType ) -> GeneratedPyAST: """Return a Python AST Node for a `deftype*` staticmethod.""" assert node.op == NodeOp.DEFTYPE_STATICMETHOD @@ -1214,11 +1229,15 @@ def __deftype_staticmethod_to_py_ast( ctx, node, cast(_CreateMethodASTFunction, __deftype_staticmethod_arity_to_py_ast), + class_name=munge(parent.name), decorators=(_PY_STATICMETHOD_FN_NAME,), ) -_DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, PyASTGenerator] = { +DefTypeASTGenerator = Callable[ + [GeneratorContext, DefTypeMember, DefType], GeneratedPyAST +] +_DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, DefTypeASTGenerator] = { NodeOp.DEFTYPE_CLASSMETHOD: __deftype_classmethod_to_py_ast, NodeOp.DEFTYPE_METHOD: __deftype_method_to_py_ast, NodeOp.DEFTYPE_PROPERTY: __deftype_property_to_py_ast, @@ -1227,14 +1246,14 @@ def __deftype_staticmethod_to_py_ast( def __deftype_member_to_py_ast( - ctx: GeneratorContext, node: DefTypeMember + ctx: GeneratorContext, node: DefTypeMember, parent: DefType, ) -> GeneratedPyAST: member_type = node.op handle_deftype_member = _DEFTYPE_MEMBER_HANDLER.get(member_type) assert ( handle_deftype_member is not None ), f"Invalid :const AST type handler for {member_type}" - return handle_deftype_member(ctx, node) + return handle_deftype_member(ctx, node, parent) _ATTR_CMP_OFF = getattr(attr, "__version_info__", (0,)) >= (19, 2) @@ -1301,7 +1320,7 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches ctx.symbol_table.new_symbol(sym.symbol(field.name), safe_field, field.local) for member in node.members: - type_ast = __deftype_member_to_py_ast(ctx, member) + type_ast = __deftype_member_to_py_ast(ctx, member, node) type_nodes.append(type_ast.node) type_nodes.extend(type_ast.dependencies) diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index f58b6e28f..6536c4341 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -1088,7 +1088,7 @@ def test_multi_arity_deftype_classmethod_dispatches_properly( kw.keyword("first-arg"), "second-arg", 3 ) - def test_multi_arity_fn_call_fails_if_no_valid_arity( + def test_multi_arity_deftype_classmethod_call_fails_if_no_valid_arity( self, lcompile: CompileFn, class_interface: Var ): Point = lcompile( @@ -1622,6 +1622,185 @@ def test_deftype_staticmethod_kwargs( {kw.keyword("x"): 1, kw.keyword("y"): 2, kw.keyword("z"): 3} ) == Point.dostatic(x=1, y=2, z=3) + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [] + :no-args) + (^:staticmethod dostatic [] + :also-no-args)) + """, + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [s] + :one-arg) + (^:staticmethod dostatic [s] + :also-one-arg)) + """, + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [] + :no-args) + (^:staticmethod dostatic [s] + :one-arg) + (^:staticmethod dostatic [a b] + [a b]) + (^:staticmethod dostatic [s3] + :also-one-arg)) + """, + ], + ) + def test_no_deftype_staticmethod_arity_has_same_fixed_arity( + self, lcompile: CompileFn, static_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [& args] + (concat [:no-starter] args)) + (^:staticmethod dostatic [s & args] + (concat [s] args))) + """, + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [s & args] + (concat [s] args)) + (^:staticmethod dostatic [& args] + (concat [:no-starter] args))) + """, + ], + ) + def test_deftype_staticmethod_cannot_have_two_variadic_arities( + self, lcompile: CompileFn, static_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_deftype_staticmethod_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( + self, lcompile: CompileFn, static_interface: Var, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [a b] + [a b]) + (^:staticmethod dostatic [& args] + (concat [:no-starter] args))) + """ + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [s] + s) + (^:staticmethod ^{:kwargs :collect} dostatic [s kwargs] + (concat [s] kwargs))) + """, + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod ^{:kwargs :collect} dostatic [kwargs] + kwargs) + (^:staticmethod ^{:kwargs :apply} dostatic [head & kwargs] + (apply hash-map :first head kwargs))) + """, + ], + ) + def test_deftype_staticmethod_does_not_support_kwargs( + self, lcompile: CompileFn, static_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithStatic] + (dostatic []) + (^:staticmethod dostatic [s] s)) + """, + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:classmethod dostatic []) + (^:staticmethod dostatic [s] s)) + """, + ], + ) + def test_deftype_staticmethod_arities_must_all_be_annotated_with_staticmethod( + self, lcompile: CompileFn, static_interface: Var, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_multi_arity_deftype_staticmethod_dispatches_properly( + self, lcompile: CompileFn, ns: runtime.Namespace, static_interface: Var + ): + code = """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [] :a) + (^:staticmethod dostatic [s] [:a s])) + """ + Point = lcompile(code) + assert callable(Point.dostatic) + assert kw.keyword("a") == Point.dostatic() + assert vec.v(kw.keyword("a"), kw.keyword("c")) == Point.dostatic( + kw.keyword("c") + ) + + code = """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [] :no-args) + (^:staticmethod dostatic [s] s) + (^:staticmethod dostatic [s & args] s + (concat [s] args))) + """ + Point = lcompile(code) + assert callable(Point.dostatic) + assert Point.dostatic() == kw.keyword("no-args") + assert Point.dostatic("STRING") == "STRING" + assert Point.dostatic(kw.keyword("first-arg"), "second-arg", 3) == llist.l( + kw.keyword("first-arg"), "second-arg", 3 + ) + + def test_multi_arity_deftype_staticmethod_call_fails_if_no_valid_arity( + self, lcompile: CompileFn, static_interface: Var + ): + Point = lcompile( + """ + (deftype* Point [x y z] + :implements [WithStatic] + (^:staticmethod dostatic [] :send-me-an-arg!) + (^:staticmethod dostatic [i] i) + (^:staticmethod dostatic [i j] (concat [i] [j]))) + """ + ) + + with pytest.raises(runtime.RuntimeException): + Point.dostatic(1, 2, 3) + class TestDefTypeReaderForm: def test_ns_does_not_exist(self, lcompile: CompileFn, test_ns: str): with pytest.raises(reader.SyntaxError): From ad87d3377ea2826c470dd4c6ac0a1e63d0e5ae2e Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 09:26:58 -0400 Subject: [PATCH 16/20] Just like home --- src/basilisp/lang/compiler/generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index 97b68c314..30ec64a13 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -997,7 +997,7 @@ def method(self, *args): @_with_ast_loc_deps -def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-locals +def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-arguments,too-many-locals ctx: GeneratorContext, node: DefTypeMethodBase, create_method_ast: _CreateMethodASTFunction, From 92a540d2faddd5a0d61abdf2134962427544e6b5 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 09:38:10 -0400 Subject: [PATCH 17/20] automatic for the people --- tests/basilisp/compiler_test.py | 140 +++++++++++++++----------------- 1 file changed, 66 insertions(+), 74 deletions(-) diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index 6536c4341..396b4e710 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -742,7 +742,7 @@ def test_deftype_member_may_not_be_multiple_tyeps( lcompile(code) class TestDefTypeClassMethod: - @pytest.fixture + @pytest.fixture(autouse=True) def class_interface(self, lcompile: CompileFn): return lcompile( """ @@ -758,7 +758,7 @@ def class_interface(self, lcompile: CompileFn): ) def test_deftype_must_implement_interface_classmethod( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -786,13 +786,13 @@ def test_deftype_must_implement_interface_classmethod( ], ) def test_deftype_classmethod_args_are_syms( - self, lcompile: CompileFn, class_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_deftype_classmethod_may_not_reference_fields( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -804,7 +804,7 @@ def test_deftype_classmethod_may_not_reference_fields( ) def test_deftype_classmethod_args_includes_cls( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -816,7 +816,7 @@ def test_deftype_classmethod_args_includes_cls( ) def test_deftype_classmethod_disallows_recur( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -829,7 +829,7 @@ def test_deftype_classmethod_disallows_recur( ) def test_deftype_can_have_classmethod( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -845,7 +845,7 @@ def test_deftype_can_have_classmethod( assert Point(1, 2, 3) == Point.create(1, 2, 3) def test_deftype_symboltable_is_restored_after_classmethod( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -860,7 +860,7 @@ def test_deftype_symboltable_is_restored_after_classmethod( assert "[1 2 3]" == str(pt) def test_deftype_empty_classmethod_body( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -871,7 +871,7 @@ def test_deftype_empty_classmethod_body( assert None is Point.create() def test_deftype_classmethod_returns_value( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -886,7 +886,7 @@ def test_deftype_classmethod_returns_value( assert kw.keyword("a") is Point.create(kw.keyword("a")) def test_deftype_classmethod_only_support_valid_kwarg_strategies( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -897,7 +897,7 @@ def test_deftype_classmethod_only_support_valid_kwarg_strategies( ) def test_deftype_classmethod_apply_kwargs( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -913,7 +913,7 @@ def test_deftype_classmethod_apply_kwargs( assert (1, 2, 3) == (pt.x, pt.y, pt.z) def test_deftype_classmethod_collect_kwargs( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -961,7 +961,7 @@ def test_deftype_classmethod_collect_kwargs( ], ) def test_no_deftype_classmethod_arity_has_same_fixed_arity( - self, lcompile: CompileFn, class_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) @@ -988,13 +988,13 @@ def test_no_deftype_classmethod_arity_has_same_fixed_arity( ], ) def test_deftype_classmethod_cannot_have_two_variadic_arities( - self, lcompile: CompileFn, class_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_deftype_classmethod_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( - self, lcompile: CompileFn, class_interface: Var, + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1030,7 +1030,7 @@ def test_deftype_classmethod_variadic_method_cannot_have_lower_fixed_arity_than_ ], ) def test_deftype_classmethod_does_not_support_kwargs( - self, lcompile: CompileFn, class_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) @@ -1053,13 +1053,13 @@ def test_deftype_classmethod_does_not_support_kwargs( ], ) def test_deftype_classmethod_arities_must_all_be_annotated_with_classmethod( - self, lcompile: CompileFn, class_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_multi_arity_deftype_classmethod_dispatches_properly( - self, lcompile: CompileFn, ns: runtime.Namespace, class_interface: Var + self, lcompile: CompileFn, ns: runtime.Namespace, ): code = """ (deftype* Point [x y z] @@ -1089,7 +1089,7 @@ def test_multi_arity_deftype_classmethod_dispatches_properly( ) def test_multi_arity_deftype_classmethod_call_fails_if_no_valid_arity( - self, lcompile: CompileFn, class_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1251,12 +1251,10 @@ def test_deftype_method_returns_value( Point = lcompile( """ (import* collections.abc) - (do - (deftype* Point [^:mutable x] - :implements [collections.abc/Callable] - (--call-- [this new-val] - (set! x new-val))) - Point)""" + (deftype* Point [^:mutable x] + :implements [collections.abc/Callable] + (--call-- [this new-val] + (set! x new-val)))""" ) pt = Point(1) assert pt.x == 1 @@ -1270,11 +1268,9 @@ def test_deftype_method_only_support_valid_kwarg_strategies( lcompile( """ (import* collections.abc) - (do - (deftype* Point [x y z] - :implements [collections.abc/Callable] - (^{:kwargs :kwarg-it} --call-- [this])) - Point)""" + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (^{:kwargs :kwarg-it} --call-- [this]))""" ) @pytest.mark.parametrize( @@ -1282,22 +1278,18 @@ def test_deftype_method_only_support_valid_kwarg_strategies( [ """ (import* collections.abc) - (do - (deftype* Point [x y z] - :implements [collections.abc/Callable] - (^{:kwargs :apply} --call-- - [this & args] - (merge {:x x :y y :z z} (apply hash-map args)))) - Point)""", + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (^{:kwargs :apply} --call-- + [this & args] + (merge {:x x :y y :z z} (apply hash-map args))))""", """ (import* collections.abc) - (do - (deftype* Point [x y z] - :implements [collections.abc/Callable] - (^{:kwargs :collect} --call-- - [this kwargs] - (merge {:x x :y y :z z} kwargs))) - Point)""", + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (^{:kwargs :collect} --call-- + [this kwargs] + (merge {:x x :y y :z z} kwargs)))""", ], ) def test_deftype_method_kwargs(self, lcompile: CompileFn, code: str): @@ -1314,7 +1306,7 @@ def test_deftype_method_kwargs(self, lcompile: CompileFn, code: str): ) == pt(w=2, y=4) class TestDefTypeProperty: - @pytest.fixture + @pytest.fixture(autouse=True) def property_interface(self, lcompile: CompileFn): return lcompile( """ @@ -1330,7 +1322,7 @@ def property_interface(self, lcompile: CompileFn): ) def test_deftype_must_implement_interface_property( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1341,7 +1333,7 @@ def test_deftype_must_implement_interface_property( ) def test_deftype_property_includes_this( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1353,7 +1345,7 @@ def test_deftype_property_includes_this( ) def test_deftype_property_args_are_syms( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1365,7 +1357,7 @@ def test_deftype_property_args_are_syms( ) def test_deftype_property_may_not_have_args( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1388,13 +1380,13 @@ def test_deftype_property_disallows_recur(self, lcompile: CompileFn): ) def test_deftype_field_can_be_property( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): Item = lcompile("(deftype* Item [prop] :implements [WithProp])") assert "prop" == Item("prop").prop def test_deftype_can_have_property( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1405,7 +1397,7 @@ def test_deftype_can_have_property( assert vec.v(1, 2, 3) == Point(1, 2, 3).prop def test_deftype_empty_property_body( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1416,7 +1408,7 @@ def test_deftype_empty_property_body( assert None is Point(1, 2, 3).prop def test_deftype_property_returns_value( - self, lcompile: CompileFn, property_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1436,7 +1428,7 @@ def test_deftype_property_returns_value( @pytest.mark.parametrize("kwarg_support", [":apply", ":collect", ":kwarg-it"]) def test_deftype_property_does_not_support_kwargs( - self, lcompile: CompileFn, property_interface: Var, kwarg_support: str + self, lcompile: CompileFn, kwarg_support: str ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1447,7 +1439,7 @@ def test_deftype_property_does_not_support_kwargs( ) class TestDefTypeStaticMethod: - @pytest.fixture + @pytest.fixture(autouse=True) def static_interface(self, lcompile: CompileFn): return lcompile( """ @@ -1463,7 +1455,7 @@ def static_interface(self, lcompile: CompileFn): ) def test_deftype_must_implement_interface_staticmethod( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1491,13 +1483,13 @@ def test_deftype_must_implement_interface_staticmethod( ], ) def test_deftype_staticmethod_args_are_syms( - self, lcompile: CompileFn, static_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_deftype_staticmethod_may_not_reference_fields( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1509,7 +1501,7 @@ def test_deftype_staticmethod_may_not_reference_fields( ) def test_deftype_staticmethod_may_have_no_args( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1521,7 +1513,7 @@ def test_deftype_staticmethod_may_have_no_args( assert None is Point.dostatic() def test_deftype_staticmethod_disallows_recur( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1534,7 +1526,7 @@ def test_deftype_staticmethod_disallows_recur( ) def test_deftype_can_have_staticmethod( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1546,7 +1538,7 @@ def test_deftype_can_have_staticmethod( assert vec.v(1, 2, 3) == Point.dostatic(1, 2, 3) def test_deftype_symboltable_is_restored_after_staticmethod( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1561,7 +1553,7 @@ def test_deftype_symboltable_is_restored_after_staticmethod( assert "[1 2 3]" == str(Point(1, 2, 3)) def test_deftype_empty_staticmethod_body( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1572,7 +1564,7 @@ def test_deftype_empty_staticmethod_body( assert None is Point.dostatic("x", "y") def test_deftype_staticmethod_returns_value( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ @@ -1587,7 +1579,7 @@ def test_deftype_staticmethod_returns_value( assert kw.keyword("a") is Point.dostatic(kw.keyword("a")) def test_deftype_staticmethod_only_support_valid_kwarg_strategies( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1615,7 +1607,7 @@ def test_deftype_staticmethod_only_support_valid_kwarg_strategies( ], ) def test_deftype_staticmethod_kwargs( - self, lcompile: CompileFn, static_interface: Var, code: str, + self, lcompile: CompileFn, code: str, ): Point = lcompile(code) assert lmap.map( @@ -1656,7 +1648,7 @@ def test_deftype_staticmethod_kwargs( ], ) def test_no_deftype_staticmethod_arity_has_same_fixed_arity( - self, lcompile: CompileFn, static_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) @@ -1683,13 +1675,13 @@ def test_no_deftype_staticmethod_arity_has_same_fixed_arity( ], ) def test_deftype_staticmethod_cannot_have_two_variadic_arities( - self, lcompile: CompileFn, static_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_deftype_staticmethod_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( - self, lcompile: CompileFn, static_interface: Var, + self, lcompile: CompileFn, ): with pytest.raises(compiler.CompilerException): lcompile( @@ -1725,7 +1717,7 @@ def test_deftype_staticmethod_variadic_method_cannot_have_lower_fixed_arity_than ], ) def test_deftype_staticmethod_does_not_support_kwargs( - self, lcompile: CompileFn, static_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) @@ -1748,13 +1740,13 @@ def test_deftype_staticmethod_does_not_support_kwargs( ], ) def test_deftype_staticmethod_arities_must_all_be_annotated_with_staticmethod( - self, lcompile: CompileFn, static_interface: Var, code: str + self, lcompile: CompileFn, code: str ): with pytest.raises(compiler.CompilerException): lcompile(code) def test_multi_arity_deftype_staticmethod_dispatches_properly( - self, lcompile: CompileFn, ns: runtime.Namespace, static_interface: Var + self, lcompile: CompileFn, ns: runtime.Namespace, ): code = """ (deftype* Point [x y z] @@ -1786,7 +1778,7 @@ def test_multi_arity_deftype_staticmethod_dispatches_properly( ) def test_multi_arity_deftype_staticmethod_call_fails_if_no_valid_arity( - self, lcompile: CompileFn, static_interface: Var + self, lcompile: CompileFn, ): Point = lcompile( """ From ab6366b78550dbfc9948d37883e2e3c8466a4975 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 18:20:12 -0400 Subject: [PATCH 18/20] method test --- tests/basilisp/compiler_test.py | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index 396b4e710..c865f9719 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -1305,6 +1305,171 @@ def test_deftype_method_kwargs(self, lcompile: CompileFn, code: str): } ) == pt(w=2, y=4) + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this] + :no-args) + (--call-- [this] + :also-no-args)) + """, + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this s] + :one-arg) + (--call-- [this s] + :also-one-arg)) + """, + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this] + :no-args) + (--call-- [this s] + :one-arg) + (--call-- [this a b] + [a b]) + (--call-- [this s3] + :also-one-arg)) + """, + ], + ) + def test_no_deftype_method_arity_has_same_fixed_arity( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this & args] + (concat [:no-starter] args)) + (--call-- [this s & args] + (concat [s] args))) + """, + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this s & args] + (concat [s] args)) + (--call-- [this & args] + (concat [:no-starter] args))) + """, + ], + ) + def test_deftype_method_cannot_have_two_variadic_arities( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_deftype_method_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this a b] + [a b]) + (--call-- [this & args] + (concat [:no-starter] args))) + """ + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this s] s) + (^{:kwargs :collect} --call-- [this s kwargs] + (concat [s] kwargs))) + """, + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (^{:kwargs :collect} --call-- [this kwargs] kwargs) + (^{:kwargs :apply} --call-- [thi shead & kwargs] + (apply hash-map :first head kwargs))) + """, + ], + ) + def test_deftype_method_does_not_support_kwargs( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_multi_arity_deftype_method_dispatches_properly( + self, lcompile: CompileFn, ns: runtime.Namespace, + ): + code = """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this] :a) + (--call-- [this s] [:a s])) + """ + Point = lcompile(code) + assert callable(Point(1, 2, 3)) + assert kw.keyword("a") == Point(1, 2, 3)() + assert vec.v(kw.keyword("a"), kw.keyword("c")) == Point(1, 2, 3)( + kw.keyword("c") + ) + + code = """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this] :no-args) + (--call-- [this s] s) + (--call-- [this s & args] + (concat [s] args))) + """ + Point = lcompile(code) + assert callable(Point(1, 2, 3)) + assert Point(1, 2, 3)() == kw.keyword("no-args") + assert Point(1, 2, 3)("STRING") == "STRING" + assert Point(1, 2, 3)(kw.keyword("first-arg"), "second-arg", 3) == llist.l( + kw.keyword("first-arg"), "second-arg", 3 + ) + + def test_multi_arity_deftype_method_call_fails_if_no_valid_arity( + self, lcompile: CompileFn, + ): + Point = lcompile( + """ + (import* collections.abc) + (deftype* Point [x y z] + :implements [collections.abc/Callable] + (--call-- [this] :send-me-an-arg!) + (--call-- [this i] i) + (--call-- [this i j] (concat [i] [j]))) + """ + ) + + with pytest.raises(runtime.RuntimeException): + Point(1, 2, 3)(4, 5, 6) + class TestDefTypeProperty: @pytest.fixture(autouse=True) def property_interface(self, lcompile: CompileFn): From a93ee06ef298d4b6a91fa6ad25b102c763e21941 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 19:24:11 -0400 Subject: [PATCH 19/20] Last cases --- src/basilisp/lang/compiler/analyzer.py | 2 +- tests/basilisp/compiler_test.py | 53 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index 68676c507..6f44e74d1 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1441,7 +1441,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq raise AnalyzerException( "deftype* property name already defined as a method", form=elem, - lisp=member, + lisp_ast=member, ) props[member.name] = member else: diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index c865f9719..dfa482b81 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -619,6 +619,48 @@ def test_deftype_interface_may_implement_only_some_object_methods( pt = Point(1, 2, 3) assert "('Point', 1, 2, 3)" == str(pt) + @pytest.mark.parametrize( + "code", + [ + """ + (deftype* Point [x y z] + :implements [WithProp] + (^:property prop [this] [x y z]) + (^:classmethod prop [cls] cls)) + """, + """ + (deftype* Point [x y z] + :implements [WithProp] + (^:classmethod prop [cls] cls) + (^:property prop [this] [x y z])) + """, + ], + ) + def test_deftype_property_and_method_names_cannot_overlap( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile( + f""" + (import* abc) + (def WithProp + (python/type "WithProp" + #py (abc/ABC) + #py {{"prop" + (python/property + (abc/abstractmethod + (fn [self])))}})) + (def WithCls + (python/type "WithCls" + #py (abc/ABC) + #py {{"prop" + (python/classmethod + (abc/abstractmethod + (fn [cls])))}})) + {code} + """ + ) + class TestDefTypeFields: def test_deftype_fields(self, lcompile: CompileFn): Point = lcompile("(deftype* Point [x y z])") @@ -1603,6 +1645,17 @@ def test_deftype_property_does_not_support_kwargs( (^:property ^{{:kwargs {kwarg_support}}} prop [this]))""" ) + def test_deftype_property_may_not_be_multi_arity(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (deftype* Point [x] + :implements [WithProp] + (^:property prop [this] :a) + (^:property prop [this] :b)) + """ + ) + class TestDefTypeStaticMethod: @pytest.fixture(autouse=True) def static_interface(self, lcompile: CompileFn): From be2a7445fe88ff3c1c2894007fbbd2e958bc9529 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 13 May 2020 20:23:17 -0400 Subject: [PATCH 20/20] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb35d9d8..1dcf0e1d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added support for `future`s (#441) * Added support for calling Python functions and methods with keyword arguments (#531) * Added support for Lisp functions being called with keyword arguments (#528) + * Added support for multi-arity methods on `deftype`s (#534) ### Fixed * Fixed a bug where the Basilisp AST nodes for return values of `deftype` members could be marked as _statements_ rather than _expressions_, resulting in an incorrect `nil` return (#523)