From 3ee8dac7a1b3882aa3aac7703bdae2de7b6402ad Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Jul 2023 23:57:20 +0200 Subject: [PATCH 1/8] gh-106368: Clean up Argument Clinic tests (#106373) --- Lib/test/test_clinic.py | 796 ++++++++++++++++++++++------------------ 1 file changed, 440 insertions(+), 356 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 51d2ac972752fd..b3602887ab6352 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -4,6 +4,7 @@ from test import support, test_tools from test.support import os_helper +from textwrap import dedent from unittest import TestCase import collections import inspect @@ -171,43 +172,43 @@ def test_solo_newline(self): def test_no_substitution(self): self._test(""" abc - """, """ + """, """ abc - """) + """) def test_empty_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc def - """, name='') + """, name='') def test_single_line_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc GARGLE def - """, name='GARGLE') + """, name='GARGLE') def test_multiline_substitution(self): self._test(""" abc {name} def - """, """ + """, """ abc bingle bungle def - """, name='bingle\nbungle\n') + """, name='bingle\nbungle\n') class InertParser: def __init__(self, clinic): @@ -240,9 +241,9 @@ def round_trip(self, input): def test_round_trip_1(self): self.round_trip(""" - verbatim text here - lah dee dah -""") + verbatim text here + lah dee dah + """) def test_round_trip_2(self): self.round_trip(""" verbatim text here @@ -286,22 +287,38 @@ def test_clinic_1(self): class ClinicParserTest(TestCase): + def checkDocstring(self, fn, expected): + self.assertTrue(hasattr(fn, "docstring")) + self.assertEqual(fn.docstring.strip(), + dedent(expected).strip()) + def test_trivial(self): parser = DSLParser(FakeClinic()) - block = clinic.Block("module os\nos.access") + block = clinic.Block(""" + module os + os.access + """) parser.parse(block) module, function = block.signatures self.assertEqual("access", function.name) self.assertEqual("os", module.name) def test_ignore_line(self): - block = self.parse("#\nmodule os\nos.access") + block = self.parse(dedent(""" + # + module os + os.access + """)) module, function = block.signatures self.assertEqual("access", function.name) self.assertEqual("os", module.name) def test_param(self): - function = self.parse_function("module os\nos.access\n path: int") + function = self.parse_function(""" + module os + os.access + path: int + """) self.assertEqual("access", function.name) self.assertEqual(2, len(function.parameters)) p = function.parameters['path'] @@ -309,236 +326,296 @@ def test_param(self): self.assertIsInstance(p.converter, clinic.int_converter) def test_param_default(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: bool = True") + function = self.parse_function(""" + module os + os.access + follow_symlinks: bool = True + """) p = function.parameters['follow_symlinks'] self.assertEqual(True, p.default) def test_param_with_continuations(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: \\\n bool \\\n =\\\n True") + function = self.parse_function(r""" + module os + os.access + follow_symlinks: \ + bool \ + = \ + True + """) p = function.parameters['follow_symlinks'] self.assertEqual(True, p.default) def test_param_default_expression(self): - function = self.parse_function("module os\nos.access\n follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize") + function = self.parse_function(""" + module os + os.access + follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize + """) p = function.parameters['follow_symlinks'] self.assertEqual(sys.maxsize, p.default) self.assertEqual("MAXSIZE", p.converter.c_default) - s = self.parse_function_should_fail("module os\nos.access\n follow_symlinks: int = sys.maxsize") - self.assertEqual(s, "Error on line 0:\nWhen you specify a named constant ('sys.maxsize') as your default value,\nyou MUST specify a valid c_default.\n") + expected_msg = ( + "Error on line 0:\n" + "When you specify a named constant ('sys.maxsize') as your default value,\n" + "you MUST specify a valid c_default.\n" + ) + out = self.parse_function_should_fail(""" + module os + os.access + follow_symlinks: int = sys.maxsize + """) + self.assertEqual(out, expected_msg) def test_param_no_docstring(self): function = self.parse_function(""" -module os -os.access - follow_symlinks: bool = True - something_else: str = ''""") + module os + os.access + follow_symlinks: bool = True + something_else: str = '' + """) p = function.parameters['follow_symlinks'] self.assertEqual(3, len(function.parameters)) - self.assertIsInstance(function.parameters['something_else'].converter, clinic.str_converter) + conv = function.parameters['something_else'].converter + self.assertIsInstance(conv, clinic.str_converter) def test_param_default_parameters_out_of_order(self): - s = self.parse_function_should_fail(""" -module os -os.access - follow_symlinks: bool = True - something_else: str""") - self.assertEqual(s, """Error on line 0: -Can't have a parameter without a default ('something_else') -after a parameter with a default! -""") + expected_msg = ( + "Error on line 0:\n" + "Can't have a parameter without a default ('something_else')\n" + "after a parameter with a default!\n" + ) + out = self.parse_function_should_fail(""" + module os + os.access + follow_symlinks: bool = True + something_else: str""") + self.assertEqual(out, expected_msg) def disabled_test_converter_arguments(self): - function = self.parse_function("module os\nos.access\n path: path_t(allow_fd=1)") + function = self.parse_function(""" + module os + os.access + path: path_t(allow_fd=1) + """) p = function.parameters['path'] self.assertEqual(1, p.converter.args['allow_fd']) def test_function_docstring(self): function = self.parse_function(""" -module os -os.stat as os_stat_fn + module os + os.stat as os_stat_fn - path: str - Path to be examined + path: str + Path to be examined -Perform a stat system call on the given path.""") - self.assertEqual(""" -stat($module, /, path) --- + Perform a stat system call on the given path. + """) + self.checkDocstring(function, """ + stat($module, /, path) + -- -Perform a stat system call on the given path. + Perform a stat system call on the given path. - path - Path to be examined -""".strip(), function.docstring) + path + Path to be examined + """) def test_explicit_parameters_in_docstring(self): - function = self.parse_function(""" -module foo -foo.bar - x: int - Documentation for x. - y: int + function = self.parse_function(dedent(""" + module foo + foo.bar + x: int + Documentation for x. + y: int -This is the documentation for foo. + This is the documentation for foo. -Okay, we're done here. -""") - self.assertEqual(""" -bar($module, /, x, y) --- + Okay, we're done here. + """)) + self.checkDocstring(function, """ + bar($module, /, x, y) + -- -This is the documentation for foo. + This is the documentation for foo. - x - Documentation for x. + x + Documentation for x. -Okay, we're done here. -""".strip(), function.docstring) + Okay, we're done here. + """) def test_parser_regression_special_character_in_parameter_column_of_docstring_first_line(self): - function = self.parse_function(""" -module os -os.stat - path: str -This/used to break Clinic! -""") - self.assertEqual("stat($module, /, path)\n--\n\nThis/used to break Clinic!", function.docstring) + function = self.parse_function(dedent(""" + module os + os.stat + path: str + This/used to break Clinic! + """)) + self.checkDocstring(function, """ + stat($module, /, path) + -- + + This/used to break Clinic! + """) def test_c_name(self): - function = self.parse_function("module os\nos.stat as os_stat_fn") + function = self.parse_function(""" + module os + os.stat as os_stat_fn + """) self.assertEqual("os_stat_fn", function.c_basename) def test_return_converter(self): - function = self.parse_function("module os\nos.stat -> int") + function = self.parse_function(""" + module os + os.stat -> int + """) self.assertIsInstance(function.return_converter, clinic.int_return_converter) def test_star(self): - function = self.parse_function("module os\nos.access\n *\n follow_symlinks: bool = True") + function = self.parse_function(""" + module os + os.access + * + follow_symlinks: bool = True + """) p = function.parameters['follow_symlinks'] self.assertEqual(inspect.Parameter.KEYWORD_ONLY, p.kind) self.assertEqual(0, p.group) def test_group(self): - function = self.parse_function("module window\nwindow.border\n [\n ls : int\n ]\n /\n") + function = self.parse_function(""" + module window + window.border + [ + ls: int + ] + / + """) p = function.parameters['ls'] self.assertEqual(1, p.group) def test_left_group(self): function = self.parse_function(""" -module curses -curses.addch - [ - y: int - Y-coordinate. - x: int - X-coordinate. - ] - ch: char - Character to add. - [ - attr: long - Attributes for the character. - ] - / -""") - for name, group in ( + module curses + curses.addch + [ + y: int + Y-coordinate. + x: int + X-coordinate. + ] + ch: char + Character to add. + [ + attr: long + Attributes for the character. + ] + / + """) + dataset = ( ('y', -1), ('x', -1), ('ch', 0), ('attr', 1), - ): - p = function.parameters[name] - self.assertEqual(p.group, group) - self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) - self.assertEqual(function.docstring.strip(), """ -addch([y, x,] ch, [attr]) - - - y - Y-coordinate. - x - X-coordinate. - ch - Character to add. - attr - Attributes for the character. - """.strip()) + ) + for name, group in dataset: + with self.subTest(name=name, group=group): + p = function.parameters[name] + self.assertEqual(p.group, group) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + self.checkDocstring(function, """ + addch([y, x,] ch, [attr]) + + + y + Y-coordinate. + x + X-coordinate. + ch + Character to add. + attr + Attributes for the character. + """) def test_nested_groups(self): function = self.parse_function(""" -module curses -curses.imaginary - [ - [ - y1: int - Y-coordinate. - y2: int - Y-coordinate. - ] - x1: int - X-coordinate. - x2: int - X-coordinate. - ] - ch: char - Character to add. - [ - attr1: long - Attributes for the character. - attr2: long - Attributes for the character. - attr3: long - Attributes for the character. - [ - attr4: long - Attributes for the character. - attr5: long - Attributes for the character. - attr6: long - Attributes for the character. - ] - ] - / -""") - for name, group in ( + module curses + curses.imaginary + [ + [ + y1: int + Y-coordinate. + y2: int + Y-coordinate. + ] + x1: int + X-coordinate. + x2: int + X-coordinate. + ] + ch: char + Character to add. + [ + attr1: long + Attributes for the character. + attr2: long + Attributes for the character. + attr3: long + Attributes for the character. + [ + attr4: long + Attributes for the character. + attr5: long + Attributes for the character. + attr6: long + Attributes for the character. + ] + ] + / + """) + dataset = ( ('y1', -2), ('y2', -2), ('x1', -1), ('x2', -1), ('ch', 0), ('attr1', 1), ('attr2', 1), ('attr3', 1), ('attr4', 2), ('attr5', 2), ('attr6', 2), - ): - p = function.parameters[name] - self.assertEqual(p.group, group) - self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) - - self.assertEqual(function.docstring.strip(), """ -imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, - attr6]]) - - - y1 - Y-coordinate. - y2 - Y-coordinate. - x1 - X-coordinate. - x2 - X-coordinate. - ch - Character to add. - attr1 - Attributes for the character. - attr2 - Attributes for the character. - attr3 - Attributes for the character. - attr4 - Attributes for the character. - attr5 - Attributes for the character. - attr6 - Attributes for the character. - """.strip()) + ) + for name, group in dataset: + with self.subTest(name=name, group=group): + p = function.parameters[name] + self.assertEqual(p.group, group) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + + self.checkDocstring(function, """ + imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5, + attr6]]) + + + y1 + Y-coordinate. + y2 + Y-coordinate. + x1 + X-coordinate. + x2 + X-coordinate. + ch + Character to add. + attr1 + Attributes for the character. + attr2 + Attributes for the character. + attr3 + Attributes for the character. + attr4 + Attributes for the character. + attr5 + Attributes for the character. + attr6 + Attributes for the character. + """) def parse_function_should_fail(self, s): with support.captured_stdout() as stdout: @@ -547,104 +624,108 @@ def parse_function_should_fail(self, s): return stdout.getvalue() def test_disallowed_grouping__two_top_groups_on_left(self): - s = self.parse_function_should_fail(""" -module foo -foo.two_top_groups_on_left - [ - group1 : int - ] - [ - group2 : int - ] - param: int - """) - self.assertEqual(s, - ('Error on line 0:\n' - 'Function two_top_groups_on_left has an unsupported group configuration. (Unexpected state 2.b)\n')) + expected_msg = ( + 'Error on line 0:\n' + 'Function two_top_groups_on_left has an unsupported group ' + 'configuration. (Unexpected state 2.b)\n' + ) + out = self.parse_function_should_fail(""" + module foo + foo.two_top_groups_on_left + [ + group1 : int + ] + [ + group2 : int + ] + param: int + """) + self.assertEqual(out, expected_msg) def test_disallowed_grouping__two_top_groups_on_right(self): self.parse_function_should_fail(""" -module foo -foo.two_top_groups_on_right - param: int - [ - group1 : int - ] - [ - group2 : int - ] - """) + module foo + foo.two_top_groups_on_right + param: int + [ + group1 : int + ] + [ + group2 : int + ] + """) def test_disallowed_grouping__parameter_after_group_on_right(self): self.parse_function_should_fail(""" -module foo -foo.parameter_after_group_on_right - param: int - [ - [ - group1 : int - ] - group2 : int - ] - """) + module foo + foo.parameter_after_group_on_right + param: int + [ + [ + group1 : int + ] + group2 : int + ] + """) def test_disallowed_grouping__group_after_parameter_on_left(self): self.parse_function_should_fail(""" -module foo -foo.group_after_parameter_on_left - [ - group2 : int - [ - group1 : int - ] - ] - param: int - """) + module foo + foo.group_after_parameter_on_left + [ + group2 : int + [ + group1 : int + ] + ] + param: int + """) def test_disallowed_grouping__empty_group_on_left(self): self.parse_function_should_fail(""" -module foo -foo.empty_group - [ - [ - ] - group2 : int - ] - param: int - """) + module foo + foo.empty_group + [ + [ + ] + group2 : int + ] + param: int + """) def test_disallowed_grouping__empty_group_on_right(self): self.parse_function_should_fail(""" -module foo -foo.empty_group - param: int - [ - [ - ] - group2 : int - ] - """) + module foo + foo.empty_group + param: int + [ + [ + ] + group2 : int + ] + """) def test_no_parameters(self): function = self.parse_function(""" -module foo -foo.bar + module foo + foo.bar -Docstring + Docstring -""") + """) self.assertEqual("bar($module, /)\n--\n\nDocstring", function.docstring) self.assertEqual(1, len(function.parameters)) # self! def test_init_with_no_parameters(self): function = self.parse_function(""" -module foo -class foo.Bar "unused" "notneeded" -foo.Bar.__init__ + module foo + class foo.Bar "unused" "notneeded" + foo.Bar.__init__ -Docstring + Docstring + + """, signatures_in_block=3, function_index=2) -""", signatures_in_block=3, function_index=2) # self is not in the signature self.assertEqual("Bar()\n--\n\nDocstring", function.docstring) # but it *is* a parameter @@ -652,113 +733,117 @@ class foo.Bar "unused" "notneeded" def test_illegal_module_line(self): self.parse_function_should_fail(""" -module foo -foo.bar => int - / -""") + module foo + foo.bar => int + / + """) def test_illegal_c_basename(self): self.parse_function_should_fail(""" -module foo -foo.bar as 935 - / -""") + module foo + foo.bar as 935 + / + """) def test_single_star(self): self.parse_function_should_fail(""" -module foo -foo.bar - * - * -""") + module foo + foo.bar + * + * + """) def test_parameters_required_after_star_without_initial_parameters_or_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - * -""") + module foo + foo.bar + * + """) def test_parameters_required_after_star_without_initial_parameters_with_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - * -Docstring here. -""") + module foo + foo.bar + * + Docstring here. + """) def test_parameters_required_after_star_with_initial_parameters_without_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - this: int - * -""") + module foo + foo.bar + this: int + * + """) def test_parameters_required_after_star_with_initial_parameters_and_docstring(self): self.parse_function_should_fail(""" -module foo -foo.bar - this: int - * -Docstring. -""") + module foo + foo.bar + this: int + * + Docstring. + """) def test_single_slash(self): self.parse_function_should_fail(""" -module foo -foo.bar - / - / -""") + module foo + foo.bar + / + / + """) def test_mix_star_and_slash(self): self.parse_function_should_fail(""" -module foo -foo.bar - x: int - y: int - * - z: int - / -""") + module foo + foo.bar + x: int + y: int + * + z: int + / + """) def test_parameters_not_permitted_after_slash_for_now(self): self.parse_function_should_fail(""" -module foo -foo.bar - / - x: int -""") + module foo + foo.bar + / + x: int + """) def test_parameters_no_more_than_one_vararg(self): - s = self.parse_function_should_fail(""" -module foo -foo.bar - *vararg1: object - *vararg2: object -""") - self.assertEqual(s, "Error on line 0:\nToo many var args\n") + expected_msg = ( + "Error on line 0:\n" + "Too many var args\n" + ) + out = self.parse_function_should_fail(""" + module foo + foo.bar + *vararg1: object + *vararg2: object + """) + self.assertEqual(out, expected_msg) def test_function_not_at_column_0(self): function = self.parse_function(""" - module foo - foo.bar - x: int - Nested docstring here, goeth. - * - y: str - Not at column 0! -""") - self.assertEqual(""" -bar($module, /, x, *, y) --- + module foo + foo.bar + x: int + Nested docstring here, goeth. + * + y: str + Not at column 0! + """) + self.checkDocstring(function, """ + bar($module, /, x, *, y) + -- -Not at column 0! + Not at column 0! - x - Nested docstring here, goeth. -""".strip(), function.docstring) + x + Nested docstring here, goeth. + """) def test_directive(self): c = FakeClinic() @@ -772,46 +857,39 @@ def test_directive(self): def test_legacy_converters(self): block = self.parse('module os\nos.access\n path: "s"') module, function = block.signatures - self.assertIsInstance((function.parameters['path']).converter, clinic.str_converter) + conv = (function.parameters['path']).converter + self.assertIsInstance(conv, clinic.str_converter) def test_legacy_converters_non_string_constant_annotation(self): - expected_failure_message = """\ -Error on line 0: -Annotations must be either a name, a function call, or a string. -""" - - s = self.parse_function_should_fail('module os\nos.access\n path: 42') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: 42.42') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: 42j') - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail('module os\nos.access\n path: b"42"') - self.assertEqual(s, expected_failure_message) - - def test_other_bizarre_things_in_annotations_fail(self): - expected_failure_message = """\ -Error on line 0: -Annotations must be either a name, a function call, or a string. -""" - - s = self.parse_function_should_fail( - 'module os\nos.access\n path: {"some": "dictionary"}' + expected_failure_message = ( + "Error on line 0:\n" + "Annotations must be either a name, a function call, or a string.\n" ) - self.assertEqual(s, expected_failure_message) - - s = self.parse_function_should_fail( - 'module os\nos.access\n path: ["list", "of", "strings"]' + dataset = ( + 'module os\nos.access\n path: 42', + 'module os\nos.access\n path: 42.42', + 'module os\nos.access\n path: 42j', + 'module os\nos.access\n path: b"42"', ) - self.assertEqual(s, expected_failure_message) + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_failure_message) - s = self.parse_function_should_fail( - 'module os\nos.access\n path: (x for x in range(42))' + def test_other_bizarre_things_in_annotations_fail(self): + expected_failure_message = ( + "Error on line 0:\n" + "Annotations must be either a name, a function call, or a string.\n" + ) + dataset = ( + 'module os\nos.access\n path: {"some": "dictionary"}', + 'module os\nos.access\n path: ["list", "of", "strings"]', + 'module os\nos.access\n path: (x for x in range(42))', ) - self.assertEqual(s, expected_failure_message) + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertEqual(out, expected_failure_message) def test_kwarg_splats_disallowed_in_function_call_annotations(self): expected_error_msg = ( @@ -945,10 +1023,16 @@ def test_scaffolding(self): self.assertEqual(repr(clinic.NULL), '') # test that fail fails + expected = ( + 'Error in file "clown.txt" on line 69:\n' + 'The igloos are melting!\n' + ) with support.captured_stdout() as stdout: with self.assertRaises(SystemExit): - clinic.fail('The igloos are melting!', filename='clown.txt', line_number=69) - self.assertEqual(stdout.getvalue(), 'Error in file "clown.txt" on line 69:\nThe igloos are melting!\n') + clinic.fail('The igloos are melting!', + filename='clown.txt', line_number=69) + actual = stdout.getvalue() + self.assertEqual(actual, expected) class ClinicExternalTest(TestCase): From b24479dcba6e8952039066564d448d5ac4b37bef Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 00:10:46 +0200 Subject: [PATCH 2/8] gh-104050: Annotate more Argument Clinic DSLParser state methods (#106376) Annotate the following methods: - state_parameter() - state_parameter_docstring_start() Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 6380b9ce38f5f6..a07fcbd8cabf76 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4781,14 +4781,16 @@ def to_required(self): for p in self.function.parameters.values(): p.group = -p.group - def state_parameter(self, line): - if self.parameter_continuation: - line = self.parameter_continuation + ' ' + line.lstrip() - self.parameter_continuation = '' + def state_parameter(self, line: str | None) -> None: + assert isinstance(self.function, Function) if not self.valid_line(line): return + if self.parameter_continuation: + line = self.parameter_continuation + ' ' + line.lstrip() + self.parameter_continuation = '' + assert self.indent.depth == 2 indent = self.indent.infer(line) if indent == -1: @@ -4839,6 +4841,7 @@ def state_parameter(self, line): fields[0] = name line = ' '.join(fields) + default: str | None base, equals, default = line.rpartition('=') if not equals: base = default @@ -4861,7 +4864,9 @@ def state_parameter(self, line): if not module: fail("Function " + self.function.name + " has an invalid parameter declaration:\n\t" + line) - function_args = module.body[0].args + function = module.body[0] + assert isinstance(function, ast.FunctionDef) + function_args = function.args if len(function_args.args) > 1: fail("Function " + self.function.name + " has an invalid parameter declaration (comma?):\n\t" + line) @@ -4884,6 +4889,7 @@ def state_parameter(self, line): if self.parameter_state is ParamState.OPTIONAL: fail(f"Can't have a parameter without a default ({parameter_name!r})\n" "after a parameter with a default!") + value: Sentinels | Null if is_vararg: value = NULL kwargs.setdefault('c_default', "NULL") @@ -4946,8 +4952,11 @@ def bad_node(self, node): if bad: fail("Unsupported expression as default value: " + repr(default)) - expr = module.body[0].value + assignment = module.body[0] + assert isinstance(assignment, ast.Assign) + expr = assignment.value # mild hack: explicitly support NULL as a default value + c_default: str | None if isinstance(expr, ast.Name) and expr.id == 'NULL': value = NULL py_default = '' @@ -4964,7 +4973,7 @@ def bad_node(self, node): value = unknown elif isinstance(expr, ast.Attribute): a = [] - n = expr + n: ast.expr | ast.Attribute = expr while isinstance(n, ast.Attribute): a.append(n.attr) n = n.value @@ -4984,7 +4993,7 @@ def bad_node(self, node): else: value = ast.literal_eval(expr) py_default = repr(value) - if isinstance(value, (bool, None.__class__)): + if isinstance(value, (bool, NoneType)): c_default = "Py_" + py_default elif isinstance(value, str): c_default = c_repr(value) @@ -5011,6 +5020,7 @@ def bad_node(self, node): # but the parameter object gets the python name converter = dict[name](c_name or parameter_name, parameter_name, self.function, value, **kwargs) + kind: inspect._ParameterKind if is_vararg: kind = inspect.Parameter.VAR_POSITIONAL elif self.keyword_only: @@ -5130,7 +5140,7 @@ def parse_special_symbol(self, symbol): fail("Function " + self.function.name + " mixes keyword-only and positional-only parameters, which is unsupported.") p.kind = inspect.Parameter.POSITIONAL_ONLY - def state_parameter_docstring_start(self, line: str) -> None: + def state_parameter_docstring_start(self, line: str | None) -> None: self.parameter_docstring_indent = len(self.indent.margin) assert self.indent.depth == 3 return self.next(self.state_parameter_docstring, line) From 506cfdf141f03186d5cdf9bb31caa40294eba4e5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 00:35:46 +0200 Subject: [PATCH 3/8] gh-106320: Remove more private _PyUnicode C API functions (#106382) Remove more private _PyUnicode C API functions: move them to the internal C API (pycore_unicodeobject.h). No longer export most pycore_unicodeobject.h functions. --- Include/cpython/unicodeobject.h | 69 ------------------------ Include/internal/pycore_unicodeobject.h | 70 ++++++++++++++++++++++++- Python/future.c | 1 + 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index c5892a80d2c54d..fcd9c28e030c1f 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -394,11 +394,6 @@ static inline int PyUnicode_READY(PyObject* Py_UNUSED(op)) } #define PyUnicode_READY(op) PyUnicode_READY(_PyObject_CAST(op)) -/* Get a copy of a Unicode string. */ -PyAPI_FUNC(PyObject*) _PyUnicode_Copy( - PyObject *unicode - ); - /* Copy character from one unicode object into another, this function performs character conversion when necessary and falls back to memcpy() if possible. @@ -425,17 +420,6 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_CopyCharacters( Py_ssize_t how_many ); -/* Unsafe version of PyUnicode_CopyCharacters(): don't check arguments and so - may crash if parameters are invalid (e.g. if the output string - is too short). */ -PyAPI_FUNC(void) _PyUnicode_FastCopyCharacters( - PyObject *to, - Py_ssize_t to_start, - PyObject *from, - Py_ssize_t from_start, - Py_ssize_t how_many - ); - /* Fill a string with a character: write fill_char into unicode[start:start+length]. @@ -451,15 +435,6 @@ PyAPI_FUNC(Py_ssize_t) PyUnicode_Fill( Py_UCS4 fill_char ); -/* Unsafe version of PyUnicode_Fill(): don't check arguments and so may crash - if parameters are invalid (e.g. if length is longer than the string). */ -PyAPI_FUNC(void) _PyUnicode_FastFill( - PyObject *unicode, - Py_ssize_t start, - Py_ssize_t length, - Py_UCS4 fill_char - ); - /* Create a new string from a buffer of Py_UCS1, Py_UCS2 or Py_UCS4 characters. Scan the string to find the maximum character. */ PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData( @@ -467,19 +442,6 @@ PyAPI_FUNC(PyObject*) PyUnicode_FromKindAndData( const void *buffer, Py_ssize_t size); -/* Create a new string from a buffer of ASCII characters. - WARNING: Don't check if the string contains any non-ASCII character. */ -PyAPI_FUNC(PyObject*) _PyUnicode_FromASCII( - const char *buffer, - Py_ssize_t size); - -/* Compute the maximum character of the substring unicode[start:end]. - Return 127 for an empty string. */ -PyAPI_FUNC(Py_UCS4) _PyUnicode_FindMaxChar ( - PyObject *unicode, - Py_ssize_t start, - Py_ssize_t end); - /* --- Manage the default encoding ---------------------------------------- */ /* Returns a pointer to the default encoding (UTF-8) of the @@ -618,37 +580,6 @@ PyAPI_FUNC(PyObject*) _PyUnicode_TransformDecimalAndSpaceToASCII( PyObject *unicode /* Unicode object */ ); -/* --- Methods & Slots ---------------------------------------------------- */ - -PyAPI_FUNC(PyObject *) _PyUnicode_JoinArray( - PyObject *separator, - PyObject *const *items, - Py_ssize_t seqlen - ); - -/* Test whether a unicode is equal to ASCII identifier. Return 1 if true, - 0 otherwise. The right argument must be ASCII identifier. - Any error occurs inside will be cleared before return. */ -PyAPI_FUNC(int) _PyUnicode_EqualToASCIIId( - PyObject *left, /* Left string */ - _Py_Identifier *right /* Right identifier */ - ); - -/* Test whether a unicode is equal to ASCII string. Return 1 if true, - 0 otherwise. The right argument must be ASCII-encoded string. - Any error occurs inside will be cleared before return. */ -PyAPI_FUNC(int) _PyUnicode_EqualToASCIIString( - PyObject *left, - const char *right /* ASCII-encoded string */ - ); - -/* Externally visible for str.strip(unicode) */ -PyAPI_FUNC(PyObject *) _PyUnicode_XStrip( - PyObject *self, - int striptype, - PyObject *sepobj - ); - /* === Characters Type APIs =============================================== */ /* These should not be used directly. Use the Py_UNICODE_IS* and diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index a8c7f1957f3600..da01f57f962793 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -14,6 +14,44 @@ extern "C" { void _PyUnicode_ExactDealloc(PyObject *op); Py_ssize_t _PyUnicode_InternedSize(void); +/* Get a copy of a Unicode string. */ +PyAPI_FUNC(PyObject*) _PyUnicode_Copy( + PyObject *unicode + ); + +/* Unsafe version of PyUnicode_Fill(): don't check arguments and so may crash + if parameters are invalid (e.g. if length is longer than the string). */ +extern void _PyUnicode_FastFill( + PyObject *unicode, + Py_ssize_t start, + Py_ssize_t length, + Py_UCS4 fill_char + ); + +/* Unsafe version of PyUnicode_CopyCharacters(): don't check arguments and so + may crash if parameters are invalid (e.g. if the output string + is too short). */ +extern void _PyUnicode_FastCopyCharacters( + PyObject *to, + Py_ssize_t to_start, + PyObject *from, + Py_ssize_t from_start, + Py_ssize_t how_many + ); + +/* Create a new string from a buffer of ASCII characters. + WARNING: Don't check if the string contains any non-ASCII character. */ +extern PyObject* _PyUnicode_FromASCII( + const char *buffer, + Py_ssize_t size); + +/* Compute the maximum character of the substring unicode[start:end]. + Return 127 for an empty string. */ +extern Py_UCS4 _PyUnicode_FindMaxChar ( + PyObject *unicode, + Py_ssize_t start, + Py_ssize_t end); + /* --- _PyUnicodeWriter API ----------------------------------------------- */ typedef struct { @@ -141,10 +179,40 @@ PyAPI_FUNC(int) _PyUnicode_FormatAdvancedWriter( /* --- Methods & Slots ---------------------------------------------------- */ +extern PyObject* _PyUnicode_JoinArray( + PyObject *separator, + PyObject *const *items, + Py_ssize_t seqlen + ); + +/* Test whether a unicode is equal to ASCII identifier. Return 1 if true, + 0 otherwise. The right argument must be ASCII identifier. + Any error occurs inside will be cleared before return. */ +extern int _PyUnicode_EqualToASCIIId( + PyObject *left, /* Left string */ + _Py_Identifier *right /* Right identifier */ + ); + +/* Test whether a unicode is equal to ASCII string. Return 1 if true, + 0 otherwise. The right argument must be ASCII-encoded string. + Any error occurs inside will be cleared before return. */ +PyAPI_FUNC(int) _PyUnicode_EqualToASCIIString( + PyObject *left, + const char *right /* ASCII-encoded string */ + ); + +/* Externally visible for str.strip(unicode) */ +extern PyObject* _PyUnicode_XStrip( + PyObject *self, + int striptype, + PyObject *sepobj + ); + + /* Using explicit passed-in values, insert the thousands grouping into the string pointed to by buffer. For the argument descriptions, see Objects/stringlib/localeutil.h */ -PyAPI_FUNC(Py_ssize_t) _PyUnicode_InsertThousandsGrouping( +extern Py_ssize_t _PyUnicode_InsertThousandsGrouping( _PyUnicodeWriter *writer, Py_ssize_t n_buffer, PyObject *digits, diff --git a/Python/future.c b/Python/future.c index d56f7330964684..0dbc7ede20f324 100644 --- a/Python/future.c +++ b/Python/future.c @@ -1,5 +1,6 @@ #include "Python.h" #include "pycore_ast.h" // _PyAST_GetDocString() +#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined" From f6d2bb18aba844f6bb5836797c72eb791b7f3644 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 00:52:27 +0200 Subject: [PATCH 4/8] gh-86085: Remove _PyCodec_Forget() declaration (#106377) The code was already removed by: commit c9f696cb96d1c362d5cad871f61da520572d9b08. --- Include/internal/pycore_codecs.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Include/internal/pycore_codecs.h b/Include/internal/pycore_codecs.h index 2f8d9d510019ba..a2465192eacd5e 100644 --- a/Include/internal/pycore_codecs.h +++ b/Include/internal/pycore_codecs.h @@ -6,8 +6,6 @@ extern "C" { extern PyObject* _PyCodec_Lookup(const char *encoding); -extern int _PyCodec_Forget(const char *encoding); - /* Text codec specific encoding and decoding API. Checks the encoding against a list of codecs which do not From 2e92edbf6de9578b30cca8e48c4bfb2ba71ae97a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 01:02:07 +0200 Subject: [PATCH 5/8] gh-106320: Remove private _PyImport C API functions (#106383) * Remove private _PyImport C API functions: move them to the internal C API (pycore_import.h). * No longer export most of these private functions. * _testcapi avoids private _PyImport_GetModuleAttrString(). --- Include/cpython/import.h | 20 -------------------- Include/internal/pycore_import.h | 20 ++++++++++++++++++++ Modules/_elementtree.c | 5 +++++ Modules/_sqlite/connection.c | 1 + Modules/_sqlite/module.c | 6 ++++++ Modules/_testcapimodule.c | 10 ++++++++-- Modules/cjkcodecs/cjkcodecs.h | 1 + Modules/pyexpat.c | 5 +++++ 8 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Include/cpython/import.h b/Include/cpython/import.h index 2bca4ade4c4f2c..cdfdd15bfa48d2 100644 --- a/Include/cpython/import.h +++ b/Include/cpython/import.h @@ -4,23 +4,6 @@ PyMODINIT_FUNC PyInit__imp(void); -PyAPI_FUNC(int) _PyImport_IsInitialized(PyInterpreterState *); - -PyAPI_FUNC(PyObject *) _PyImport_GetModuleId(_Py_Identifier *name); -PyAPI_FUNC(int) _PyImport_SetModule(PyObject *name, PyObject *module); -PyAPI_FUNC(int) _PyImport_SetModuleString(const char *name, PyObject* module); - -PyAPI_FUNC(void) _PyImport_AcquireLock(PyInterpreterState *interp); -PyAPI_FUNC(int) _PyImport_ReleaseLock(PyInterpreterState *interp); - -PyAPI_FUNC(int) _PyImport_FixupBuiltin( - PyObject *mod, - const char *name, /* UTF-8 encoded string */ - PyObject *modules - ); -PyAPI_FUNC(int) _PyImport_FixupExtensionObject(PyObject*, PyObject *, - PyObject *, PyObject *); - struct _inittab { const char *name; /* ASCII encoded string */ PyObject* (*initfunc)(void); @@ -41,6 +24,3 @@ struct _frozen { collection of frozen modules: */ PyAPI_DATA(const struct _frozen *) PyImport_FrozenModules; - -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index ee93f7d99d9155..457a654aff4644 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -7,6 +7,26 @@ extern "C" { #include "pycore_time.h" // _PyTime_t +extern int _PyImport_IsInitialized(PyInterpreterState *); + +PyAPI_FUNC(PyObject *) _PyImport_GetModuleId(_Py_Identifier *name); +PyAPI_FUNC(int) _PyImport_SetModule(PyObject *name, PyObject *module); +PyAPI_FUNC(int) _PyImport_SetModuleString(const char *name, PyObject* module); + +extern void _PyImport_AcquireLock(PyInterpreterState *interp); +extern int _PyImport_ReleaseLock(PyInterpreterState *interp); + +extern int _PyImport_FixupBuiltin( + PyObject *mod, + const char *name, /* UTF-8 encoded string */ + PyObject *modules + ); +extern int _PyImport_FixupExtensionObject(PyObject*, PyObject *, + PyObject *, PyObject *); + +PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); +PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); + struct _import_runtime_state { /* The builtin modules (defined in config.c). */ diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 48280690a707a4..3e742e067e7db1 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -11,7 +11,12 @@ *-------------------------------------------------------------------- */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() #include "structmember.h" // PyMemberDef #include "expat.h" #include "pyexpat.h" diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 967ba2812080e5..d71cef14779e51 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -33,6 +33,7 @@ #include "blob.h" #include "prepare_protocol.h" #include "util.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() #include "pycore_weakref.h" // _PyWeakref_IS_DEAD() #include diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index ea4d8c58b7ee0e..368e581b4f3355 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -21,6 +21,10 @@ * 3. This notice may not be removed or altered from any source distribution. */ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "connection.h" #include "statement.h" #include "cursor.h" @@ -29,6 +33,8 @@ #include "row.h" #include "blob.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() + #if SQLITE_VERSION_NUMBER < 3015002 #error "SQLite 3.15.2 or higher required" #endif diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ce1131743eb2a4..d1044b5445202d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1267,9 +1267,15 @@ test_pep3118_obsolete_write_locks(PyObject* self, PyObject *Py_UNUSED(ignored)) if (ret != -1 || match == 0) goto error; + PyObject *mod_io = PyImport_ImportModule("_io"); + if (mod_io == NULL) { + return NULL; + } + /* bytesiobuf_getbuffer() */ - PyTypeObject *type = (PyTypeObject *)_PyImport_GetModuleAttrString( - "_io", "_BytesIOBuffer"); + PyTypeObject *type = (PyTypeObject *)PyObject_GetAttrString( + mod_io, "_BytesIOBuffer"); + Py_DECREF(mod_io); if (type == NULL) { return NULL; } diff --git a/Modules/cjkcodecs/cjkcodecs.h b/Modules/cjkcodecs/cjkcodecs.h index 97290aac3ba439..ee588785e7403f 100644 --- a/Modules/cjkcodecs/cjkcodecs.h +++ b/Modules/cjkcodecs/cjkcodecs.h @@ -13,6 +13,7 @@ #include "Python.h" #include "multibytecodec.h" +#include "pycore_import.h" // _PyImport_GetModuleAttrString() /* a unicode "undefined" code point */ diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index e3333fff00b2b2..28915359fb49e2 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1,4 +1,9 @@ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + #include "Python.h" +#include "pycore_import.h" // _PyImport_SetModule() #include #include "structmember.h" // PyMemberDef From 648688c137744a623a71dc2413d2879b80c99eae Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 01:10:47 +0200 Subject: [PATCH 6/8] gh-106368: Harden Argument Clinic parser tests (#106384) --- Lib/test/test_clinic.py | 110 +++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index b3602887ab6352..c5cfe53e0df99b 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -643,7 +643,7 @@ def test_disallowed_grouping__two_top_groups_on_left(self): self.assertEqual(out, expected_msg) def test_disallowed_grouping__two_top_groups_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.two_top_groups_on_right param: int @@ -654,9 +654,14 @@ def test_disallowed_grouping__two_top_groups_on_right(self): group2 : int ] """) + msg = ( + "Function two_top_groups_on_right has an unsupported group " + "configuration. (Unexpected state 6.b)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__parameter_after_group_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.parameter_after_group_on_right param: int @@ -667,9 +672,14 @@ def test_disallowed_grouping__parameter_after_group_on_right(self): group2 : int ] """) + msg = ( + "Function parameter_after_group_on_right has an unsupported group " + "configuration. (Unexpected state 6.a)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__group_after_parameter_on_left(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.group_after_parameter_on_left [ @@ -680,9 +690,14 @@ def test_disallowed_grouping__group_after_parameter_on_left(self): ] param: int """) + msg = ( + "Function group_after_parameter_on_left has an unsupported group " + "configuration. (Unexpected state 2.b)" + ) + self.assertIn(msg, out) def test_disallowed_grouping__empty_group_on_left(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.empty_group [ @@ -692,9 +707,14 @@ def test_disallowed_grouping__empty_group_on_left(self): ] param: int """) + msg = ( + "Function empty_group has an empty group.\n" + "All groups must contain at least one parameter." + ) + self.assertIn(msg, out) def test_disallowed_grouping__empty_group_on_right(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.empty_group param: int @@ -704,6 +724,11 @@ def test_disallowed_grouping__empty_group_on_right(self): group2 : int ] """) + msg = ( + "Function empty_group has an empty group.\n" + "All groups must contain at least one parameter." + ) + self.assertIn(msg, out) def test_no_parameters(self): function = self.parse_function(""" @@ -732,69 +757,60 @@ class foo.Bar "unused" "notneeded" self.assertEqual(1, len(function.parameters)) def test_illegal_module_line(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar => int / """) + msg = "Illegal function name: foo.bar => int" + self.assertIn(msg, out) def test_illegal_c_basename(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar as 935 / """) + msg = "Illegal C basename: 935" + self.assertIn(msg, out) def test_single_star(self): - self.parse_function_should_fail(""" - module foo - foo.bar - * - * - """) - - def test_parameters_required_after_star_without_initial_parameters_or_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - * - """) - - def test_parameters_required_after_star_without_initial_parameters_with_docstring(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar * - Docstring here. - """) - - def test_parameters_required_after_star_with_initial_parameters_without_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - this: int * """) + self.assertIn("Function bar uses '*' more than once.", out) - def test_parameters_required_after_star_with_initial_parameters_and_docstring(self): - self.parse_function_should_fail(""" - module foo - foo.bar - this: int - * - Docstring. - """) + def test_parameters_required_after_star(self): + dataset = ( + "module foo\nfoo.bar\n *", + "module foo\nfoo.bar\n *\nDocstring here.", + "module foo\nfoo.bar\n this: int\n *", + "module foo\nfoo.bar\n this: int\n *\nDocstring.", + ) + msg = "Function bar specifies '*' without any parameters afterwards." + for block in dataset: + with self.subTest(block=block): + out = self.parse_function_should_fail(block) + self.assertIn(msg, out) def test_single_slash(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar / / """) + msg = ( + "Function bar has an unsupported group configuration. " + "(Unexpected state 0.d)" + ) + self.assertIn(msg, out) def test_mix_star_and_slash(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar x: int @@ -803,14 +819,24 @@ def test_mix_star_and_slash(self): z: int / """) + msg = ( + "Function bar mixes keyword-only and positional-only parameters, " + "which is unsupported." + ) + self.assertIn(msg, out) def test_parameters_not_permitted_after_slash_for_now(self): - self.parse_function_should_fail(""" + out = self.parse_function_should_fail(""" module foo foo.bar / x: int """) + msg = ( + "Function bar has an unsupported group configuration. " + "(Unexpected state 0.d)" + ) + self.assertIn(msg, out) def test_parameters_no_more_than_one_vararg(self): expected_msg = ( From b4256135809d78d342e9d92e8bc3f527d3d3057f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 4 Jul 2023 01:37:48 +0200 Subject: [PATCH 7/8] gh-106320: Fix _PyImport_GetModuleAttr() declaration (#106386) Replace PyAPI_DATA() with PyAPI_FUNC(). --- Include/internal/pycore_import.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 457a654aff4644..c048ae88d9000c 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -24,8 +24,8 @@ extern int _PyImport_FixupBuiltin( extern int _PyImport_FixupExtensionObject(PyObject*, PyObject *, PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); -PyAPI_DATA(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); +PyAPI_FUNC(PyObject *) _PyImport_GetModuleAttr(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) _PyImport_GetModuleAttrString(const char *, const char *); struct _import_runtime_state { From 3406f8cce542ea4edf4153c0fac5216df283a9b1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 01:58:27 +0200 Subject: [PATCH 8/8] gh-106368: Increase Argument Clinic test coverage (#106389) Add: - test_disallowed_gropuing__no_matching_bracket - test_double_slash --- Lib/test/test_clinic.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index c5cfe53e0df99b..03754d0bf123be 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -730,6 +730,18 @@ def test_disallowed_grouping__empty_group_on_right(self): ) self.assertIn(msg, out) + def test_disallowed_grouping__no_matching_bracket(self): + out = self.parse_function_should_fail(""" + module foo + foo.empty_group + param: int + ] + group2: int + ] + """) + msg = "Function empty_group has a ] without a matching [." + self.assertIn(msg, out) + def test_no_parameters(self): function = self.parse_function(""" module foo @@ -809,6 +821,18 @@ def test_single_slash(self): ) self.assertIn(msg, out) + def test_double_slash(self): + out = self.parse_function_should_fail(""" + module foo + foo.bar + a: int + / + b: int + / + """) + msg = "Function bar uses '/' more than once." + self.assertIn(msg, out) + def test_mix_star_and_slash(self): out = self.parse_function_should_fail(""" module foo