Skip to content

Commit 2299454

Browse files
coadometa-codesync[bot]
authored andcommitted
Fix Doxygen merging base classes from primary templates into specializations
Summary: Doxygen incorrectly merges base classes from primary templates into their partial specializations. In C++, a specialization's inheritance list completely replaces the primary template's, but Doxygen combines both into a single basecompoundref list. - **Contradictory type traits** — is_optional<std::optional<T>> and is_variant_of_data_types<std::variant<Ts...>> showed inheritance from both std::false_type (primary) and std::true_type (specialization) - **Duplicate base classes** — Converter<jsi::Object> listed ConverterBase<jsi::Object> twice (once from the primary template after substitution, once from the specialization) This diff fixes both issues with two complementary mechanisms: - **Dedup-by-name (Extendable._deduplicate_base_classes)**: removes exact duplicate base classes, keeping the last occurrence. Handles cases where Doxygen's template argument substitution produces identical names. - **Primary template base subtraction (StructLikeScopeKind._remove_merged_primary_bases)**: for partial specializations, looks up the primary template among sibling scopes and performs count-based subtraction of its bases. Count-based (rather than set-based) subtraction correctly preserves bases that a specialization explicitly re-inherits from the same class as the primary. Changelog: [Internal] Differential Revision: D98291360
1 parent ae16dfb commit 2299454

10 files changed

Lines changed: 137 additions & 32 deletions

File tree

scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,7 +2466,7 @@ class facebook::react::HostPlatformScrollViewProps : public facebook::react::Bas
24662466
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
24672467
}
24682468

2469-
class facebook::react::HostPlatformViewProps : public facebook::react::BaseViewProps, public facebook::react::BaseViewProps {
2469+
class facebook::react::HostPlatformViewProps : public facebook::react::BaseViewProps {
24702470
public HostPlatformViewProps() = default;
24712471
public HostPlatformViewProps() = default;
24722472
public HostPlatformViewProps(const facebook::react::PropsParserContext& context, const facebook::react::HostPlatformViewProps& sourceProps, const facebook::react::RawProps& rawProps);
@@ -10879,7 +10879,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
1087910879
}
1088010880

1088110881
template <CSSDataType... Ts>
10882-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
10882+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
1088310883
}
1088410884

1088510885
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -12476,21 +12476,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
1247612476
}
1247712477

1247812478
template <typename T>
12479-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
12479+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1248012480
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
1248112481
public operator std::optional<T>();
1248212482
}
1248312483

1248412484
template <typename T>
12485-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
12485+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
1248612486
}
1248712487

12488-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
12488+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
1248912489
public operator jsi::Array();
1249012490
public operator jsi::Function();
1249112491
}
1249212492

12493-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
12493+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1249412494
public operator jsi::Array();
1249512495
public operator jsi::Function();
1249612496
public operator jsi::Object();

scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2463,7 +2463,7 @@ class facebook::react::HostPlatformScrollViewProps : public facebook::react::Bas
24632463
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
24642464
}
24652465

2466-
class facebook::react::HostPlatformViewProps : public facebook::react::BaseViewProps, public facebook::react::BaseViewProps {
2466+
class facebook::react::HostPlatformViewProps : public facebook::react::BaseViewProps {
24672467
public HostPlatformViewProps() = default;
24682468
public HostPlatformViewProps() = default;
24692469
public HostPlatformViewProps(const facebook::react::PropsParserContext& context, const facebook::react::HostPlatformViewProps& sourceProps, const facebook::react::RawProps& rawProps);
@@ -10706,7 +10706,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
1070610706
}
1070710707

1070810708
template <CSSDataType... Ts>
10709-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
10709+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
1071010710
}
1071110711

1071210712
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -12303,21 +12303,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
1230312303
}
1230412304

1230512305
template <typename T>
12306-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
12306+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1230712307
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
1230812308
public operator std::optional<T>();
1230912309
}
1231012310

1231112311
template <typename T>
12312-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
12312+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
1231312313
}
1231412314

