Skip to content

Commit 562b5cc

Browse files
authored
Merge pull request #153 from robotpy/parse-free-conversion-operators
Correctly parse free conversion operators
2 parents 0bc2f51 + b5faef3 commit 562b5cc

2 files changed

Lines changed: 255 additions & 5 deletions

File tree

cxxheaderparser/parser.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,10 +1925,23 @@ def _parse_pqname(
19251925
break
19261926

19271927
# If no more segments, we're done
1928-
if not self.lex.token_if("DBL_COLON"):
1928+
tok = self.lex.token_if("DBL_COLON")
1929+
if not tok:
1930+
break
1931+
1932+
next_tok = self._next_token_must_be(
1933+
"NAME", "operator", "template", "decltype"
1934+
)
1935+
if next_tok.type == "operator" and not fn_ok:
1936+
# Qualified conversion operators (for example,
1937+
# ``Foo::operator int()``) do not have a leading return type.
1938+
# When parsing what might be a type, stop before the operator
1939+
# so declaration parsing can consume the qualified operator as
1940+
# the function name and parse the conversion type separately.
1941+
self.lex.return_tokens([tok, next_tok])
19291942
break
19301943

1931-
tok = self._next_token_must_be("NAME", "operator", "template", "decltype")
1944+
tok = next_tok
19321945

19331946
pqname = PQName(segments, classkey, has_typename)
19341947

@@ -2898,6 +2911,7 @@ def _parse_operator_conversion(
28982911
is_typedef: bool,
28992912
is_friend: bool,
29002913
attributes: typing.List[Attribute],
2914+
parsed_type: typing.Optional[Type] = None,
29012915
) -> None:
29022916
tok = self._next_token_must_be("operator")
29032917

@@ -2917,9 +2931,17 @@ def _parse_operator_conversion(
29172931
# then this must be a method
29182932
self._next_token_must_be("(")
29192933

2920-
# make our own pqname/op here
2921-
segments: typing.List[PQNameSegment] = [NameSpecifier("operator")]
2922-
pqname = PQName(segments)
2934+
# if parsed_type is present, then create the pqname from it and apply
2935+
# any modifiers also
2936+
if parsed_type:
2937+
ctype.const = ctype.const or parsed_type.const
2938+
ctype.volatile = ctype.volatile or parsed_type.volatile
2939+
pqname = PQName([*parsed_type.typename.segments, NameSpecifier("operator")])
2940+
else:
2941+
# make our own pqname/op here
2942+
segments: typing.List[PQNameSegment] = [NameSpecifier("operator")]
2943+
pqname = PQName(segments)
2944+
29232945
op = "conversion"
29242946

29252947
if self._parse_function(
@@ -3024,6 +3046,25 @@ def _parse_declarations(
30243046
)
30253047
return
30263048

3049+
qtok = self.lex.token_if("DBL_COLON")
3050+
if qtok:
3051+
otok = self.lex.token_if("operator")
3052+
if otok:
3053+
self.lex.return_tokens([qtok, otok])
3054+
self._next_token_must_be("DBL_COLON")
3055+
self._parse_operator_conversion(
3056+
mods,
3057+
location,
3058+
doxygen,
3059+
template,
3060+
is_typedef,
3061+
is_friend,
3062+
attributes,
3063+
parsed_type,
3064+
)
3065+
return
3066+
self.lex.return_token(qtok)
3067+
30273068
# Ok, dealing with a variable or function/method
30283069
while True:
30293070
if self._parse_decl(

tests/test_operators.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
PQName,
1212
Parameter,
1313
Reference,
14+
TemplateArgument,
15+
TemplateDecl,
16+
TemplateSpecialization,
17+
TemplateTypeParam,
1418
Type,
1519
)
1620
from cxxheaderparser.simple import (
@@ -694,6 +698,211 @@ def test_conversion_operators_decorated() -> None:
694698
)
695699

696700

701+
def test_qualified_conversion_operator_impls() -> None:
702+
content = """
703+
foo::operator bool() const { return bar; }
704+
foo::operator bar() {;}
705+
706+
Foo::operator Type1() { return SomeMethod(); }
707+
const Foo::operator Type2() const { return SomeMethod(); }
708+
volatile Foo::operator Type3() const { return SomeMethod(); }
709+
710+
Foo::operator Foo::Type4() { return SomeMethod(); }
711+
const Foo::operator Foo::Type5() const { return SomeMethod(); }
712+
volatile Foo::operator Foo::Type6() const { return SomeMethod(); }
713+
"""
714+
data = parse_string(content, cleandoc=True)
715+
716+
assert data == ParsedData(
717+
namespace=NamespaceScope(
718+
method_impls=[
719+
Method(
720+
return_type=Type(
721+
typename=PQName(segments=[FundamentalSpecifier(name="bool")])
722+
),
723+
name=PQName(
724+
segments=[
725+
NameSpecifier(name="foo"),
726+
NameSpecifier(name="operator"),
727+
]
728+
),
729+
parameters=[],
730+
has_body=True,
731+
operator="conversion",
732+
const=True,
733+
),
734+
Method(
735+
return_type=Type(
736+
typename=PQName(segments=[NameSpecifier(name="bar")])
737+
),
738+
name=PQName(
739+
segments=[
740+
NameSpecifier(name="foo"),
741+
NameSpecifier(name="operator"),
742+
]
743+
),
744+
parameters=[],
745+
has_body=True,
746+
operator="conversion",
747+
),
748+
Method(
749+
return_type=Type(
750+
typename=PQName(segments=[NameSpecifier(name="Type1")])
751+
),
752+
name=PQName(
753+
segments=[
754+
NameSpecifier(name="Foo"),
755+
NameSpecifier(name="operator"),
756+
]
757+
),
758+
parameters=[],
759+
has_body=True,
760+
operator="conversion",
761+
),
762+
Method(
763+
return_type=Type(
764+
typename=PQName(segments=[NameSpecifier(name="Type2")]),
765+
const=True,
766+
),
767+
name=PQName(
768+
segments=[
769+
NameSpecifier(name="Foo"),
770+
NameSpecifier(name="operator"),
771+
]
772+
),
773+
parameters=[],
774+
has_body=True,
775+
operator="conversion",
776+
const=True,
777+
),
778+
Method(
779+
return_type=Type(
780+
typename=PQName(segments=[NameSpecifier(name="Type3")]),
781+
volatile=True,
782+
),
783+
name=PQName(
784+
segments=[
785+
NameSpecifier(name="Foo"),
786+
NameSpecifier(name="operator"),
787+
]
788+
),
789+
parameters=[],
790+
has_body=True,
791+
operator="conversion",
792+
const=True,
793+
),
794+
Method(
795+
return_type=Type(
796+
typename=PQName(
797+
segments=[
798+
NameSpecifier(name="Foo"),
799+
NameSpecifier(name="Type4"),
800+
]
801+
)
802+
),
803+
name=PQName(
804+
segments=[
805+
NameSpecifier(name="Foo"),
806+
NameSpecifier(name="operator"),
807+
]
808+
),
809+
parameters=[],
810+
has_body=True,
811+
operator="conversion",
812+
),
813+
Method(
814+
return_type=Type(
815+
typename=PQName(
816+
segments=[
817+
NameSpecifier(name="Foo"),
818+
NameSpecifier(name="Type5"),
819+
]
820+
),
821+
const=True,
822+
),
823+
name=PQName(
824+
segments=[
825+
NameSpecifier(name="Foo"),
826+
NameSpecifier(name="operator"),
827+
]
828+
),
829+
parameters=[],
830+
has_body=True,
831+
operator="conversion",
832+
const=True,
833+
),
834+
Method(
835+
return_type=Type(
836+
typename=PQName(
837+
segments=[
838+
NameSpecifier(name="Foo"),
839+
NameSpecifier(name="Type6"),
840+
]
841+
),
842+
volatile=True,
843+
),
844+
name=PQName(
845+
segments=[
846+
NameSpecifier(name="Foo"),
847+
NameSpecifier(name="operator"),
848+
]
849+
),
850+
parameters=[],
851+
has_body=True,
852+
operator="conversion",
853+
const=True,
854+
),
855+
]
856+
)
857+
)
858+
859+
860+
def test_template_conversion_operator_impl() -> None:
861+
content = """
862+
template <class T> ON_SimpleArray<T>::operator T *() {
863+
return (m_count > 0) ? m_a : 0;
864+
}
865+
"""
866+
data = parse_string(content, cleandoc=True)
867+
868+
assert data == ParsedData(
869+
namespace=NamespaceScope(
870+
method_impls=[
871+
Method(
872+
return_type=Pointer(
873+
ptr_to=Type(typename=PQName(segments=[NameSpecifier(name="T")]))
874+
),
875+
name=PQName(
876+
segments=[
877+
NameSpecifier(
878+
name="ON_SimpleArray",
879+
specialization=TemplateSpecialization(
880+
args=[
881+
TemplateArgument(
882+
arg=Type(
883+
typename=PQName(
884+
segments=[NameSpecifier(name="T")]
885+
)
886+
)
887+
)
888+
]
889+
),
890+
),
891+
NameSpecifier(name="operator"),
892+
]
893+
),
894+
parameters=[],
895+
has_body=True,
896+
template=TemplateDecl(
897+
params=[TemplateTypeParam(typekey="class", name="T")]
898+
),
899+
operator="conversion",
900+
)
901+
]
902+
)
903+
)
904+
905+
697906
def test_free_operator() -> None:
698907
content = """
699908
std::ostream& operator<<(std::ostream& os, const MyDate& dt);

0 commit comments

Comments
 (0)