From 51d29a0791fa60b67b51c1299a3e524ec8f4b2f3 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 5 Oct 2023 02:24:27 -0400 Subject: [PATCH] Support multiple template declarations on a class or function - Fixes #20 --- cxxheaderparser/parser.py | 27 ++++++-- cxxheaderparser/types.py | 26 +++++++- tests/test_template.py | 136 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 9 deletions(-) diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 2be0537..a2c031a 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -44,6 +44,7 @@ Reference, TemplateArgument, TemplateDecl, + TemplateDeclTypeVar, TemplateInst, TemplateNonTypeParam, TemplateParam, @@ -615,7 +616,18 @@ def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: template = self._parse_template_decl() + # Check for multiple specializations tok = self.lex.token() + if tok.type == "template": + templates = [template] + while tok.type == "template": + templates.append(self._parse_template_decl()) + tok = self.lex.token() + + # Can only be followed by declarations + self._parse_declarations(tok, doxygen, templates) + return + if tok.type == "using": self._parse_using(tok, doxygen, template) elif tok.type == "friend": @@ -1094,7 +1106,7 @@ def _parse_class_decl( typename: PQName, tok: LexToken, doxygen: typing.Optional[str], - template: typing.Optional[TemplateDecl], + template: TemplateDeclTypeVar, typedef: bool, location: Location, mods: ParsedTypeModifiers, @@ -1795,7 +1807,7 @@ def _parse_function( return_type: typing.Optional[DecoratedType], pqname: PQName, op: typing.Optional[str], - template: typing.Optional[TemplateDecl], + template: TemplateDeclTypeVar, doxygen: typing.Optional[str], location: Location, constructor: bool, @@ -2168,7 +2180,7 @@ def _parse_decl( mods: ParsedTypeModifiers, location: Location, doxygen: typing.Optional[str], - template: typing.Optional[TemplateDecl], + template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, ) -> bool: @@ -2295,6 +2307,9 @@ def _parse_decl( if not dtype: raise CxxParseError("appear to be parsing a field without a type") + if isinstance(template, list): + raise CxxParseError("multiple template declarations on a field") + self._parse_field(mods, dtype, pqname, template, doxygen, location, is_typedef) return False @@ -2303,7 +2318,7 @@ def _parse_operator_conversion( mods: ParsedTypeModifiers, location: Location, doxygen: typing.Optional[str], - template: typing.Optional[TemplateDecl], + template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, ) -> None: @@ -2356,7 +2371,7 @@ def _parse_declarations( self, tok: LexToken, doxygen: typing.Optional[str], - template: typing.Optional[TemplateDecl] = None, + template: TemplateDeclTypeVar = None, is_typedef: bool = False, is_friend: bool = False, ) -> None: @@ -2426,7 +2441,7 @@ def _maybe_parse_class_enum_decl( parsed_type: Type, mods: ParsedTypeModifiers, doxygen: typing.Optional[str], - template: typing.Optional[TemplateDecl], + template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, location: Location, diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index c594001..b71ff6b 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -514,6 +514,26 @@ class Foo {}; params: typing.List[TemplateParam] = field(default_factory=list) +#: If no template, this is None. This is a TemplateDecl if this there is a single +#: declaration: +#: +#: .. code-block:: c++ +#: +#: template +#: struct C {}; +#: +#: If there are multiple template declarations, then this is a list of +#: declarations in the order that they're encountered: +#: +#: .. code-block:: c++ +#: +#: template<> +#: template +#: struct A::C {}; +#: +TemplateDeclTypeVar = typing.Union[None, TemplateDecl, typing.List[TemplateDecl]] + + @dataclass class TemplateInst: """ @@ -538,7 +558,7 @@ class ForwardDecl: """ typename: PQName - template: typing.Optional[TemplateDecl] = None + template: TemplateDeclTypeVar = None doxygen: typing.Optional[str] = None #: Set if this is a forward declaration of an enum and it has a base @@ -576,7 +596,7 @@ class ClassDecl: typename: PQName bases: typing.List[BaseClass] = field(default_factory=list) - template: typing.Optional[TemplateDecl] = None + template: TemplateDeclTypeVar = None explicit: bool = False final: bool = False @@ -642,7 +662,7 @@ class Function: #: whatever the trailing return type was has_trailing_return: bool = False - template: typing.Optional[TemplateDecl] = None + template: TemplateDeclTypeVar = None #: Value of any throw specification for this function. The value omits the #: outer parentheses. diff --git a/tests/test_template.py b/tests/test_template.py index 478f814..344e98f 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -2027,3 +2027,139 @@ def test_fwd_declared_method() -> None: ] ) ) + + +def test_multiple_explicit_member_specialization() -> None: + content = """ + template <> + template <> + inline Standard_CString + StdObjMgt_Attribute::Simple::PName() const { + return "PDF_TagSource"; + } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + method_impls=[ + Method( + return_type=Type( + typename=PQName( + segments=[NameSpecifier(name="Standard_CString")] + ) + ), + name=PQName( + segments=[ + NameSpecifier( + name="StdObjMgt_Attribute", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="TDF_TagSource" + ) + ] + ) + ) + ) + ] + ), + ), + NameSpecifier( + name="Simple", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Standard_Integer" + ) + ] + ) + ) + ) + ] + ), + ), + NameSpecifier(name="PName"), + ] + ), + parameters=[], + inline=True, + has_body=True, + template=[TemplateDecl(), TemplateDecl()], + const=True, + ) + ] + ) + ) + + +def test_member_class_template_specialization() -> None: + content = """ + template <> // specialization of a member class template + template + struct A::C { + void f(); + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[ + NameSpecifier( + name="A", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="char" + ) + ] + ) + ) + ) + ] + ), + ), + NameSpecifier(name="C"), + ], + classkey="struct", + ), + template=[ + TemplateDecl(), + TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="U")] + ), + ], + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + access="public", + ) + ], + ) + ] + ) + )