12315-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
12315+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
1231612316
public operator jsi::Array();
1231712317
public operator jsi::Function();
1231812318
}
1231912319

12320-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
12320+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1232112321
public operator jsi::Array();
1232212322
public operator jsi::Function();
1232312323
public operator jsi::Object();

scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13330,7 +13330,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
1333013330
}
1333113331

1333213332
template <CSSDataType... Ts>
13333-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
13333+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
1333413334
}
1333513335

1333613336
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -14862,21 +14862,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
1486214862
}
1486314863

1486414864
template <typename T>
14865-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
14865+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1486614866
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
1486714867
public operator std::optional<T>();
1486814868
}
1486914869

1487014870
template <typename T>
14871-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
14871+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
1487214872
}
1487314873

14874-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
14874+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
1487514875
public operator jsi::Array();
1487614876
public operator jsi::Function();
1487714877
}
1487814878

14879-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
14879+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1488014880
public operator jsi::Array();
1488114881
public operator jsi::Function();
1488214882
public operator jsi::Object();

scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13157,7 +13157,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
1315713157
}
1315813158

1315913159
template <CSSDataType... Ts>
13160-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
13160+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
1316113161
}
1316213162

1316313163
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -14689,21 +14689,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
1468914689
}
1469014690

1469114691
template <typename T>
14692-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
14692+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1469314693
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
1469414694
public operator std::optional<T>();
1469514695
}
1469614696

1469714697
template <typename T>
14698-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
14698+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
1469914699
}
1470014700

14701-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
14701+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
1470214702
public operator jsi::Array();
1470314703
public operator jsi::Function();
1470414704
}
1470514705

14706-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
14706+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
1470714707
public operator jsi::Array();
1470814708
public operator jsi::Function();
1470914709
public operator jsi::Object();

scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7630,7 +7630,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
76307630
}
76317631

76327632
template <CSSDataType... Ts>
7633-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
7633+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
76347634
}
76357635

76367636
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -9155,21 +9155,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
91559155
}
91569156

91579157
template <typename T>
9158-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
9158+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
91599159
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
91609160
public operator std::optional<T>();
91619161
}
91629162

91639163
template <typename T>
9164-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
9164+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
91659165
}
91669166

9167-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
9167+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
91689168
public operator jsi::Array();
91699169
public operator jsi::Function();
91709170
}
91719171

9172-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
9172+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
91739173
public operator jsi::Array();
91749174
public operator jsi::Function();
91759175
public operator jsi::Object();

scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7621,7 +7621,7 @@ struct facebook::react::detail::merge_data_types<std::variant<AllowedTypesT...>>
76217621
}
76227622

76237623
template <CSSDataType... Ts>
7624-
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::false_type, public std::true_type {
7624+
struct facebook::react::detail::is_variant_of_data_types<std::variant<Ts...>> : public std::true_type {
76257625
}
76267626

76277627
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
@@ -9146,21 +9146,21 @@ struct facebook::react::bridging::is_optional : public std::false_type {
91469146
}
91479147

91489148
template <typename T>
9149-
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<T>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
9149+
struct facebook::react::bridging::Converter<std::optional<T>> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
91509150
public Converter(facebook::jsi::Runtime& rt, std::optional<T> value);
91519151
public operator std::optional<T>();
91529152
}
91539153

