Skip to content

Commit 8ebde7d

Browse files
coadometa-codesync[bot]
authored andcommitted
Add support for parsing interfaces
Summary: This diff adds support for parsing Objective-C interfaces in the C++ API snapshot parser. Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D94071995
1 parent d693fc6 commit 8ebde7d

File tree

12 files changed

+226
-3
lines changed

12 files changed

+226
-3
lines changed

scripts/cxx-api/manual_test/.doxygen.config.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2442,7 +2442,7 @@ INCLUDE_FILE_PATTERNS =
24422442
# recursively expanded use the := operator instead of the = operator.
24432443
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
24442444

2445-
PREDEFINED =
2445+
PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" ${PREDEFINED}
24462446

24472447
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
24482448
# tag can be used to specify a list of macro names that should be expanded. The

scripts/cxx-api/parser/main.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import os
99
import re
1010
from enum import Enum
11-
from pprint import pprint
1211

1312
from doxmlparser import compound, index
1413

@@ -20,7 +19,7 @@
2019
TypedefMember,
2120
VariableMember,
2221
)
23-
from .scope import ProtocolScopeKind, StructLikeScopeKind
22+
from .scope import InterfaceScopeKind, ProtocolScopeKind, StructLikeScopeKind
2423
from .snapshot import Snapshot
2524
from .template import Template
2625
from .utils import Argument, extract_qualifiers, parse_qualified_path
@@ -516,6 +515,66 @@ def create_protocol_scope(snapshot: Snapshot, scope_def: compound.CompounddefTyp
516515
)
517516

518517

