From 2957e708230ca127170ecf2efb8fdb0aa4370e79 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 10 Oct 2023 04:05:34 -0400 Subject: [PATCH] Add support for C++20 abbreviated function templates Co-authored-by: David Vo --- cxxheaderparser/parser.py | 87 +++++++-- cxxheaderparser/types.py | 7 + tests/test_abv_template.py | 353 +++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 18 deletions(-) create mode 100644 tests/test_abv_template.py diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 52a035b..24b940f 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -606,9 +606,13 @@ class CxxParser: lex.return_token(ptok) param = self._parse_template_type_parameter(tok, None) else: - param = self._parse_parameter(ptok, TemplateNonTypeParam, ">") + param, _ = self._parse_parameter( + ptok, TemplateNonTypeParam, False, ">" + ) else: - param = self._parse_parameter(tok, TemplateNonTypeParam, ">") + param, _ = self._parse_parameter( + tok, TemplateNonTypeParam, concept_ok=False, end=">" + ) params.append(param) @@ -1644,23 +1648,43 @@ class CxxParser: # def _parse_parameter( - self, tok: typing.Optional[LexToken], cls: typing.Type[PT], end: str = ")" - ) -> PT: + self, + tok: typing.Optional[LexToken], + cls: typing.Type[PT], + concept_ok: bool, + end: str = ")", + ) -> typing.Tuple[PT, typing.Optional[Type]]: """ Parses a single parameter (excluding vararg parameters). Also used to parse template non-type parameters + + Returns parameter type, abbreviated template type """ param_name = None default = None param_pack = False + parsed_type: typing.Optional[Type] + at_type: typing.Optional[Type] = None - # required typename + decorators - parsed_type, mods = self._parse_type(tok) - if parsed_type is None: - raise self._parse_error(None) + if not tok: + tok = self.lex.token() - mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter") + # placeholder type, skip typename + if tok.type == "auto": + at_type = parsed_type = Type(PQName([AutoSpecifier()])) + else: + # required typename + decorators + parsed_type, mods = self._parse_type(tok) + if parsed_type is None: + raise self._parse_error(None) + + mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter") + + # Could be a concept + if concept_ok and self.lex.token_if("auto"): + at_type = Type(parsed_type.typename) + parsed_type.typename = PQName([AutoSpecifier()]) dtype = self._parse_cv_ptr(parsed_type) @@ -1688,23 +1712,32 @@ class CxxParser: if self.lex.token_if("="): default = self._create_value(self._consume_value_until([], ",", end)) + # abbreviated template pack + if at_type and self.lex.token_if("ELLIPSIS"): + param_pack = True + param = cls(type=dtype, name=param_name, default=default, param_pack=param_pack) self.debug_print("parameter: %s", param) - return param + return param, at_type - def _parse_parameters(self) -> typing.Tuple[typing.List[Parameter], bool]: + def _parse_parameters( + self, concept_ok: bool + ) -> typing.Tuple[typing.List[Parameter], bool, typing.List[TemplateParam]]: """ - Consumes function parameters and returns them, and vararg if found + Consumes function parameters and returns them, and vararg if found, and + promotes abbreviated template parameters to actual template parameters + if concept_ok is True """ # starting at a ( # special case: zero parameters if self.lex.token_if(")"): - return [], False + return [], False, [] params: typing.List[Parameter] = [] vararg = False + at_params: typing.List[TemplateParam] = [] while True: if self.lex.token_if("ELLIPSIS"): @@ -1712,8 +1745,17 @@ class CxxParser: self._next_token_must_be(")") break - param = self._parse_parameter(None, Parameter) + param, at_type = self._parse_parameter(None, Parameter, concept_ok) params.append(param) + if at_type: + at_params.append( + TemplateNonTypeParam( + type=at_type, + param_idx=len(params) - 1, + param_pack=param.param_pack, + ) + ) + tok = self._next_token_must_be(",", ")") if tok.value == ")": break @@ -1728,7 +1770,7 @@ class CxxParser: ): params = [] - return params, vararg + return params, vararg, at_params _auto_return_typename = PQName([AutoSpecifier()]) @@ -1875,7 +1917,16 @@ class CxxParser: state.location = location is_class_block = isinstance(state, ClassBlockState) - params, vararg = self._parse_parameters() + params, vararg, at_params = self._parse_parameters(True) + + # Promote abbreviated template parameters + if at_params: + if template is None: + template = TemplateDecl(at_params) + elif isinstance(template, TemplateDecl): + template.params.extend(at_params) + else: + template[-1].params.extend(at_params) # A method outside of a class has multiple name segments multiple_name_segments = len(pqname.segments) > 1 @@ -2048,7 +2099,7 @@ class CxxParser: toks = self._consume_balanced_tokens(gtok) self.lex.return_tokens(toks[1:-1]) - fn_params, vararg = self._parse_parameters() + fn_params, vararg, _ = self._parse_parameters(False) assert not isinstance(dtype, FunctionType) dtype = dtype_fn = FunctionType(dtype, fn_params, vararg) @@ -2076,7 +2127,7 @@ class CxxParser: assert not isinstance(dtype, FunctionType) dtype = self._parse_array_type(aptok, dtype) elif aptok.type == "(": - fn_params, vararg = self._parse_parameters() + fn_params, vararg, _ = self._parse_parameters(False) # the type we already have is the return type of the function pointer assert not isinstance(dtype, FunctionType) diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index 722960a..674398e 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -454,12 +454,19 @@ class TemplateNonTypeParam: template ~~~~~~ + + // abbreviated template parameters are converted to this and param_idx is set + void fn(C auto p) + ~~~~~~ """ type: DecoratedType name: typing.Optional[str] = None default: typing.Optional[Value] = None + #: If this was promoted, the parameter index that this corresponds with + param_idx: typing.Optional[int] = None + #: Contains a ``...`` param_pack: bool = False diff --git a/tests/test_abv_template.py b/tests/test_abv_template.py new file mode 100644 index 0000000..288c2d3 --- /dev/null +++ b/tests/test_abv_template.py @@ -0,0 +1,353 @@ +# Note: testcases generated via `python -m cxxheaderparser.gentest` +# +# Tests various aspects of abbreviated function templates +# + +from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + AutoSpecifier, + Function, + FundamentalSpecifier, + NameSpecifier, + PQName, + Parameter, + Pointer, + Reference, + TemplateDecl, + TemplateNonTypeParam, + Type, +) + + +def test_abv_template_f1() -> None: + content = """ + void f1(auto); // same as template void f1(T) + void f1p(auto p); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f1")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f1p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f2() -> None: + content = """ + void f2(C1 auto); // same as template void f2(T), if C1 is a concept + void f2p(C1 auto p); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f2")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C1")]) + ), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f2p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C1")]) + ), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f3() -> None: + content = """ + void f3(C2 auto...); // same as template void f3(Ts...), if C2 is a + // concept + void f3p(C2 auto p...); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f3")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + param_pack=True, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f3p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + param_pack=True, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f4() -> None: + content = """ + void f4(C2 auto, ...); // same as template void f4(T...), if C2 is a concept + void f4p(C2 auto p,...); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f4")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + vararg=True, + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f4p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + vararg=True, + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f5() -> None: + content = """ + void f5(const C3 auto *, C4 auto &); // same as template void f5(const T*, U&); + void f5p(const C3 auto * p1, C4 auto &p2); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f5")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[AutoSpecifier()], + ), + const=True, + ) + ) + ), + Parameter( + type=Reference( + ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ), + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="C3")] + ), + ), + param_idx=0, + ), + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C4")]) + ), + param_idx=1, + ), + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f5p")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[AutoSpecifier()], + ), + const=True, + ) + ), + name="p1", + ), + Parameter( + type=Reference( + ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) + ), + name="p2", + ), + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="C3")] + ), + ), + param_idx=0, + ), + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C4")]) + ), + param_idx=1, + ), + ] + ), + ), + ] + ) + )