Skip to content

Commit 5cc80b0

Browse files
authored
Fix rendering of throws, allowing it to co-exist with return intents (#103)
Fixes an issue where `throws` and a return intent would break parsing of chapel signatures. This was because the parser was capturing `throws` and treating it like a return intent, so it didn't know what to do with the actual return intent. To implement this, I actually had to fix several things - return intents and return types where parsed together, complicating the addition of throws. I split them apart - just using regex, the pattern `const throws` would get treated as a single intent, which is not correct. This required me to add a special function to fixup the regex match. Fixes the issue described in chapel-lang/chapel#23776, once this fix propagates to Chapel main. [Reviewed by @lydia-duncan]
2 parents 518a888 + 9ce13aa commit 5cc80b0

File tree

4 files changed

+214
-105
lines changed

4 files changed

+214
-105
lines changed

doc-test/classes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ Chapel classes
110110

111111
Some method that does something.
112112

113+
.. class:: ClassA:ClassB,ClassC
114+
113115
Python classes
114116
--------------
115117

doc-test/functions.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ Chapel Functions
140140
:throw PermissionError: when the message is not for you
141141

142142

143+
.. function:: proc throwsWithIntent() const ref throws
144+
145+
.. function:: proc throwsWithIntent2() const throws
146+
147+
.. function:: proc throwsWithIntentAndReturn() ref : int throws
148+
143149
Other stuff...
144150
--------------
145151

sphinxcontrib/chapeldomain/__init__.py

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,70 @@
5656
(?:[+*/!~%<>=&^|\-:]+) # or operator name
5757
) \s*
5858
(?:\((.*?)\))? # opt: arguments
59-
(\s+(?:const\s)? (?:\w+?)| # or return intent
60-
\s* : \s* (?:[^:]+?)| # or return type
61-
\s+(?:const\s)? \w+\s* : \s* (?:[^:]+?) # or return intent and type
59+
(\s+(?:const\s)? (?:\w+?) # opt: return intent
60+
)?
61+
(\s*:\s*(?:[^:]+?) # opt: return type
62+
)?
63+
(\s+throws # opt: throws
6264
)?
6365
(\s+where\s+.* # Where clause
6466
)?
6567
$""", re.VERBOSE)
6668

69+
70+
def match_chpl_sig_pattern(sig: str):
71+
"""
72+
Match a Chapel signature against the regex pattern defined in
73+
chpl_sig_pattern. chpl_sig_pattern cannot be used directly because we need
74+
to fix some things up
75+
76+
For example, `const throws` will be treated by the regex as a return
77+
intent, when really it is a `const` return intent and a `throws`. This
78+
function splits them apart
79+
80+
Additionally, this function cleans up the whitespace on the return type
81+
(and removes ':'!), return intent, and any captured throws to make testing
82+
more consistent.
83+
"""
84+
sig_match = chpl_sig_pattern.match(sig)
85+
if not sig_match:
86+
return None
87+
(
88+
func_prefix,
89+
name_prefix,
90+
name,
91+
arglist,
92+
return_intent,
93+
return_type,
94+
throws,
95+
where_clause,
96+
) = sig_match.groups()
97+
98+
if return_type:
99+
return_type = return_type.strip().removeprefix(':').lstrip()
100+
if return_intent:
101+
return_intent = return_intent.strip()
102+
if throws:
103+
throws = throws.strip()
104+
105+
if return_intent and "throws" in return_intent:
106+
return_intent = return_intent.replace("throws", "").strip()
107+
if not return_intent:
108+
return_intent = None
109+
throws = "throws"
110+
111+
return (
112+
func_prefix,
113+
name_prefix,
114+
name,
115+
arglist,
116+
return_intent,
117+
return_type,
118+
throws,
119+
where_clause,
120+
)
121+
122+
67123
# regex for parsing attribute and data directives.
68124
chpl_attr_sig_pattern = re.compile(
69125
r"""^ ((?:\w+\s+)*)? # optional: prefixes
@@ -210,25 +266,25 @@ def _pseudo_parse_arglist(signode, arglist):
210266
signode += paramlist
211267

212268
@staticmethod
213-
def _handle_signature_suffix(signode, retann, anno, where_clause):
269+
def _handle_signature_suffix(
270+
signode, return_intent, return_type, throws, anno, where_clause
271+
):
214272
"""
215273
handle the signature suffix items like return intent, return type,
216274
where clause, annotation, etc.
217275
"""
218-
if retann:
219-
if ':' in retann:
220-
retintent, _, rettype = retann.partition(':')
221-
rettype = rettype.strip()
222-
else:
223-
retintent, rettype = retann, None
224-
retintent = retintent.strip()
225-
if retintent:
226-
signode += addnodes.desc_sig_space(' ', ' ')
227-
signode += addnodes.desc_annotation(' ' + retintent,
228-
' ' + retintent)
229-
if rettype:
230-
signode += addnodes.desc_annotation(' : ' + rettype,
231-
' : ' + rettype)
276+
277+
if return_intent:
278+
signode += addnodes.desc_sig_space(' ', ' ')
279+
signode += addnodes.desc_annotation(' ' + return_intent,
280+
' ' + return_intent)
281+
if return_type:
282+
signode += addnodes.desc_sig_space(' ', ' ')
283+
signode += addnodes.desc_annotation(' : ' + return_type,
284+
' : ' + return_type)
285+
if throws:
286+
signode += addnodes.desc_sig_space(' ', ' ')
287+
signode += addnodes.desc_annotation(' throws', ' throws')
232288
if anno:
233289
signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
234290
if where_clause:
@@ -253,11 +309,11 @@ def _get_proc_like_prefix(self, sig):
253309
"""Return prefix text for function or method directive
254310
(and similar).
255311
"""
256-
sig_match = chpl_sig_pattern.match(sig)
312+
sig_match = match_chpl_sig_pattern(sig)
257313
if sig_match is None:
258314
return ChapelObject.get_signature_prefix(self, sig)
259315

260-
prefixes, _, _, _, _, _ = sig_match.groups()
316+
prefixes = sig_match[0]
261317
if prefixes:
262318
return prefixes.strip() + ' '
263319
elif self.objtype.startswith('iter'):
@@ -321,16 +377,26 @@ def handle_signature(self, sig, signode):
321377
sig_match = chpl_attr_sig_pattern.match(sig)
322378
if sig_match is None:
323379
raise ValueError('Signature does not parse: {0}'.format(sig))
324-
func_prefix, name_prefix, name, retann = sig_match.groups()
380+
func_prefix, name_prefix, name, return_type = sig_match.groups()
381+
return_intent = None
382+
throws = None
325383
arglist = None
326384
where_clause = None
327385
else:
328-
sig_match = chpl_sig_pattern.match(sig)
386+
sig_match = match_chpl_sig_pattern(sig)
329387
if sig_match is None:
330388
raise ValueError('Signature does not parse: {0}'.format(sig))
331389

332-
func_prefix, name_prefix, name, arglist, retann, where_clause = \
333-
sig_match.groups()
390+
(
391+
func_prefix,
392+
name_prefix,
393+
name,
394+
arglist,
395+
return_intent,
396+
return_type,
397+
throws,
398+
where_clause,
399+
) = sig_match
334400

335401
# check if where clause is valid
336402
if where_clause is not None and not self._is_proc_like():
@@ -386,11 +452,15 @@ def handle_signature(self, sig, signode):
386452
if self.needs_arglist() and arglist is not None:
387453
# for callables, add an empty parameter list
388454
signode += addnodes.desc_parameterlist()
389-
self._handle_signature_suffix(signode, retann, anno, where_clause)
455+
self._handle_signature_suffix(
456+
signode, return_intent, return_type, throws, anno, where_clause
457+
)
390458
return fullname, name_prefix
391459

392460
self._pseudo_parse_arglist(signode, arglist)
393-
self._handle_signature_suffix(signode, retann, anno, where_clause)
461+
self._handle_signature_suffix(
462+
signode, return_intent, return_type, throws, anno, where_clause
463+
)
394464

395465
return fullname, name_prefix
396466

0 commit comments

Comments
 (0)