Skip to content

Commit

Permalink
Merge pull request #73 from robotpy/multiple-template-declarations
Browse files Browse the repository at this point in the history
Support multiple template declarations on a class or function
  • Loading branch information
virtuald committed Oct 5, 2023
2 parents 1758155 + 51d29a0 commit 955214c
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 9 deletions.
27 changes: 21 additions & 6 deletions cxxheaderparser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
Reference,
TemplateArgument,
TemplateDecl,
TemplateDeclTypeVar,
TemplateInst,
TemplateNonTypeParam,
TemplateParam,
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 23 additions & 3 deletions cxxheaderparser/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
#: 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<class U>
#: struct A<char>::C {};
#:
TemplateDeclTypeVar = typing.Union[None, TemplateDecl, typing.List[TemplateDecl]]


@dataclass
class TemplateInst:
"""
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
136 changes: 136 additions & 0 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<TDF_TagSource>::Simple<Standard_Integer>::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 <class U>
struct A<char>::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",
)
],
)
]
)
)

0 comments on commit 955214c

Please sign in to comment.