Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
24 changes: 18 additions & 6 deletions 24 Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ def converter_init(self):
self.expect_failure(raw, err)

def test_clone_mismatch(self):
err = "'kind' of function and cloned function don't match!"
err = ("'kind' of function and cloned function don't match: "
"STATIC_METHOD != CLASS_METHOD")
block = """
/*[clinic input]
module m
Expand Down Expand Up @@ -574,7 +575,7 @@ class C "void *" ""
C.__init__ = C.meth
[clinic start generated code]*/
"""
err = "'__init__' must be a normal method; got 'FunctionKind.CLASS_METHOD'!"
err = "'__init__' must be an instance method; got 'FunctionKind.CLASS_METHOD'"
self.expect_failure(block, err, lineno=8)

def test_validate_cloned_new(self):
Expand Down Expand Up @@ -1906,7 +1907,9 @@ def test_parameters_not_permitted_after_slash_for_now(self):
self.expect_failure(block, err)

def test_parameters_no_more_than_one_vararg(self):
err = "Too many var args"
err = ("Cannot specify multiple vararg parameters: "
"'vararg2' is a vararg, but "
"'vararg1' was already provided")
block = """
module foo
foo.bar
Expand Down Expand Up @@ -2039,6 +2042,15 @@ def test_legacy_converters_non_string_constant_annotation(self):
with self.subTest(block=block):
self.expect_failure(block, err, lineno=2)

def test_str_converter_invalid_format_unit(self):
block = """
module foo
foo.bar
a: str(encoding='foo', zeroes=True, accept={})
"""
err = "unsupported combination of str converter arguments"
self.expect_failure(block, err, lineno=2)

def test_other_bizarre_things_in_annotations_fail(self):
err = "Annotations must be either a name, a function call, or a string"
dataset = (
Expand Down Expand Up @@ -2120,7 +2132,7 @@ class Foo "" ""
self.parse_function(block)

def test_new_must_be_a_class_method(self):
err = "'__new__' must be a class method!"
err = "'__new__' must be a class method; got 'FunctionKind.CALLABLE'"
block = """
module foo
class Foo "" ""
Expand All @@ -2129,7 +2141,7 @@ class Foo "" ""
self.expect_failure(block, err, lineno=2)

