Skip to content

Commit 940aba6

Browse files
authored
Support multi-arity methods with definterface (#551)
Adds support for multi-arity methods to `definterface`. Removes multi-arity support for class and static methods of `deftype`s, because they don't really make sense. Class and static methods are Python interop features, rather than Basilisp language features and Python does not support multi-arity methods, so there's little point in supporting multi-arity variants of these method types. `definterface` will not support class or static methods at all, since this is a feature intended only for Python interop. Fix a bug where function (and method) arities with the same fixed arity as a variadic arity were allowed when they appear _after_ the variadic arity in the definition, but not allowed if they appeared before.
1 parent 5773d9c commit 940aba6

File tree

7 files changed

+426
-800
lines changed

7 files changed

+426
-800
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
* Added support for Lisp functions being called with keyword arguments (#528)
1212
* Added support for multi-arity methods on `deftype`s (#534)
1313
* Added metadata about the function or method context of a Lisp AST node in the `NodeEnv` (#548)
14+
* Added `reify*` special form (#425)
15+
* Added support for multi-arity methods on `definterface` (#538)
1416

1517
### Fixed
1618
* 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)

src/basilisp/core.lpy

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4480,7 +4480,7 @@
44804480
Options may be specified as key-value pairs. The following options are supported:
44814481
- :name - the name of the interface as a string; required
44824482
- :extends - a vector of interfaces the new interface should extend; optional
4483-
- :methods - an optional vector of method signatures like:
4483+
- :methods - an optional vector of method signatures without `self` or `this`, like:
44844484
[ (method-name [args ...] docstring) ... ]
44854485

44864486
Callers should use `definterface` to generate new interfaces."
@@ -4490,28 +4490,73 @@
44904490
extends (as-> (:extends opt-map []) $
44914491
(remove #(identical? abc/ABC %) $)
44924492
(concat $ [abc/ABC])
4493-
(python/tuple $))
4494-
4495-
methods (reduce (fn [m [method-name args docstring]]
4496-
(let [method-args (->> (concat ['^:no-warn-when-unused self] args)
4497-
(apply vector))
4498-
method (->> (list 'fn* method-name method-args)
4499-
(eval)
4500-
(abc/abstractmethod))]
4501-
(when docstring
4502-
(set! (.- method __doc__) docstring))
4503-
(assoc m (munge method-name) method)))
4504-
{}
4505-
(:methods opt-map))]
4506-
(python/type interface-name
4507-
extends
4508-
(lisp->py methods))))
4509-
4510-
;; The behavior described below where `definterface` forms are not permitted
4511-
;; to be used in `deftype` and `defrecord` forms when they are not top-level
4512-
;; is a bug, documented in the following issue:
4513-
;;
4514-
;; https://github.com/chrisrink10/basilisp/issues/376
4493+
(python/tuple $))]
4494+
(->> (:methods opt-map)
4495+
(map (fn [[method-name args docstring]]
4496+
(let [total-arity (count args)
4497+
is-variadic? (let [[_ [amp rest-arg]] (split-with #(not= '& %) args)]
4498+
(and (= '& amp) (not (nil? rest-arg))))
4499+
fixed-arity (cond-> total-arity is-variadic? (- 2))]
4500+
{:method-name method-name
4501+
:args args
4502+
:fixed-arity fixed-arity
4503+
:is-variadic? is-variadic?
4504+
:docstring docstring
4505+
:python-name (->> (if is-variadic? "_rest" fixed-arity)
4506+
(str "_" (munge method-name) "_arity"))})))
4507+
(group-by :method-name)
4508+
(mapcat (fn [[method-name arities]]
4509+
(if (> (count arities) 1)
4510+
(let [fixed-arities (->> arities
4511+
(remove :is-variadic?)
4512+
(map :fixed-arity))
4513+
variadic-arities (filter :is-variadic? arities)
4514+
fixed-arity-for-variadic (some-> variadic-arities first :fixed-arity)
4515+
num-variadic (count variadic-arities)]
4516+
(when (not= (count (set fixed-arities))
4517+
(count fixed-arities))
4518+
(throw
4519+
(ex-info (str "Interface methods may not have multiple methods "
4520+
"with the same fixed arity")
4521+
{:arities arities
4522+
:fixed-arities (vec fixed-arities)})))
4523+
(when (> num-variadic 1)
4524+
(throw
4525+
(ex-info "Interface methods may have at most one variadic arity"
4526+
{:arities arities
4527+
:num-variadic num-variadic})))
4528+
(when (and fixed-arity-for-variadic
4529+
(some #(< fixed-arity-for-variadic %) fixed-arities))
4530+
(throw
4531+
(ex-info (str "Interface methods may not have a fixed arity "
4532+
"greater than the arity of a variadic method")
4533+
{:arities arities
4534+
:fixed-arity-for-variadic fixed-arity-for-variadic
4535+
:fixed-arities fixed-arities})))
4536+
(conj arities
4537+
{:method-name method-name
4538+
:python-name (munge method-name)
4539+
:args '[& args]
4540+
:docstring (-> arities first :docstring)}))
4541+
;; single arity methods should not have the special arity
4542+
;; python name
4543+
(map (fn [{:keys [method-name] :as arity}]
4544+
(assoc arity :python-name (munge method-name)))
4545+
arities))))
4546+
(reduce (fn [m {:keys [method-name python-name args docstring]}]
4547+
(let [method (->> args
4548+
(map #(vary-meta % assoc :no-warn-when-unused true))
4549+
(concat ['^:no-warn-when-unused self])
4550+
(apply vector)
4551+
(list 'fn* method-name)
4552+
(eval)
4553+
(abc/abstractmethod))]
4554+
(when docstring
4555+
(set! (.- method __doc__) docstring))
4556+
(assoc m python-name method)))
4557+
{})
4558+
(lisp->py)
4559+
(python/type interface-name extends))))
45154560

45164561
(defmacro definterface
45174562
"Define a new Python interface (abstract base clase) with the given name
@@ -4527,11 +4572,8 @@
45274572
definitions are declared as `abc.abstractmethod`s and thus must be implemented
45284573
by a concrete type.
45294574

4530-
The generated interface will be immediately available after this form has
4531-
been evaluated _if it is evaluated as a top-level form_. If this form appears
4532-
within a function and is referred to later in a `deftype` or `defrecord` form,
4533-
a compilation error will occur since the compiler will be unable to evaluate
4534-
whether the generated value refers to an abstract class."
4575+
Interfaces created by `definterface` cannot be declared as properties, class
4576+
methods, or static methods, as with `deftype`."
45354577
[interface-name & methods]
45364578
(let [name-str (name interface-name)
45374579
method-sigs (map #(list 'quote %) methods)]

src/basilisp/lang/compiler/analyzer.py

Lines changed: 37 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,12 @@
7575
DefType,
7676
DefTypeBase,
7777
DefTypeClassMethod,
78-
DefTypeClassMethodArity,
7978
DefTypeMember,
8079
DefTypeMethod,
8180
DefTypeMethodArity,
82-
DefTypeMethodArityBase,
83-
DefTypeMethodBase,
8481
DefTypeProperty,
82+
DefTypePythonMember,
8583
DefTypeStaticMethod,
86-
DefTypeStaticMethodArity,
8784
Do,
8885
Fn,
8986
FnArity,
@@ -125,6 +122,7 @@
125122
VarRef,
126123
Vector as VectorNode,
127124
WithMeta,
125+
deftype_or_reify_python_member_names,
128126
)
129127
from basilisp.lang.interfaces import IMeta, IRecord, ISeq, IType, IWithMeta
130128
from basilisp.lang.runtime import Var
@@ -1038,7 +1036,7 @@ def __deftype_classmethod(
10381036
method_name: str,
10391037
args: vec.Vector,
10401038
kwarg_support: Optional[KeywordArgSupport] = None,
1041-
) -> DefTypeClassMethodArity:
1039+
) -> DefTypeClassMethod:
10421040
"""Emit a node for a :classmethod member of a `deftype*` form."""
10431041
with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(
10441042
method_name, is_context_boundary=True
@@ -1068,7 +1066,7 @@ def __deftype_classmethod(
10681066
)
10691067
with ctx.new_func_ctx(FunctionContext.CLASSMETHOD), ctx.expr_pos():
10701068
stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2))
1071-
method = DefTypeClassMethodArity(
1069+
method = DefTypeClassMethod(
10721070
form=form,
10731071
name=method_name,
10741072
params=vec.vector(param_nodes),
@@ -1228,7 +1226,7 @@ def __deftype_staticmethod(
12281226
method_name: str,
12291227
args: vec.Vector,
12301228
kwarg_support: Optional[KeywordArgSupport] = None,
1231-
) -> DefTypeStaticMethodArity:
1229+
) -> DefTypeStaticMethod:
12321230
"""Emit a node for a :staticmethod member of a `deftype*` form."""
12331231
with ctx.hide_parent_symbol_table(), ctx.new_symbol_table(
12341232
method_name, is_context_boundary=True
@@ -1238,7 +1236,7 @@ def __deftype_staticmethod(
12381236
)
12391237
with ctx.new_func_ctx(FunctionContext.STATICMETHOD), ctx.expr_pos():
12401238
stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2))
1241-
method = DefTypeStaticMethodArity(
1239+
method = DefTypeStaticMethod(
12421240
form=form,
12431241
name=method_name,
12441242
params=vec.vector(param_nodes),
@@ -1262,7 +1260,7 @@ def __deftype_staticmethod(
12621260

12631261
def __deftype_or_reify_prop_or_method_arity( # pylint: disable=too-many-branches
12641262
ctx: AnalyzerContext, form: Union[llist.List, ISeq], special_form: sym.Symbol
1265-
) -> Union[DefTypeMethodArityBase, DefTypeProperty]:
1263+
) -> Union[DefTypeMethodArity, DefTypePythonMember]:
12661264
"""Emit either a `deftype*` or `reify*` property node or an arity of a `deftype*`
12671265
or `reify*` method.
12681266
@@ -1343,9 +1341,9 @@ def __deftype_or_reify_prop_or_method_arity( # pylint: disable=too-many-branche
13431341
def __deftype_or_reify_method_node_from_arities( # pylint: disable=too-many-branches
13441342
ctx: AnalyzerContext,
13451343
form: Union[llist.List, ISeq],
1346-
arities: List[DefTypeMethodArityBase],
1344+
arities: List[DefTypeMethodArity],
13471345
special_form: sym.Symbol,
1348-
) -> DefTypeMethodBase:
1346+
) -> DefTypeMember:
13491347
"""Roll all of the collected `deftype*` or `reify*` arities up into a single
13501348
method node."""
13511349
assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
@@ -1354,13 +1352,6 @@ def __deftype_or_reify_method_node_from_arities( # pylint: disable=too-many-bra
13541352
fixed_arity_for_variadic: Optional[int] = None
13551353
num_variadic = 0
13561354
for arity in arities:
1357-
if fixed_arity_for_variadic is not None:
1358-
if arity.fixed_arity >= fixed_arity_for_variadic:
1359-
raise AnalyzerException(
1360-
f"{special_form} method may not have a method with fixed arity "
1361-
"greater than fixed arity of variadic function",
1362-
form=arity.form,
1363-
)
13641355
if arity.is_variadic:
13651356
if num_variadic > 0:
13661357
raise AnalyzerException(
@@ -1397,41 +1388,14 @@ def __deftype_or_reify_method_node_from_arities( # pylint: disable=too-many-bra
13971388
form=form,
13981389
)
13991390

1400-
max_fixed_arity = max(arity.fixed_arity for arity in arities)
1401-
1402-
if all(isinstance(e, DefTypeMethodArity) for e in arities):
1403-
return DefTypeMethod(
1404-
form=form,
1405-
name=arities[0].name,
1406-
max_fixed_arity=max_fixed_arity,
1407-
arities=vec.vector(arities), # type: ignore[arg-type]
1408-
is_variadic=num_variadic == 1,
1409-
env=ctx.get_node_env(),
1410-
)
1411-
elif all(isinstance(e, DefTypeClassMethodArity) for e in arities):
1412-
return DefTypeClassMethod(
1413-
form=form,
1414-
name=arities[0].name,
1415-
max_fixed_arity=max_fixed_arity,
1416-
arities=vec.vector(arities), # type: ignore[arg-type]
1417-
is_variadic=num_variadic == 1,
1418-
env=ctx.get_node_env(),
1419-
)
1420-
elif all(isinstance(e, DefTypeStaticMethodArity) for e in arities):
1421-
return DefTypeStaticMethod(
1422-
form=form,
1423-
name=arities[0].name,
1424-
max_fixed_arity=max_fixed_arity,
1425-
arities=vec.vector(arities), # type: ignore[arg-type]
1426-
is_variadic=num_variadic == 1,
1427-
env=ctx.get_node_env(),
1428-
)
1429-
else:
1430-
raise AnalyzerException(
1431-
"deftype* method arities must all be declared one of :classmethod, "
1432-
":property, :staticmethod, or none (for a standard method)",
1433-
form=form,
1434-
)
1391+
return DefTypeMethod(
1392+
form=form,
1393+
name=arities[0].name,
1394+
max_fixed_arity=max(arity.fixed_arity for arity in arities),
1395+
arities=vec.vector(arities),
1396+
is_variadic=num_variadic == 1,
1397+
env=ctx.get_node_env(),
1398+
)
14351399

14361400

14371401
def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-locals # noqa: MC0001
@@ -1484,10 +1448,10 @@ def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-loca
14841448
# keys to act as an ordered set of members we've seen. We don't want to register
14851449
# duplicates.
14861450
member_order = {}
1487-
methods: MutableMapping[
1488-
str, List[DefTypeMethodArityBase]
1489-
] = collections.defaultdict(list)
1490-
props: MutableMapping[str, DefTypeProperty] = {}
1451+
methods: MutableMapping[str, List[DefTypeMethodArity]] = collections.defaultdict(
1452+
list
1453+
)
1454+
py_members: MutableMapping[str, DefTypePythonMember] = {}
14911455
for elem in runtime.nthrest(form, 2):
14921456
if not isinstance(elem, ISeq):
14931457
raise AnalyzerException(
@@ -1497,24 +1461,29 @@ def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-loca
14971461

14981462
member = __deftype_or_reify_prop_or_method_arity(ctx, elem, special_form)
14991463
member_order[member.name] = True
1500-
if isinstance(member, DefTypeProperty):
1501-
if member.name in props:
1464+
if isinstance(
1465+
member, (DefTypeClassMethod, DefTypeProperty, DefTypeStaticMethod)
1466+
):
1467+
if member.name in py_members:
15021468
raise AnalyzerException(
1503-
f"{special_form} property may only have one arity defined",
1469+
f"{special_form} class methods, properties, and static methods "
1470+
"may only have one arity defined",
15041471
form=elem,
15051472
lisp_ast=member,
15061473
)
15071474
elif member.name in methods:
15081475
raise AnalyzerException(
1509-
f"{special_form} property name already defined as a method",
1476+
f"{special_form} class method, property, or static method name "
1477+
"already defined as a method",
15101478
form=elem,
15111479
lisp_ast=member,
15121480
)
1513-
props[member.name] = member
1481+
py_members[member.name] = member
15141482
else:
1515-
if member.name in props:
1483+
if member.name in py_members:
15161484
raise AnalyzerException(
1517-
f"{special_form} method name already defined as a property",
1485+
f"{special_form} method name already defined as a class method, "
1486+
"property, or static method",
15181487
form=elem,
15191488
lisp_ast=member,
15201489
)
@@ -1531,9 +1500,9 @@ def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-loca
15311500
)
15321501
continue
15331502

1534-
prop = props.get(member_name)
1535-
assert prop is not None, "Member must be a method or property"
1536-
members.append(prop)
1503+
py_member = py_members.get(member_name)
1504+
assert py_member is not None, "Member must be a method or property"
1505+
members.append(py_member)
15371506

15381507
return interfaces, members
15391508

@@ -1557,7 +1526,7 @@ def __deftype_and_reify_impls_are_all_abstract( # pylint: disable=too-many-bran
15571526
assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY}
15581527

15591528
field_names = frozenset(fields)
1560-
member_names = frozenset(munge(member.name) for member in members)
1529+
member_names = frozenset(deftype_or_reify_python_member_names(members))
15611530
all_member_names = field_names.union(member_names)
15621531
all_interface_methods: Set[str] = set()
15631532
for interface in interfaces:
@@ -1916,13 +1885,6 @@ def _fn_ast( # pylint: disable=too-many-branches
19161885
fixed_arity_for_variadic: Optional[int] = None
19171886
num_variadic = 0
19181887
for arity in arities:
1919-
if fixed_arity_for_variadic is not None:
1920-
if arity.fixed_arity >= fixed_arity_for_variadic:
1921-
raise AnalyzerException(
1922-
"fn may not have a method with fixed arity greater than "
1923-
"fixed arity of variadic function",
1924-
form=arity.form,
1925-
)
19261888
if arity.is_variadic:
19271889
if num_variadic > 0:
19281890
raise AnalyzerException(

0 commit comments

Comments
 (0)