Skip to content

Commit 4898114

Browse files
j-piaseckimeta-codesync[bot]
authored andcommitted
Extract member specializations from name
Summary: Changelog: [Internal] Updates the C++ API snapshot generator to extract specializations from name when a member is created, instead of relying on the one stored in the name string. It also makes out-of-class definitions of members to be skipped in the snapshot as those were essentially duplicated between their declaration inside the class, and the initialization outside of it. Differential Revision: D96303740
1 parent 7ef15f7 commit 4898114

9 files changed

Lines changed: 79 additions & 33 deletions

File tree

scripts/cxx-api/parser/main.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
get_variable_member,
2626
)
2727
from .snapshot import Snapshot
28-
from .utils import parse_qualified_path
28+
from .utils import has_scope_resolution_outside_angles, parse_qualified_path
2929

3030

3131
def build_snapshot(xml_dir: str) -> Snapshot:
@@ -84,12 +84,22 @@ def build_snapshot(xml_dir: str) -> Snapshot:
8484
for section_def in compound_object.sectiondef:
8585
if section_def.kind == "var":
8686
for variable_def in section_def.memberdef:
87+
# Skip out-of-class definitions (e.g. "Strct<T>::VALUE")
88+
if has_scope_resolution_outside_angles(
89+
variable_def.get_name()
90+
):
91+
continue
8792
is_static = variable_def.static == "yes"
8893
namespace_scope.add_member(
8994
get_variable_member(variable_def, "public", is_static)
9095
)
9196
elif section_def.kind == "func":
9297
for function_def in section_def.memberdef:
98+
# Skip out-of-class definitions (e.g. "Strct<T>::convert")
99+
if has_scope_resolution_outside_angles(
100+
function_def.get_name()
101+
):
102+
continue
93103
function_static = function_def.static == "yes"
94104

95105
if not function_static:

scripts/cxx-api/parser/member/function_member.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
format_arguments,
1313
parse_arg_string,
1414
qualify_arguments,
15-
qualify_template_args_only,
1615
qualify_type_str,
16+
split_specialization,
1717
)
1818
from .base import Member, MemberKind
1919

@@ -34,7 +34,9 @@ def __init__(
3434
doxygen_params: list[Argument] | None = None,
3535
is_constexpr: bool = False,
3636
) -> None:
37-
super().__init__(name, visibility)
37+
base_name, specialization_args = split_specialization(name)
38+
super().__init__(base_name, visibility)
39+
self.specialization_args: list[str] | None = specialization_args
3840
self.type: str = type
3941
self.is_virtual: bool = is_virtual
4042
self.is_static: bool = is_static
@@ -62,10 +64,16 @@ def member_kind(self) -> MemberKind:
6264
def close(self, scope: Scope):
6365
self.type = qualify_type_str(self.type, scope)
6466
self.arguments = qualify_arguments(self.arguments, scope)
65-
# Qualify template arguments in function name for explicit specializations
66-
# e.g., "convert<MyType>" -> "convert<ns::MyType>"
67-
if "<" in self.name:
68-
self.name = qualify_template_args_only(self.name, scope)
67+
if self.specialization_args is not None:
68+
self.specialization_args = [
69+
qualify_type_str(arg, scope) for arg in self.specialization_args
70+
]
71+
72+
def _get_qualified_name(self, qualification: str | None) -> str:
73+
name = self.name
74+
if self.specialization_args is not None:
75+
name = f"{name}<{', '.join(self.specialization_args)}>"
76+
return f"{qualification}::{name}" if qualification else name
6977

7078
def to_string(
7179
self,

scripts/cxx-api/parser/member/variable_member.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
parse_type_with_argstrings,
1616
qualify_arguments,
1717
qualify_parsed_type,
18-
qualify_template_args_only,
18+
qualify_type_str,
19+
split_specialization,
1920
)
2021
from .base import Member, MemberKind, STORE_INITIALIZERS_IN_SNAPSHOT
2122

@@ -38,7 +39,9 @@ def __init__(
3839
argstring: str | None = None,
3940
is_brace_initializer: bool = False,
4041
) -> None:
41-
super().__init__(name, visibility)
42+
base_name, specialization_args = split_specialization(name)
43+
super().__init__(base_name, visibility)
44+
self.specialization_args: list[str] | None = specialization_args
4245
self.type: str = type
4346
self.value: str | None = value
4447
self.is_const: bool = is_const
@@ -62,15 +65,21 @@ def member_kind(self) -> MemberKind:
6265
def close(self, scope: Scope):
6366
self._fp_arguments = qualify_arguments(self._fp_arguments, scope)
6467
self._parsed_type = qualify_parsed_type(self._parsed_type, scope)
65-
# Qualify template arguments in variable name for explicit specializations
66-
# e.g., "default_value<MyType>" -> "default_value<ns::MyType>"
67-
if "<" in self.name:
68-
self.name = qualify_template_args_only(self.name, scope)
68+
if self.specialization_args is not None:
69+
self.specialization_args = [
70+
qualify_type_str(arg, scope) for arg in self.specialization_args
71+
]
6972

