diff --git a/scripts/cxx-api/parser/member.py b/scripts/cxx-api/parser/member.py index 2069aa80a84e..3fd415f2d229 100644 --- a/scripts/cxx-api/parser/member.py +++ b/scripts/cxx-api/parser/member.py @@ -19,6 +19,7 @@ parse_type_with_argstrings, qualify_arguments, qualify_parsed_type, + qualify_template_args_only, qualify_type_str, ) @@ -142,6 +143,10 @@ def member_kind(self) -> MemberKind: def close(self, scope: Scope): self._fp_arguments = qualify_arguments(self._fp_arguments, scope) self._parsed_type = qualify_parsed_type(self._parsed_type, scope) + # Qualify template arguments in variable name for explicit specializations + # e.g., "default_value" -> "default_value" + if "<" in self.name: + self.name = qualify_template_args_only(self.name, scope) def _is_function_pointer(self) -> bool: """Check if this variable is a function pointer type.""" @@ -237,6 +242,10 @@ def member_kind(self) -> MemberKind: def close(self, scope: Scope): self.type = qualify_type_str(self.type, scope) self.arguments = qualify_arguments(self.arguments, scope) + # Qualify template arguments in function name for explicit specializations + # e.g., "convert" -> "convert" + if "<" in self.name: + self.name = qualify_template_args_only(self.name, scope) def to_string( self, diff --git a/scripts/cxx-api/parser/scope.py b/scripts/cxx-api/parser/scope.py index d637fde2d4d2..6b953eac9106 100644 --- a/scripts/cxx-api/parser/scope.py +++ b/scripts/cxx-api/parser/scope.py @@ -13,7 +13,7 @@ from .member import FriendMember, Member, MemberKind from .template import Template, TemplateList -from .utils import parse_qualified_path +from .utils import parse_qualified_path, qualify_template_args_only, qualify_type_str # Pre-create natsort key function for efficiency @@ -28,6 +28,10 @@ def __init__(self, name) -> None: def to_string(self, scope: Scope) -> str: pass + def close(self, scope: Scope) -> None: + """Called when the scope is closed. Override to perform cleanup.""" + pass + def print_scope(self, scope: Scope) -> None: print(self.to_string(scope)) @@ -72,6 +76,11 @@ def add_template(self, template: Template | [Template]) -> None: else: self.template_list.add(template) + def close(self, scope: Scope) -> None: + """Qualify base class names and their template arguments.""" + for base in self.base_classes: + base.name = qualify_type_str(base.name, scope) + def to_string(self, scope: Scope) -> str: result = "" @@ -174,6 +183,11 @@ def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> N else: self.base_classes.append(base) + def close(self, scope: Scope) -> None: + """Qualify base class names and their template arguments.""" + for base in self.base_classes: + base.name = qualify_type_str(base.name, scope) + def to_string(self, scope: Scope) -> str: result = "" @@ -225,6 +239,11 @@ def add_base( else: self.base_classes.append(base) + def close(self, scope: Scope) -> None: + """Qualify base class names and their template arguments.""" + for base in self.base_classes: + base.name = qualify_type_str(base.name, scope) + def to_string(self, scope: Scope) -> str: result = "" @@ -297,13 +316,17 @@ def __init__(self, kind: ScopeKindT, name: str | None = None) -> None: def get_qualified_name(self) -> str: """ - Get the qualified name of the scope. + Get the qualified name of the scope, with template arguments qualified. """ path = [] current_scope = self while current_scope is not None: if current_scope.name is not None: - path.append(current_scope.name) + # Qualify template arguments in the scope name if it has any + name = current_scope.name + if "<" in name and current_scope.parent_scope is not None: + name = qualify_template_args_only(name, current_scope.parent_scope) + path.append(name) current_scope = current_scope.parent_scope path.reverse() return "::".join(path) @@ -328,10 +351,27 @@ def qualify_name(self, name: str | None) -> str | None: current_scope = self # Walk up to find a scope that contains the first path segment + # Check both inner_scopes AND members (for type aliases, etc.) base_first = self._get_base_name(path[0]) - while ( - current_scope is not None and base_first not in current_scope.inner_scopes - ): + while current_scope is not None: + # Check if it's an inner scope + if base_first in current_scope.inner_scopes: + break + + # Skip self-qualification if name matches current scope's name + if ( + current_scope.name + and self._get_base_name(current_scope.name) == base_first + ): + current_scope = current_scope.parent_scope + continue + + # Check if it's a member (type alias, variable, etc.) + for m in current_scope._members: + if m.name == base_first and not isinstance(m, FriendMember): + prefix = current_scope.get_qualified_name() + return f"{prefix}::{name}" if prefix else name + current_scope = current_scope.parent_scope if current_scope is None: @@ -404,6 +444,8 @@ def close(self) -> None: for member in self.get_members(): member.close(self) + self.kind.close(self) + for _, inner_scope in self.inner_scopes.items(): inner_scope.close() diff --git a/scripts/cxx-api/parser/utils/__init__.py b/scripts/cxx-api/parser/utils/__init__.py index 248136e621f9..2a9b7536adf6 100644 --- a/scripts/cxx-api/parser/utils/__init__.py +++ b/scripts/cxx-api/parser/utils/__init__.py @@ -21,7 +21,12 @@ normalize_pointer_spacing, resolve_linked_text_name, ) -from .type_qualification import qualify_arguments, qualify_parsed_type, qualify_type_str +from .type_qualification import ( + qualify_arguments, + qualify_parsed_type, + qualify_template_args_only, + qualify_type_str, +) __all__ = [ "Argument", @@ -38,6 +43,7 @@ "parse_type_with_argstrings", "qualify_arguments", "qualify_parsed_type", + "qualify_template_args_only", "qualify_type_str", "resolve_linked_text_name", ] diff --git a/scripts/cxx-api/parser/utils/argument_parsing.py b/scripts/cxx-api/parser/utils/argument_parsing.py index 2e0d0bb47851..5a540d3974b2 100644 --- a/scripts/cxx-api/parser/utils/argument_parsing.py +++ b/scripts/cxx-api/parser/utils/argument_parsing.py @@ -297,6 +297,13 @@ def _parse_single_argument(arg: str) -> Argument: ) ): return (qualifiers, base_arg, None, default_value) + # C++ parameter names cannot contain "::". Doxygen may + # incorrectly cross-reference a parameter name to a member + # variable, producing a qualified path (e.g. "bool + # ns::Class::isActive" instead of "bool isActive"). Strip + # the namespace prefix to recover the original name. + if "::" in potential_name: + potential_name = potential_name.rsplit("::", 1)[-1] return (qualifiers, prefix, potential_name, default_value) else: return (qualifiers, base_arg, None, default_value) diff --git a/scripts/cxx-api/parser/utils/type_qualification.py b/scripts/cxx-api/parser/utils/type_qualification.py index d3b1595c9613..86cbc309f99c 100644 --- a/scripts/cxx-api/parser/utils/type_qualification.py +++ b/scripts/cxx-api/parser/utils/type_qualification.py @@ -28,6 +28,44 @@ def qualify_arguments(arguments: list[Argument], scope: Scope) -> list[Argument] def qualify_type_str(type_str: str, scope: Scope) -> str: """Qualify a type string, handling trailing decorators (*, &, &&, etc.).""" + return _qualify_type_str_impl(type_str, scope, qualify_base=True) + + +def qualify_template_args_only(type_str: str, scope: Scope) -> str: + """Qualify only template arguments in a type string, leaving the base type unchanged. + + This is useful for class names in template specializations where the base type + is already positioned in the correct scope but the template arguments need + qualification (e.g., "MyVector< Test >" -> "MyVector< ns::Test >"). + """ + return _qualify_type_str_impl(type_str, scope, qualify_base=False) + + +def _qualify_prefix_with_decorators(prefix: str, scope: Scope) -> str: + """Qualify a template prefix that may have leading const/volatile qualifiers.""" + stripped = prefix.lstrip() + decorator_prefix = "" + changed = True + while changed: + changed = False + # Handle leading const/volatile qualifiers (must have trailing space) + for qualifier in ("const ", "volatile "): + if stripped.startswith(qualifier): + decorator_prefix += qualifier + stripped = stripped[len(qualifier) :].lstrip() + changed = True + break + + if decorator_prefix and stripped: + qualified_inner = scope.qualify_name(stripped) + if qualified_inner is not None: + return decorator_prefix + qualified_inner + + return prefix + + +def _qualify_type_str_impl(type_str: str, scope: Scope, qualify_base: bool) -> str: + """Implementation of type string qualification with control over base type handling.""" if not type_str: return type_str @@ -44,28 +82,57 @@ def qualify_type_str(type_str: str, scope: Scope) -> str: template_args = type_str[angle_start + 1 : angle_end] suffix = type_str[angle_end + 1 :] - # Qualify the prefix (outer type before the template) - qualified_prefix = scope.qualify_name(prefix) or prefix + # Qualify the prefix (outer type before the template) only if requested + # Use recursive qualification to handle leading decorators like "const *Type" + if qualify_base: + # Try simple qualification first + simple_qualified = scope.qualify_name(prefix) + if simple_qualified is not None: + qualified_prefix = simple_qualified + else: + # Handle prefixes with leading decorators (const, *, &, etc.) + qualified_prefix = _qualify_prefix_with_decorators(prefix, scope) + else: + qualified_prefix = prefix - # Split template arguments and qualify each one + # Split template arguments and qualify each one (always qualify args) args = _split_arguments(template_args) - qualified_args = [qualify_type_str(arg.strip(), scope) for arg in args] + qualified_args = [ + _qualify_type_str_impl(arg.strip(), scope, qualify_base=True) + for arg in args + ] qualified_template = "<" + ", ".join(qualified_args) + ">" # Recursively qualify the suffix (handles nested templates, pointers, etc.) - qualified_suffix = qualify_type_str(suffix, scope) if suffix else "" + qualified_suffix = ( + _qualify_type_str_impl(suffix, scope, qualify_base) if suffix else "" + ) return qualified_prefix + qualified_template + qualified_suffix - # Handle leading qualifiers (const, volatile) that prevent qualify_name - # from matching. Strip them, qualify the rest, and prepend back. - for qualifier in ("const ", "volatile "): - if type_str.startswith(qualifier): - inner = type_str[len(qualifier) :] - qualified_inner = qualify_type_str(inner, scope) - if qualified_inner != inner: - return qualifier + qualified_inner - break + # If not qualifying base types, return as-is for non-template types + if not qualify_base: + return type_str + + # Handle leading const/volatile qualifiers. + # Strip leading qualifiers, qualify the rest, and prepend back. + stripped = type_str.lstrip() + prefix = "" + changed = True + while changed: + changed = False + # Handle leading const/volatile qualifiers (must have trailing space) + for qualifier in ("const ", "volatile "): + if stripped.startswith(qualifier): + prefix += qualifier + stripped = stripped[len(qualifier) :].lstrip() + changed = True + break + + if prefix and stripped: + qualified_inner = _qualify_type_str_impl(stripped, scope, qualify_base=True) + if qualified_inner != stripped: + return prefix + qualified_inner # Try qualifying the entire string (handles simple cases without templates) qualified = scope.qualify_name(type_str) diff --git a/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/snapshot.api b/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/snapshot.api new file mode 100644 index 000000000000..18ab5a83ae63 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/snapshot.api @@ -0,0 +1,5 @@ +class test::Tracer { + public bool isActive; + public uint32_t subscribe(test::Tracer::StateCallback callback); + public using StateCallback = void(*)(bool isActive); +} diff --git a/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/test.h b/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/test.h new file mode 100644 index 000000000000..c4d402c2bc57 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_qualify_parameter_names_in_function_types/test.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +class Tracer { + public: + bool isActive; + using StateCallback = void (*)(bool isActive); + uint32_t subscribe(StateCallback callback); +}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/snapshot.api new file mode 100644 index 000000000000..30da1897b686 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/snapshot.api @@ -0,0 +1,9 @@ +using test::BaseAlias = test::Base; + +class test::Base { + public void method(); +} + +class test::DerivedFromAlias : public test::Base { + public void derivedMethod(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/test.h new file mode 100644 index 000000000000..40e5defba3db --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_base_class_type_alias/test.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +class Base { + public: + void method(); +}; + +using BaseAlias = Base; + +class DerivedFromAlias : public BaseAlias { + public: + void derivedMethod(); +}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/snapshot.api new file mode 100644 index 000000000000..50f60f16d49c --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/snapshot.api @@ -0,0 +1,9 @@ +template +T test::convert(T value); +template +void test::process(T* ptr); +test::MyType test::convert(test::MyType value); +void test::process(test::MyType* ptr); + +struct test::MyType { +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/test.h new file mode 100644 index 000000000000..d2c2a63bc3da --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_function_template_specialization/test.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +struct MyType {}; + +template +T convert(T value); + +template <> +MyType convert(MyType value); + +template +void process(T *ptr); + +template <> +void process(MyType *ptr); + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/snapshot.api new file mode 100644 index 000000000000..88826b278ab1 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/snapshot.api @@ -0,0 +1,17 @@ +struct test::Inner { +} + +template +class test::Container { + public T get(); + public void set(T value); +} + +template +struct test::Outer { +} + +class test::Container *> { + public const test::Outer* get(); + public void set(const test::Outer* value); +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/test.h new file mode 100644 index 000000000000..5207aa305b18 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_with_decorators/test.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +struct Inner {}; + +template +struct Outer {}; + +template +class Container { + public: + void set(T value); + T get(); +}; + +template <> +class Container *> { + public: + void set(const Outer *value); + const Outer *get(); +}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/snapshot.api new file mode 100644 index 000000000000..1ad3aabb0afa --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/snapshot.api @@ -0,0 +1,13 @@ +struct test::MyType { +} + +template +class test::Container { + public void pop(); + public void push(T value); +} + +class test::Container { + public void pop(); + public void push(test::MyType value); +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/test.h new file mode 100644 index 000000000000..d49537abe838 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_args/test.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +struct MyType {}; + +template +class Container { + public: + void push(T value); + void pop(); +}; + +template <> +class Container { + public: + void push(MyType value); + void pop(); +}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/snapshot.api new file mode 100644 index 000000000000..c83c2f01f45e --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/snapshot.api @@ -0,0 +1,13 @@ +struct test::MyType { +} + +template +class test::Wrapper { + public T get(); + public void set(T value); +} + +class test::Wrapper { + public test::MyType* get(); + public void set(test::MyType* value); +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/test.h new file mode 100644 index 000000000000..bfef20f5123f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_template_specialization_pointer_args/test.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +struct MyType {}; + +template +class Wrapper { + public: + void set(T value); + T get(); +}; + +template <> +class Wrapper { + public: + void set(MyType *value); + MyType *get(); +}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/snapshot.api new file mode 100644 index 000000000000..cd394b8df01f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/snapshot.api @@ -0,0 +1,8 @@ +using test::MyType = int; + +struct test::Component : public test::Wrapper { +} + +template +struct test::Wrapper { +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/test.h new file mode 100644 index 000000000000..b3b265200a91 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_base_class_template/test.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +using MyType = int; + +template +struct Wrapper {}; + +struct Component : public Wrapper {}; + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/snapshot.api new file mode 100644 index 000000000000..386a63644a1f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/snapshot.api @@ -0,0 +1,7 @@ +using test::MyType = int; +template +T test::convert(T value); +template +void test::process(T* ptr); +test::MyType test::convert(test::MyType value); +void test::process(test::MyType* ptr); diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/test.h new file mode 100644 index 000000000000..6438e7c3960f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_type_alias_in_template_specialization/test.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +using MyType = int; + +template +T convert(T value); + +template <> +MyType convert(MyType value); + +template +void process(T *ptr); + +template <> +void process(MyType *ptr); + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/snapshot.api new file mode 100644 index 000000000000..2d598603d29e --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/snapshot.api @@ -0,0 +1,7 @@ +constexpr T test::default_value; +constexpr test::MyType test::default_value; +T* test::null_ptr; +test::MyType* test::null_ptr; + +struct test::MyType { +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/test.h new file mode 100644 index 000000000000..54e6183e060d --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_template_specialization/test.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +struct MyType {}; + +template +inline constexpr T default_value = T{}; + +template <> +inline constexpr MyType default_value = MyType{}; + +template +inline T *null_ptr = nullptr; + +template <> +inline MyType *null_ptr = nullptr; + +} // namespace test diff --git a/scripts/cxx-api/tests/test_parse_arg_string.py b/scripts/cxx-api/tests/test_parse_arg_string.py index ae99fb034ab0..f24f61bef121 100644 --- a/scripts/cxx-api/tests/test_parse_arg_string.py +++ b/scripts/cxx-api/tests/test_parse_arg_string.py @@ -374,6 +374,33 @@ def test_full_complex_signature(self): self.assertTrue(mods.is_noexcept) self.assertTrue(mods.is_override) + # ========================================================================= + # Qualified parameter names (Doxygen cross-reference cleanup) + # ========================================================================= + + def test_qualified_parameter_name_stripped(self): + """Parameter names containing '::' should be stripped to base name. + + Doxygen may incorrectly cross-reference parameter names to member + variables, producing qualified paths like 'ns::Class::paramName'. + C++ parameter names cannot contain '::', so the qualification must + be stripped to just the base name. + """ + args, mods = parse_arg_string( + "(bool facebook::react::PerformanceTracer::isTracing)" + ) + self.assertEqual(args, [(None, "bool", "isTracing", None)]) + + def test_qualified_parameter_name_simple(self): + """Simple qualified parameter name: test::Foo::value""" + args, mods = parse_arg_string("(int test::Foo::value)") + self.assertEqual(args, [(None, "int", "value", None)]) + + def test_unqualified_parameter_name_unchanged(self): + """Regular parameter names without '::' should not be affected.""" + args, mods = parse_arg_string("(bool isActive)") + self.assertEqual(args, [(None, "bool", "isActive", None)]) + if __name__ == "__main__": unittest.main()