From a151674c11014ce564eaf7584995ccf420ab8c45 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 1/7] Handle template variables in the API snapshot Summary: Changelog: [Internal] Adds support for templates in variable declaration to the c++ API snapshot generator Differential Revision: D96279463 --- scripts/cxx-api/parser/builders.py | 6 +++++- scripts/cxx-api/parser/member.py | 3 +++ .../snapshot.api | 7 +++++++ .../should_handle_template_variable/test.h | 20 +++++++++++++++++++ .../snapshot.api | 4 +++- 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_template_variable/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_template_variable/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index 9beb9a93880d..1122433b9c14 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -249,7 +249,7 @@ def get_variable_member( if initializer_type == InitializerType.BRACE: is_brace_initializer = True - return VariableMember( + member = VariableMember( variable_name, variable_type, visibility, @@ -263,6 +263,10 @@ def get_variable_member( is_brace_initializer, ) + member.add_template(get_template_params(member_def)) + + return member + def get_doxygen_params( function_def: compound.MemberdefType, diff --git a/scripts/cxx-api/parser/member.py b/scripts/cxx-api/parser/member.py index 7681b42dc1df..7c8290dad50e 100644 --- a/scripts/cxx-api/parser/member.py +++ b/scripts/cxx-api/parser/member.py @@ -162,6 +162,9 @@ def to_string( result = " " * indent + if self.template_list is not None: + result += self.template_list.to_string() + "\n" + " " * indent + if not hide_visibility: result += self.visibility + " " diff --git a/scripts/cxx-api/tests/snapshots/should_handle_template_variable/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_template_variable/snapshot.api new file mode 100644 index 000000000000..52711af8da35 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_template_variable/snapshot.api @@ -0,0 +1,7 @@ +template +const test::Strct test::Strct::VALUE; + +template +struct test::Strct { + public static const test::Strct VALUE; +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_template_variable/test.h b/scripts/cxx-api/tests/snapshots/should_handle_template_variable/test.h new file mode 100644 index 000000000000..c1caf34d39df --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_template_variable/test.h @@ -0,0 +1,20 @@ +/* + * 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 { + +template +struct Strct { + static const Strct VALUE; +}; + +template +const Strct Strct::VALUE = {}; + +} // 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 index 2d598603d29e..6248acdb5c77 100644 --- 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 @@ -1,5 +1,7 @@ -constexpr T test::default_value; constexpr test::MyType test::default_value; +template +constexpr T test::default_value; +template T* test::null_ptr; test::MyType* test::null_ptr; From a35bcecf67f6011a1073954a75ce85405e0a4b34 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 2/7] Fix handling of member function pointers in the API snapshot Summary: Changelog: [Internal] Fixes handling of member function pointers in the C++ Api snapshot. Differential Revision: D96279461 --- scripts/cxx-api/parser/builders.py | 15 +++++++++++++ .../snapshot.api | 6 ++++++ .../test.h | 21 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index 1122433b9c14..f72c62f45e2b 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -317,6 +317,21 @@ def get_doxygen_params( else: param_type += param_array + # Handle pointer-to-member-function types where the name must be + # embedded inside the declarator group. Doxygen gives: + # type = "void(ns::*)() const", name = "asFoo" + # We need to produce: + # "void(ns::*asFoo)() const" + if param_name: + m = re.search(r"\([^)]*::\*\)", param_type) + if m: + # Insert name before the closing ')' of the ptr-to-member group + insert_pos = m.end() - 1 + param_type = ( + param_type[:insert_pos] + param_name + param_type[insert_pos:] + ) + param_name = None + qualifiers, core_type = extract_qualifiers(param_type) arguments.append((qualifiers, core_type, param_name, param_default)) diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/snapshot.api new file mode 100644 index 000000000000..3df3cf570cee --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/snapshot.api @@ -0,0 +1,6 @@ +struct folly::dynamic { +} + + +template +R test::jsArg(const folly::dynamic& arg, R(folly::dynamic::*asFoo)() const, const T &... desc); diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/test.h b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/test.h new file mode 100644 index 000000000000..8aaea82b5a17 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param/test.h @@ -0,0 +1,21 @@ +/* + * 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 folly { + +struct dynamic {}; + +} // namespace folly + +namespace test { + +template +R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const, const T &...desc); + +} // namespace test From 51834febc4feea34f1cfb60108e2e2189b23d2d0 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 3/7] Fix qualification of enum values in template specialization args Summary: Changelog: [Internal] Fixes qualification of unscoped enum values used as template specialization arguments. Differential Revision: D96279462 --- scripts/cxx-api/parser/scope.py | 7 +++- .../snapshot.api | 19 +++++++++++ .../test.h | 32 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/test.h diff --git a/scripts/cxx-api/parser/scope.py b/scripts/cxx-api/parser/scope.py index b4cc776cd055..615af32a1967 100644 --- a/scripts/cxx-api/parser/scope.py +++ b/scripts/cxx-api/parser/scope.py @@ -396,8 +396,13 @@ def qualify_name(self, name: str | None) -> str | None: elif any( m.name == base_name and not isinstance(m, FriendMember) for m in current_scope._members + ) or any( + any(m.name == base_name for m in inner._members) + for inner in current_scope.inner_scopes.values() + if isinstance(inner.kind, EnumScopeKind) ): - # Found as a member, assume following segments exist in the scope + # Found as a member (or as an unscoped enum value accessible + # from the parent scope), assume following segments exist prefix = "::".join(matched_segments) suffix = "::".join(path[i:]) anchor_prefix = anchor_scope.get_qualified_name() diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/snapshot.api new file mode 100644 index 000000000000..690ad506546f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/snapshot.api @@ -0,0 +1,19 @@ +struct test::Event { +} + +enum test::Event::Type { + NodeAllocation, + NodeDeallocation, +} + +template +struct test::Event::TypedData { +} + +struct test::Event::TypedData { + public int config; +} + +struct test::Event::TypedData { + public int config; +} diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/test.h new file mode 100644 index 000000000000..b1c42974c792 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_qualify_enum_value_in_template_specialization/test.h @@ -0,0 +1,32 @@ +/* + * 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 Event { + enum Type { + NodeAllocation, + NodeDeallocation, + }; + + template + struct TypedData {}; +}; + +template <> +struct Event::TypedData { + int config; +}; + +template <> +struct Event::TypedData { + int config; +}; + +} // namespace test From 16ffd31f3ac006efb71d1c2ab0ad890cb0cf658d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 4/7] Add option to generate a single snapshot view from CLI Summary: Changelog: [Internal] Adds `--view` argument to the snapshot generator, which allows to generate a single snapshot view instead of all of them. Differential Revision: D96280524 --- scripts/cxx-api/parser/__main__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/cxx-api/parser/__main__.py b/scripts/cxx-api/parser/__main__.py index 4e4e88089d38..bdaac3147bc0 100644 --- a/scripts/cxx-api/parser/__main__.py +++ b/scripts/cxx-api/parser/__main__.py @@ -198,6 +198,11 @@ def main(): type=str, help="Directory containing committed snapshots for comparison (used with --check)", ) + parser.add_argument( + "--view", + type=str, + help="Name of the API view to generate", + ) parser.add_argument( "--test", action="store_true", @@ -250,6 +255,9 @@ def main(): def build_snapshots(output_dir: str, verbose: bool) -> None: if not args.test: for config in snapshot_configs: + if args.view and config.snapshot_name != args.view: + continue + build_snapshot_for_view( api_view=config.snapshot_name, react_native_dir=react_native_package_dir, From 2affee4077100190b3c556442f09f5b85cb41b4c Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 5/7] Split member definitions into smaller files Summary: Changelog: [Internal] Splits `member.py` into multiple smaller files, each containing single member definition. Differential Revision: D96282653 --- scripts/cxx-api/parser/member.py | 549 ------------------ scripts/cxx-api/parser/member/__init__.py | 26 + scripts/cxx-api/parser/member/base.py | 69 +++ .../cxx-api/parser/member/concept_member.py | 102 ++++ scripts/cxx-api/parser/member/enum_member.py | 31 + .../cxx-api/parser/member/friend_member.py | 30 + .../cxx-api/parser/member/function_member.py | 124 ++++ .../cxx-api/parser/member/property_member.py | 62 ++ .../cxx-api/parser/member/typedef_member.py | 99 ++++ .../cxx-api/parser/member/variable_member.py | 123 ++++ 10 files changed, 666 insertions(+), 549 deletions(-) delete mode 100644 scripts/cxx-api/parser/member.py create mode 100644 scripts/cxx-api/parser/member/__init__.py create mode 100644 scripts/cxx-api/parser/member/base.py create mode 100644 scripts/cxx-api/parser/member/concept_member.py create mode 100644 scripts/cxx-api/parser/member/enum_member.py create mode 100644 scripts/cxx-api/parser/member/friend_member.py create mode 100644 scripts/cxx-api/parser/member/function_member.py create mode 100644 scripts/cxx-api/parser/member/property_member.py create mode 100644 scripts/cxx-api/parser/member/typedef_member.py create mode 100644 scripts/cxx-api/parser/member/variable_member.py diff --git a/scripts/cxx-api/parser/member.py b/scripts/cxx-api/parser/member.py deleted file mode 100644 index 7c8290dad50e..000000000000 --- a/scripts/cxx-api/parser/member.py +++ /dev/null @@ -1,549 +0,0 @@ -# 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. - -from __future__ import annotations - -from abc import ABC, abstractmethod -from enum import IntEnum -from typing import TYPE_CHECKING - -from .template import Template, TemplateList -from .utils import ( - Argument, - format_arguments, - format_parsed_type, - parse_arg_string, - parse_function_pointer_argstring, - parse_type_with_argstrings, - qualify_arguments, - qualify_parsed_type, - qualify_template_args_only, - qualify_type_str, -) - -if TYPE_CHECKING: - from .scope import Scope - -STORE_INITIALIZERS_IN_SNAPSHOT = False - - -class MemberKind(IntEnum): - """ - Classification of member kinds for grouping in output. - The order here determines the output order within namespace scopes. - """ - - CONSTANT = 0 - TYPE_ALIAS = 1 - CONCEPT = 2 - FUNCTION = 3 - OPERATOR = 4 - VARIABLE = 5 - FRIEND = 6 - - -class Member(ABC): - def __init__(self, name: str, visibility: str) -> None: - self.name: str = name - self.visibility: str = visibility - self.template_list: TemplateList | None = None - - @property - @abstractmethod - def member_kind(self) -> MemberKind: - pass - - @abstractmethod - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - pass - - def close(self, scope: Scope): - pass - - def _get_qualified_name(self, qualification: str | None): - return f"{qualification}::{self.name}" if qualification else self.name - - def add_template(self, template: Template | [Template]) -> None: - if template and self.template_list is None: - self.template_list = TemplateList() - - if isinstance(template, list): - for t in template: - self.template_list.add(t) - else: - self.template_list.add(template) - - -class EnumMember(Member): - def __init__(self, name: str, value: str | None) -> None: - super().__init__(name, "public") - self.value: str | None = value - - @property - def member_kind(self) -> MemberKind: - return MemberKind.CONSTANT - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - - if not STORE_INITIALIZERS_IN_SNAPSHOT or self.value is None: - return " " * indent + f"{name}" - - return " " * indent + f"{name} = {self.value}" - - -class VariableMember(Member): - def __init__( - self, - name: str, - type: str, - visibility: str, - is_const: bool, - is_static: bool, - is_constexpr: bool, - is_mutable: bool, - value: str | None, - definition: str, - argstring: str | None = None, - is_brace_initializer: bool = False, - ) -> None: - super().__init__(name, visibility) - self.type: str = type - self.value: str | None = value - self.is_const: bool = is_const - self.is_static: bool = is_static - self.is_constexpr: bool = is_constexpr - self.is_mutable: bool = is_mutable - self.is_brace_initializer: bool = is_brace_initializer - self.definition: str = definition - self.argstring: str | None = argstring - self._fp_arguments: list[Argument] = ( - parse_function_pointer_argstring(argstring) if argstring else [] - ) - self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type) - - @property - def member_kind(self) -> MemberKind: - if self.is_const or self.is_constexpr: - return MemberKind.CONSTANT - return MemberKind.VARIABLE - - 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.""" - return self.argstring is not None and self.argstring.startswith(")(") - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - - result = " " * indent - - if self.template_list is not None: - result += self.template_list.to_string() + "\n" + " " * indent - - if not hide_visibility: - result += self.visibility + " " - - if self.is_static: - result += "static " - - if self.is_constexpr: - result += "constexpr " - - if self.is_mutable: - result += "mutable " - - if self.is_const and not self.is_constexpr: - result += "const " - - if self._is_function_pointer(): - formatted_args = format_arguments(self._fp_arguments) - qualified_type = format_parsed_type(self._parsed_type) - # Function pointer types: argstring is ")(args...)" - # If type already contains "(*", e.g. "void *(*" or "void(*", use directly - # Otherwise add "(*" to form proper function pointer syntax - if "(*" in qualified_type: - result += f"{qualified_type}{name})({formatted_args})" - else: - result += f"{qualified_type} (*{name})({formatted_args})" - else: - result += f"{format_parsed_type(self._parsed_type)} {name}" - - if STORE_INITIALIZERS_IN_SNAPSHOT and self.value is not None: - if self.is_brace_initializer: - result += f"{{{self.value}}}" - else: - result += f" = {self.value}" - - result += ";" - - return result - - -class FunctionMember(Member): - def __init__( - self, - name: str, - type: str, - visibility: str, - arg_string: str, - is_virtual: bool, - is_pure_virtual: bool, - is_static: bool, - doxygen_params: list[Argument] | None = None, - is_constexpr: bool = False, - ) -> None: - super().__init__(name, visibility) - self.type: str = type - self.is_virtual: bool = is_virtual - self.is_static: bool = is_static - self.is_constexpr: bool = is_constexpr - parsed_arguments, self.modifiers = parse_arg_string(arg_string) - self.arguments = ( - doxygen_params if doxygen_params is not None else parsed_arguments - ) - - # Doxygen signals pure-virtual via the virt attribute, but the arg string - # may not contain "= 0" (e.g. trailing return type syntax), so the - # modifiers parsed from the arg string may miss it. Propagate the flag. - if is_pure_virtual: - self.modifiers.is_pure_virtual = True - - self.is_const = self.modifiers.is_const - self.is_override = self.modifiers.is_override - - @property - def member_kind(self) -> MemberKind: - if self.name.startswith("operator"): - return MemberKind.OPERATOR - return MemberKind.FUNCTION - - 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, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - result = "" - - if self.template_list is not None: - result += " " * indent + self.template_list.to_string() + "\n" - - result += " " * indent - - if not hide_visibility: - result += self.visibility + " " - - if self.is_virtual: - result += "virtual " - - if self.is_static: - result += "static " - - if self.is_constexpr: - result += "constexpr " - - if self.type: - result += f"{self.type} " - - result += f"{name}({format_arguments(self.arguments)})" - - if self.modifiers.is_const: - result += " const" - - if self.modifiers.is_noexcept: - if self.modifiers.noexcept_expr: - result += f" noexcept({self.modifiers.noexcept_expr})" - else: - result += " noexcept" - - if self.modifiers.is_override: - result += " override" - - if self.modifiers.is_final: - result += " final" - - if self.modifiers.is_pure_virtual: - result += " = 0" - elif self.modifiers.is_default: - result += " = default" - elif self.modifiers.is_delete: - result += " = delete" - - result += ";" - return result - - -class TypedefMember(Member): - def __init__( - self, name: str, type: str, argstring: str | None, visibility: str, keyword: str - ) -> None: - super().__init__(name, visibility) - self.keyword: str = keyword - self.argstring: str | None = argstring - - # Parse function pointer argstrings (e.g. ")(int x, float y)") - self._fp_arguments: list[Argument] = ( - parse_function_pointer_argstring(argstring) if argstring else [] - ) - - # Parse inline function signatures in the type so that argument - # lists are stored as structured data, not raw strings. - self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type) - self.type: str = type - - @property - def member_kind(self) -> MemberKind: - return MemberKind.TYPE_ALIAS - - def close(self, scope: Scope): - self._fp_arguments = qualify_arguments(self._fp_arguments, scope) - self._parsed_type = qualify_parsed_type(self._parsed_type, scope) - - def _is_function_pointer(self) -> bool: - """Check if this typedef is a function pointer type.""" - return self.argstring is not None and self.argstring.startswith(")(") - - def get_value(self) -> str: - if self.keyword == "using": - return format_parsed_type(self._parsed_type) - elif self._is_function_pointer(): - formatted_args = format_arguments(self._fp_arguments) - qualified_type = format_parsed_type(self._parsed_type) - if "(*" in qualified_type: - return f"{qualified_type})({formatted_args})" - else: - return f"{qualified_type}(*)({formatted_args})" - else: - return self.type - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - result = " " * indent - - if self.keyword == "using" and self.template_list is not None: - result += self.template_list.to_string() + "\n" + " " * indent - - if not hide_visibility: - result += self.visibility + " " - - result += self.keyword - - if self.keyword == "using": - result += f" {name} = {format_parsed_type(self._parsed_type)};" - elif self._is_function_pointer(): - formatted_args = format_arguments(self._fp_arguments) - qualified_type = format_parsed_type(self._parsed_type) - # Function pointer typedef: "typedef return_type (*name)(args);" - # type is e.g. "void(*", argstring is ")(args...)" - if "(*" in qualified_type: - result += f" {qualified_type}{name})({formatted_args});" - else: - result += f" {qualified_type}(*{name})({formatted_args});" - else: - result += f" {self.type} {name};" - - return result - - -class PropertyMember(Member): - def __init__( - self, - name: str, - type: str, - visibility: str, - is_static: bool, - accessor: str | None, - is_readable: bool, - is_writable: bool, - ) -> None: - super().__init__(name, visibility) - self.type: str = type - self.is_static: bool = is_static - self.accessor: str | None = accessor - self.is_readable: bool = is_readable - self.is_writable: bool = is_writable - - @property - def member_kind(self) -> MemberKind: - return MemberKind.VARIABLE - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - result = " " * indent - - if not hide_visibility: - result += self.visibility + " " - - attributes = [] - if self.accessor: - attributes.append(self.accessor) - if not self.is_writable and self.is_readable: - attributes.append("readonly") - - attrs_str = f"({', '.join(attributes)}) " if attributes else "" - - if self.is_static: - result += "static " - - # For block properties, name is embedded in the type (e.g., "void(^eventInterceptor)(args)") - if name: - result += f"@property {attrs_str}{self.type} {name};" - else: - result += f"@property {attrs_str}{self.type};" - - return result - - -class FriendMember(Member): - def __init__(self, name: str, visibility: str = "public") -> None: - super().__init__(name, visibility) - - @property - def member_kind(self) -> MemberKind: - return MemberKind.FRIEND - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - result = " " * indent - if not hide_visibility: - result += self.visibility + " " - result += f"friend {name};" - return result - - -class ConceptMember(Member): - def __init__( - self, - name: str, - constraint: str, - ) -> None: - super().__init__(name, "public") - self.constraint: str = self._normalize_constraint(constraint) - - @property - def member_kind(self) -> MemberKind: - return MemberKind.CONCEPT - - @staticmethod - def _normalize_constraint(constraint: str) -> str: - """ - Normalize the whitespace in a concept constraint expression. - - Doxygen preserves original source indentation, which becomes - inconsistent when we flatten namespaces and use qualified names. - This method normalizes the indentation by dedenting all lines - to the minimum non-empty indentation level. - """ - if not constraint: - return constraint - - lines = constraint.split("\n") - if len(lines) <= 1: - return constraint.strip() - - # Find minimum indentation (excluding the first line and empty lines) - min_indent = float("inf") - for line in lines[1:]: - stripped = line.lstrip() - if stripped: # Skip empty lines - indent = len(line) - len(stripped) - min_indent = min(min_indent, indent) - - if min_indent == float("inf"): - min_indent = 0 - - # Dedent all lines by the minimum indentation - result_lines = [lines[0].strip()] - for line in lines[1:]: - if line.strip(): # Non-empty line - # Remove the minimum indentation to normalize - dedented = ( - line[int(min_indent) :] - if len(line) >= min_indent - else line.lstrip() - ) - result_lines.append(dedented.rstrip()) - else: - result_lines.append("") - - # Check if no line is indented - if all(not line.startswith(" ") for line in result_lines): - # Re-indent all lines but the first by 2 spaces - not_indented = result_lines - result_lines = [not_indented[0]] - for line in not_indented[1:]: - if line.strip(): # Non-empty line - result_lines.append(" " + line) - else: - result_lines.append("") - - return "\n".join(result_lines) - - def close(self, scope: Scope): - # TODO: handle unqualified references - pass - - def to_string( - self, - indent: int = 0, - qualification: str | None = None, - hide_visibility: bool = False, - ) -> str: - name = self._get_qualified_name(qualification) - result = "" - - if self.template_list is not None: - result += " " * indent + self.template_list.to_string() + "\n" - - result += " " * indent + f"concept {name} = {self.constraint};" - - return result diff --git a/scripts/cxx-api/parser/member/__init__.py b/scripts/cxx-api/parser/member/__init__.py new file mode 100644 index 000000000000..7cf0b49ba053 --- /dev/null +++ b/scripts/cxx-api/parser/member/__init__.py @@ -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. + +from .base import Member, MemberKind, STORE_INITIALIZERS_IN_SNAPSHOT +from .concept_member import ConceptMember +from .enum_member import EnumMember +from .friend_member import FriendMember +from .function_member import FunctionMember +from .property_member import PropertyMember +from .typedef_member import TypedefMember +from .variable_member import VariableMember + +__all__ = [ + "ConceptMember", + "EnumMember", + "FriendMember", + "FunctionMember", + "Member", + "MemberKind", + "PropertyMember", + "STORE_INITIALIZERS_IN_SNAPSHOT", + "TypedefMember", + "VariableMember", +] diff --git a/scripts/cxx-api/parser/member/base.py b/scripts/cxx-api/parser/member/base.py new file mode 100644 index 000000000000..47ce69906120 --- /dev/null +++ b/scripts/cxx-api/parser/member/base.py @@ -0,0 +1,69 @@ +# 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. + +from __future__ import annotations + +from abc import ABC, abstractmethod +from enum import IntEnum +from typing import TYPE_CHECKING + +from ..template import Template, TemplateList + +if TYPE_CHECKING: + from ..scope import Scope + +STORE_INITIALIZERS_IN_SNAPSHOT = False + + +class MemberKind(IntEnum): + """ + Classification of member kinds for grouping in output. + The order here determines the output order within namespace scopes. + """ + + CONSTANT = 0 + TYPE_ALIAS = 1 + CONCEPT = 2 + FUNCTION = 3 + OPERATOR = 4 + VARIABLE = 5 + FRIEND = 6 + + +class Member(ABC): + def __init__(self, name: str, visibility: str) -> None: + self.name: str = name + self.visibility: str = visibility + self.template_list: TemplateList | None = None + + @property + @abstractmethod + def member_kind(self) -> MemberKind: + pass + + @abstractmethod + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + pass + + def close(self, scope: Scope): + pass + + def _get_qualified_name(self, qualification: str | None): + return f"{qualification}::{self.name}" if qualification else self.name + + def add_template(self, template: Template | [Template]) -> None: + if template and self.template_list is None: + self.template_list = TemplateList() + + if isinstance(template, list): + for t in template: + self.template_list.add(t) + else: + self.template_list.add(template) diff --git a/scripts/cxx-api/parser/member/concept_member.py b/scripts/cxx-api/parser/member/concept_member.py new file mode 100644 index 000000000000..3e6c07a6e34f --- /dev/null +++ b/scripts/cxx-api/parser/member/concept_member.py @@ -0,0 +1,102 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .base import Member, MemberKind + +if TYPE_CHECKING: + from ..scope import Scope + + +class ConceptMember(Member): + def __init__( + self, + name: str, + constraint: str, + ) -> None: + super().__init__(name, "public") + self.constraint: str = self._normalize_constraint(constraint) + + @property + def member_kind(self) -> MemberKind: + return MemberKind.CONCEPT + + @staticmethod + def _normalize_constraint(constraint: str) -> str: + """ + Normalize the whitespace in a concept constraint expression. + + Doxygen preserves original source indentation, which becomes + inconsistent when we flatten namespaces and use qualified names. + This method normalizes the indentation by dedenting all lines + to the minimum non-empty indentation level. + """ + if not constraint: + return constraint + + lines = constraint.split("\n") + if len(lines) <= 1: + return constraint.strip() + + # Find minimum indentation (excluding the first line and empty lines) + min_indent = float("inf") + for line in lines[1:]: + stripped = line.lstrip() + if stripped: # Skip empty lines + indent = len(line) - len(stripped) + min_indent = min(min_indent, indent) + + if min_indent == float("inf"): + min_indent = 0 + + # Dedent all lines by the minimum indentation + result_lines = [lines[0].strip()] + for line in lines[1:]: + if line.strip(): # Non-empty line + # Remove the minimum indentation to normalize + dedented = ( + line[int(min_indent) :] + if len(line) >= min_indent + else line.lstrip() + ) + result_lines.append(dedented.rstrip()) + else: + result_lines.append("") + + # Check if no line is indented + if all(not line.startswith(" ") for line in result_lines): + # Re-indent all lines but the first by 2 spaces + not_indented = result_lines + result_lines = [not_indented[0]] + for line in not_indented[1:]: + if line.strip(): # Non-empty line + result_lines.append(" " + line) + else: + result_lines.append("") + + return "\n".join(result_lines) + + def close(self, scope: Scope): + # TODO: handle unqualified references + pass + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + result = "" + + if self.template_list is not None: + result += " " * indent + self.template_list.to_string() + "\n" + + result += " " * indent + f"concept {name} = {self.constraint};" + + return result diff --git a/scripts/cxx-api/parser/member/enum_member.py b/scripts/cxx-api/parser/member/enum_member.py new file mode 100644 index 000000000000..a838b4221f75 --- /dev/null +++ b/scripts/cxx-api/parser/member/enum_member.py @@ -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. + +from __future__ import annotations + +from .base import Member, MemberKind, STORE_INITIALIZERS_IN_SNAPSHOT + + +class EnumMember(Member): + def __init__(self, name: str, value: str | None) -> None: + super().__init__(name, "public") + self.value: str | None = value + + @property + def member_kind(self) -> MemberKind: + return MemberKind.CONSTANT + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + + if not STORE_INITIALIZERS_IN_SNAPSHOT or self.value is None: + return " " * indent + f"{name}" + + return " " * indent + f"{name} = {self.value}" diff --git a/scripts/cxx-api/parser/member/friend_member.py b/scripts/cxx-api/parser/member/friend_member.py new file mode 100644 index 000000000000..0a00c03d7b7e --- /dev/null +++ b/scripts/cxx-api/parser/member/friend_member.py @@ -0,0 +1,30 @@ +# 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. + +from __future__ import annotations + +from .base import Member, MemberKind + + +class FriendMember(Member): + def __init__(self, name: str, visibility: str = "public") -> None: + super().__init__(name, visibility) + + @property + def member_kind(self) -> MemberKind: + return MemberKind.FRIEND + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + result = " " * indent + if not hide_visibility: + result += self.visibility + " " + result += f"friend {name};" + return result diff --git a/scripts/cxx-api/parser/member/function_member.py b/scripts/cxx-api/parser/member/function_member.py new file mode 100644 index 000000000000..6f7c90778877 --- /dev/null +++ b/scripts/cxx-api/parser/member/function_member.py @@ -0,0 +1,124 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..utils import ( + Argument, + format_arguments, + parse_arg_string, + qualify_arguments, + qualify_template_args_only, + qualify_type_str, +) +from .base import Member, MemberKind + +if TYPE_CHECKING: + from ..scope import Scope + + +class FunctionMember(Member): + def __init__( + self, + name: str, + type: str, + visibility: str, + arg_string: str, + is_virtual: bool, + is_pure_virtual: bool, + is_static: bool, + doxygen_params: list[Argument] | None = None, + is_constexpr: bool = False, + ) -> None: + super().__init__(name, visibility) + self.type: str = type + self.is_virtual: bool = is_virtual + self.is_static: bool = is_static + self.is_constexpr: bool = is_constexpr + parsed_arguments, self.modifiers = parse_arg_string(arg_string) + self.arguments = ( + doxygen_params if doxygen_params is not None else parsed_arguments + ) + + # Doxygen signals pure-virtual via the virt attribute, but the arg string + # may not contain "= 0" (e.g. trailing return type syntax), so the + # modifiers parsed from the arg string may miss it. Propagate the flag. + if is_pure_virtual: + self.modifiers.is_pure_virtual = True + + self.is_const = self.modifiers.is_const + self.is_override = self.modifiers.is_override + + @property + def member_kind(self) -> MemberKind: + if self.name.startswith("operator"): + return MemberKind.OPERATOR + return MemberKind.FUNCTION + + 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, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + result = "" + + if self.template_list is not None: + result += " " * indent + self.template_list.to_string() + "\n" + + result += " " * indent + + if not hide_visibility: + result += self.visibility + " " + + if self.is_virtual: + result += "virtual " + + if self.is_static: + result += "static " + + if self.is_constexpr: + result += "constexpr " + + if self.type: + result += f"{self.type} " + + result += f"{name}({format_arguments(self.arguments)})" + + if self.modifiers.is_const: + result += " const" + + if self.modifiers.is_noexcept: + if self.modifiers.noexcept_expr: + result += f" noexcept({self.modifiers.noexcept_expr})" + else: + result += " noexcept" + + if self.modifiers.is_override: + result += " override" + + if self.modifiers.is_final: + result += " final" + + if self.modifiers.is_pure_virtual: + result += " = 0" + elif self.modifiers.is_default: + result += " = default" + elif self.modifiers.is_delete: + result += " = delete" + + result += ";" + return result diff --git a/scripts/cxx-api/parser/member/property_member.py b/scripts/cxx-api/parser/member/property_member.py new file mode 100644 index 000000000000..46c90e97af98 --- /dev/null +++ b/scripts/cxx-api/parser/member/property_member.py @@ -0,0 +1,62 @@ +# 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. + +from __future__ import annotations + +from .base import Member, MemberKind + + +class PropertyMember(Member): + def __init__( + self, + name: str, + type: str, + visibility: str, + is_static: bool, + accessor: str | None, + is_readable: bool, + is_writable: bool, + ) -> None: + super().__init__(name, visibility) + self.type: str = type + self.is_static: bool = is_static + self.accessor: str | None = accessor + self.is_readable: bool = is_readable + self.is_writable: bool = is_writable + + @property + def member_kind(self) -> MemberKind: + return MemberKind.VARIABLE + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + result = " " * indent + + if not hide_visibility: + result += self.visibility + " " + + attributes = [] + if self.accessor: + attributes.append(self.accessor) + if not self.is_writable and self.is_readable: + attributes.append("readonly") + + attrs_str = f"({', '.join(attributes)}) " if attributes else "" + + if self.is_static: + result += "static " + + # For block properties, name is embedded in the type (e.g., "void(^eventInterceptor)(args)") + if name: + result += f"@property {attrs_str}{self.type} {name};" + else: + result += f"@property {attrs_str}{self.type};" + + return result diff --git a/scripts/cxx-api/parser/member/typedef_member.py b/scripts/cxx-api/parser/member/typedef_member.py new file mode 100644 index 000000000000..4e7f09b5d3a8 --- /dev/null +++ b/scripts/cxx-api/parser/member/typedef_member.py @@ -0,0 +1,99 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..utils import ( + Argument, + format_arguments, + format_parsed_type, + parse_function_pointer_argstring, + parse_type_with_argstrings, + qualify_arguments, + qualify_parsed_type, +) +from .base import Member, MemberKind + +if TYPE_CHECKING: + from ..scope import Scope + + +class TypedefMember(Member): + def __init__( + self, name: str, type: str, argstring: str | None, visibility: str, keyword: str + ) -> None: + super().__init__(name, visibility) + self.keyword: str = keyword + self.argstring: str | None = argstring + + # Parse function pointer argstrings (e.g. ")(int x, float y)") + self._fp_arguments: list[Argument] = ( + parse_function_pointer_argstring(argstring) if argstring else [] + ) + + # Parse inline function signatures in the type so that argument + # lists are stored as structured data, not raw strings. + self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type) + self.type: str = type + + @property + def member_kind(self) -> MemberKind: + return MemberKind.TYPE_ALIAS + + def close(self, scope: Scope): + self._fp_arguments = qualify_arguments(self._fp_arguments, scope) + self._parsed_type = qualify_parsed_type(self._parsed_type, scope) + + def _is_function_pointer(self) -> bool: + """Check if this typedef is a function pointer type.""" + return self.argstring is not None and self.argstring.startswith(")(") + + def get_value(self) -> str: + if self.keyword == "using": + return format_parsed_type(self._parsed_type) + elif self._is_function_pointer(): + formatted_args = format_arguments(self._fp_arguments) + qualified_type = format_parsed_type(self._parsed_type) + if "(*" in qualified_type: + return f"{qualified_type})({formatted_args})" + else: + return f"{qualified_type}(*)({formatted_args})" + else: + return self.type + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + result = " " * indent + + if self.keyword == "using" and self.template_list is not None: + result += self.template_list.to_string() + "\n" + " " * indent + + if not hide_visibility: + result += self.visibility + " " + + result += self.keyword + + if self.keyword == "using": + result += f" {name} = {format_parsed_type(self._parsed_type)};" + elif self._is_function_pointer(): + formatted_args = format_arguments(self._fp_arguments) + qualified_type = format_parsed_type(self._parsed_type) + # Function pointer typedef: "typedef return_type (*name)(args);" + # type is e.g. "void(*", argstring is ")(args...)" + if "(*" in qualified_type: + result += f" {qualified_type}{name})({formatted_args});" + else: + result += f" {qualified_type}(*{name})({formatted_args});" + else: + result += f" {self.type} {name};" + + return result diff --git a/scripts/cxx-api/parser/member/variable_member.py b/scripts/cxx-api/parser/member/variable_member.py new file mode 100644 index 000000000000..69c18509caa7 --- /dev/null +++ b/scripts/cxx-api/parser/member/variable_member.py @@ -0,0 +1,123 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..utils import ( + Argument, + format_arguments, + format_parsed_type, + parse_function_pointer_argstring, + parse_type_with_argstrings, + qualify_arguments, + qualify_parsed_type, + qualify_template_args_only, +) +from .base import Member, MemberKind, STORE_INITIALIZERS_IN_SNAPSHOT + +if TYPE_CHECKING: + from ..scope import Scope + + +class VariableMember(Member): + def __init__( + self, + name: str, + type: str, + visibility: str, + is_const: bool, + is_static: bool, + is_constexpr: bool, + is_mutable: bool, + value: str | None, + definition: str, + argstring: str | None = None, + is_brace_initializer: bool = False, + ) -> None: + super().__init__(name, visibility) + self.type: str = type + self.value: str | None = value + self.is_const: bool = is_const + self.is_static: bool = is_static + self.is_constexpr: bool = is_constexpr + self.is_mutable: bool = is_mutable + self.is_brace_initializer: bool = is_brace_initializer + self.definition: str = definition + self.argstring: str | None = argstring + self._fp_arguments: list[Argument] = ( + parse_function_pointer_argstring(argstring) if argstring else [] + ) + self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type) + + @property + def member_kind(self) -> MemberKind: + if self.is_const or self.is_constexpr: + return MemberKind.CONSTANT + return MemberKind.VARIABLE + + 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.""" + return self.argstring is not None and self.argstring.startswith(")(") + + def to_string( + self, + indent: int = 0, + qualification: str | None = None, + hide_visibility: bool = False, + ) -> str: + name = self._get_qualified_name(qualification) + + result = " " * indent + + if self.template_list is not None: + result += self.template_list.to_string() + "\n" + " " * indent + + if not hide_visibility: + result += self.visibility + " " + + if self.is_static: + result += "static " + + if self.is_constexpr: + result += "constexpr " + + if self.is_mutable: + result += "mutable " + + if self.is_const and not self.is_constexpr: + result += "const " + + if self._is_function_pointer(): + formatted_args = format_arguments(self._fp_arguments) + qualified_type = format_parsed_type(self._parsed_type) + # Function pointer types: argstring is ")(args...)" + # If type already contains "(*", e.g. "void *(*" or "void(*", use directly + # Otherwise add "(*" to form proper function pointer syntax + if "(*" in qualified_type: + result += f"{qualified_type}{name})({formatted_args})" + else: + result += f"{qualified_type} (*{name})({formatted_args})" + else: + result += f"{format_parsed_type(self._parsed_type)} {name}" + + if STORE_INITIALIZERS_IN_SNAPSHOT and self.value is not None: + if self.is_brace_initializer: + result += f"{{{self.value}}}" + else: + result += f" = {self.value}" + + result += ";" + + return result From bf1046d4b25e073aa6139b71bc94f6b9de66192d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 00:07:52 -0700 Subject: [PATCH 6/7] Split scope definitions into smaller files Summary: Changelog: [Internal] Splits `scope.py` into multiple smaller files, each containing a separate defitnition. Differential Revision: D96284004 --- scripts/cxx-api/parser/scope/__init__.py | 27 ++ .../cxx-api/parser/scope/base_scope_kind.py | 36 +++ .../parser/scope/category_scope_kind.py | 37 +++ .../cxx-api/parser/scope/enum_scope_kind.py | 42 +++ .../parser/scope/interface_scope_kind.py | 72 +++++ .../parser/scope/namespace_scope_kind.py | 40 +++ .../parser/scope/protocol_scope_kind.py | 70 ++++ scripts/cxx-api/parser/{ => scope}/scope.py | 304 +----------------- .../parser/scope/struct_like_scope_kind.py | 93 ++++++ .../parser/scope/temporary_scope_kind.py | 21 ++ 10 files changed, 446 insertions(+), 296 deletions(-) create mode 100644 scripts/cxx-api/parser/scope/__init__.py create mode 100644 scripts/cxx-api/parser/scope/base_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/category_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/enum_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/interface_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/namespace_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/protocol_scope_kind.py rename scripts/cxx-api/parser/{ => scope}/scope.py (50%) create mode 100644 scripts/cxx-api/parser/scope/struct_like_scope_kind.py create mode 100644 scripts/cxx-api/parser/scope/temporary_scope_kind.py diff --git a/scripts/cxx-api/parser/scope/__init__.py b/scripts/cxx-api/parser/scope/__init__.py new file mode 100644 index 000000000000..350bad569ba4 --- /dev/null +++ b/scripts/cxx-api/parser/scope/__init__.py @@ -0,0 +1,27 @@ +# 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. + +from .base_scope_kind import ScopeKind, ScopeKindT +from .category_scope_kind import CategoryScopeKind +from .enum_scope_kind import EnumScopeKind +from .interface_scope_kind import InterfaceScopeKind +from .namespace_scope_kind import NamespaceScopeKind +from .protocol_scope_kind import ProtocolScopeKind +from .scope import Scope +from .struct_like_scope_kind import StructLikeScopeKind +from .temporary_scope_kind import TemporaryScopeKind + +__all__ = [ + "CategoryScopeKind", + "EnumScopeKind", + "InterfaceScopeKind", + "NamespaceScopeKind", + "ProtocolScopeKind", + "Scope", + "ScopeKind", + "ScopeKindT", + "StructLikeScopeKind", + "TemporaryScopeKind", +] diff --git a/scripts/cxx-api/parser/scope/base_scope_kind.py b/scripts/cxx-api/parser/scope/base_scope_kind.py new file mode 100644 index 000000000000..4c3a09431162 --- /dev/null +++ b/scripts/cxx-api/parser/scope/base_scope_kind.py @@ -0,0 +1,36 @@ +# 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. + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, TypeVar + +from natsort import natsort_keygen + +if TYPE_CHECKING: + from .scope import Scope + +# Pre-create natsort key function for efficiency +_natsort_key = natsort_keygen() + + +class ScopeKind(ABC): + def __init__(self, name) -> None: + self.name: str = name + + @abstractmethod + 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)) + + +ScopeKindT = TypeVar("ScopeKindT", bound=ScopeKind) diff --git a/scripts/cxx-api/parser/scope/category_scope_kind.py b/scripts/cxx-api/parser/scope/category_scope_kind.py new file mode 100644 index 000000000000..ac62b7e9d4d1 --- /dev/null +++ b/scripts/cxx-api/parser/scope/category_scope_kind.py @@ -0,0 +1,37 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from natsort import natsorted + +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class CategoryScopeKind(ScopeKind): + def __init__(self, class_name: str, category_name: str) -> None: + super().__init__("category") + self.class_name: str = class_name + self.category_name: str = category_name + + def to_string(self, scope: Scope) -> str: + result = f"{self.name} {self.class_name}({self.category_name}) {{" + + stringified_members = [] + for member in scope.get_members(): + stringified_members.append(member.to_string(2)) + stringified_members = natsorted(stringified_members) + result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( + stringified_members + ) + + result += "\n}" + + return result diff --git a/scripts/cxx-api/parser/scope/enum_scope_kind.py b/scripts/cxx-api/parser/scope/enum_scope_kind.py new file mode 100644 index 000000000000..1e1addb0214b --- /dev/null +++ b/scripts/cxx-api/parser/scope/enum_scope_kind.py @@ -0,0 +1,42 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from natsort import natsorted + +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class EnumScopeKind(ScopeKind): + def __init__(self) -> None: + super().__init__("enum") + self.type: str | None = None + + def to_string(self, scope: Scope) -> str: + result = "" + inheritance_string = f" : {self.type}" if self.type else "" + + result += ( + "\n" + f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" + ) + + stringified_members = [] + for member in scope.get_members(): + stringified_members.append(member.to_string(2) + ",") + + stringified_members = natsorted(stringified_members) + result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( + stringified_members + ) + + result += "\n}" + + return result diff --git a/scripts/cxx-api/parser/scope/interface_scope_kind.py b/scripts/cxx-api/parser/scope/interface_scope_kind.py new file mode 100644 index 000000000000..a39517de5b2d --- /dev/null +++ b/scripts/cxx-api/parser/scope/interface_scope_kind.py @@ -0,0 +1,72 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from natsort import natsorted + +from ..utils import qualify_type_str +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class InterfaceScopeKind(ScopeKind): + class Base: + def __init__( + self, name: str, protection: str, virtual: bool, refid: str + ) -> None: + self.name: str = name + self.protection: str = protection + self.virtual: bool = virtual + self.refid: str = refid + + def __init__(self) -> None: + super().__init__("interface") + self.base_classes: [InterfaceScopeKind.Base] = [] + + def add_base( + self, base: InterfaceScopeKind.Base | [InterfaceScopeKind.Base] + ) -> None: + if isinstance(base, list): + for b in base: + self.base_classes.append(b) + 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 = "" + + bases = [] + for base in self.base_classes: + base_text = [base.protection] + if base.virtual: + base_text.append("virtual") + base_text.append(base.name) + bases.append(" ".join(base_text)) + + inheritance_string = " : " + ", ".join(bases) if bases else "" + + result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" + + stringified_members = [] + for member in scope.get_members(): + stringified_members.append(member.to_string(2)) + stringified_members = natsorted(stringified_members) + result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( + stringified_members + ) + + result += "\n}" + + return result diff --git a/scripts/cxx-api/parser/scope/namespace_scope_kind.py b/scripts/cxx-api/parser/scope/namespace_scope_kind.py new file mode 100644 index 000000000000..0d7444be5473 --- /dev/null +++ b/scripts/cxx-api/parser/scope/namespace_scope_kind.py @@ -0,0 +1,40 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from natsort import natsorted + +from ..member import MemberKind +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class NamespaceScopeKind(ScopeKind): + def __init__(self) -> None: + super().__init__("namespace") + + def to_string(self, scope: Scope) -> str: + qualification = scope.get_qualified_name() + + # Group members by kind + groups: dict[MemberKind, list[str]] = {kind: [] for kind in MemberKind} + + for member in scope.get_members(): + kind = member.member_kind + stringified = member.to_string(0, qualification, hide_visibility=True) + groups[kind].append(stringified) + + # Sort within each group and combine in kind order + result = [] + for kind in MemberKind: + sorted_group = natsorted(groups[kind]) + result.extend(sorted_group) + + return "\n".join(result) diff --git a/scripts/cxx-api/parser/scope/protocol_scope_kind.py b/scripts/cxx-api/parser/scope/protocol_scope_kind.py new file mode 100644 index 000000000000..0ae304fad7c7 --- /dev/null +++ b/scripts/cxx-api/parser/scope/protocol_scope_kind.py @@ -0,0 +1,70 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from natsort import natsorted + +from ..utils import qualify_type_str +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class ProtocolScopeKind(ScopeKind): + class Base: + def __init__( + self, name: str, protection: str, virtual: bool, refid: str + ) -> None: + self.name: str = name + self.protection: str = protection + self.virtual: bool = virtual + self.refid: str = refid + + def __init__(self) -> None: + super().__init__("protocol") + self.base_classes: [ProtocolScopeKind.Base] = [] + + def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> None: + if isinstance(base, list): + for b in base: + self.base_classes.append(b) + 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 = "" + + bases = [] + for base in self.base_classes: + base_text = [base.protection] + if base.virtual: + base_text.append("virtual") + base_text.append(base.name) + bases.append(" ".join(base_text)) + + inheritance_string = " : " + ", ".join(bases) if bases else "" + + result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" + + stringified_members = [] + for member in scope.get_members(): + stringified_members.append(member.to_string(2)) + stringified_members = natsorted(stringified_members) + result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( + stringified_members + ) + + result += "\n}" + + return result diff --git a/scripts/cxx-api/parser/scope.py b/scripts/cxx-api/parser/scope/scope.py similarity index 50% rename from scripts/cxx-api/parser/scope.py rename to scripts/cxx-api/parser/scope/scope.py index 615af32a1967..6586207ed523 100644 --- a/scripts/cxx-api/parser/scope.py +++ b/scripts/cxx-api/parser/scope/scope.py @@ -5,304 +5,16 @@ from __future__ import annotations -from abc import ABC, abstractmethod -from enum import Enum -from typing import Generic, TypeVar +from typing import Generic -from natsort import natsort_keygen, natsorted +from natsort import natsorted -from .member import FriendMember, Member, MemberKind, TypedefMember -from .template import Template, TemplateList -from .utils import parse_qualified_path, qualify_template_args_only, qualify_type_str - - -# Pre-create natsort key function for efficiency -_natsort_key = natsort_keygen() - - -class ScopeKind(ABC): - def __init__(self, name) -> None: - self.name: str = name - - @abstractmethod - 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)) - - -class StructLikeScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - - class Type(Enum): - CLASS = "class" - STRUCT = "struct" - UNION = "union" - - def __init__(self, type: Type) -> None: - super().__init__(type.value) - - self.base_classes: [StructLikeScopeKind.Base] = [] - self.template_list: TemplateList | None = None - - def add_base( - self, base: StructLikeScopeKind.Base | [StructLikeScopeKind.Base] - ) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - else: - self.base_classes.append(base) - - def add_template(self, template: Template | [Template]) -> None: - if template and self.template_list is None: - self.template_list = TemplateList() - - if isinstance(template, list): - for t in template: - self.template_list.add(t) - 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 = "" - - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - - if self.template_list is not None: - result += "\n" + self.template_list.to_string() + "\n" - result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" - - stringified_members = [] - for member in scope.get_members(): - stringified_members.append(member.to_string(2)) - stringified_members = natsorted(stringified_members) - result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( - stringified_members - ) - - result += "\n}" - - return result - - -class NamespaceScopeKind(ScopeKind): - def __init__(self) -> None: - super().__init__("namespace") - - def to_string(self, scope: Scope) -> str: - qualification = scope.get_qualified_name() - - # Group members by kind - groups: dict[MemberKind, list[str]] = {kind: [] for kind in MemberKind} - - for member in scope.get_members(): - kind = member.member_kind - stringified = member.to_string(0, qualification, hide_visibility=True) - groups[kind].append(stringified) - - # Sort within each group and combine in kind order - result = [] - for kind in MemberKind: - sorted_group = natsorted(groups[kind]) - result.extend(sorted_group) - - return "\n".join(result) - - -class EnumScopeKind(ScopeKind): - def __init__(self) -> None: - super().__init__("enum") - self.type: str | None = None - - def to_string(self, scope: Scope) -> str: - result = "" - inheritance_string = f" : {self.type}" if self.type else "" - - result += ( - "\n" + f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" - ) - - stringified_members = [] - for member in scope.get_members(): - stringified_members.append(member.to_string(2) + ",") - - stringified_members = natsorted(stringified_members) - result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( - stringified_members - ) - - result += "\n}" - - return result - - -class ProtocolScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - - def __init__(self) -> None: - super().__init__("protocol") - self.base_classes: [ProtocolScopeKind.Base] = [] - - def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - 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 = "" - - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - - result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" - - stringified_members = [] - for member in scope.get_members(): - stringified_members.append(member.to_string(2)) - stringified_members = natsorted(stringified_members) - result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( - stringified_members - ) - - result += "\n}" - - return result - - -class InterfaceScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - - def __init__(self) -> None: - super().__init__("interface") - self.base_classes: [InterfaceScopeKind.Base] = [] - - def add_base( - self, base: InterfaceScopeKind.Base | [InterfaceScopeKind.Base] - ) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - 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 = "" - - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - - result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" - - stringified_members = [] - for member in scope.get_members(): - stringified_members.append(member.to_string(2)) - stringified_members = natsorted(stringified_members) - result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( - stringified_members - ) - - result += "\n}" - - return result - - -class CategoryScopeKind(ScopeKind): - def __init__(self, class_name: str, category_name: str) -> None: - super().__init__("category") - self.class_name: str = class_name - self.category_name: str = category_name - - def to_string(self, scope: Scope) -> str: - result = f"{self.name} {self.class_name}({self.category_name}) {{" - - stringified_members = [] - for member in scope.get_members(): - stringified_members.append(member.to_string(2)) - stringified_members = natsorted(stringified_members) - result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( - stringified_members - ) - - result += "\n}" - - return result - - -class TemporaryScopeKind(ScopeKind): - def __init__(self) -> None: - super().__init__("temporary") - - def to_string(self, scope: Scope) -> str: - raise RuntimeError("Temporary scope should not be printed") - - -ScopeKindT = TypeVar("ScopeKindT", bound=ScopeKind) +from ..member import FriendMember, Member, TypedefMember +from ..utils import parse_qualified_path, qualify_template_args_only +from .base_scope_kind import _natsort_key, ScopeKindT +from .enum_scope_kind import EnumScopeKind +from .namespace_scope_kind import NamespaceScopeKind +from .struct_like_scope_kind import StructLikeScopeKind class Scope(Generic[ScopeKindT]): diff --git a/scripts/cxx-api/parser/scope/struct_like_scope_kind.py b/scripts/cxx-api/parser/scope/struct_like_scope_kind.py new file mode 100644 index 000000000000..68a7abc53179 --- /dev/null +++ b/scripts/cxx-api/parser/scope/struct_like_scope_kind.py @@ -0,0 +1,93 @@ +# 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. + +from __future__ import annotations + +from enum import Enum +from typing import TYPE_CHECKING + +from natsort import natsorted + +from ..template import Template, TemplateList +from ..utils import qualify_type_str +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class StructLikeScopeKind(ScopeKind): + class Base: + def __init__( + self, name: str, protection: str, virtual: bool, refid: str + ) -> None: + self.name: str = name + self.protection: str = protection + self.virtual: bool = virtual + self.refid: str = refid + + class Type(Enum): + CLASS = "class" + STRUCT = "struct" + UNION = "union" + + def __init__(self, type: Type) -> None: + super().__init__(type.value) + + self.base_classes: [StructLikeScopeKind.Base] = [] + self.template_list: TemplateList | None = None + + def add_base( + self, base: StructLikeScopeKind.Base | [StructLikeScopeKind.Base] + ) -> None: + if isinstance(base, list): + for b in base: + self.base_classes.append(b) + else: + self.base_classes.append(base) + + def add_template(self, template: Template | [Template]) -> None: + if template and self.template_list is None: + self.template_list = TemplateList() + + if isinstance(template, list): + for t in template: + self.template_list.add(t) + 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 = "" + + bases = [] + for base in self.base_classes: + base_text = [base.protection] + if base.virtual: + base_text.append("virtual") + base_text.append(base.name) + bases.append(" ".join(base_text)) + + inheritance_string = " : " + ", ".join(bases) if bases else "" + + if self.template_list is not None: + result += "\n" + self.template_list.to_string() + "\n" + result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" + + stringified_members = [] + for member in scope.get_members(): + stringified_members.append(member.to_string(2)) + stringified_members = natsorted(stringified_members) + result += ("\n" if len(stringified_members) > 0 else "") + "\n".join( + stringified_members + ) + + result += "\n}" + + return result diff --git a/scripts/cxx-api/parser/scope/temporary_scope_kind.py b/scripts/cxx-api/parser/scope/temporary_scope_kind.py new file mode 100644 index 000000000000..04e3c9953a9c --- /dev/null +++ b/scripts/cxx-api/parser/scope/temporary_scope_kind.py @@ -0,0 +1,21 @@ +# 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. + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .base_scope_kind import ScopeKind + +if TYPE_CHECKING: + from .scope import Scope + + +class TemporaryScopeKind(ScopeKind): + def __init__(self) -> None: + super().__init__("temporary") + + def to_string(self, scope: Scope) -> str: + raise RuntimeError("Temporary scope should not be printed") From 96ffaf49bd80640cee50852dbf1092f913d8423e Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 13 Mar 2026 03:17:24 -0700 Subject: [PATCH 7/7] Move inheritance logic to a separate class (#56074) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56074 Changelog: [Internal] Extracts the duplicated handling of inheritance and base classes to a separate, reusable class. Differential Revision: D96287394 --- scripts/cxx-api/parser/scope/extendable.py | 38 +++++++++++++++++++ .../parser/scope/interface_scope_kind.py | 36 +++--------------- .../parser/scope/protocol_scope_kind.py | 34 +++-------------- .../parser/scope/struct_like_scope_kind.py | 37 +++--------------- 4 files changed, 54 insertions(+), 91 deletions(-) create mode 100644 scripts/cxx-api/parser/scope/extendable.py diff --git a/scripts/cxx-api/parser/scope/extendable.py b/scripts/cxx-api/parser/scope/extendable.py new file mode 100644 index 000000000000..4051f79c3722 --- /dev/null +++ b/scripts/cxx-api/parser/scope/extendable.py @@ -0,0 +1,38 @@ +# 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. + +from __future__ import annotations + + +class Extendable: + class Base: + def __init__( + self, name: str, protection: str, virtual: bool, refid: str + ) -> None: + self.name: str = name + self.protection: str = protection + self.virtual: bool = virtual + self.refid: str = refid + + def __init__(self) -> None: + self.base_classes = [] + + def add_base(self, base: Base | list[Base]) -> None: + if isinstance(base, list): + for b in base: + self.base_classes.append(b) + else: + self.base_classes.append(base) + + def get_inheritance_string(self) -> str: + bases = [] + for base in self.base_classes: + base_text = [base.protection] + if base.virtual: + base_text.append("virtual") + base_text.append(base.name) + bases.append(" ".join(base_text)) + + return (" : " + ", ".join(bases)) if bases else "" diff --git a/scripts/cxx-api/parser/scope/interface_scope_kind.py b/scripts/cxx-api/parser/scope/interface_scope_kind.py index a39517de5b2d..bba180194792 100644 --- a/scripts/cxx-api/parser/scope/interface_scope_kind.py +++ b/scripts/cxx-api/parser/scope/interface_scope_kind.py @@ -11,33 +11,16 @@ from ..utils import qualify_type_str from .base_scope_kind import ScopeKind +from .extendable import Extendable if TYPE_CHECKING: from .scope import Scope -class InterfaceScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - +class InterfaceScopeKind(ScopeKind, Extendable): def __init__(self) -> None: - super().__init__("interface") - self.base_classes: [InterfaceScopeKind.Base] = [] - - def add_base( - self, base: InterfaceScopeKind.Base | [InterfaceScopeKind.Base] - ) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - else: - self.base_classes.append(base) + ScopeKind.__init__(self, "interface") + Extendable.__init__(self) def close(self, scope: Scope) -> None: """Qualify base class names and their template arguments.""" @@ -47,16 +30,7 @@ def close(self, scope: Scope) -> None: def to_string(self, scope: Scope) -> str: result = "" - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - + inheritance_string = self.get_inheritance_string() result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" stringified_members = [] diff --git a/scripts/cxx-api/parser/scope/protocol_scope_kind.py b/scripts/cxx-api/parser/scope/protocol_scope_kind.py index 0ae304fad7c7..fcbd77624c67 100644 --- a/scripts/cxx-api/parser/scope/protocol_scope_kind.py +++ b/scripts/cxx-api/parser/scope/protocol_scope_kind.py @@ -11,31 +11,16 @@ from ..utils import qualify_type_str from .base_scope_kind import ScopeKind +from .extendable import Extendable if TYPE_CHECKING: from .scope import Scope -class ProtocolScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - +class ProtocolScopeKind(ScopeKind, Extendable): def __init__(self) -> None: - super().__init__("protocol") - self.base_classes: [ProtocolScopeKind.Base] = [] - - def add_base(self, base: ProtocolScopeKind.Base | [ProtocolScopeKind.Base]) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - else: - self.base_classes.append(base) + ScopeKind.__init__(self, "protocol") + Extendable.__init__(self) def close(self, scope: Scope) -> None: """Qualify base class names and their template arguments.""" @@ -45,16 +30,7 @@ def close(self, scope: Scope) -> None: def to_string(self, scope: Scope) -> str: result = "" - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - + inheritance_string = self.get_inheritance_string() result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" stringified_members = [] diff --git a/scripts/cxx-api/parser/scope/struct_like_scope_kind.py b/scripts/cxx-api/parser/scope/struct_like_scope_kind.py index 68a7abc53179..11c0db3e9291 100644 --- a/scripts/cxx-api/parser/scope/struct_like_scope_kind.py +++ b/scripts/cxx-api/parser/scope/struct_like_scope_kind.py @@ -13,41 +13,24 @@ from ..template import Template, TemplateList from ..utils import qualify_type_str from .base_scope_kind import ScopeKind +from .extendable import Extendable if TYPE_CHECKING: from .scope import Scope -class StructLikeScopeKind(ScopeKind): - class Base: - def __init__( - self, name: str, protection: str, virtual: bool, refid: str - ) -> None: - self.name: str = name - self.protection: str = protection - self.virtual: bool = virtual - self.refid: str = refid - +class StructLikeScopeKind(ScopeKind, Extendable): class Type(Enum): CLASS = "class" STRUCT = "struct" UNION = "union" def __init__(self, type: Type) -> None: - super().__init__(type.value) + ScopeKind.__init__(self, type.value) + Extendable.__init__(self) - self.base_classes: [StructLikeScopeKind.Base] = [] self.template_list: TemplateList | None = None - def add_base( - self, base: StructLikeScopeKind.Base | [StructLikeScopeKind.Base] - ) -> None: - if isinstance(base, list): - for b in base: - self.base_classes.append(b) - else: - self.base_classes.append(base) - def add_template(self, template: Template | [Template]) -> None: if template and self.template_list is None: self.template_list = TemplateList() @@ -66,18 +49,10 @@ def close(self, scope: Scope) -> None: def to_string(self, scope: Scope) -> str: result = "" - bases = [] - for base in self.base_classes: - base_text = [base.protection] - if base.virtual: - base_text.append("virtual") - base_text.append(base.name) - bases.append(" ".join(base_text)) - - inheritance_string = " : " + ", ".join(bases) if bases else "" - if self.template_list is not None: result += "\n" + self.template_list.to_string() + "\n" + + inheritance_string = self.get_inheritance_string() result += f"{self.name} {scope.get_qualified_name()}{inheritance_string} {{" stringified_members = []