Skip to content

Commit 235f140

Browse files
[SymbolGraphGen] check implicit clang decls for having extension parents (#83143)
Resolves rdar://151911998 This PR addresses an uncommon situation in the symbol graph when importing Objective-C symbols into Swift. When a class conforms to a protocol that includes an initializer, that initializer is automatically generated for that class in the AST. This initializer has the same USR as the original protocol symbol, but is housed in a different DeclContext to indicate the membership. Right now, we catch this situation when the protocol conformance is declared on the class definition: There's a branch to check for "implicit" decls with an underlying Clang symbol, and creates a synthesized USR if that symbols DeclContext points to a type. However, when the protocol conformance is added in a category extension, the DeclContext for the generated initializer points to the extension, causing the symbol to bypass that check and get added to the symbol graph with a duplicated USR. This PR adds a check to look for ExtensionDecls as the DeclContext so that the symbol can correctly receive a synthesized USR. One of the tests in this PR (`SymbolGraph/ClangImporter/ObjCInitializer.swift`) tests a similar situation where this "implicit decl with a Clang node" is created: Some initializers in Objective-C get imported into Swift twice, with differently-adapted parameter names. This is covered by the original code, but i wanted to leave the test in because i broke this case in my initial investigation! 😅 The other test (`SymbolGraph/ClangImporter/ProtocolInitializer.swift`) tests the new behavior that is fixed by this PR.
1 parent 0cd5721 commit 235f140

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

lib/SymbolGraphGen/SymbolGraphASTWalker.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ bool SymbolGraphASTWalker::walkToDeclPre(Decl *D, CharSourceRange Range) {
308308

309309
// We also might establish some synthesized members because we
310310
// extended an external type.
311-
if (ExtendedNominal->getModuleContext() != &M) {
311+
if (!areModulesEqual(ExtendedNominal->getModuleContext(), &M)) {
312312
ExtendedSG->recordConformanceSynthesizedMemberRelationships(Source);
313313
}
314314
}
@@ -341,10 +341,13 @@ bool SymbolGraphASTWalker::walkToDeclPre(Decl *D, CharSourceRange Range) {
341341
// symbol, regardless of which class it's actually appearing on. To prevent
342342
// multiple of these symbols colliding with each other, treat them as
343343
// synthesized symbols and use their parent type as the base type.
344-
if (VD->isImplicit() && VD->hasClangNode() &&
345-
VD->getClangNode().getAsDecl()) {
346-
if (const auto *Parent =
347-
dyn_cast_or_null<NominalTypeDecl>(VD->getDeclContext())) {
344+
if (VD->isImplicit() && VD->getClangDecl()) {
345+
const NominalTypeDecl *Parent = dyn_cast_or_null<NominalTypeDecl>(VD->getDeclContext());
346+
if (!Parent) {
347+
if (const auto *ParentExt = dyn_cast_or_null<ExtensionDecl>(VD->getDeclContext()))
348+
Parent = ParentExt->getExtendedNominal();
349+
}
350+
if (Parent) {
348351
SG->recordNode(Symbol(SG, VD, Parent));
349352
return true;
350353
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// REQUIRES: objc_interop
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: split-file %s %t
5+
6+
// RUN: %target-swift-symbolgraph-extract -sdk %clang-importer-sdk -module-name ObjCInitializer -Fsystem %t -output-dir %t -pretty-print -v
7+
// RUN: %FileCheck %s --input-file %t/ObjCInitializer.symbols.json
8+
// RUN: %FileCheck %s --input-file %t/ObjCInitializer.symbols.json --check-prefix SYNTH
9+
10+
//--- ObjCInitializer.framework/Modules/module.modulemap
11+
framework module ObjCInitializer [system] {
12+
header "ObjCInitializer.h"
13+
}
14+
15+
//--- ObjCInitializer.framework/Headers/ObjCInitializer.h
16+
@import Foundation;
17+
18+
@interface NSScrubberLayoutAttributes : NSObject <NSCopying>
19+
20+
// This initializer gets imported twice - once as `init(forItemAt:)` and once as
21+
// `init(forItemAtIndex:)`. Make sure one of them gets a synthesized USR
22+
// CHECK: "precise": "c:objc(cs)NSScrubberLayoutAttributes(cm)layoutAttributesForItemAtIndex:"
23+
// CHECK-NOT: "precise": "c:objc(cs)NSScrubberLayoutAttributes(cm)layoutAttributesForItemAtIndex:"
24+
// SYNTH: "precise": "c:objc(cs)NSScrubberLayoutAttributes(cm)layoutAttributesForItemAtIndex:::SYNTHESIZED::c:objc(cs)NSScrubberLayoutAttributes",
25+
// SYNTH-NOT: "precise": "c:objc(cs)NSScrubberLayoutAttributes(cm)layoutAttributesForItemAtIndex:::SYNTHESIZED::c:objc(cs)NSScrubberLayoutAttributes",
26+
+ (instancetype)layoutAttributesForItemAtIndex:(NSInteger)index;
27+
28+
@end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// REQUIRES: objc_interop
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: split-file %s %t
5+
6+
// RUN: %target-swift-symbolgraph-extract -sdk %clang-importer-sdk -module-name ProtocolInitializer -I %t/ProtocolInitializer -output-dir %t -pretty-print -v
7+
// RUN: %FileCheck %s --input-file %t/ProtocolInitializer.symbols.json
8+
9+
// the initializer's base USR should only appear as the source of one relationship
10+
// CHECK: "source": "c:objc(pl)MyProtocol(im)initWithSomeNumber:"
11+
// CHECK-NOT: "source": "c:objc(pl)MyProtocol(im)initWithSomeNumber:"
12+
13+
//--- ProtocolInitializer/module.modulemap
14+
module ProtocolInitializer {
15+
header "ProtocolInitializer.h"
16+
}
17+
18+
//--- ProtocolInitializer/ProtocolInitializer.h
19+
@import Foundation;
20+
21+
@protocol MyProtocol <NSObject>
22+
23+
@optional
24+
- (nullable id)initWithSomeNumber:(NSInteger)number;
25+
26+
@end
27+
28+
@interface MyClass : NSObject
29+
@end
30+
31+
@interface MyClass () <MyProtocol>
32+
@end

0 commit comments

Comments
 (0)