Skip to content

Commit c0f2f63

Browse files
j-piaseckimeta-codesync[bot]
authored andcommitted
Qualify identifiers in template specializations (#55929)
Summary: Pull Request resolved: #55929 Changelog: [Internal] Adds support for identifier qualification when using template specializations and when inheriting from template objects. Reviewed By: cortinico Differential Revision: D95367957 fbshipit-source-id: 60f7057e3fa1c5fbc117ff03a50de43c2f0dc371
1 parent 9130df6 commit c0f2f63

20 files changed

Lines changed: 436 additions & 21 deletions

File tree

scripts/cxx-api/parser/member.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
parse_type_with_argstrings,
2020
qualify_arguments,
2121
qualify_parsed_type,
22+
qualify_template_args_only,
2223
qualify_type_str,
2324
)
2425

@@ -142,6 +143,10 @@ def member_kind(self) -> MemberKind:
142143
def close(self, scope: Scope):
143144
self._fp_arguments = qualify_arguments(self._fp_arguments, scope)
144145
self._parsed_type = qualify_parsed_type(self._parsed_type, scope)
146+
# Qualify template arguments in variable name for explicit specializations
147+
# e.g., "default_value<MyType>" -> "default_value<ns::MyType>"
148+
if "<" in self.name:
149+
self.name = qualify_template_args_only(self.name, scope)
145150

146151
def _is_function_pointer(self) -> bool:
147152
"""Check if this variable is a function pointer type."""
@@ -237,6 +242,10 @@ def member_kind(self) -> MemberKind:
237242
def close(self, scope: Scope):
238243
self.type = qualify_type_str(self.type, scope)
239244
self.arguments = qualify_arguments(self.arguments, scope)
245+
# Qualify template arguments in function name for explicit specializations
246+
# e.g., "convert<MyType>" -> "convert<ns::MyType>"
247+
if "<" in self.name:
248+
self.name = qualify_template_args_only(self.name, scope)
240249

