From 41eba88af3adf662446ca803a90c4bc6386e40cf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 22 Jul 2025 17:49:13 -0700 Subject: [PATCH 1/2] [API Notes] Attach API notes to forward declarations of tags (#149951) Forward declarations can still have useful API notes applied to them. When the use of the tag is not a definition, apply the API notes immediately. Fixes rdar://156288588. (cherry picked from commit 8c26858f1a8bc5796c493f4f720552edbcb09fb4) --- clang/lib/Sema/SemaDecl.cpp | 4 ++++ .../Inputs/Headers/SwiftImportAs.apinotes | 4 ++++ .../test/APINotes/Inputs/Headers/SwiftImportAs.h | 6 ++++++ clang/test/APINotes/swift-import-as.cpp | 15 +++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 6f447397f37f7..17ecf3d396e6a 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -19610,6 +19610,10 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc, // record. AddPushedVisibilityAttribute(New); + // If this is not a definition, process API notes for it now. + if (TUK != TagUseKind::Definition) + ProcessAPINotes(New); + if (isMemberSpecialization && !New->isInvalidDecl()) CompleteMemberSpecialization(New, Previous); diff --git a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes index 66fc46e50ba0d..c096822fb92b5 100644 --- a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes +++ b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.apinotes @@ -19,6 +19,10 @@ Tags: SwiftReleaseOp: release SwiftRetainOp: retain SwiftDefaultOwnership: unretained +- Name: OpaqueRefCountedType + SwiftImportAs: reference + SwiftReleaseOp: ORCRelease + SwiftRetainOp: ORCRetain - Name: NonCopyableType SwiftCopyable: false SwiftConformsTo: MySwiftModule.MySwiftNonCopyableProtocol diff --git a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h index 20b8f04b4a914..5f817ac89bdd3 100644 --- a/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h +++ b/clang/test/APINotes/Inputs/Headers/SwiftImportAs.h @@ -23,3 +23,9 @@ struct EscapableType { int value; }; struct RefCountedTypeWithDefaultConvention {}; inline void retain(RefCountedType *x) {} inline void release(RefCountedType *x) {} + +struct OpaqueRefCountedType; +struct OpaqueRefCountedType; // redeclaration + +inline void ORCRetain(struct OpaqueRefCountedType *x); +inline void ORCRelease(struct OpaqueRefCountedType *x); diff --git a/clang/test/APINotes/swift-import-as.cpp b/clang/test/APINotes/swift-import-as.cpp index 929f924f2afee..179170fbc0994 100644 --- a/clang/test/APINotes/swift-import-as.cpp +++ b/clang/test/APINotes/swift-import-as.cpp @@ -3,6 +3,7 @@ // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter ImmortalRefType | FileCheck -check-prefix=CHECK-IMMORTAL %s // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter RefCountedType | FileCheck -check-prefix=CHECK-REF-COUNTED %s // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter RefCountedTypeWithDefaultConvention | FileCheck -check-prefix=CHECK-REF-COUNTED-DEFAULT %s +// RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter OpaqueRefCountedType | FileCheck -check-prefix=CHECK-OPAQUE-REF-COUNTED %s // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter NonCopyableType | FileCheck -check-prefix=CHECK-NON-COPYABLE %s // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter CopyableType | FileCheck -check-prefix=CHECK-COPYABLE %s // RUN: %clang_cc1 -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -x c++ -ast-dump -ast-dump-filter NonEscapableType | FileCheck -check-prefix=CHECK-NON-ESCAPABLE %s @@ -34,6 +35,20 @@ // CHECK-REF-COUNTED-DEFAULT: SwiftAttrAttr {{.+}} <> "release:release" // CHECK-REF-COUNTED-DEFAULT: SwiftAttrAttr {{.+}} <> "returned_as_unretained_by_default" +// CHECK-OPAQUE-REF-COUNTED: Dumping OpaqueRefCountedType: +// CHECK-OPAQUE-REF-COUNTED-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs{{.*}}struct OpaqueRefCountedType +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "import_reference" +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "retain:ORCRetain" +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "release:ORCRelease" +// CHECK-OPAQUE-REF-COUNTED-NOT: SwiftAttrAttr {{.+}} <> "release:ORCRelease" + +// CHECK-OPAQUE-REF-COUNTED: Dumping OpaqueRefCountedType: +// CHECK-OPAQUE-REF-COUNTED-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs{{.*}}struct OpaqueRefCountedType +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "import_reference" +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "retain:ORCRetain" +// CHECK-OPAQUE-REF-COUNTED: SwiftAttrAttr {{.+}} <> "release:ORCRelease" + +// CHECK-OPAQUE-REF-COUNTED-NOT: SwiftAttrAttr {{.+}} <> "release: // CHECK-NON-COPYABLE: Dumping NonCopyableType: // CHECK-NON-COPYABLE-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs {{.+}} struct NonCopyableType // CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <> "conforms_to:MySwiftModule.MySwiftNonCopyableProtocol" From 3cf7fe3bc55e64040749f596c521064edfe8763d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 22 Jul 2025 16:07:14 -0700 Subject: [PATCH 2/2] [API notes] Allow SwiftConformsTo on Typedefs SwiftConformsTo specifies an additional conformance that should be applied on import. Allow this on typedefs, because those can be imported as wrapper types. Clang side of rdar://156290361 (cherry picked from commit 01b28c49dee1a3a57d43c343366dd8120e0f40be) --- clang/include/clang/APINotes/Types.h | 23 ++++++++++++------- clang/lib/APINotes/APINotesFormat.h | 2 +- clang/lib/APINotes/APINotesReader.cpp | 13 ++++++----- clang/lib/APINotes/APINotesWriter.cpp | 16 ++++++------- clang/lib/APINotes/APINotesYAMLCompiler.cpp | 8 +++++-- clang/lib/Sema/SemaAPINotes.cpp | 8 +++---- .../Simple.framework/Headers/Simple.apinotes | 3 +++ clang/test/APINotes/swift-import-as.cpp | 2 +- clang/test/APINotes/yaml-roundtrip.test | 2 +- 9 files changed, 46 insertions(+), 31 deletions(-) diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 39c9d5b41a17c..925fd019ddf8c 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -141,6 +141,9 @@ class CommonTypeInfo : public CommonEntityInfo { /// The NS error domain for this type. std::optional NSErrorDomain; + /// The Swift protocol that this type should be automatically conformed to. + std::optional SwiftConformance; + public: CommonTypeInfo() {} @@ -165,6 +168,14 @@ class CommonTypeInfo : public CommonEntityInfo { : std::nullopt; } + std::optional getSwiftConformance() const { + return SwiftConformance; + } + + void setSwiftConformance(std::optional conformance) { + SwiftConformance = conformance; + } + friend bool operator==(const CommonTypeInfo &, const CommonTypeInfo &); CommonTypeInfo &operator|=(const CommonTypeInfo &RHS) { @@ -175,6 +186,8 @@ class CommonTypeInfo : public CommonEntityInfo { setSwiftBridge(RHS.getSwiftBridge()); if (!NSErrorDomain) setNSErrorDomain(RHS.getNSErrorDomain()); + if (SwiftConformance) + setSwiftConformance(RHS.getSwiftConformance()); return *this; } @@ -185,7 +198,8 @@ class CommonTypeInfo : public CommonEntityInfo { inline bool operator==(const CommonTypeInfo &LHS, const CommonTypeInfo &RHS) { return static_cast(LHS) == RHS && LHS.SwiftBridge == RHS.SwiftBridge && - LHS.NSErrorDomain == RHS.NSErrorDomain; + LHS.NSErrorDomain == RHS.NSErrorDomain && + LHS.SwiftConformance == RHS.SwiftConformance; } inline bool operator!=(const CommonTypeInfo &LHS, const CommonTypeInfo &RHS) { @@ -825,9 +839,6 @@ class TagInfo : public CommonTypeInfo { std::optional SwiftReleaseOp; std::optional SwiftDefaultOwnership; - /// The Swift protocol that this type should be automatically conformed to. - std::optional SwiftConformance; - std::optional EnumExtensibility; TagInfo() @@ -876,9 +887,6 @@ class TagInfo : public CommonTypeInfo { if (!SwiftDefaultOwnership) SwiftDefaultOwnership = RHS.SwiftDefaultOwnership; - if (!SwiftConformance) - SwiftConformance = RHS.SwiftConformance; - if (!HasFlagEnum) setFlagEnum(RHS.isFlagEnum()); @@ -905,7 +913,6 @@ inline bool operator==(const TagInfo &LHS, const TagInfo &RHS) { LHS.SwiftRetainOp == RHS.SwiftRetainOp && LHS.SwiftReleaseOp == RHS.SwiftReleaseOp && LHS.SwiftDefaultOwnership == RHS.SwiftDefaultOwnership && - LHS.SwiftConformance == RHS.SwiftConformance && LHS.isFlagEnum() == RHS.isFlagEnum() && LHS.isSwiftCopyable() == RHS.isSwiftCopyable() && LHS.isSwiftEscapable() == RHS.isSwiftEscapable() && diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 8b1c2a31f0c0f..e93237e91ca99 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 36; // TO_UPSTREAM(BoundsSafety) +const uint16_t VERSION_MINOR = 37; // Typedef SwiftConformsTo const uint8_t kSwiftConforms = 1; const uint8_t kSwiftDoesNotConform = 2; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index ca7f73495e5ea..7bce99656ba9c 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -134,6 +134,13 @@ void ReadCommonTypeInfo(const uint8_t *&Data, CommonTypeInfo &Info) { reinterpret_cast(Data), ErrorDomainLength - 1))); Data += ErrorDomainLength - 1; } + + if (unsigned ConformanceLength = + endian::readNext(Data)) { + Info.setSwiftConformance(std::string(reinterpret_cast(Data), + ConformanceLength - 1)); + Data += ConformanceLength - 1; + } } /// Used to deserialize the on-disk identifier table. @@ -668,12 +675,6 @@ class TagTableInfo reinterpret_cast(Data), DefaultOwnershipLength - 1); Data += DefaultOwnershipLength - 1; } - if (unsigned ConformanceLength = - endian::readNext(Data)) { - Info.SwiftConformance = std::string(reinterpret_cast(Data), - ConformanceLength - 1); - Data += ConformanceLength - 1; - } ReadCommonTypeInfo(Data, Info); return Info; diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index f41f109d1b60a..44426030d771e 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -543,7 +543,8 @@ unsigned getCommonEntityInfoSize(const CommonEntityInfo &CEI) { // in on-disk hash tables. unsigned getCommonTypeInfoSize(const CommonTypeInfo &CTI) { return 2 + (CTI.getSwiftBridge() ? CTI.getSwiftBridge()->size() : 0) + 2 + - (CTI.getNSErrorDomain() ? CTI.getNSErrorDomain()->size() : 0) + + (CTI.getNSErrorDomain() ? CTI.getNSErrorDomain()->size() : 0) + 2 + + (CTI.getSwiftConformance() ? CTI.getSwiftConformance()->size() : 0) + getCommonEntityInfoSize(CTI); } @@ -564,6 +565,12 @@ void emitCommonTypeInfo(raw_ostream &OS, const CommonTypeInfo &CTI) { } else { writer.write(0); } + if (auto conformance = CTI.getSwiftConformance()) { + writer.write(conformance->size() + 1); + OS.write(conformance->c_str(), conformance->size()); + } else { + writer.write(0); + } } /// Used to serialize the on-disk Objective-C property table. @@ -1331,7 +1338,6 @@ class TagTableInfo : public CommonTypeTableInfo { 2 + (TI.SwiftRetainOp ? TI.SwiftRetainOp->size() : 0) + 2 + (TI.SwiftReleaseOp ? TI.SwiftReleaseOp->size() : 0) + 2 + (TI.SwiftDefaultOwnership ? TI.SwiftDefaultOwnership->size() : 0) + - 2 + (TI.SwiftConformance ? TI.SwiftConformance->size() : 0) + 3 + getCommonTypeInfoSize(TI); // clang-format on } @@ -1385,12 +1391,6 @@ class TagTableInfo : public CommonTypeTableInfo { } else { writer.write(0); } - if (auto Conformance = TI.SwiftConformance) { - writer.write(Conformance->size() + 1); - OS.write(Conformance->c_str(), Conformance->size()); - } else { - writer.write(0); - } emitCommonTypeInfo(OS, TI); } diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index c9a0930d67de1..925c75d5028a7 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -413,6 +413,7 @@ struct Class { std::optional NSErrorDomain; std::optional SwiftImportAsNonGeneric; std::optional SwiftObjCMembers; + std::optional SwiftConformance; MethodsSeq Methods; PropertiesSeq Properties; }; @@ -437,6 +438,7 @@ template <> struct MappingTraits { IO.mapOptional("NSErrorDomain", C.NSErrorDomain); IO.mapOptional("SwiftImportAsNonGeneric", C.SwiftImportAsNonGeneric); IO.mapOptional("SwiftObjCMembers", C.SwiftObjCMembers); + IO.mapOptional("SwiftConformsTo", C.SwiftConformance); IO.mapOptional("Methods", C.Methods); IO.mapOptional("Properties", C.Properties); } @@ -693,6 +695,7 @@ struct Typedef { std::optional SwiftBridge; std::optional NSErrorDomain; std::optional SwiftType; + std::optional SwiftConformance; }; typedef std::vector TypedefsSeq; @@ -721,6 +724,7 @@ template <> struct MappingTraits { IO.mapOptional("SwiftBridge", T.SwiftBridge); IO.mapOptional("NSErrorDomain", T.NSErrorDomain); IO.mapOptional("SwiftWrapper", T.SwiftType); + IO.mapOptional("SwiftConformsTo", T.SwiftConformance); } }; } // namespace yaml @@ -979,6 +983,8 @@ class YAMLConverter { if (Common.SwiftBridge) Info.setSwiftBridge(std::string(*Common.SwiftBridge)); Info.setNSErrorDomain(Common.NSErrorDomain); + if (auto conformance = Common.SwiftConformance) + Info.setSwiftConformance(conformance); } // Translate from Method into ObjCMethodInfo and write it out. @@ -1176,8 +1182,6 @@ class YAMLConverter { TI.SwiftRetainOp = T.SwiftRetainOp; if (T.SwiftReleaseOp) TI.SwiftReleaseOp = T.SwiftReleaseOp; - if (T.SwiftConformance) - TI.SwiftConformance = T.SwiftConformance; if (T.SwiftDefaultOwnership) TI.SwiftDefaultOwnership = T.SwiftDefaultOwnership; diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 060d0be1a61b7..e1029a4bae836 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -336,6 +336,10 @@ static void ProcessAPINotes(Sema &S, Decl *D, }); } + if (auto ConformsTo = Info.getSwiftConformance()) + D->addAttr( + SwiftAttrAttr::Create(S.Context, "conforms_to:" + ConformsTo.value())); + ProcessAPINotes(S, D, static_cast(Info), Metadata); } @@ -777,10 +781,6 @@ static void ProcessAPINotes(Sema &S, TagDecl *D, const api_notes::TagInfo &Info, D->addAttr(SwiftAttrAttr::Create( S.Context, "returned_as_" + DefaultOwnership.value() + "_by_default")); - if (auto ConformsTo = Info.SwiftConformance) - D->addAttr( - SwiftAttrAttr::Create(S.Context, "conforms_to:" + ConformsTo.value())); - if (auto Copyable = Info.isSwiftCopyable()) { if (!*Copyable) D->addAttr(SwiftAttrAttr::Create(S.Context, "~Copyable")); diff --git a/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes index 8c915bd8b5913..9ba38edb8a6f1 100644 --- a/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes +++ b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes @@ -26,3 +26,6 @@ Classes: - Name: scalarNewProperty PropertyKind: Instance Nullability: Scalar +Typedefs: + - Name: MyTypedef + SwiftConformsTo: Swift.Equatable diff --git a/clang/test/APINotes/swift-import-as.cpp b/clang/test/APINotes/swift-import-as.cpp index 179170fbc0994..747aa29e95761 100644 --- a/clang/test/APINotes/swift-import-as.cpp +++ b/clang/test/APINotes/swift-import-as.cpp @@ -51,8 +51,8 @@ // CHECK-OPAQUE-REF-COUNTED-NOT: SwiftAttrAttr {{.+}} <> "release: // CHECK-NON-COPYABLE: Dumping NonCopyableType: // CHECK-NON-COPYABLE-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs {{.+}} struct NonCopyableType -// CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <> "conforms_to:MySwiftModule.MySwiftNonCopyableProtocol" // CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <> "~Copyable" +// CHECK-NON-COPYABLE: SwiftAttrAttr {{.+}} <> "conforms_to:MySwiftModule.MySwiftNonCopyableProtocol" // CHECK-COPYABLE: Dumping CopyableType: // CHECK-COPYABLE-NEXT: CXXRecordDecl {{.+}} imported in SwiftImportAs {{.+}} struct CopyableType diff --git a/clang/test/APINotes/yaml-roundtrip.test b/clang/test/APINotes/yaml-roundtrip.test index bcf84afda8df0..f69038ca828b1 100644 --- a/clang/test/APINotes/yaml-roundtrip.test +++ b/clang/test/APINotes/yaml-roundtrip.test @@ -24,7 +24,7 @@ CHECK-NEXT: 25c26 CHECK-NEXT: < Nullability: S CHECK-NEXT: --- CHECK-NEXT: > Nullability: Unspecified -CHECK-NEXT: 28c29,30 +CHECK-NEXT: 28c29 CHECK-NEXT: < Nullability: Scalar CHECK-NEXT: --- CHECK-NEXT: > Nullability: Unspecified