Skip to content

Commit 5334b31

Browse files
authored
Merge pull request #142 from robotpy/fix/issue-141-constexpr-friend
Fix reordered friend declaration parsing
2 parents 02b221c + 2ec8265 commit 5334b31

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

cxxheaderparser/parser.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,7 @@ def _parse_type(
25732573

25742574
pqname: typing.Optional[PQName] = None
25752575
pqname_optional = False
2576+
friend_tok: typing.Optional[LexToken] = None
25762577

25772578
_pqname_start_tokens = self._pqname_start_tokens
25782579
_attribute_start = self._attribute_start_tokens
@@ -2597,6 +2598,8 @@ def _parse_type(
25972598
break
25982599
elif tok_type == "const":
25992600
const = True
2601+
elif tok_type == "friend" and pqname is None:
2602+
friend_tok = tok
26002603
elif tok_type in self._type_kwd_both:
26012604
if tok_type == "extern":
26022605
# TODO: store linkage
@@ -2635,7 +2638,7 @@ def _parse_type(
26352638
self.lex.return_token(tok)
26362639

26372640
# Always return the modifiers
2638-
mods = ParsedTypeModifiers(vars, both, meths, explicit_value)
2641+
mods = ParsedTypeModifiers(vars, both, meths, explicit_value, friend_tok)
26392642
return parsed_type, mods
26402643

26412644
def _parse_decl(
@@ -2871,6 +2874,12 @@ def _parse_declarations(
28712874
parsed_type, mods = self._parse_type(tok, operator_ok=True)
28722875
attributes.extend(self._take_pending_attributes())
28732876

2877+
if mods.friend is not None:
2878+
if is_friend or is_typedef or not isinstance(self.state, ClassBlockState):
2879+
raise self._parse_error(mods.friend)
2880+
is_friend = True
2881+
mods = mods._replace(friend=None)
2882+
28742883
# Check to see if this might be a class/enum declaration
28752884
if (
28762885
parsed_type is not None

cxxheaderparser/parserstate.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ class ParsedTypeModifiers(typing.NamedTuple):
1616
#: parens (omitting the parens themselves). ``None`` if absent or if
1717
#: ``explicit`` was used as a bare keyword.
1818
explicit_value: typing.Optional[Value] = None
19+
#: ``friend`` if encountered while parsing declaration specifiers.
20+
friend: typing.Optional[LexToken] = None
1921

20-
def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None:
22+
def validate(
23+
self, *, var_ok: bool, meth_ok: bool, msg: str, friend_ok: bool = False
24+
) -> None:
2125
# Almost there! Do any checks the caller asked for
2226
if not var_ok and self.vars:
2327
for tok in self.vars.values():
@@ -31,6 +35,9 @@ def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None:
3135
for tok in self.both.values():
3236
raise CxxParseError(f"{msg}: unexpected '{tok.value}'")
3337

38+
if not friend_ok and self.friend is not None:
39+
raise CxxParseError(f"{msg}: unexpected '{self.friend.value}'")
40+
3441

3542
#: custom user data for this state type
3643
T = typing.TypeVar("T")

tests/test_friends.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Note: testcases generated via `python -m cxxheaderparser.gentest`
22

33
from cxxheaderparser.types import (
4+
AutoSpecifier,
45
ClassDecl,
56
Field,
67
ForwardDecl,
@@ -381,3 +382,42 @@ class Garlic {
381382
]
382383
)
383384
)
385+
386+
387+
def test_constexpr_friend_decl_order() -> None:
388+
content = """
389+
struct friend_constexpr_test {
390+
constexpr friend auto foo() {
391+
}
392+
};
393+
"""
394+
data = parse_string(content, cleandoc=True)
395+
396+
assert data == ParsedData(
397+
namespace=NamespaceScope(
398+
classes=[
399+
ClassScope(
400+
class_decl=ClassDecl(
401+
typename=PQName(
402+
segments=[NameSpecifier(name="friend_constexpr_test")],
403+
classkey="struct",
404+
)
405+
),
406+
friends=[
407+
FriendDecl(
408+
fn=Method(
409+
return_type=Type(
410+
typename=PQName(segments=[AutoSpecifier()])
411+
),
412+
name=PQName(segments=[NameSpecifier(name="foo")]),
413+
parameters=[],
414+
constexpr=True,
415+
has_body=True,
416+
access="public",
417+
)
418+
)
419+
],
420+
)
421+
]
422+
)
423+
)

0 commit comments

Comments
 (0)