7073
def _is_function_pointer(self) -> bool:
7174
"""Check if this variable is a function pointer type."""
7275
return self.argstring is not None and self.argstring.startswith(")(")
7376

77+
def _get_qualified_name(self, qualification: str | None) -> str:
78+
name = self.name
79+
if self.specialization_args is not None:
80+
name = f"{name}<{', '.join(self.specialization_args)}>"
81+
return f"{qualification}::{name}" if qualification else name
82+
7483
def to_string(
7584
self,
7685
indent: int = 0,

scripts/cxx-api/parser/utils/__init__.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
format_arguments,
1010
format_parsed_type,
1111
FunctionModifiers,
12+
has_scope_resolution_outside_angles,
1213
parse_arg_string,
1314
parse_function_pointer_argstring,
1415
parse_type_with_argstrings,
@@ -22,12 +23,7 @@
2223
normalize_pointer_spacing,
2324
resolve_linked_text_name,
2425
)
25-
from .type_qualification import (
26-
qualify_arguments,
27-
qualify_parsed_type,
28-
qualify_template_args_only,
29-
qualify_type_str,
30-
)
26+
from .type_qualification import qualify_arguments, qualify_parsed_type, qualify_type_str
3127

3228
__all__ = [
3329
"Argument",
@@ -36,6 +32,7 @@
3632
"format_arguments",
3733
"format_parsed_type",
3834
"FunctionModifiers",
35+
"has_scope_resolution_outside_angles",
3936
"InitializerType",
4037
"normalize_angle_brackets",
4138
"normalize_pointer_spacing",
@@ -45,7 +42,6 @@
4542
"parse_type_with_argstrings",
4643
"qualify_arguments",
4744
"qualify_parsed_type",
48-
"qualify_template_args_only",
4945
"qualify_type_str",
5046
"resolve_linked_text_name",
5147
"split_specialization",

scripts/cxx-api/parser/utils/argument_parsing.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ def _find_matching_angle(s: str, start: int = 0) -> int:
116116
return _find_matching_bracket(s, start, "<", ">", ignore_inside="(")
117117

118118

119+
def has_scope_resolution_outside_angles(name: str) -> bool:
120+
"""Check if '::' appears outside angle brackets in a name.
121+
122+
Returns True for class-prefixed out-of-class definitions
123+
(e.g. 'Strct< T >::VALUE') but False when '::' only appears inside
124+
template arguments (e.g. 'func<std::string>').
125+
"""
126+
depth = 0
127+
i = 0
128+
while i < len(name):
129+
ch = name[i]
130+
if ch == "<":
131+
depth += 1
132+
elif ch == ">":
133+
depth -= 1
134+
elif ch == ":" and depth == 0 and i + 1 < len(name) and name[i + 1] == ":":
135+
return True
136+
i += 1
137+
return False
138+
139+
119140
def _iter_at_depth_zero(s: str):
120141
"""Iterate over string, yielding (index, char, at_depth_zero) tuples.
121142

scripts/cxx-api/parser/utils/type_qualification.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:
3131
return _qualify_type_str_impl(type_str, scope, qualify_base=True)
3232

3333

34-
def qualify_template_args_only(type_str: str, scope: Scope) -> str:
35-
"""Qualify only template arguments in a type string, leaving the base type unchanged.
36-
37-
This is useful for class names in template specializations where the base type
38-
is already positioned in the correct scope but the template arguments need
39-
qualification (e.g., "MyVector< Test >" -> "MyVector< ns::Test >").
40-
"""
41-
return _qualify_type_str_impl(type_str, scope, qualify_base=False)
42-
43-
4434
def _qualify_prefix_with_decorators(prefix: str, scope: Scope) -> str:
4535
"""Qualify a template prefix that may have leading const/volatile qualifiers."""
4636
stripped = prefix.lstrip()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int test::helloWorld;
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+
#pragma once
9+
10+
namespace test {
11+
12+
extern int helloWorld;
13+
14+
} // namespace test

scripts/cxx-api/tests/snapshots/should_handle_template_variable/snapshot.api

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
template <typename T>
2-
const test::Strct<T> test::Strct<T>::VALUE;
3-
41
template <typename T>
52
struct test::Strct {
63
public static const test::Strct<T> VALUE;

0 commit comments

Comments
 (0)