From 66893c4eccb987cad065719851014e597e6e9ef0 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 13 Jun 2026 21:57:56 -0400 Subject: [PATCH] Fix parsing of explicit instantiations of function templates - Fixes #102 --- cxxheaderparser/parser.py | 10 ++++- tests/test_template.py | 79 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index ff34db5..91f0430 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -795,7 +795,9 @@ def _parse_template_instantiation( tok = self.lex.token_if("class", "struct") if not tok: - raise self._parse_error(tok) + tok = self.lex.token() + self._parse_declarations(tok, doxygen, TemplateDecl(), allow_fields=False) + return atok = self.lex.token_if_in_set(self._attribute_start_tokens) if atok: @@ -2754,6 +2756,7 @@ def _parse_decl( is_typedef: bool, is_friend: bool, attributes: typing.List[Attribute], + allow_fields: bool = True, ) -> bool: toks: LexTokenList = [] @@ -2894,6 +2897,9 @@ def _parse_decl( if not dtype: raise CxxParseError("appear to be parsing a field without a type") + if not allow_fields: + raise self._parse_error(None) + if isinstance(template, list): raise CxxParseError("multiple template declarations on a field") @@ -2975,6 +2981,7 @@ def _parse_declarations( template: TemplateDeclTypeVar = None, is_typedef: bool = False, is_friend: bool = False, + allow_fields: bool = True, ) -> None: """ Parses a sequence of declarations @@ -3076,6 +3083,7 @@ def _parse_declarations( is_typedef, is_friend, attributes, + allow_fields, ): # if it returns True then it handled the end of the statement break diff --git a/tests/test_template.py b/tests/test_template.py index b7bb7dc..9cd0485 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -1,5 +1,8 @@ # Note: testcases generated via `python -m cxxheaderparser.gentest` +import pytest + +from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( Array, BaseClass, @@ -1816,6 +1819,82 @@ class C { ) +def test_template_function_instantiation() -> None: + content = """ + template T tFunction(T val); + template T tFunction(T val) { return val; } + template bool tFunction(bool val); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="tFunction")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="val", + ) + ], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="tFunction")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="val", + ) + ], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="bool")]) + ), + name=PQName(segments=[NameSpecifier(name="tFunction")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="bool")] + ) + ), + name="val", + ) + ], + template=TemplateDecl(), + ), + ] + ) + ) + + +def test_template_rejects_variable_instantiation() -> None: + with pytest.raises(CxxParseError): + parse_string("template int x;") + + with pytest.raises(CxxParseError): + parse_string("template int (x);") + + def test_template_instantiation() -> None: content = """ template class MyClass<1,2>;