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/builders.py b/scripts/cxx-api/parser/builders.py index c595183d86c1..03e0d1bc6a3e 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 @@ -161,8 +166,14 @@ def get_base_classes( ) -> list: """ Get the base classes of a compound object. + + Deduplicates base classes by name. Doxygen can emit duplicate + ``basecompoundref`` entries when a class inherits constructors via + ``using Base::Base;`` — the using-declaration is incorrectly reported + as an additional base class reference. """ base_classes = [] + seen_names: set[str] = set() if compound_object.basecompoundref: for base in compound_object.basecompoundref: # base is a compoundRefType with: @@ -179,6 +190,10 @@ def get_base_classes( # Ignore private base classes continue + if base_name in seen_names: + continue + seen_names.add(base_name) + base_classes.append( base_class( base_name, @@ -307,6 +322,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 @@ -333,6 +358,8 @@ def get_doxygen_params( + param_array ) param_name = None + elif param_name: + param_name += param_array else: param_type += param_array @@ -350,6 +377,29 @@ def get_doxygen_params( param_type[:insert_pos] + param_name + param_type[insert_pos:] ) param_name = None + else: + # Doxygen bug: for pointer-to-member-function params with + # ref-qualifiers (& or &&), Doxygen incorrectly embeds the + # parameter name in the type string between cv-qualifiers + # and the ref-qualifier, and omits entirely: + # R(ns::*)() const asFoo & + # Detect this pattern and reconstruct the correct type: + # R(ns::*asFoo)() const & + m = re.search( + r"(\([^)]*::\*\))" # group 1: ptr-to-member declarator + r"(.+?)" # group 2: param list + cv-qualifiers + r"\s+([a-zA-Z_]\w*)" # group 3: misplaced identifier + r"\s*(&{1,2})\s*$", # group 4: ref-qualifier + param_type, + ) + if m: + param_type = ( + param_type[: m.end(1) - 1] # up to ')' of (ns::*) + + m.group(3) # insert extracted name + + param_type[m.end(1) - 1 : m.end(2)] # ')' + params + cv-quals + + " " + + m.group(4) # ref-qualifier + ) qualifiers, core_type = extract_qualifiers(param_type) arguments.append((qualifiers, core_type, param_name, param_default)) 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/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/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_deduplicate_base_classes_with_using_constructor/snapshot.api b/scripts/cxx-api/tests/snapshots/should_deduplicate_base_classes_with_using_constructor/snapshot.api new file mode 100644 index 000000000000..068fda219250 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_deduplicate_base_classes_with_using_constructor/snapshot.api @@ -0,0 +1,12 @@ +struct test::Converter : public test::ConverterBase { + public void doSomething(); +} + +template +struct test::Converter { +} + +template +struct test::ConverterBase { + public ConverterBase() = default; +} diff --git a/scripts/cxx-api/tests/snapshots/should_deduplicate_base_classes_with_using_constructor/test.h b/scripts/cxx-api/tests/snapshots/should_deduplicate_base_classes_with_using_constructor/test.h new file mode 100644 index 000000000000..c0e82719c055 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_deduplicate_base_classes_with_using_constructor/test.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +template +struct ConverterBase { + ConverterBase() = default; +}; + +template +struct Converter {}; + +template <> +struct Converter : public ConverterBase { + using ConverterBase::ConverterBase; + void doSomething(); +}; + +} // namespace test 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 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 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 diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param_with_ref_qualifier/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param_with_ref_qualifier/snapshot.api new file mode 100644 index 000000000000..1906db310c8f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param_with_ref_qualifier/snapshot.api @@ -0,0 +1,8 @@ +struct folly::dynamic { +} + + +template +R test::jsArg(const folly::dynamic& arg, R(folly::dynamic::*asFoo)() const &, const T &... desc); +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_with_ref_qualifier/test.h b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param_with_ref_qualifier/test.h new file mode 100644 index 000000000000..fcdb31e047a7 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_pointer_to_member_function_param_with_ref_qualifier/test.h @@ -0,0 +1,23 @@ +/* + * 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); +template +R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const &, const T &...desc); + +} // namespace test 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..0b0c7299f77f --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_qualify_function_pointer_param_args_to_outer_class/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 { + +class Runtime { + public: + int data; + virtual void getStringData( + void* ctx, + void (*cb)(void* ctx, bool ascii, const void* data, size_t num)); +}; + +} // namespace test