Skip to content

Commit 00e52ac

Browse files
gh-104683: Argument Clinic: Modernise parse_special_symbol() (#106837)
Co-authored-by: Alex Waygood <[email protected]>
1 parent 1654916 commit 00e52ac

File tree

1 file changed

+80
-56
lines changed

1 file changed

+80
-56
lines changed

Tools/clinic/clinic.py

Lines changed: 80 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4864,11 +4864,21 @@ def state_parameter(self, line: str | None) -> None:
48644864
self.parameter_continuation = line[:-1]
48654865
return
48664866

4867-
line = line.lstrip()
4868-
4869-
if line in ('*', '/', '[', ']'):
4870-
self.parse_special_symbol(line)
4871-
return
4867+
func = self.function
4868+
match line.lstrip():
4869+
case '*':
4870+
self.parse_star(func)
4871+
case '[':
4872+
self.parse_opening_square_bracket(func)
4873+
case ']':
4874+
self.parse_closing_square_bracket(func)
4875+
case '/':
4876+
self.parse_slash(func)
4877+
case param:
4878+
self.parse_parameter(param)
4879+
4880+
def parse_parameter(self, line: str) -> None:
4881+
assert self.function is not None
48724882

48734883
match self.parameter_state:
48744884
case ParamState.START | ParamState.REQUIRED:
@@ -5146,57 +5156,71 @@ def parse_converter(
51465156
"Annotations must be either a name, a function call, or a string."
51475157
)
51485158

5149-
def parse_special_symbol(self, symbol):
5150-
if symbol == '*':
5151-
if self.keyword_only:
5152-
fail("Function " + self.function.name + " uses '*' more than once.")
5153-
self.keyword_only = True
5154-
elif symbol == '[':
5155-
match self.parameter_state:
5156-
case ParamState.START | ParamState.LEFT_SQUARE_BEFORE:
5157-
self.parameter_state = ParamState.LEFT_SQUARE_BEFORE
5158-
case ParamState.REQUIRED | ParamState.GROUP_AFTER:
5159-
self.parameter_state = ParamState.GROUP_AFTER
5160-
case st:
5161-
fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {st}.b)")
5162-
self.group += 1
5163-
self.function.docstring_only = True
5164-
elif symbol == ']':
5165-
if not self.group:
5166-
fail("Function " + self.function.name + " has a ] without a matching [.")
5167-
if not any(p.group == self.group for p in self.function.parameters.values()):
5168-
fail("Function " + self.function.name + " has an empty group.\nAll groups must contain at least one parameter.")
5169-
self.group -= 1
5170-
match self.parameter_state:
5171-
case ParamState.LEFT_SQUARE_BEFORE | ParamState.GROUP_BEFORE:
5172-
self.parameter_state = ParamState.GROUP_BEFORE
5173-
case ParamState.GROUP_AFTER | ParamState.RIGHT_SQUARE_AFTER:
5174-
self.parameter_state = ParamState.RIGHT_SQUARE_AFTER
5175-
case st:
5176-
fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {st}.c)")
5177-
elif symbol == '/':
5178-
if self.positional_only:
5179-
fail("Function " + self.function.name + " uses '/' more than once.")
5180-
self.positional_only = True
5181-
# REQUIRED and OPTIONAL are allowed here, that allows positional-only without option groups
5182-
# to work (and have default values!)
5183-
allowed = {
5184-
ParamState.REQUIRED,
5185-
ParamState.OPTIONAL,
5186-
ParamState.RIGHT_SQUARE_AFTER,
5187-
ParamState.GROUP_BEFORE,
5188-
}
5189-
if (self.parameter_state not in allowed) or self.group:
5190-
fail(f"Function {self.function.name} has an unsupported group configuration. (Unexpected state {self.parameter_state}.d)")
5191-
if self.keyword_only:
5192-
fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
5193-
# fixup preceding parameters
5194-
for p in self.function.parameters.values():
5195-
if p.is_vararg():
5196-
continue
5197-
if (p.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD and not isinstance(p.converter, self_converter)):
5198-
fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.")
5199-
p.kind = inspect.Parameter.POSITIONAL_ONLY
5159+
def parse_star(self, function: Function) -> None:
5160+
"""Parse keyword-only parameter marker '*'."""
5161+
if self.keyword_only:
5162+
fail(f"Function {function.name} uses '*' more than once.")
5163+
self.keyword_only = True
5164+
5165+
def parse_opening_square_bracket(self, function: Function) -> None:
5166+
"""Parse opening parameter group symbol '['."""
5167+
match self.parameter_state:
5168+
case ParamState.START | ParamState.LEFT_SQUARE_BEFORE:
5169+
self.parameter_state = ParamState.LEFT_SQUARE_BEFORE
5170+
case ParamState.REQUIRED | ParamState.GROUP_AFTER:
5171+
self.parameter_state = ParamState.GROUP_AFTER
5172+
case st:
5173+
fail(f"Function {function.name} has an unsupported group configuration. "
5174+
f"(Unexpected state {st}.b)")
5175+
self.group += 1
5176+
function.docstring_only = True
5177+
5178+
def parse_closing_square_bracket(self, function: Function) -> None:
5179+
"""Parse closing parameter group symbol ']'."""
5180+
if not self.group:
5181+
fail(f"Function {function.name} has a ] without a matching [.")
5182+
if not any(p.group == self.group for p in function.parameters.values()):
5183+
fail(f"Function {function.name} has an empty group.\n"
5184+
"All groups must contain at least one parameter.")
5185+
self.group -= 1
5186+
match self.parameter_state:
5187+
case ParamState.LEFT_SQUARE_BEFORE | ParamState.GROUP_BEFORE:
5188+
self.parameter_state = ParamState.GROUP_BEFORE
5189+
case ParamState.GROUP_AFTER | ParamState.RIGHT_SQUARE_AFTER:
5190+
self.parameter_state = ParamState.RIGHT_SQUARE_AFTER
5191+
case st:
5192+
fail(f"Function {function.name} has an unsupported group configuration. "
5193+
f"(Unexpected state {st}.c)")
5194+
5195+
def parse_slash(self, function: Function) -> None:
5196+
"""Parse positional-only parameter marker '/'."""
5197+
if self.positional_only:
5198+
fail(f"Function {function.name} uses '/' more than once.")
5199+
self.positional_only = True
5200+
# REQUIRED and OPTIONAL are allowed here, that allows positional-only
5201+
# without option groups to work (and have default values!)
5202+
allowed = {
5203+
ParamState.REQUIRED,
5204+
ParamState.OPTIONAL,
5205+
ParamState.RIGHT_SQUARE_AFTER,
5206+
ParamState.GROUP_BEFORE,
5207+
}
5208+
if (self.parameter_state not in allowed) or self.group:
5209+
fail(f"Function {function.name} has an unsupported group configuration. "
5210+
f"(Unexpected state {self.parameter_state}.d)")
5211+
if self.keyword_only:
5212+
fail(f"Function {function.name} mixes keyword-only and "
5213+
"positional-only parameters, which is unsupported.")
5214+
# fixup preceding parameters
5215+
for p in function.parameters.values():
5216+
if p.is_vararg():
5217+
continue
5218+
if (p.kind is not inspect.Parameter.POSITIONAL_OR_KEYWORD and
5219+
not isinstance(p.converter, self_converter)
5220+
):
5221+
fail(f"Function {function.name} mixes keyword-only and "
5222+
"positional-only parameters, which is unsupported.")
5223+
p.kind = inspect.Parameter.POSITIONAL_ONLY
52005224

52015225
def state_parameter_docstring_start(self, line: str | None) -> None:
52025226
self.parameter_docstring_indent = len(self.indent.margin)

0 commit comments

Comments
 (0)