From 3c51a30efe7399a2118b301c978ae51c5b59e01e Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 9 Jan 2021 12:54:45 -0500 Subject: [PATCH] Support using 'typename' qualifier before type names - Fixes #7 --- cxxheaderparser/parser.py | 15 ++++-- cxxheaderparser/types.py | 5 +- tests/test_fn.py | 108 ++++++++++++++++++++++++++++++++++++++ tests/test_template.py | 8 +-- 4 files changed, 125 insertions(+), 11 deletions(-) diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 735901f..3ec9e86 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -574,7 +574,6 @@ class CxxParser: # tokens. If it succeeds we're done, otherwise we use the value param_pack = False - has_typename = True if self.lex.token_if("typename") else False raw_toks = self._consume_value_until([], ",", ">", "ELLIPSIS") val = self._create_value(raw_toks) @@ -601,9 +600,9 @@ class CxxParser: param_pack = True if dtype: - args.append(TemplateArgument(dtype, has_typename, param_pack)) + args.append(TemplateArgument(dtype, param_pack)) else: - args.append(TemplateArgument(val, has_typename, param_pack)) + args.append(TemplateArgument(val, param_pack)) tok = self._next_token_must_be(",", ">") if tok.type == ">": @@ -1251,7 +1250,7 @@ class CxxParser: return parts _pqname_start_tokens = ( - {"auto", "decltype", "NAME", "operator", "template", "DBL_COLON"} + {"auto", "decltype", "NAME", "operator", "template", "typename", "DBL_COLON"} | _name_compound_start | _fundamentals ) @@ -1329,6 +1328,7 @@ class CxxParser: classkey = None segments: typing.List[PQNameSegment] = [] op = None + has_typename = False if tok is None: tok = self.lex.token() @@ -1369,6 +1369,11 @@ class CxxParser: self.anon_id += 1 segments.append(AnonymousName(self.anon_id)) return PQName(segments, classkey), None + elif tok.type == "typename": + has_typename = True + tok = self.lex.token() + if tok.type not in self._pqname_start_tokens: + raise self._parse_error(tok) # First section of the name: Add empty segment if starting out with a # namespace specifier @@ -1415,7 +1420,7 @@ class CxxParser: tok = self._next_token_must_be("NAME", "operator", "template", "decltype") - pqname = PQName(segments, classkey) + pqname = PQName(segments, classkey, has_typename) self.debug_print( "parse_pqname: %s op=%s", diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index 61cfca5..b978218 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -140,6 +140,9 @@ class PQName: #: Set if the name starts with class/enum/struct classkey: typing.Optional[str] = None + #: Set to true if the type was preceded with 'typename' + has_typename: bool = False + @dataclass class Enumerator: @@ -192,8 +195,6 @@ class TemplateArgument: #: otherwise it's stored as an unparsed set of values arg: typing.Union["DecoratedType", Value] - #: Set if starts with "typename" - has_typename: bool = False param_pack: bool = False diff --git a/tests/test_fn.py b/tests/test_fn.py index e5fee55..9b571be 100644 --- a/tests/test_fn.py +++ b/tests/test_fn.py @@ -17,6 +17,7 @@ from cxxheaderparser.types import ( TemplateTypeParam, Token, Type, + Value, ) from cxxheaderparser.simple import ( NamespaceScope, @@ -71,6 +72,61 @@ def test_fn_returns_class(): ) +def test_fn_returns_typename(): + content = """ + typename ns::X fn(); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="ns"), + NameSpecifier(name="X"), + ], + has_typename=True, + ) + ), + name=PQName(segments=[NameSpecifier(name="fn")]), + parameters=[], + ) + ] + ) + ) + + +def test_fn_returns_typename_const(): + content = """ + const typename ns::X fn(); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="ns"), + NameSpecifier(name="X"), + ], + has_typename=True, + ), + const=True, + ), + name=PQName(segments=[NameSpecifier(name="fn")]), + parameters=[], + ) + ] + ) + ) + + def test_fn_pointer_params(): content = """ int fn1(int *); @@ -194,6 +250,58 @@ def test_fn_array_param(): ) +def test_fn_typename_param(): + content = """ + void MethodA(const mynamespace::SomeObject &x, + typename mynamespace::SomeObject * = 0); + + """ + 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="MethodA")]), + parameters=[ + Parameter( + type=Reference( + ref_to=Type( + typename=PQName( + segments=[ + NameSpecifier(name="mynamespace"), + NameSpecifier(name="SomeObject"), + ] + ), + const=True, + ) + ), + name="x", + ), + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[ + NameSpecifier(name="mynamespace"), + NameSpecifier(name="SomeObject"), + ], + has_typename=True, + ) + ) + ), + default=Value(tokens=[Token(value="0")]), + ), + ], + ) + ] + ) + ) + + def test_fn_weird_refs(): content = """ int aref(int(&x)); diff --git a/tests/test_template.py b/tests/test_template.py index e0eb5e1..85e0e72 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -1702,10 +1702,10 @@ def test_template_specialized_fn_typename(): segments=[ NameSpecifier(name=""), NameSpecifier(name="T"), - ] + ], + has_typename=True, ) ), - has_typename=True, ) ] ), @@ -1795,10 +1795,10 @@ def test_template_specialized_fn_typename_template(): ] ), ), - ] + ], + has_typename=True, ) ), - has_typename=True, ) ] ),