def test_init_must_be_a_normal_method(self):
err_template = "'__init__' must be a normal method; got 'FunctionKind.{}'!"
err_template = "'__init__' must be an instance method; got 'FunctionKind.{}'"
annotations = {
"@classmethod": "CLASS_METHOD",
"@staticmethod": "STATIC_METHOD",
Expand Down Expand Up @@ -2317,7 +2329,7 @@ def test_non_ascii_character_in_docstring(self):
self.assertEqual(stdout.getvalue(), expected)

def test_illegal_c_identifier(self):
err = "Illegal C identifier: 17a"
err = "Expected a legal C identifier; got '17a'"
block = """
module test
test.fn
Expand Down
50 changes: 36 additions & 14 deletions 50 Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3965,14 +3965,27 @@ class buffer: pass
class rwbuffer: pass
class robuffer: pass

StrConverterKeyType = tuple[frozenset[type[object]], bool, bool]
@dc.dataclass
class StrConverterKey:
accept: frozenset[type[object]]
encoding: bool
zeroes: bool

def __hash__(self) -> int:
return hash((self.accept, self.encoding, self.zeroes))

def __str__(self) -> str:
accept = "{" + ", ".join([tp.__name__ for tp in self.accept]) + "}"
encoding = 'encodingname' if self.encoding else None
zeroes = self.zeroes
return f"{accept=!r}, {encoding=!r}, {zeroes=!r}"

def str_converter_key(
types: TypeSet, encoding: bool | str | None, zeroes: bool
) -> StrConverterKeyType:
return (frozenset(types), bool(encoding), bool(zeroes))
) -> StrConverterKey:
return StrConverterKey(frozenset(types), bool(encoding), bool(zeroes))

str_converter_argument_map: dict[StrConverterKeyType, str] = {}
str_converter_argument_map: dict[StrConverterKey, str] = {}

class str_converter(CConverter):
type = 'const char *'
Expand All @@ -3990,7 +4003,10 @@ def converter_init(
key = str_converter_key(accept, encoding, zeroes)
format_unit = str_converter_argument_map.get(key)
if not format_unit:
fail("str_converter: illegal combination of arguments", key)
allowed = "\n".join([str(k) for k in str_converter_argument_map.keys()])
fail("unsupported combination of str converter arguments: "
f"{accept=!r}, {encoding=!r}, {zeroes=!r}; "
f"allowed combinations are:\n\n{allowed}")

self.format_unit = format_unit
self.length = bool(zeroes)
Expand Down Expand Up @@ -4931,12 +4947,14 @@ def directive_preserve(self) -> None:

def at_classmethod(self) -> None:
if self.kind is not CALLABLE:
fail("Can't set @classmethod, function is not a normal callable")
fail("Can't set @classmethod, "
f"function is not a normal callable; got: {self.kind!r}")
self.kind = CLASS_METHOD

def at_critical_section(self, *args: str) -> None:
if len(args) > 2:
fail("Up to 2 critical section variables are supported")
fail("Only 2 critical section variables are supported; "
f"{len(args)} were given")
self.target_critical_section.extend(args)
self.critical_section = True

Expand All @@ -4960,7 +4978,8 @@ def at_setter(self) -> None:

def at_staticmethod(self) -> None:
if self.kind is not CALLABLE:
fail("Can't set @staticmethod, function is not a normal callable")
fail("Can't set @staticmethod, "
f"function is not a normal callable; got: {self.kind!r}")
self.kind = STATIC_METHOD

def at_coexist(self) -> None:
Expand Down Expand Up @@ -5074,9 +5093,9 @@ def normalize_function_kind(self, fullname: str) -> None:
if name in unsupported_special_methods:
fail(f"{name!r} is a special method and cannot be converted to Argument Clinic!")
if name == '__init__' and (self.kind is not CALLABLE or not cls):
fail(f"{name!r} must be a normal method; got '{self.kind}'!")
fail(f"{name!r} must be an instance method; got '{self.kind}'!")
if name == '__new__' and (self.kind is not CLASS_METHOD or not cls):
fail("'__new__' must be a class method!")
fail(f"'__new__' must be a class method; got '{self.kind}'")
if self.kind in {GETTER, SETTER} and not cls:
fail("@getter and @setter must be methods")

Expand Down Expand Up @@ -5150,8 +5169,8 @@ def parse_cloned_function(self, names: FunctionNames, existing: str) -> None:
# Future enhancement: allow custom return converters
overrides["return_converter"] = CReturnConverter()
else:
fail("'kind' of function and cloned function don't match! "
"(@classmethod/@staticmethod/@coexist)")
fail("'kind' of function and cloned function don't match: "
f"{self.kind.name} != {existing_function.kind.name}")
function = existing_function.copy(**overrides)
self.function = function
self.block.signatures.append(function)
Expand Down Expand Up @@ -5424,10 +5443,13 @@ def parse_parameter(self, line: str) -> None:
f"invalid parameter declaration (**kwargs?): {line!r}")

if function_args.vararg:
if any(p.is_vararg() for p in self.function.parameters.values()):
fail("Too many var args")
is_vararg = True
parameter = function_args.vararg
for p in self.function.parameters.values():
if p.is_vararg():
fail("Cannot specify multiple vararg parameters: "
f"{parameter.arg!r} is a vararg, but "
f"{p.name!r} was already provided as a vararg")
else:
is_vararg = False
parameter = function_args.args[0]
Expand Down
2 changes: 1 addition & 1 deletion 2 Tools/clinic/libclinic/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def is_legal_py_identifier(identifier: str) -> bool:
def ensure_legal_c_identifier(identifier: str) -> str:
# For now, just complain if what we're given isn't legal.
if not is_legal_c_identifier(identifier):
raise ClinicError(f"Illegal C identifier: {identifier}")
raise ClinicError(f"Expected a legal C identifier; got {identifier!r}")
# But if we picked a C keyword, pick something else.
if identifier in _c_keywords:
return identifier + "_value"
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.