Fix parsing of Objective-C interface generic inheritance (#55775)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/55775

Doxygen incorrectly parses Objective-C interface declarations with protocol conformance. For example:

```objc
interface RCTAppearance : RCTEventEmitter <RCTBridgeModule>
```

Doxygen splits this into **two separate base classes** in the XML:
```xml
<basecompoundref>RCTEventEmitter</basecompoundref>
<basecompoundref>&lt;RCTBridgeModule&gt;</basecompoundref>
```

This caused the parser to output:
```
interface RCTAppearance : public RCTEventEmitter, public <RCTBridgeModule> {
```

Instead of the expected:
```
interface RCTAppearance : public RCTEventEmitter <RCTBridgeModule> {
```

The fix detects when a "base class" name starts and ends with `<...>` (indicating it's a protocol conformance) and combines it with the preceding actual base class name.

Also for multiple generics like:
```
interface RCTAlertManager : NSObject <RCTBridgeModule, RCTInvalidating>

end
```

The output should be
```
interface RCTAlertManager : public NSObject <RCTBridgeModule, RCTInvalidating>
end
```

Changelog:
[Internal]

Reviewed By: cipolleschi

Differential Revision: D94351731

fbshipit-source-id: 05ee2729c7a60800e9350193ef04ee6e2b125522
This commit is contained in:
Dawid Małecki
2026-03-05 07:21:42 -08:00
committed by meta-codesync[bot]
parent 6d4fcbf5d4
commit d473d9b598
5 changed files with 55 additions and 1 deletions
+18 -1
View File
@@ -465,7 +465,24 @@ def create_interface_scope(
interface_scope = snapshot.create_interface(interface_name)
base_classes = get_base_classes(scope_def, base_class=InterfaceScopeKind.Base)
interface_scope.kind.add_base(base_classes)
# Doxygen incorrectly splits "Foo <Protocol1, Protocol2>" into separate base classes:
# "Foo", "<Protocol1>", "<Protocol2>". Combine them back into "Foo <Protocol1, Protocol2>".
combined_bases = []
for base in base_classes:
if base.name.startswith("<") and base.name.endswith(">") and combined_bases:
prev_name = combined_bases[-1].name
protocol = base.name[1:-1] # Strip < and >
if "<" in prev_name and prev_name.endswith(">"):
# Previous base already has protocols, merge inside the brackets
combined_bases[-1].name = f"{prev_name[:-1]}, {protocol}>"
else:
# First protocol for this base class
combined_bases[-1].name = f"{prev_name} <{protocol}>"
else:
combined_bases.append(base)
interface_scope.kind.add_base(combined_bases)
interface_scope.location = scope_def.location.file
_process_objc_sections(
@@ -0,0 +1,3 @@
interface RCTAppearance : public RCTEventEmitter <RCTBridgeModule> {
public virtual instancetype init();
}
@@ -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 {
@interface RCTAppearance : RCTEventEmitter <RCTBridgeModule>
- (instancetype)init;
@end
} // namespace test
@@ -0,0 +1,2 @@
interface RCTAlertManager : public NSObject <RCTBridgeModule, RCTInvalidating> {
}
@@ -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 {
@interface RCTAlertManager : NSObject <RCTBridgeModule, RCTInvalidating>
@end
} // namespace test