Skip to content

Commit bf54be5

Browse files
committed
Constrain visitor state types further to valid combinations
1 parent 326da61 commit bf54be5

File tree

4 files changed

+94
-53
lines changed

4 files changed

+94
-53
lines changed

cxxheaderparser/parser.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ClassBlockState,
1313
ExternBlockState,
1414
NamespaceBlockState,
15+
NonClassBlockState,
1516
ParsedTypeModifiers,
1617
State,
1718
)
@@ -61,7 +62,6 @@
6162
LexTokenList = typing.List[LexToken]
6263
T = typing.TypeVar("T")
6364

64-
ST = typing.TypeVar("ST", bound=State)
6565
PT = typing.TypeVar("PT", Parameter, TemplateNonTypeParam)
6666

6767

@@ -89,7 +89,9 @@ def __init__(
8989
global_ns = NamespaceDecl([], False)
9090
self.current_namespace = global_ns
9191

92-
self.state: State = NamespaceBlockState(None, global_ns)
92+
self.state: State = NamespaceBlockState(
93+
None, self.lex.current_location(), global_ns
94+
)
9395
self.anon_id = 0
9496

9597
self.verbose = True if self.options.verbose else False
@@ -110,13 +112,9 @@ def debug_print(fmt: str, *args: typing.Any) -> None:
110112
# State management
111113
#
112114

113-
def _push_state(self, cls: typing.Type[ST], *args) -> ST:
114-
state = cls(self.state, *args)
115+
def _setup_state(self, state: State):
115116
state._prior_visitor = self.visitor
116-
if isinstance(state, NamespaceBlockState):
117-
self.current_namespace = state.namespace
118117
self.state = state
119-
return state
120118

121119
def _pop_state(self) -> State:
122120
prev_state = self.state
@@ -446,24 +444,37 @@ def _parse_namespace(
446444
if inline and len(names) > 1:
447445
raise CxxParseError("a nested namespace definition cannot be inline")
448446

447+
state = self.state
448+
if not isinstance(state, (NamespaceBlockState, ExternBlockState)):
449+
raise CxxParseError("namespace cannot be defined in a class")
450+
449451
if ns_alias:
450452
alias = NamespaceAlias(ns_alias.value, names)
451-
self.visitor.on_namespace_alias(self.state, alias)
453+
self.visitor.on_namespace_alias(state, alias)
452454
return
453455

454456
ns = NamespaceDecl(names, inline, doxygen)
455-
state = self._push_state(NamespaceBlockState, ns)
456-
state.location = location
457+
458+
state = NamespaceBlockState(state, location, ns)
459+
self._setup_state(state)
460+
self.current_namespace = state.namespace
461+
457462
if self.visitor.on_namespace_start(state) is False:
458463
self.visitor = null_visitor
459464

460465
def _parse_extern(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
461466
etok = self.lex.token_if("STRING_LITERAL", "template")
462467
if etok:
468+
# classes cannot contain extern blocks/templates
469+
state = self.state
470+
if isinstance(state, ClassBlockState):
471+
raise self._parse_error(tok)
472+
463473
if etok.type == "STRING_LITERAL":
464474
if self.lex.token_if("{"):
465-
state = self._push_state(ExternBlockState, etok.value)
466-
state.location = tok.location
475+
state = ExternBlockState(state, tok.location, etok.value)
476+
self._setup_state(state)
477+
467478
if self.visitor.on_extern_block_start(state) is False:
468479
self.visitor = null_visitor
469480
return
@@ -819,7 +830,7 @@ def _consume_attribute_specifier_seq(
819830
# Using directive/declaration/typealias
820831
#
821832

822-
def _parse_using_directive(self) -> None:
833+
def _parse_using_directive(self, state: NonClassBlockState) -> None:
823834
"""
824835
using_directive: [attribute_specifier_seq] "using" "namespace" ["::"] [nested_name_specifier] IDENTIFIER ";"
825836
"""
@@ -838,7 +849,7 @@ def _parse_using_directive(self) -> None:
838849
if not names:
839850
raise self._parse_error(None, "NAME")
840851

841-
self.visitor.on_using_namespace(self.state, names)
852+
self.visitor.on_using_namespace(state, names)
842853

843854
def _parse_using_declaration(self, tok: LexToken) -> None:
844855
"""
@@ -890,10 +901,11 @@ def _parse_using(
890901
raise CxxParseError(
891902
"unexpected using-directive when parsing alias-declaration", tok
892903
)
893-
if isinstance(self.state, ClassBlockState):
904+
state = self.state
905+
if not isinstance(state, (NamespaceBlockState, ExternBlockState)):
894906
raise self._parse_error(tok)
895907

896-
self._parse_using_directive()
908+
self._parse_using_directive(state)
897909
elif tok.type in ("DBL_COLON", "typename") or not self.lex.token_if("="):
898910
if template:
899911
raise CxxParseError(
@@ -1140,10 +1152,11 @@ def _parse_class_decl(
11401152
clsdecl = ClassDecl(
11411153
typename, bases, template, explicit, final, doxygen, self._current_access
11421154
)
1143-
state = self._push_state(
1144-
ClassBlockState, clsdecl, default_access, typedef, mods
1155+
state: ClassBlockState = ClassBlockState(
1156+
self.state, location, clsdecl, default_access, typedef, mods
11451157
)
1146-
state.location = location
1158+
self._setup_state(state)
1159+
11471160
if self.visitor.on_class_start(state) is False:
11481161
self.visitor = null_visitor
11491162

@@ -1850,6 +1863,7 @@ def _parse_function(
18501863

18511864
self.visitor.on_class_method(state, method)
18521865
else:
1866+
assert isinstance(state, (ExternBlockState, NamespaceBlockState))
18531867
if not method.has_body:
18541868
raise self._parse_error(None, expected="Method body")
18551869
self.visitor.on_method_impl(state, method)
@@ -1909,6 +1923,9 @@ def _parse_function(
19091923
self.visitor.on_typedef(state, typedef)
19101924
return False
19111925
else:
1926+
if not isinstance(state, (ExternBlockState, NamespaceBlockState)):
1927+
raise CxxParseError("internal error")
1928+
19121929
self.visitor.on_function(state, fn)
19131930
return fn.has_body or fn.has_trailing_return
19141931

cxxheaderparser/parserstate.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,59 +35,65 @@ def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None:
3535
PT = typing.TypeVar("PT")
3636

3737

38-
class State(typing.Generic[T, PT]):
38+
class BaseState(typing.Generic[T, PT]):
3939
#: Uninitialized user data available for use by visitor implementations. You
4040
#: should set this in a ``*_start`` method.
4141
user_data: T
4242

4343
#: parent state
44-
parent: typing.Optional["State[PT, typing.Any]"]
44+
parent: typing.Optional["State"]
4545

4646
#: Approximate location that the parsed element was found at
4747
location: Location
4848

4949
#: internal detail used by parser
5050
_prior_visitor: "CxxVisitor"
5151

52-
def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None:
52+
def __init__(self, parent: typing.Optional["State"], location: Location) -> None:
5353
self.parent = parent
54+
self.location = location
5455

5556
def _finish(self, visitor: "CxxVisitor") -> None:
5657
pass
5758

5859

59-
class ExternBlockState(State[T, PT]):
60-
parent: State[PT, typing.Any]
60+
class ExternBlockState(BaseState[T, PT]):
61+
parent: "NonClassBlockState"
6162

6263
#: The linkage for this extern block
6364
linkage: str
6465

65-
def __init__(self, parent: typing.Optional[State], linkage: str) -> None:
66-
super().__init__(parent)
66+
def __init__(
67+
self, parent: "NonClassBlockState", location: Location, linkage: str
68+
) -> None:
69+
super().__init__(parent, location)
6770
self.linkage = linkage
6871

6972
def _finish(self, visitor: "CxxVisitor") -> None:
7073
visitor.on_extern_block_end(self)
7174

7275

73-
class NamespaceBlockState(State[T, PT]):
74-
parent: State[PT, typing.Any]
76+
class NamespaceBlockState(BaseState[T, PT]):
77+
parent: "NonClassBlockState"
7578

7679
#: The incremental namespace for this block
7780
namespace: NamespaceDecl
7881

7982
def __init__(
80-
self, parent: typing.Optional[State], namespace: NamespaceDecl
83+
self,
84+
parent: typing.Optional["NonClassBlockState"],
85+
location: Location,
86+
namespace: NamespaceDecl,
8187
) -> None:
82-
super().__init__(parent)
88+
super().__init__(parent, location)
8389
self.namespace = namespace
8490

8591
def _finish(self, visitor: "CxxVisitor") -> None:
8692
visitor.on_namespace_end(self)
8793

8894

89-
class ClassBlockState(State[T, PT]):
90-
parent: State[PT, typing.Any]
95+
class ClassBlockState(BaseState[T, PT]):
96+
parent: "State"
9197

9298
#: class decl block being processed
9399
class_decl: ClassDecl
@@ -103,13 +109,14 @@ class ClassBlockState(State[T, PT]):
103109

104110
def __init__(
105111
self,
106-
parent: typing.Optional[State],
112+
parent: typing.Optional["State"],
113+
location: Location,
107114
class_decl: ClassDecl,
108115
access: str,
109116
typedef: bool,
110117
mods: ParsedTypeModifiers,
111118
) -> None:
112-
super().__init__(parent)
119+
super().__init__(parent, location)
113120
self.class_decl = class_decl
114121
self.access = access
115122
self.typedef = typedef
@@ -120,3 +127,9 @@ def _set_access(self, access: str) -> None:
120127

121128
def _finish(self, visitor: "CxxVisitor") -> None:
122129
visitor.on_class_end(self)
130+
131+
132+
State = typing.Union[
133+
NamespaceBlockState[T, PT], ExternBlockState[T, PT], ClassBlockState[T, PT]
134+
]
135+
NonClassBlockState = typing.Union[ExternBlockState[T, PT], NamespaceBlockState[T, PT]]

cxxheaderparser/simple.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,12 @@ class N::C {
179179
#
180180

181181
# define what user data we store in each state type
182-
SState = State[Block, Block]
183-
SExternBlockState = ExternBlockState[Block, Block]
184-
SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope]
185182
SClassBlockState = ClassBlockState[ClassScope, Block]
183+
SExternBlockState = ExternBlockState[NamespaceScope, NamespaceScope]
184+
SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope]
185+
186+
SState = typing.Union[SClassBlockState, SExternBlockState, SNamespaceBlockState]
187+
SNonClassBlockState = typing.Union[SExternBlockState, SNamespaceBlockState]
186188

187189

188190
class SimpleCxxVisitor:
@@ -242,8 +244,9 @@ def on_namespace_start(self, state: SNamespaceBlockState) -> typing.Optional[boo
242244
def on_namespace_end(self, state: SNamespaceBlockState) -> None:
243245
pass
244246

245-
def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None:
246-
assert isinstance(state.user_data, NamespaceScope)
247+
def on_namespace_alias(
248+
self, state: SNonClassBlockState, alias: NamespaceAlias
249+
) -> None:
247250
state.user_data.ns_alias.append(alias)
248251

249252
def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
@@ -257,19 +260,18 @@ def on_variable(self, state: SState, v: Variable) -> None:
257260
assert isinstance(state.user_data, NamespaceScope)
258261
state.user_data.variables.append(v)
259262

260-
def on_function(self, state: SState, fn: Function) -> None:
261-
assert isinstance(state.user_data, NamespaceScope)
263+
def on_function(self, state: SNonClassBlockState, fn: Function) -> None:
262264
state.user_data.functions.append(fn)
263265

264-
def on_method_impl(self, state: SState, method: Method) -> None:
265-
assert isinstance(state.user_data, NamespaceScope)
266+
def on_method_impl(self, state: SNonClassBlockState, method: Method) -> None:
266267
state.user_data.method_impls.append(method)
267268

268269
def on_typedef(self, state: SState, typedef: Typedef) -> None:
269270
state.user_data.typedefs.append(typedef)
270271

271-
def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None:
272-
assert isinstance(state.user_data, NamespaceScope)
272+
def on_using_namespace(
273+
self, state: SNonClassBlockState, namespace: typing.List[str]
274+
) -> None:
273275
ns = UsingNamespace("::".join(namespace))
274276
state.user_data.using_ns.append(ns)
275277

cxxheaderparser/visitor.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ClassBlockState,
2929
ExternBlockState,
3030
NamespaceBlockState,
31+
NonClassBlockState,
3132
)
3233

3334

@@ -81,7 +82,9 @@ def on_namespace_end(self, state: NamespaceBlockState) -> None:
8182
Called at the end of a ``namespace`` block
8283
"""
8384

84-
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
85+
def on_namespace_alias(
86+
self, state: NonClassBlockState, alias: NamespaceAlias
87+
) -> None:
8588
"""
8689
Called when a ``namespace`` alias is encountered
8790
"""
@@ -101,12 +104,12 @@ def on_variable(self, state: State, v: Variable) -> None:
101104
Called when a global variable is encountered
102105
"""
103106

104-
def on_function(self, state: State, fn: Function) -> None:
107+
def on_function(self, state: NonClassBlockState, fn: Function) -> None:
105108
"""
106109
Called when a function is encountered that isn't part of a class
107110
"""
108111

109-
def on_method_impl(self, state: State, method: Method) -> None:
112+
def on_method_impl(self, state: NonClassBlockState, method: Method) -> None:
110113
"""
111114
Called when a method implementation is encountered outside of a class
112115
declaration. For example:
@@ -134,7 +137,9 @@ def on_typedef(self, state: State, typedef: Typedef) -> None:
134137
once for ``*PT``
135138
"""
136139

137-
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None:
140+
def on_using_namespace(
141+
self, state: NonClassBlockState, namespace: typing.List[str]
142+
) -> None:
138143
"""
139144
.. code-block:: c++
140145
@@ -249,7 +254,9 @@ def on_namespace_start(self, state: NamespaceBlockState) -> typing.Optional[bool
249254
def on_namespace_end(self, state: NamespaceBlockState) -> None:
250255
return None
251256

252-
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
257+
def on_namespace_alias(
258+
self, state: NonClassBlockState, alias: NamespaceAlias
259+
) -> None:
253260
return None
254261

255262
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
@@ -261,16 +268,18 @@ def on_template_inst(self, state: State, inst: TemplateInst) -> None:
261268
def on_variable(self, state: State, v: Variable) -> None:
262269
return None
263270

264-
def on_function(self, state: State, fn: Function) -> None:
271+
def on_function(self, state: NonClassBlockState, fn: Function) -> None:
265272
return None
266273

267-
def on_method_impl(self, state: State, method: Method) -> None:
274+
def on_method_impl(self, state: NonClassBlockState, method: Method) -> None:
268275
return None
269276

270277
def on_typedef(self, state: State, typedef: Typedef) -> None:
271278
return None
272279

273-
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None:
280+
def on_using_namespace(
281+
self, state: NonClassBlockState, namespace: typing.List[str]
282+
) -> None:
274283
return None
275284

276285
def on_using_alias(self, state: State, using: UsingAlias) -> None:

0 commit comments

Comments
 (0)