518+
def create_interface_scope(snapshot: Snapshot, scope_def: compound.CompounddefType):
519+
"""
520+
Create an interface scope in the snapshot (Objective-C @interface).
521+
"""
522+
interface_name = scope_def.compoundname
523+
524+
interface_scope = snapshot.create_interface(interface_name)
525+
base_classes = get_base_classes(scope_def, base_class=InterfaceScopeKind.Base)
526+
interface_scope.kind.add_base(base_classes)
527+
interface_scope.location = scope_def.location.file
528+
529+
for section_def in scope_def.sectiondef:
530+
kind = section_def.kind
531+
parts = kind.split("-")
532+
visibility = parts[0]
533+
is_static = "static" in parts
534+
member_type = parts[-1]
535+
536+
if visibility == "private":
537+
pass
538+
elif visibility in ("public", "protected"):
539+
if member_type == "attrib":
540+
for member_def in section_def.memberdef:
541+
if member_def.kind == "variable":
542+
interface_scope.add_member(
543+
get_variable_member(member_def, visibility, is_static)
544+
)
545+
elif member_type == "func":
546+
for function_def in section_def.memberdef:
547+
interface_scope.add_member(
548+
get_function_member(function_def, visibility, is_static)
549+
)
550+
elif member_type == "type":
551+
for member_def in section_def.memberdef:
552+
if member_def.kind == "enum":
553+
create_enum_scope(snapshot, member_def)
554+
elif member_def.kind == "typedef":
555+
interface_scope.add_member(
556+
get_typedef_member(member_def, visibility)
557+
)
558+
else:
559+
print(
560+
f"Unknown section member kind: {member_def.kind} in {scope_def.location.file}"
561+
)
562+
else:
563+
print(
564+
f"Unknown interface section kind: {kind} in {scope_def.location.file}"
565+
)
566+
elif visibility == "property":
567+
for member_def in section_def.memberdef:
568+
if member_def.kind == "property":
569+
interface_scope.add_member(
570+
get_property_member(member_def, "public", is_static)
571+
)
572+
else:
573+
print(
574+
f"Unknown interface visibility: {visibility} in {scope_def.location.file}"
575+
)
576+
577+
519578
def build_snapshot(xml_dir: str) -> Snapshot:
520579
"""
521580
Reads the Doxygen XML output and builds a snapshot of the C++ API.
@@ -536,12 +595,23 @@ def build_snapshot(xml_dir: str) -> Snapshot:
536595
doxygen_object = compound.parse(detail_file, silence=True)
537596

538597
for compound_object in doxygen_object.compounddef:
598+
# Check if this is an Objective-C interface by looking at the compound id
599+
# Doxygen reports ObjC interfaces as kind="class" but with id starting with "interface"
600+
is_objc_interface = (
601+
compound_object.kind == "class"
602+
and compound_object.id.startswith("interface")
603+
)
604+
539605
# classes and structs are represented by the same scope with a different kind
540606
if (
541607
compound_object.kind == "class"
542608
or compound_object.kind == "struct"
543609
or compound_object.kind == "union"
544610
):
611+
# Handle Objective-C interfaces separately
612+
if is_objc_interface:
613+
create_interface_scope(snapshot, compound_object)
614+
continue
545615
class_scope = (
546616
snapshot.create_struct_like(
547617
compound_object.compoundname, StructLikeScopeKind.Type.CLASS
@@ -669,6 +739,8 @@ def build_snapshot(xml_dir: str) -> Snapshot:
669739
pass
670740
elif compound_object.kind == "protocol":
671741
create_protocol_scope(snapshot, compound_object)
742+
elif compound_object.kind == "interface":
743+
create_interface_scope(snapshot, compound_object)
672744
else:
673745
print(f"Unknown compound kind: {compound_object.kind}")
674746

scripts/cxx-api/parser/scope.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,57 @@ def to_string(self, scope: Scope) -> str:
202202
return result
203203

204204

205+
class InterfaceScopeKind(ScopeKind):
206+
class Base:
207+
def __init__(
208+
self, name: str, protection: str, virtual: bool, refid: str
209+
) -> None:
210+
self.name: str = name
211+
self.protection: str = protection
212+
self.virtual: bool = virtual
213+
self.refid: str = refid
214+
215+
def __init__(self) -> None:
216+
super().__init__("interface")
217+
self.base_classes: [InterfaceScopeKind.Base] = []
218+
219+
def add_base(
220+
self, base: InterfaceScopeKind.Base | [InterfaceScopeKind.Base]
221+
) -> None:
222+
if isinstance(base, list):
223+
for b in base:
224+
self.base_classes.append(b)
225+
else:
226+
self.base_classes.append(base)
227+
228+
def to_string(self, scope: Scope) -> str:
229+
result = ""
230+
231+
bases = []
232+
for base in self.base_classes:
233+
base_text = [base.protection]
234+
if base.virtual:
235+
base_text.append("virtual")
236+
base_text.append(base.name)
237+
bases.append(" ".join(base_text))
238+
239+
inheritance_string = " : " + ", ".join(bases) if bases else ""
240+
241+
result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{"
242+
243+
stringified_members = []
244+
for member in scope.get_members():
245+
stringified_members.append(member.to_string(2))
246+
stringified_members = natsorted(stringified_members)
247+
result += ("\n" if len(stringified_members) > 0 else "") + "\n".join(
248+
stringified_members
249+
)
250+
251+
result += "\n}"
252+
253+
return result
254+
255+
205256
class TemporaryScopeKind(ScopeKind):
206257
def __init__(self) -> None:
207258
super().__init__("temporary")

scripts/cxx-api/parser/snapshot.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .scope import (
99
EnumScopeKind,
10+
InterfaceScopeKind,
1011
NamespaceScopeKind,
1112
ProtocolScopeKind,
1213
Scope,
@@ -109,6 +110,30 @@ def create_protocol(self, qualified_name: str) -> Scope[ProtocolScopeKind]:
109110
current_scope.inner_scopes[scope_name] = new_scope
110111
return new_scope
111112

113+
def create_interface(self, qualified_name: str) -> Scope[InterfaceScopeKind]:
114+
"""
115+
Create an interface in the snapshot.
116+
"""
117+
path = parse_qualified_path(qualified_name)
118+
scope_path = path[0:-1]
119+
scope_name = path[-1]
120+
current_scope = self.ensure_scope(scope_path)
121+
122+
if scope_name in current_scope.inner_scopes:
123+
scope = current_scope.inner_scopes[scope_name]
124+
if scope.kind.name == "temporary":
125+
scope.kind = InterfaceScopeKind()
126+
else:
127+
raise RuntimeError(
128+
f"Identifier {scope_name} already exists in scope {current_scope.name}"
129+
)
130+
return scope
131+
else:
132+
new_scope = Scope(InterfaceScopeKind(), scope_name)
133+
new_scope.parent_scope = current_scope
134+
current_scope.inner_scopes[scope_name] = new_scope
135+
return new_scope
136+
112137
def create_enum(self, qualified_name: str) -> Scope[EnumScopeKind]:
113138
"""
114139
Create an enum in the snapshot.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
interface RCTEmptyInterface {
2+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@interface RCTEmptyInterface
9+
10+
@end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface RCTBaseInterface {
2+
public virtual void baseMethod();
3+
}
4+
5+
interface RCTChildInterface : public RCTBaseInterface {
6+
public virtual void childMethod();
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@interface RCTBaseInterface
9+
10+
- (void)baseMethod;
11+
12+
@end
13+
14+
@interface RCTChildInterface : RCTBaseInterface
15+
16+
- (void)childMethod;
17+
18+
@end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
interface RCTInterfaceWithMethod {
2+
public virtual NSString * getTitle();
3+
public virtual void doSomething();
4+
public virtual void processValue:withName:(int value, NSString * name);
5+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@interface RCTInterfaceWithMethod
9+
10+
- (void)doSomething;
11+
- (NSString *)getTitle;
12+
- (void)processValue:(int)value withName:(NSString *)name;
13+
14+
@end

0 commit comments

Comments
 (0)