diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 57d9c45..ff34db5 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -1925,10 +1925,23 @@ def _parse_pqname( break # If no more segments, we're done - if not self.lex.token_if("DBL_COLON"): + tok = self.lex.token_if("DBL_COLON") + if not tok: + break + + next_tok = self._next_token_must_be( + "NAME", "operator", "template", "decltype" + ) + if next_tok.type == "operator" and not fn_ok: + # Qualified conversion operators (for example, + # ``Foo::operator int()``) do not have a leading return type. + # When parsing what might be a type, stop before the operator + # so declaration parsing can consume the qualified operator as + # the function name and parse the conversion type separately. + self.lex.return_tokens([tok, next_tok]) break - tok = self._next_token_must_be("NAME", "operator", "template", "decltype") + tok = next_tok pqname = PQName(segments, classkey, has_typename) @@ -2898,6 +2911,7 @@ def _parse_operator_conversion( is_typedef: bool, is_friend: bool, attributes: typing.List[Attribute], + parsed_type: typing.Optional[Type] = None, ) -> None: tok = self._next_token_must_be("operator") @@ -2917,9 +2931,17 @@ def _parse_operator_conversion( # then this must be a method self._next_token_must_be("(") - # make our own pqname/op here - segments: typing.List[PQNameSegment] = [NameSpecifier("operator")] - pqname = PQName(segments) + # if parsed_type is present, then create the pqname from it and apply + # any modifiers also + if parsed_type: + ctype.const = ctype.const or parsed_type.const + ctype.volatile = ctype.volatile or parsed_type.volatile + pqname = PQName([*parsed_type.typename.segments, NameSpecifier("operator")]) + else: + # make our own pqname/op here + segments: typing.List[PQNameSegment] = [NameSpecifier("operator")] + pqname = PQName(segments) + op = "conversion" if self._parse_function( @@ -3024,6 +3046,25 @@ def _parse_declarations( ) return + qtok = self.lex.token_if("DBL_COLON") + if qtok: + otok = self.lex.token_if("operator") + if otok: + self.lex.return_tokens([qtok, otok]) + self._next_token_must_be("DBL_COLON") + self._parse_operator_conversion( + mods, + location, + doxygen, + template, + is_typedef, + is_friend, + attributes, + parsed_type, + ) + return + self.lex.return_token(qtok) + # Ok, dealing with a variable or function/method while True: if self._parse_decl( diff --git a/tests/test_operators.py b/tests/test_operators.py index 2c0a82b..dec6585 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -11,6 +11,10 @@ PQName, Parameter, Reference, + TemplateArgument, + TemplateDecl, + TemplateSpecialization, + TemplateTypeParam, Type, ) from cxxheaderparser.simple import ( @@ -694,6 +698,211 @@ def test_conversion_operators_decorated() -> None: ) +def test_qualified_conversion_operator_impls() -> None: + content = """ + foo::operator bool() const { return bar; } + foo::operator bar() {;} + + Foo::operator Type1() { return SomeMethod(); } + const Foo::operator Type2() const { return SomeMethod(); } + volatile Foo::operator Type3() const { return SomeMethod(); } + + Foo::operator Foo::Type4() { return SomeMethod(); } + const Foo::operator Foo::Type5() const { return SomeMethod(); } + volatile Foo::operator Foo::Type6() const { return SomeMethod(); } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + method_impls=[ + Method( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="bool")]) + ), + name=PQName( + segments=[ + NameSpecifier(name="foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + const=True, + ), + Method( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="bar")]) + ), + name=PQName( + segments=[ + NameSpecifier(name="foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + ), + Method( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="Type1")]) + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + ), + Method( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="Type2")]), + const=True, + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + const=True, + ), + Method( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="Type3")]), + volatile=True, + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + const=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="Type4"), + ] + ) + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + ), + Method( + return_type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="Type5"), + ] + ), + const=True, + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + const=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="Type6"), + ] + ), + volatile=True, + ), + name=PQName( + segments=[ + NameSpecifier(name="Foo"), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + operator="conversion", + const=True, + ), + ] + ) + ) + + +def test_template_conversion_operator_impl() -> None: + content = """ + template ON_SimpleArray::operator T *() { + return (m_count > 0) ? m_a : 0; + } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + method_impls=[ + Method( + return_type=Pointer( + ptr_to=Type(typename=PQName(segments=[NameSpecifier(name="T")])) + ), + name=PQName( + segments=[ + NameSpecifier( + name="ON_SimpleArray", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[NameSpecifier(name="T")] + ) + ) + ) + ] + ), + ), + NameSpecifier(name="operator"), + ] + ), + parameters=[], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + operator="conversion", + ) + ] + ) + ) + + def test_free_operator() -> None: content = """ std::ostream& operator<<(std::ostream& os, const MyDate& dt);