91549154
template <typename T>
9155-
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::false_type, public std::true_type {
9155+
struct facebook::react::bridging::is_optional<std::optional<T>> : public std::true_type {
91569156
}
91579157

9158-
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object>, public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
9158+
struct facebook::react::bridging::Converter<facebook::jsi::Object> : public facebook::react::bridging::ConverterBase<facebook::jsi::Object> {
91599159
public operator jsi::Array();
91609160
public operator jsi::Function();
91619161
}
91629162

9163-
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value>, public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
9163+
struct facebook::react::bridging::Converter<facebook::jsi::Value> : public facebook::react::bridging::ConverterBase<facebook::jsi::Value> {
91649164
public operator jsi::Array();
91659165
public operator jsi::Function();
91669166
public operator jsi::Object();

scripts/cxx-api/parser/scope/extendable.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ def add_base(self, base: Base | list[Base]) -> None:
2525
self.base_classes.append(b)
2626
else:
2727
self.base_classes.append(base)
28+
self._deduplicate_base_classes()
29+
30+
def _deduplicate_base_classes(self) -> None:
31+
"""Remove duplicate base classes.
32+
33+
Doxygen sometimes reports the same base class multiple times (e.g.
34+
when template argument substitution produces identical names for
35+
a primary template and its specialization). This keeps only the
36+
last occurrence of each name.
37+
"""
38+
seen: dict[str, int] = {}
39+
for i, base in enumerate(self.base_classes):
40+
seen[base.name] = i
41+
self.base_classes = [self.base_classes[i] for i in sorted(seen.values())]
2842

2943
def qualify_base_classes(self, scope) -> None:
3044
"""Qualify base class names and their template arguments."""

scripts/cxx-api/parser/scope/struct_like_scope_kind.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,48 @@ def add_template(self, template: Template | [Template]) -> None:
4242
self.template_list.add(template)
4343

4444
def close(self, scope: Scope) -> None:
45+
self._remove_merged_primary_bases(scope)
4546
self.qualify_base_classes(scope)
4647

48+
def _remove_merged_primary_bases(self, scope: Scope) -> None:
49+
"""Remove base classes that Doxygen incorrectly merged from the
50+
primary template into a partial specialization.
51+
52+
In C++ a partial specialization's inheritance list completely
53+
replaces the primary template's, but Doxygen merges both lists
54+
into the specialization's ``basecompoundref`` elements.
55+
56+
This method performs count-based subtraction: for each base in
57+
the primary template, one matching occurrence (by name) is removed
58+
from this specialization's base list. Count-based subtraction
59+
correctly preserves bases that the specialization explicitly
60+
re-inherits from the same class as the primary template.
61+
"""
62+
if self.specialization_args is None:
63+
return
64+
if scope.parent_scope is None:
65+
return
66+
67+
for sibling in scope.parent_scope.inner_scopes.values():
68+
if (
69+
sibling is not scope
70+
and sibling.name == scope.name
71+
and isinstance(sibling.kind, StructLikeScopeKind)
72+
and sibling.kind.specialization_args is None
73+
):
74+
primary_base_counts: dict[str, int] = {}
75+
for b in sibling.kind.base_classes:
76+
primary_base_counts[b.name] = primary_base_counts.get(b.name, 0) + 1
77+
78+
result = []
79+
for b in self.base_classes:
80+
if primary_base_counts.get(b.name, 0) > 0:
81+
primary_base_counts[b.name] -= 1
82+
else:
83+
result.append(b)
84+
self.base_classes = result
85+
break
86+
4787
def to_string(self, scope: Scope) -> str:
4888
result = ""
4989

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
struct test::BaseA {
2+
}
3+
4+
struct test::Derived : public test::BaseA {
5+
}
6+
7+
struct test::false_type {
8+
}
9+
10+
struct test::true_type {
11+
}
12+
13+
template <typename T>
14+
struct test::is_special<T *> : public test::true_type {
15+
}
16+
17+
template <typename>
18+
struct test::is_special : public test::false_type {
19+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
namespace test {
11+
12+
struct false_type {};
13+
struct true_type {};
14+
15+
// Primary template inherits from false_type.
16+
// Partial specialization inherits from true_type.
17+
// Doxygen may incorrectly merge both base class lists into the
18+
// specialization, producing : false_type, true_type. The parser
19+
// should subtract the primary template's bases and keep only true_type.
20+
21+
template <typename>
22+
struct is_special : public false_type {};
23+
24+
template <typename T>
25+
struct is_special<T *> : public true_type {};
26+
27+
// Duplicate base class via Doxygen merging template-substituted bases.
28+
struct BaseA {};
29+
30+
struct Derived : public BaseA, public BaseA {};
31+
32+
} // namespace test

0 commit comments

Comments
 (0)