241250
def to_string(
242251
self,

scripts/cxx-api/parser/scope.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from .member import FriendMember, Member, MemberKind
1515
from .template import Template, TemplateList
16-
from .utils import parse_qualified_path
16+
from .utils import parse_qualified_path, qualify_template_args_only, qualify_type_str
1717

1818

1919
# Pre-create natsort key function for efficiency
@@ -28,6 +28,10 @@ def __init__(self, name) -> None:
2828
def to_string(self, scope: Scope) -> str:
2929
pass
3030

31+
def close(self, scope: Scope) -> None:
32+
"""Called when the scope is closed. Override to perform cleanup."""
33+
pass
34+
3135
def print_scope(self, scope: Scope) -> None:
3236
print(self.to_string(scope))
3337

@@ -72,6 +76,11 @@ def add_template(self, template: Template | [Template]) -> None:
7276
else:
7377
self.template_list.add(template)
7478

79+
def close(self, scope: Scope) -> None:
80+
"""Qualify base class names and their template arguments."""
81+
for base in self.base_classes:
82+
base.name = qualify_type_str(base.name, scope)
83+
7584
def to_string(self, scope: Scope) -> str:
7685
result = ""
7786

@@ -174,6 +183,11 @@ def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> N
174183
else:
175184
self.base_classes.append(base)
176185

186+
def close(self, scope: Scope) -> None:
187+
"""Qualify base class names and their template arguments."""
188+
for base in self.base_classes:
189+
base.name = qualify_type_str(base.name, scope)
190+
177191
def to_string(self, scope: Scope) -> str:
178192
result = ""
179193

@@ -225,6 +239,11 @@ def add_base(
225239
else:
226240
self.base_classes.append(base)
227241

242+
def close(self, scope: Scope) -> None:
243+
"""Qualify base class names and their template arguments."""
244+
for base in self.base_classes:
245+
base.name = qualify_type_str(base.name, scope)
246+
228247
def to_string(self, scope: Scope) -> str:
229248
result = ""
230249

@@ -297,13 +316,17 @@ def __init__(self, kind: ScopeKindT, name: str | None = None) -> None:
297316

298317
def get_qualified_name(self) -> str:
299318
"""
300-
Get the qualified name of the scope.
319+
Get the qualified name of the scope, with template arguments qualified.
301320
"""
302321
path = []
303322
current_scope = self
304323
while current_scope is not None:
305324
if current_scope.name is not None:
306-
path.append(current_scope.name)
325+
# Qualify template arguments in the scope name if it has any
326+
name = current_scope.name
327+
if "<" in name and current_scope.parent_scope is not None:
328+
name = qualify_template_args_only(name, current_scope.parent_scope)
329+
path.append(name)
307330
current_scope = current_scope.parent_scope
308331
path.reverse()
309332
return "::".join(path)
@@ -328,10 +351,27 @@ def qualify_name(self, name: str | None) -> str | None:
328351

329352
current_scope = self
330353
# Walk up to find a scope that contains the first path segment
354+
# Check both inner_scopes AND members (for type aliases, etc.)
331355
base_first = self._get_base_name(path[0])
332-
while (
333-
current_scope is not None and base_first not in current_scope.inner_scopes
334-
):
356+
while current_scope is not None:
357+
# Check if it's an inner scope
358+
if base_first in current_scope.inner_scopes:
359+
break
360+
361+
# Skip self-qualification if name matches current scope's name
362+
if (
363+
current_scope.name
364+
and self._get_base_name(current_scope.name) == base_first
365+
):
366+
current_scope = current_scope.parent_scope
367+
continue
368+
369+
# Check if it's a member (type alias, variable, etc.)
370+
for m in current_scope._members:
371+
if m.name == base_first and not isinstance(m, FriendMember):
372+
prefix = current_scope.get_qualified_name()
373+
return f"{prefix}::{name}" if prefix else name
374+
335375
current_scope = current_scope.parent_scope
336376

337377
if current_scope is None:
@@ -404,6 +444,8 @@ def close(self) -> None:
404444
for member in self.get_members():
405445
member.close(self)
406446

447+
self.kind.close(self)
448+
407449
for _, inner_scope in self.inner_scopes.items():
408450
inner_scope.close()
409451

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
normalize_pointer_spacing,
2222
resolve_linked_text_name,
2323
)
24-
from .type_qualification import qualify_arguments, qualify_parsed_type, qualify_type_str
24+
from .type_qualification import (
25+
qualify_arguments,
26+
qualify_parsed_type,
27+
qualify_template_args_only,
28+
qualify_type_str,
29+
)
2530

2631
__all__ = [
2732
"Argument",
@@ -38,6 +43,7 @@
3843
"parse_type_with_argstrings",
3944
"qualify_arguments",
4045
"qualify_parsed_type",
46+
"qualify_template_args_only",
4147
"qualify_type_str",
4248
"resolve_linked_text_name",
4349
]

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

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,44 @@ def qualify_arguments(arguments: list[Argument], scope: Scope) -> list[Argument]
2828

2929
def qualify_type_str(type_str: str, scope: Scope) -> str:
3030
"""Qualify a type string, handling trailing decorators (*, &, &&, etc.)."""
31+
return _qualify_type_str_impl(type_str, scope, qualify_base=True)
32+
33+
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+
44+
def _qualify_prefix_with_decorators(prefix: str, scope: Scope) -> str:
45+
"""Qualify a template prefix that may have leading const/volatile qualifiers."""
46+
stripped = prefix.lstrip()
47+
decorator_prefix = ""
48+
changed = True
49+
while changed:
50+
changed = False
51+
# Handle leading const/volatile qualifiers (must have trailing space)
52+
for qualifier in ("const ", "volatile "):
53+
if stripped.startswith(qualifier):
54+
decorator_prefix += qualifier
55+
stripped = stripped[len(qualifier) :].lstrip()
56+
changed = True
57+
break
58+
59+
if decorator_prefix and stripped:
60+
qualified_inner = scope.qualify_name(stripped)
61+
if qualified_inner is not None:
62+
return decorator_prefix + qualified_inner
63+
64+
return prefix
65+
66+
67+
def _qualify_type_str_impl(type_str: str, scope: Scope, qualify_base: bool) -> str:
68+
"""Implementation of type string qualification with control over base type handling."""
3169
if not type_str:
3270
return type_str
3371

@@ -44,28 +82,57 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:
4482
template_args = type_str[angle_start + 1 : angle_end]
4583
suffix = type_str[angle_end + 1 :]
4684

47-
# Qualify the prefix (outer type before the template)
48-
qualified_prefix = scope.qualify_name(prefix) or prefix
85+
# Qualify the prefix (outer type before the template) only if requested
86+
# Use recursive qualification to handle leading decorators like "const *Type"
87+
if qualify_base:
88+
# Try simple qualification first
89+
simple_qualified = scope.qualify_name(prefix)
90+
if simple_qualified is not None:
91+
qualified_prefix = simple_qualified
92+
else:
93+
# Handle prefixes with leading decorators (const, *, &, etc.)
94+
qualified_prefix = _qualify_prefix_with_decorators(prefix, scope)
95+
else:
96+
qualified_prefix = prefix
4997

50-
# Split template arguments and qualify each one
98+
# Split template arguments and qualify each one (always qualify args)
5199
args = _split_arguments(template_args)
52-
qualified_args = [qualify_type_str(arg.strip(), scope) for arg in args]
100+
qualified_args = [
101+
_qualify_type_str_impl(arg.strip(), scope, qualify_base=True)
102+
for arg in args
103+
]
53104
qualified_template = "<" + ", ".join(qualified_args) + ">"
54105

55106
# Recursively qualify the suffix (handles nested templates, pointers, etc.)
56-
qualified_suffix = qualify_type_str(suffix, scope) if suffix else ""
107+
qualified_suffix = (
108+
_qualify_type_str_impl(suffix, scope, qualify_base) if suffix else ""
109+
)
57110

58111
return qualified_prefix + qualified_template + qualified_suffix
59112

60-
# Handle leading qualifiers (const, volatile) that prevent qualify_name
61-
# from matching. Strip them, qualify the rest, and prepend back.
62-
for qualifier in ("const ", "volatile "):
63-
if type_str.startswith(qualifier):
64-
inner = type_str[len(qualifier) :]
65-
qualified_inner = qualify_type_str(inner, scope)
66-
if qualified_inner != inner:
67-
return qualifier + qualified_inner
68-
break
113+
# If not qualifying base types, return as-is for non-template types
114+
if not qualify_base:
115+
return type_str
116+
117+
# Handle leading const/volatile qualifiers.
118+
# Strip leading qualifiers, qualify the rest, and prepend back.
119+
stripped = type_str.lstrip()
120+
prefix = ""
121+
changed = True
122+
while changed:
123+
changed = False
124+
# Handle leading const/volatile qualifiers (must have trailing space)
125+
for qualifier in ("const ", "volatile "):
126+
if stripped.startswith(qualifier):
127+
prefix += qualifier
128+
stripped = stripped[len(qualifier) :].lstrip()
129+
changed = True
130+
break
131+
132+
if prefix and stripped:
133+
qualified_inner = _qualify_type_str_impl(stripped, scope, qualify_base=True)
134+
if qualified_inner != stripped:
135+
return prefix + qualified_inner
69136

70137
# Try qualifying the entire string (handles simple cases without templates)
71138
qualified = scope.qualify_name(type_str)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using test::BaseAlias = test::Base;
2+
3+
class test::Base {
4+
public void method();
5+
}
6+
7+
class test::DerivedFromAlias : public test::Base {
8+
public void derivedMethod();
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
class Base {
13+
public:
14+
void method();
15+
};
16+
17+
using BaseAlias = Base;
18+
19+
class DerivedFromAlias : public BaseAlias {
20+
public:
21+
void derivedMethod();
22+
};
23+
24+
} // namespace test
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
template <typename T>
2+
T test::convert(T value);
3+
template <typename T>
4+
void test::process(T* ptr);
5+
test::MyType test::convert<test::MyType>(test::MyType value);
6+
void test::process<test::MyType>(test::MyType* ptr);
7+
8+
struct test::MyType {
9+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
struct MyType {};
13+
14+
template <typename T>
15+
T convert(T value);
16+
17+
template <>
18+
MyType convert<MyType>(MyType value);
19+
20+
template <typename T>
21+
void process(T *ptr);
22+
23+
template <>
24+
void process<MyType>(MyType *ptr);
25+
26+
} // namespace test
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
struct test::Inner {
2+
}
3+
4+
template <typename T>
5+
class test::Container {
6+
public T get();
7+
public void set(T value);
8+
}
9+
10+
template <typename T>
11+
struct test::Outer {
12+
}
13+
14+
class test::Container<const test::Outer<const test::Inner*> *> {
15+
public const test::Outer<const test::Inner*>* get();
16+
public void set(const test::Outer<const test::Inner*>* value);
17+
}

0 commit comments

Comments
 (0)