From c5783ed0a95f765fb8b32bf0b515054c33918d61 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 25 Mar 2026 23:59:49 -0700 Subject: [PATCH 1/4] Handle member array types in the snapshot Summary: Changelog: [Internal] Correctly handles array types in member definitions in the API snapshot. Differential Revision: D97923375 --- scripts/cxx-api/parser/member/variable_member.py | 2 ++ .../should_handle_array_variable/snapshot.api | 1 + .../snapshots/should_handle_array_variable/test.h | 14 ++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_array_variable/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_array_variable/test.h diff --git a/scripts/cxx-api/parser/member/variable_member.py b/scripts/cxx-api/parser/member/variable_member.py index 8c456f6c1e17..403af09eae45 100644 --- a/scripts/cxx-api/parser/member/variable_member.py +++ b/scripts/cxx-api/parser/member/variable_member.py @@ -111,6 +111,8 @@ def to_string( result += f"{qualified_type} (*{name})({formatted_args})" else: result += f"{format_parsed_type(self._parsed_type)} {name}" + if self.argstring: + result += self.argstring if STORE_INITIALIZERS_IN_SNAPSHOT and self.value is not None: if self.is_brace_initializer: diff --git a/scripts/cxx-api/tests/snapshots/should_handle_array_variable/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_array_variable/snapshot.api new file mode 100644 index 000000000000..fd3f119e9564 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_array_variable/snapshot.api @@ -0,0 +1 @@ +const char test::ViewComponentName[]; diff --git a/scripts/cxx-api/tests/snapshots/should_handle_array_variable/test.h b/scripts/cxx-api/tests/snapshots/should_handle_array_variable/test.h new file mode 100644 index 000000000000..cf1624f6b919 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_array_variable/test.h @@ -0,0 +1,14 @@ +/* + * 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 { + +const char ViewComponentName[] = "View"; + +} // namespace test From 75688d0623fe25173f03f6ce8bb98e55d2627ab5 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 25 Mar 2026 23:59:49 -0700 Subject: [PATCH 2/4] Deduplicate constructors when a class has multiple base classes Summary: Changelog: [Internal] When a class has multiple base classes, all defining a constructor with the same signature, the inherited constructors were being duplicated in the snapshot (for each base class). This diff deduplicates them. Differential Revision: D98102286 --- scripts/cxx-api/parser/__main__.py | 30 +++++++-------- .../cxx-api/parser/scope/base_scope_kind.py | 5 +++ .../parser/scope/namespace_scope_kind.py | 2 + .../snapshot.api | 27 ++++++++++++++ .../test.h | 37 +++++++++++++++++++ 5 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/test.h diff --git a/scripts/cxx-api/parser/__main__.py b/scripts/cxx-api/parser/__main__.py index 84dbb9a57cbd..00f5d219356b 100644 --- a/scripts/cxx-api/parser/__main__.py +++ b/scripts/cxx-api/parser/__main__.py @@ -193,22 +193,20 @@ def build_snapshots( failed_views = ", ".join(name for name, _ in errors) raise RuntimeError(f"Failed to generate snapshots: {failed_views}") else: - with tempfile.TemporaryDirectory(prefix="cxx-api-test-") as work_dir: - snapshot = build_snapshot_for_view( - api_view="Test", - react_native_dir=react_native_dir, - include_directories=[], - exclude_patterns=[], - definitions={}, - output_dir=output_dir, - codegen_platform=None, - verbose=verbose, - input_filter=input_filter, - work_dir=work_dir, - ) - - if verbose: - print(snapshot) + snapshot = build_snapshot_for_view( + api_view="Test", + react_native_dir=react_native_dir, + include_directories=[], + exclude_patterns=[], + definitions={}, + output_dir=output_dir, + codegen_platform=None, + verbose=verbose, + input_filter=input_filter, + ) + + if verbose: + print(snapshot) def get_default_snapshot_dir() -> str: diff --git a/scripts/cxx-api/parser/scope/base_scope_kind.py b/scripts/cxx-api/parser/scope/base_scope_kind.py index 02aa9720687b..ae27e0a583c4 100644 --- a/scripts/cxx-api/parser/scope/base_scope_kind.py +++ b/scripts/cxx-api/parser/scope/base_scope_kind.py @@ -34,7 +34,12 @@ def _format_scope_body(self, scope: Scope, member_suffix: str = "") -> str: stringified_members = [ member.to_string(2) + member_suffix for member in scope.get_members() ] + stringified_members = natsorted(stringified_members) + # Deduplicate members that produce identical signatures (e.g. + # constructors inherited from multiple bases). + stringified_members = list(dict.fromkeys(stringified_members)) + result = "{" if stringified_members: result += "\n" + "\n".join(stringified_members) diff --git a/scripts/cxx-api/parser/scope/namespace_scope_kind.py b/scripts/cxx-api/parser/scope/namespace_scope_kind.py index 0d7444be5473..57aaae3df7a9 100644 --- a/scripts/cxx-api/parser/scope/namespace_scope_kind.py +++ b/scripts/cxx-api/parser/scope/namespace_scope_kind.py @@ -35,6 +35,8 @@ def to_string(self, scope: Scope) -> str: result = [] for kind in MemberKind: sorted_group = natsorted(groups[kind]) + # Deduplicate members with identical signatures. + sorted_group = list(dict.fromkeys(sorted_group)) result.extend(sorted_group) return "\n".join(result) diff --git a/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/snapshot.api new file mode 100644 index 000000000000..4b008a74d180 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/snapshot.api @@ -0,0 +1,27 @@ +class test::BaseTextShadowNode : public test::ShadowNode { + public BaseTextShadowNode(const test::ShadowNode& source, const test::ShadowNode::Fragment& fragment); + public BaseTextShadowNode(const test::ShadowNode::Fragment& fragment, const test::ShadowNode::Shared& family, test::ShadowNode::Traits traits); +} + +class test::ConcreteViewShadowNode : public test::ShadowNode { + public ConcreteViewShadowNode(const test::ShadowNode& source, const test::ShadowNode::Fragment& fragment); + public ConcreteViewShadowNode(const test::ShadowNode::Fragment& fragment, const test::ShadowNode::Shared& family, test::ShadowNode::Traits traits); +} + +class test::ParagraphShadowNode : public test::ConcreteViewShadowNode, public test::BaseTextShadowNode { + public ParagraphShadowNode(const test::ShadowNode& source, const test::ShadowNode::Fragment& fragment); + public ParagraphShadowNode(const test::ShadowNode::Fragment& fragment, const test::ShadowNode::Shared& family, test::ShadowNode::Traits traits); +} + +class test::ShadowNode { +} + +enum test::ShadowNode::Traits { + None, +} + +struct test::ShadowNode::Fragment { +} + +struct test::ShadowNode::Shared { +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/test.h b/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/test.h new file mode 100644 index 000000000000..2d19852e49cc --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_multiple_inherited_constructors/test.h @@ -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. + */ + +#pragma once + +namespace test { + +class ShadowNode { + public: + struct Fragment {}; + struct Shared {}; + enum class Traits { None }; +}; + +class ConcreteViewShadowNode : public ShadowNode { + public: + ConcreteViewShadowNode(const ShadowNode &source, const Fragment &fragment); + ConcreteViewShadowNode(const Fragment &fragment, const Shared &family, Traits traits); +}; + +class BaseTextShadowNode : public ShadowNode { + public: + BaseTextShadowNode(const ShadowNode &source, const Fragment &fragment); + BaseTextShadowNode(const Fragment &fragment, const Shared &family, Traits traits); +}; + +class ParagraphShadowNode : public ConcreteViewShadowNode, public BaseTextShadowNode { + public: + using BaseTextShadowNode::BaseTextShadowNode; + using ConcreteViewShadowNode::ConcreteViewShadowNode; +}; + +} // namespace test From 888a7374c6b77ae10b5c66c7bbb125ceccf16202 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 25 Mar 2026 23:59:49 -0700 Subject: [PATCH 3/4] Fix array brackets appearing in the wrong place in the snapshot Summary: Changelog: [Internal] Snapshot generator was putting `[]` in wrong place when generating array type - it was adding it to the type name instead of the variable name. This diff addresses that. Differential Revision: D98102328 --- scripts/cxx-api/parser/builders.py | 2 ++ .../snapshot.api | 3 +++ .../should_handle_array_syntax_correctly/test.h | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index c595183d86c1..beb9be1c51cf 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -333,6 +333,8 @@ def get_doxygen_params( + param_array ) param_name = None + elif param_name: + param_name += param_array else: param_type += param_array diff --git a/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/snapshot.api new file mode 100644 index 000000000000..d0f9dd2bf878 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/snapshot.api @@ -0,0 +1,3 @@ +void test::arrayParam(const char name[]); +void test::arrayParamSized(int values[10]); +void test::arrayParamUnnamed(const char[]); diff --git a/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/test.h b/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/test.h new file mode 100644 index 000000000000..43240dd86f06 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_array_syntax_correctly/test.h @@ -0,0 +1,16 @@ +/* + * 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 { + +void arrayParam(const char name[]); +void arrayParamSized(int values[10]); +void arrayParamUnnamed(const char[]); + +} // namespace test From 568a43903ccee62917cac1f693053a038e3be22d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 26 Mar 2026 07:04:31 -0700 Subject: [PATCH 4/4] Fix function pointer param args being qualified to outer class (#56217) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56217 Changelog: [Internal] Doxygen may incorrectly cross-reference parameter names inside function pointer types to member variables of the enclosing class. For example, `void (*cb)(const void* data)` inside a class that has a `data` member would produce `const void* Class::data` instead of `const void* data` in the snapshot. This diff addresses two issues: 1. In `get_doxygen_params`, re-parse function pointer type strings through `parse_type_with_argstrings` which delegates to `_parse_single_argument` that already strips "::" from parameter names. 2. In `parse_type_with_argstrings`, recognize complex declarator groups starting with `*` or `&` (e.g. `*(*fp)(int)`) as non-argument-list parenthesized sections, preventing incorrect name extraction from nested function pointer declarators. Reviewed By: cipolleschi Differential Revision: D98118629 --- scripts/cxx-api/parser/builders.py | 17 ++++++++++++++++- .../cxx-api/parser/utils/argument_parsing.py | 8 ++++++++ .../snapshot.api | 4 ++++ .../test.h | 18 ++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index beb9be1c51cf..e479660ee840 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -41,7 +41,12 @@ resolve_linked_text_name, split_specialization, ) -from .utils.argument_parsing import _find_matching_angle, _split_arguments +from .utils.argument_parsing import ( + _find_matching_angle, + _split_arguments, + format_parsed_type, + parse_type_with_argstrings, +) @dataclass @@ -307,6 +312,16 @@ def get_doxygen_params( if param.get_type() else "" ) + + # Doxygen may incorrectly cross-reference parameter names inside + # inline function pointer types to member variables of the enclosing + # class, producing qualified paths like "const void* + # ns::Class::data" instead of "const void* data". Re-parse the + # type through parse_type_with_argstrings which delegates to + # _parse_single_argument — that already strips "::" from names. + segments = parse_type_with_argstrings(param_type) + if len(segments) > 1: + param_type = format_parsed_type(segments) param_name = param.declname or param.defname or None param_default = ( resolve_linked_text_name(param.defval)[0].strip() if param.defval else None diff --git a/scripts/cxx-api/parser/utils/argument_parsing.py b/scripts/cxx-api/parser/utils/argument_parsing.py index e9bcfb010eeb..1449f46bac30 100644 --- a/scripts/cxx-api/parser/utils/argument_parsing.py +++ b/scripts/cxx-api/parser/utils/argument_parsing.py @@ -530,6 +530,14 @@ def parse_type_with_argstrings( i = close + 1 continue + # Complex declarator starting with * or &, e.g. *(*fp)(int) + # in "int(*(*fp)(int))(double)". Argument lists never start + # with pointer/reference characters. + if stripped and stripped[0] in ("*", "&"): + current_text.append(type_str[i : close + 1]) + i = close + 1 + continue + # Try to parse as a function argument list args: list[Argument] = [] if stripped: diff --git a/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/snapshot.api b/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/snapshot.api new file mode 100644 index 000000000000..85ed57b79540 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/snapshot.api @@ -0,0 +1,4 @@ +class test::Runtime { + public int data; + public virtual void getStringData(void* ctx, void(*)(void* ctx, bool ascii, const void* data, size_t num) cb); +} diff --git a/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/test.h b/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/test.h new file mode 100644 index 000000000000..5ffb6c927c00 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/test.h @@ -0,0 +1,18 @@ +/* + * 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 Runtime { + public: + int data; + virtual void getStringData(void *ctx, void (*cb)(void *ctx, bool ascii, const void *data, size_t num)); +}; + +} // namespace test